diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-02-25 21:04:23 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-02-25 21:04:23 -0700 |
| commit | 0b58b617998b79a765b54f88fbe90ca2798b3d3e (patch) | |
| tree | ca6cc4b6bcc2b93166d196049ee49416afe781ad /lib/std | |
| parent | dc325669e360f7a9dfa24f85a62fa386529dade6 (diff) | |
| parent | fd208d9d5913a0929e444deb97b91092c427bb14 (diff) | |
| download | zig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.tar.gz zig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.zip | |
Merge remote-tracking branch 'origin/master' into llvm12
Conflicts:
* src/clang.zig
* src/llvm.zig
- this file got moved to src/llvm/bindings.zig in master branch so I
had to put the new LLVM arch/os enum tags into it.
* lib/std/target.zig, src/stage1/target.cpp
- haiku had an inconsistency with its default target ABI, gnu vs
eabi. In this commit we make it gnu in both places to match the
latest changes by @hoanga.
* src/translate_c.zig
Diffstat (limited to 'lib/std')
539 files changed, 24192 insertions, 16172 deletions
diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig new file mode 100644 index 0000000000..4afd191b93 --- /dev/null +++ b/lib/std/Progress.zig @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! This API non-allocating, non-fallible, and thread-safe. +//! The tradeoff is that users of this API must provide the storage +//! for each `Progress.Node`. +//! +//! Initialize the struct directly, overriding these fields as desired: +//! * `refresh_rate_ms` +//! * `initial_delay_ms` + +const std = @import("std"); +const windows = std.os.windows; +const testing = std.testing; +const assert = std.debug.assert; +const Progress = @This(); + +/// `null` if the current node (and its children) should +/// not print on update() +terminal: ?std.fs.File = undefined, + +/// Whether the terminal supports ANSI escape codes. +supports_ansi_escape_codes: bool = false, + +/// If the terminal is "dumb", don't print output. +/// This can be useful if you don't want to print all +/// the stages of code generation if there are a lot. +/// You should not use it if the user should see output +/// for example showing the user what tests run. +dont_print_on_dumb: bool = false, + +root: Node = undefined, + +/// Keeps track of how much time has passed since the beginning. +/// Used to compare with `initial_delay_ms` and `refresh_rate_ms`. +timer: std.time.Timer = undefined, + +/// When the previous refresh was written to the terminal. +/// Used to compare with `refresh_rate_ms`. +prev_refresh_timestamp: u64 = undefined, + +/// This buffer represents the maximum number of bytes written to the terminal +/// with each refresh. +output_buffer: [100]u8 = undefined, + +/// How many nanoseconds between writing updates to the terminal. +refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, + +/// How many nanoseconds to keep the output hidden +initial_delay_ns: u64 = 500 * std.time.ns_per_ms, + +done: bool = true, + +/// Protects the `refresh` function, as well as `node.recently_updated_child`. +/// Without this, callsites would call `Node.end` and then free `Node` memory +/// while it was still being accessed by the `refresh` function. +update_lock: std.Thread.Mutex = .{}, + +/// Keeps track of how many columns in the terminal have been output, so that +/// we can move the cursor back later. +columns_written: usize = undefined, + +/// Represents one unit of progress. Each node can have children nodes, or +/// one can use integers with `update`. +pub const Node = struct { + context: *Progress, + parent: ?*Node, + name: []const u8, + /// Must be handled atomically to be thread-safe. + recently_updated_child: ?*Node = null, + /// Must be handled atomically to be thread-safe. 0 means null. + unprotected_estimated_total_items: usize, + /// Must be handled atomically to be thread-safe. + unprotected_completed_items: usize, + + /// Create a new child progress node. Thread-safe. + /// Call `Node.end` when done. + /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this + /// API to set `self.parent.recently_updated_child` with the return value. + /// Until that is fixed you probably want to call `activate` on the return value. + /// Passing 0 for `estimated_total_items` means unknown. + pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node { + return Node{ + .context = self.context, + .parent = self, + .name = name, + .unprotected_estimated_total_items = estimated_total_items, + .unprotected_completed_items = 0, + }; + } + + /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. + pub fn completeOne(self: *Node) void { + self.activate(); + _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .Monotonic); + self.context.maybeRefresh(); + } + + /// Finish a started `Node`. Thread-safe. + pub fn end(self: *Node) void { + self.context.maybeRefresh(); + if (self.parent) |parent| { + { + const held = self.context.update_lock.acquire(); + defer held.release(); + _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .Monotonic, .Monotonic); + } + parent.completeOne(); + } else { + const held = self.context.update_lock.acquire(); + defer held.release(); + self.context.done = true; + self.context.refreshWithHeldLock(); + } + } + + /// Tell the parent node that this node is actively being worked on. Thread-safe. + pub fn activate(self: *Node) void { + if (self.parent) |parent| { + @atomicStore(?*Node, &parent.recently_updated_child, self, .Release); + } + } + + /// Thread-safe. 0 means unknown. + pub fn setEstimatedTotalItems(self: *Node, count: usize) void { + @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic); + } + + /// Thread-safe. + pub fn setCompletedItems(self: *Node, completed_items: usize) void { + @atomicStore(usize, &self.unprotected_completed_items, completed_items, .Monotonic); + } +}; + +/// Create a new progress node. +/// Call `Node.end` when done. +/// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this +/// API to return Progress rather than accept it as a parameter. +/// `estimated_total_items` value of 0 means unknown. +pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*Node { + const stderr = std.io.getStdErr(); + self.terminal = null; + if (stderr.supportsAnsiEscapeCodes()) { + self.terminal = stderr; + self.supports_ansi_escape_codes = true; + } else if (std.builtin.os.tag == .windows and stderr.isTty()) { + self.terminal = stderr; + } else if (std.builtin.os.tag != .windows) { + // we are in a "dumb" terminal like in acme or writing to a file + self.terminal = stderr; + } + self.root = Node{ + .context = self, + .parent = null, + .name = name, + .unprotected_estimated_total_items = estimated_total_items, + .unprotected_completed_items = 0, + }; + self.columns_written = 0; + self.prev_refresh_timestamp = 0; + self.timer = try std.time.Timer.start(); + self.done = false; + return &self.root; +} + +/// Updates the terminal if enough time has passed since last update. Thread-safe. +pub fn maybeRefresh(self: *Progress) void { + const now = self.timer.read(); + if (now < self.initial_delay_ns) return; + const held = self.update_lock.tryAcquire() orelse return; + defer held.release(); + // TODO I have observed this to happen sometimes. I think we need to follow Rust's + // lead and guarantee monotonically increasing times in the std lib itself. + if (now < self.prev_refresh_timestamp) return; + if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; + return self.refreshWithHeldLock(); +} + +/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe. +pub fn refresh(self: *Progress) void { + const held = self.update_lock.tryAcquire() orelse return; + defer held.release(); + + return self.refreshWithHeldLock(); +} + +fn refreshWithHeldLock(self: *Progress) void { + const is_dumb = !self.supports_ansi_escape_codes and !(std.builtin.os.tag == .windows); + if (is_dumb and self.dont_print_on_dumb) return; + const file = self.terminal orelse return; + + const prev_columns_written = self.columns_written; + var end: usize = 0; + if (self.columns_written > 0) { + // restore the cursor position by moving the cursor + // `columns_written` cells to the left, then clear the rest of the + // line + if (self.supports_ansi_escape_codes) { + end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len; + end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; + } else if (std.builtin.os.tag == .windows) winapi: { + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) + unreachable; + + var cursor_pos = windows.COORD{ + .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written), + .Y = info.dwCursorPosition.Y, + }; + + if (cursor_pos.X < 0) + cursor_pos.X = 0; + + const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X); + + var written: windows.DWORD = undefined; + if (windows.kernel32.FillConsoleOutputAttribute( + file.handle, + info.wAttributes, + fill_chars, + cursor_pos, + &written, + ) != windows.TRUE) { + // Stop trying to write to this file. + self.terminal = null; + break :winapi; + } + if (windows.kernel32.FillConsoleOutputCharacterA( + file.handle, + ' ', + fill_chars, + cursor_pos, + &written, + ) != windows.TRUE) unreachable; + + if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) + unreachable; + } else { + // we are in a "dumb" terminal like in acme or writing to a file + self.output_buffer[end] = '\n'; + end += 1; + } + + self.columns_written = 0; + } + + if (!self.done) { + var need_ellipse = false; + var maybe_node: ?*Node = &self.root; + while (maybe_node) |node| { + if (need_ellipse) { + self.bufWrite(&end, "... ", .{}); + } + need_ellipse = false; + const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic); + const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic); + if (node.name.len != 0 or eti > 0) { + if (node.name.len != 0) { + self.bufWrite(&end, "{s}", .{node.name}); + need_ellipse = true; + } + if (eti > 0) { + if (need_ellipse) self.bufWrite(&end, " ", .{}); + self.bufWrite(&end, "[{d}/{d}] ", .{ completed_items + 1, eti }); + need_ellipse = false; + } else if (completed_items != 0) { + if (need_ellipse) self.bufWrite(&end, " ", .{}); + self.bufWrite(&end, "[{d}] ", .{completed_items + 1}); + need_ellipse = false; + } + } + maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Acquire); + } + if (need_ellipse) { + self.bufWrite(&end, "... ", .{}); + } + } + + _ = file.write(self.output_buffer[0..end]) catch |e| { + // Stop trying to write to this file once it errors. + self.terminal = null; + }; + self.prev_refresh_timestamp = self.timer.read(); +} + +pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { + const file = self.terminal orelse return; + self.refresh(); + file.writer().print(format, args) catch { + self.terminal = null; + return; + }; + self.columns_written = 0; +} + +fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { + if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { + const amt = written.len; + end.* += amt; + self.columns_written += amt; + } else |err| switch (err) { + error.NoSpaceLeft => { + self.columns_written += self.output_buffer.len - end.*; + end.* = self.output_buffer.len; + }, + } + const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; + const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; + if (end.* > max_end) { + const suffix = "... "; + self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; + std.mem.copy(u8, self.output_buffer[max_end..], suffix); + end.* = max_end + suffix.len; + } +} + +test "basic functionality" { + var disable = true; + if (disable) { + // This test is disabled because it uses time.sleep() and is therefore slow. It also + // prints bogus progress data to stderr. + return error.SkipZigTest; + } + var progress = Progress{}; + const root_node = try progress.start("", 100); + defer root_node.end(); + + const sub_task_names = [_][]const u8{ + "reticulating splines", + "adjusting shoes", + "climbing towers", + "pouring juice", + }; + var next_sub_task: usize = 0; + + var i: usize = 0; + while (i < 100) : (i += 1) { + var node = root_node.start(sub_task_names[next_sub_task], 5); + node.activate(); + next_sub_task = (next_sub_task + 1) % sub_task_names.len; + + node.completeOne(); + std.time.sleep(5 * std.time.ns_per_ms); + node.completeOne(); + node.completeOne(); + std.time.sleep(5 * std.time.ns_per_ms); + node.completeOne(); + node.completeOne(); + std.time.sleep(5 * std.time.ns_per_ms); + + node.end(); + + std.time.sleep(5 * std.time.ns_per_ms); + } + { + var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0); + node.activate(); + std.time.sleep(10 * std.time.ns_per_ms); + progress.refresh(); + std.time.sleep(10 * std.time.ns_per_ms); + node.end(); + } +} diff --git a/lib/std/SemanticVersion.zig b/lib/std/SemanticVersion.zig index 74e515f9d1..52c3ae59df 100644 --- a/lib/std/SemanticVersion.zig +++ b/lib/std/SemanticVersion.zig @@ -102,7 +102,7 @@ pub fn parse(text: []const u8) !Version { if (extra_index == null) return ver; // Slice optional pre-release or build metadata components. - const extra = text[extra_index.?..text.len]; + const extra: []const u8 = text[extra_index.?..text.len]; if (extra[0] == '-') { const build_index = std.mem.indexOfScalar(u8, extra, '+'); ver.pre = extra[1..(build_index orelse extra.len)]; @@ -163,9 +163,9 @@ pub fn format( out_stream: anytype, ) !void { if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); - try std.fmt.format(out_stream, "{}.{}.{}", .{ self.major, self.minor, self.patch }); - if (self.pre) |pre| try std.fmt.format(out_stream, "-{}", .{pre}); - if (self.build) |build| try std.fmt.format(out_stream, "+{}", .{build}); + try std.fmt.format(out_stream, "{d}.{d}.{d}", .{ self.major, self.minor, self.patch }); + if (self.pre) |pre| try std.fmt.format(out_stream, "-{s}", .{pre}); + if (self.build) |build| try std.fmt.format(out_stream, "+{s}", .{build}); } const expect = std.testing.expect; @@ -205,7 +205,7 @@ test "SemanticVersion format" { "1.2.3----R-S.12.9.1--.12+meta", "1.2.3----RC-SNAPSHOT.12.9.1--.12", "1.0.0+0.build.1-rc.10000aaa-kk-0.1", - }) |valid| try testFmt(valid, "{}", .{try parse(valid)}); + }) |valid| try std.testing.expectFmt(valid, "{}", .{try parse(valid)}); // Invalid version strings should be rejected. for ([_][]const u8{ @@ -253,7 +253,9 @@ test "SemanticVersion format" { // Valid version string that may overflow. const big_valid = "99999999999999999999999.999999999999999999.99999999999999999"; - if (parse(big_valid)) |ver| try testFmt(big_valid, "{}", .{ver}) else |err| expect(err == error.Overflow); + if (parse(big_valid)) |ver| { + try std.testing.expectFmt(big_valid, "{}", .{ver}); + } else |err| expect(err == error.Overflow); // Invalid version string that may overflow. const big_invalid = "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12"; @@ -280,16 +282,11 @@ test "SemanticVersion precedence" { expect(order(try parse("1.0.0-rc.1"), try parse("1.0.0")) == .lt); } -// This is copy-pasted from fmt.zig since it is not public. -fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { - var buf: [100]u8 = undefined; - const result = try std.fmt.bufPrint(buf[0..], template, args); - if (std.mem.eql(u8, result, expected)) return; - - std.debug.warn("\n====== expected this output: =========\n", .{}); - std.debug.warn("{}", .{expected}); - std.debug.warn("\n======== instead found this: =========\n", .{}); - std.debug.warn("{}", .{result}); - std.debug.warn("\n======================================\n", .{}); - return error.TestFailed; +test "zig_version" { + // An approximate Zig build that predates this test. + const older_version = .{ .major = 0, .minor = 8, .patch = 0, .pre = "dev.874" }; + + // Simulated compatibility check using Zig version. + const compatible = comptime @import("builtin").zig_version.order(older_version) == .gt; + if (!compatible) @compileError("zig_version test failed"); } diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig new file mode 100644 index 0000000000..77277bd154 --- /dev/null +++ b/lib/std/Thread.zig @@ -0,0 +1,565 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! This struct represents a kernel thread, and acts as a namespace for concurrency +//! primitives that operate on kernel threads. For concurrency primitives that support +//! both evented I/O and async I/O, see the respective names in the top level std namespace. + +data: Data, + +pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig"); +pub const ResetEvent = @import("Thread/ResetEvent.zig"); +pub const StaticResetEvent = @import("Thread/StaticResetEvent.zig"); +pub const Mutex = @import("Thread/Mutex.zig"); +pub const Semaphore = @import("Thread/Semaphore.zig"); +pub const Condition = @import("Thread/Condition.zig"); + +pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; + +const Thread = @This(); +const std = @import("std.zig"); +const builtin = std.builtin; +const os = std.os; +const mem = std.mem; +const windows = std.os.windows; +const c = std.c; +const assert = std.debug.assert; + +const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; + +/// Represents a kernel thread handle. +/// May be an integer or a pointer depending on the platform. +/// On Linux and POSIX, this is the same as Id. +pub const Handle = if (use_pthreads) + c.pthread_t +else switch (std.Target.current.os.tag) { + .linux => i32, + .windows => windows.HANDLE, + else => void, +}; + +/// Represents a unique ID per thread. +/// May be an integer or pointer depending on the platform. +/// On Linux and POSIX, this is the same as Handle. +pub const Id = switch (std.Target.current.os.tag) { + .windows => windows.DWORD, + else => Handle, +}; + +pub const Data = if (use_pthreads) + struct { + handle: Thread.Handle, + memory: []u8, + } +else switch (std.Target.current.os.tag) { + .linux => struct { + handle: Thread.Handle, + memory: []align(mem.page_size) u8, + }, + .windows => struct { + handle: Thread.Handle, + alloc_start: *c_void, + heap_handle: windows.HANDLE, + }, + else => struct {}, +}; + +/// Signals the processor that it is inside a busy-wait spin-loop ("spin lock"). +pub fn spinLoopHint() void { + switch (std.Target.current.cpu.arch) { + .i386, .x86_64 => asm volatile ("pause" ::: "memory"), + .arm, .aarch64 => asm volatile ("yield" ::: "memory"), + else => {}, + } +} + +/// Returns the ID of the calling thread. +/// Makes a syscall every time the function is called. +/// On Linux and POSIX, this Id is the same as a Handle. +pub fn getCurrentId() Id { + if (use_pthreads) { + return c.pthread_self(); + } else return switch (std.Target.current.os.tag) { + .linux => os.linux.gettid(), + .windows => windows.kernel32.GetCurrentThreadId(), + else => @compileError("Unsupported OS"), + }; +} + +/// Returns the handle of this thread. +/// On Linux and POSIX, this is the same as Id. +/// On Linux, it is possible that the thread spawned with `spawn` +/// finishes executing entirely before the clone syscall completes. In this +/// case, this function will return 0 rather than the no-longer-existing thread's +/// pid. +pub fn handle(self: Thread) Handle { + return self.data.handle; +} + +pub fn wait(self: *Thread) void { + if (use_pthreads) { + const err = c.pthread_join(self.data.handle, null); + switch (err) { + 0 => {}, + os.EINVAL => unreachable, + os.ESRCH => unreachable, + os.EDEADLK => unreachable, + else => unreachable, + } + std.heap.c_allocator.free(self.data.memory); + std.heap.c_allocator.destroy(self); + } else switch (std.Target.current.os.tag) { + .linux => { + while (true) { + const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); + if (pid_value == 0) break; + const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null); + switch (os.linux.getErrno(rc)) { + 0 => continue, + os.EINTR => continue, + os.EAGAIN => continue, + else => unreachable, + } + } + os.munmap(self.data.memory); + }, + .windows => { + windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable; + windows.CloseHandle(self.data.handle); + windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start); + }, + else => @compileError("Unsupported OS"), + } +} + +pub const SpawnError = error{ + /// A system-imposed limit on the number of threads was encountered. + /// There are a number of limits that may trigger this error: + /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), + /// which limits the number of processes and threads for a real + /// user ID, was reached; + /// * the kernel's system-wide limit on the number of processes and + /// threads, /proc/sys/kernel/threads-max, was reached (see + /// proc(5)); + /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was + /// reached (see proc(5)); or + /// * the PID limit (pids.max) imposed by the cgroup "process num‐ + /// ber" (PIDs) controller was reached. + ThreadQuotaExceeded, + + /// The kernel cannot allocate sufficient memory to allocate a task structure + /// for the child, or to copy those parts of the caller's context that need to + /// be copied. + SystemResources, + + /// Not enough userland memory to spawn the thread. + OutOfMemory, + + /// `mlockall` is enabled, and the memory needed to spawn the thread + /// would exceed the limit. + LockedMemoryLimitExceeded, + + Unexpected, +}; + +/// caller must call wait on the returned thread +/// fn startFn(@TypeOf(context)) T +/// where T is u8, noreturn, void, or !void +/// caller must call wait on the returned thread +pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread { + if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); + // TODO compile-time call graph analysis to determine stack upper bound + // https://github.com/ziglang/zig/issues/157 + const default_stack_size = 16 * 1024 * 1024; + + const Context = @TypeOf(context); + comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context); + + if (std.Target.current.os.tag == .windows) { + const WinThread = struct { + const OuterContext = struct { + thread: Thread, + inner: Context, + }; + fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { + const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; + + switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { + .NoReturn => { + startFn(arg); + }, + .Void => { + startFn(arg); + return 0; + }, + .Int => |info| { + if (info.bits != 8) { + @compileError(bad_startfn_ret); + } + return startFn(arg); + }, + .ErrorUnion => |info| { + if (info.payload != void) { + @compileError(bad_startfn_ret); + } + startFn(arg) catch |err| { + std.debug.warn("error: {s}\n", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }; + return 0; + }, + else => @compileError(bad_startfn_ret), + } + } + }; + + const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; + const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); + const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; + errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0); + const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; + const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; + outer_context.* = WinThread.OuterContext{ + .thread = Thread{ + .data = Thread.Data{ + .heap_handle = heap_handle, + .alloc_start = bytes_ptr, + .handle = undefined, + }, + }, + .inner = context, + }; + + const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); + outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { + switch (windows.kernel32.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + }; + return &outer_context.thread; + } + + const MainFuncs = struct { + fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { + const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; + + switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { + .NoReturn => { + startFn(arg); + }, + .Void => { + startFn(arg); + return 0; + }, + .Int => |info| { + if (info.bits != 8) { + @compileError(bad_startfn_ret); + } + return startFn(arg); + }, + .ErrorUnion => |info| { + if (info.payload != void) { + @compileError(bad_startfn_ret); + } + startFn(arg) catch |err| { + std.debug.warn("error: {s}\n", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }; + return 0; + }, + else => @compileError(bad_startfn_ret), + } + } + fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { + const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; + + switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { + .NoReturn => { + startFn(arg); + }, + .Void => { + startFn(arg); + return null; + }, + .Int => |info| { + if (info.bits != 8) { + @compileError(bad_startfn_ret); + } + // pthreads don't support exit status, ignore value + _ = startFn(arg); + return null; + }, + .ErrorUnion => |info| { + if (info.payload != void) { + @compileError(bad_startfn_ret); + } + startFn(arg) catch |err| { + std.debug.warn("error: {s}\n", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }; + return null; + }, + else => @compileError(bad_startfn_ret), + } + } + }; + + if (Thread.use_pthreads) { + var attr: c.pthread_attr_t = undefined; + if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; + defer assert(c.pthread_attr_destroy(&attr) == 0); + + const thread_obj = try std.heap.c_allocator.create(Thread); + errdefer std.heap.c_allocator.destroy(thread_obj); + if (@sizeOf(Context) > 0) { + thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced( + u8, + @alignOf(Context), + @sizeOf(Context), + .at_least, + ); + errdefer std.heap.c_allocator.free(thread_obj.data.memory); + mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context)); + } else { + thread_obj.data.memory = @as([*]u8, undefined)[0..0]; + } + + // Use the same set of parameters used by the libc-less impl. + assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0); + assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0); + + const err = c.pthread_create( + &thread_obj.data.handle, + &attr, + MainFuncs.posixThreadMain, + thread_obj.data.memory.ptr, + ); + switch (err) { + 0 => return thread_obj, + os.EAGAIN => return error.SystemResources, + os.EPERM => unreachable, + os.EINVAL => unreachable, + else => return os.unexpectedErrno(@intCast(usize, err)), + } + + return thread_obj; + } + + var guard_end_offset: usize = undefined; + var stack_end_offset: usize = undefined; + var thread_start_offset: usize = undefined; + var context_start_offset: usize = undefined; + var tls_start_offset: usize = undefined; + const mmap_len = blk: { + var l: usize = mem.page_size; + // Allocate a guard page right after the end of the stack region + guard_end_offset = l; + // The stack itself, which grows downwards. + l = mem.alignForward(l + default_stack_size, mem.page_size); + stack_end_offset = l; + // Above the stack, so that it can be in the same mmap call, put the Thread object. + l = mem.alignForward(l, @alignOf(Thread)); + thread_start_offset = l; + l += @sizeOf(Thread); + // Next, the Context object. + if (@sizeOf(Context) != 0) { + l = mem.alignForward(l, @alignOf(Context)); + context_start_offset = l; + l += @sizeOf(Context); + } + // Finally, the Thread Local Storage, if any. + l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align); + tls_start_offset = l; + l += os.linux.tls.tls_image.alloc_size; + // Round the size to the page size. + break :blk mem.alignForward(l, mem.page_size); + }; + + const mmap_slice = mem: { + // Map the whole stack with no rw permissions to avoid + // committing the whole region right away + const mmap_slice = os.mmap( + null, + mmap_len, + os.PROT_NONE, + os.MAP_PRIVATE | os.MAP_ANONYMOUS, + -1, + 0, + ) catch |err| switch (err) { + error.MemoryMappingNotSupported => unreachable, + error.AccessDenied => unreachable, + error.PermissionDenied => unreachable, + else => |e| return e, + }; + errdefer os.munmap(mmap_slice); + + // Map everything but the guard page as rw + os.mprotect( + mmap_slice[guard_end_offset..], + os.PROT_READ | os.PROT_WRITE, + ) catch |err| switch (err) { + error.AccessDenied => unreachable, + else => |e| return e, + }; + + break :mem mmap_slice; + }; + + const mmap_addr = @ptrToInt(mmap_slice.ptr); + + const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); + thread_ptr.data.memory = mmap_slice; + + var arg: usize = undefined; + if (@sizeOf(Context) != 0) { + arg = mmap_addr + context_start_offset; + const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); + context_ptr.* = context; + } + + if (std.Target.current.os.tag == .linux) { + const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | + os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | + os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | + os.CLONE_DETACHED | os.CLONE_SETTLS; + // This structure is only needed when targeting i386 + var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; + + const tls_area = mmap_slice[tls_start_offset..]; + const tp_value = os.linux.tls.prepareTLS(tls_area); + + const newtls = blk: { + if (std.Target.current.cpu.arch == .i386) { + user_desc = os.linux.user_desc{ + .entry_number = os.linux.tls.tls_image.gdt_entry_number, + .base_addr = tp_value, + .limit = 0xfffff, + .seg_32bit = 1, + .contents = 0, // Data + .read_exec_only = 0, + .limit_in_pages = 1, + .seg_not_present = 0, + .useable = 1, + }; + break :blk @ptrToInt(&user_desc); + } else { + break :blk tp_value; + } + }; + + const rc = os.linux.clone( + MainFuncs.linuxThreadMain, + mmap_addr + stack_end_offset, + flags, + arg, + &thread_ptr.data.handle, + newtls, + &thread_ptr.data.handle, + ); + switch (os.errno(rc)) { + 0 => return thread_ptr, + os.EAGAIN => return error.ThreadQuotaExceeded, + os.EINVAL => unreachable, + os.ENOMEM => return error.SystemResources, + os.ENOSPC => unreachable, + os.EPERM => unreachable, + os.EUSERS => unreachable, + else => |err| return os.unexpectedErrno(err), + } + } else { + @compileError("Unsupported OS"); + } +} + +pub const CpuCountError = error{ + PermissionDenied, + SystemResources, + Unexpected, +}; + +pub fn cpuCount() CpuCountError!usize { + switch (std.Target.current.os.tag) { + .linux => { + const cpu_set = try os.sched_getaffinity(0); + return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast + }, + .windows => { + return os.windows.peb().NumberOfProcessors; + }, + .openbsd => { + var count: c_int = undefined; + var count_size: usize = @sizeOf(c_int); + const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; + os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { + error.NameTooLong, error.UnknownName => unreachable, + else => |e| return e, + }; + return @intCast(usize, count); + }, + .haiku => { + var count: u32 = undefined; + var system_info: os.system_info = undefined; + const rc = os.system.get_system_info(&system_info); + count = system_info.cpu_count; + return @intCast(usize, count); + }, + else => { + var count: c_int = undefined; + var count_len: usize = @sizeOf(c_int); + const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; + os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { + error.NameTooLong, error.UnknownName => unreachable, + else => |e| return e, + }; + return @intCast(usize, count); + }, + } +} + +pub fn getCurrentThreadId() u64 { + switch (std.Target.current.os.tag) { + .linux => { + // Use the syscall directly as musl doesn't provide a wrapper. + return @bitCast(u32, os.linux.gettid()); + }, + .windows => { + return os.windows.kernel32.GetCurrentThreadId(); + }, + .macos, .ios, .watchos, .tvos => { + var thread_id: u64 = undefined; + // Pass thread=null to get the current thread ID. + assert(c.pthread_threadid_np(null, &thread_id) == 0); + return thread_id; + }, + .netbsd => { + return @bitCast(u32, c._lwp_self()); + }, + .freebsd => { + return @bitCast(u32, c.pthread_getthreadid_np()); + }, + .openbsd => { + return @bitCast(u32, c.getthrid()); + }, + .haiku => { + return @bitCast(u32, c.find_thread(null)); + }, + else => { + @compileError("getCurrentThreadId not implemented for this platform"); + }, + } +} + +test { + if (!builtin.single_threaded) { + std.testing.refAllDecls(@This()); + } +} diff --git a/lib/std/Thread/AutoResetEvent.zig b/lib/std/Thread/AutoResetEvent.zig new file mode 100644 index 0000000000..8b8b5658bf --- /dev/null +++ b/lib/std/Thread/AutoResetEvent.zig @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`. +//! Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like). +//! +//! AutoResetEvent has 3 possible states: +//! - UNSET: the AutoResetEvent is currently unset +//! - SET: the AutoResetEvent was notified before a wait() was called +//! - <StaticResetEvent pointer>: there is an active waiter waiting for a notification. +//! +//! When attempting to wait: +//! if the event is unset, it registers a ResetEvent pointer to be notified when the event is set +//! if the event is already set, then it consumes the notification and resets the event. +//! +//! When attempting to notify: +//! if the event is unset, then we set the event +//! if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent +//! +//! This ensures that the event is automatically reset after a wait() has been issued +//! and avoids the race condition when using StaticResetEvent in the following scenario: +//! thread 1 | thread 2 +//! StaticResetEvent.wait() | +//! | StaticResetEvent.set() +//! | StaticResetEvent.set() +//! StaticResetEvent.reset() | +//! StaticResetEvent.wait() | (missed the second .set() notification above) + +state: usize = UNSET, + +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const testing = std.testing; +const assert = std.debug.assert; +const StaticResetEvent = std.Thread.StaticResetEvent; +const AutoResetEvent = @This(); + +const UNSET = 0; +const SET = 1; + +/// the minimum alignment for the `*StaticResetEvent` created by wait*() +const event_align = std.math.max(@alignOf(StaticResetEvent), 2); + +pub fn wait(self: *AutoResetEvent) void { + self.waitFor(null) catch unreachable; +} + +pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { + return self.waitFor(timeout); +} + +fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { + // lazily initialized StaticResetEvent + var reset_event: StaticResetEvent align(event_align) = undefined; + var has_reset_event = false; + + var state = @atomicLoad(usize, &self.state, .SeqCst); + while (true) { + // consume a notification if there is any + if (state == SET) { + @atomicStore(usize, &self.state, UNSET, .SeqCst); + return; + } + + // check if theres currently a pending ResetEvent pointer already registered + if (state != UNSET) { + unreachable; // multiple waiting threads on the same AutoResetEvent + } + + // lazily initialize the ResetEvent if it hasn't been already + if (!has_reset_event) { + has_reset_event = true; + reset_event = .{}; + } + + // Since the AutoResetEvent currently isnt set, + // try to register our ResetEvent on it to wait + // for a set() call from another thread. + if (@cmpxchgWeak( + usize, + &self.state, + UNSET, + @ptrToInt(&reset_event), + .SeqCst, + .SeqCst, + )) |new_state| { + state = new_state; + continue; + } + + // if no timeout was specified, then just wait forever + const timeout_ns = timeout orelse { + reset_event.wait(); + return; + }; + + // wait with a timeout and return if signalled via set() + switch (reset_event.timedWait(timeout_ns)) { + .event_set => return, + .timed_out => {}, + } + + // If we timed out, we need to transition the AutoResetEvent back to UNSET. + // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. + state = @cmpxchgStrong( + usize, + &self.state, + @ptrToInt(&reset_event), + UNSET, + .SeqCst, + .SeqCst, + ) orelse return error.TimedOut; + + // We didn't manage to unregister ourselves from the state. + if (state == SET) { + unreachable; // AutoResetEvent notified without waking up the waiting thread + } else if (state != UNSET) { + unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out + } + + // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. + // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. + // We don't return error.TimedOut here as it technically notified us while we were "timing out". + reset_event.wait(); + return; + } +} + +pub fn set(self: *AutoResetEvent) void { + var state = @atomicLoad(usize, &self.state, .SeqCst); + while (true) { + // If the AutoResetEvent is already set, there is nothing else left to do + if (state == SET) { + return; + } + + // If the AutoResetEvent isn't set, + // then try to leave a notification for the wait() thread that we set() it. + if (state == UNSET) { + state = @cmpxchgWeak( + usize, + &self.state, + UNSET, + SET, + .SeqCst, + .SeqCst, + ) orelse return; + continue; + } + + // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. + // Try to acquire ownership of it so that we can wake it up. + // This also resets the AutoResetEvent so that there is no race condition as defined above. + if (@cmpxchgWeak( + usize, + &self.state, + state, + UNSET, + .SeqCst, + .SeqCst, + )) |new_state| { + state = new_state; + continue; + } + + const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state); + reset_event.set(); + return; + } +} + +test "basic usage" { + // test local code paths + { + var event = AutoResetEvent{}; + testing.expectError(error.TimedOut, event.timedWait(1)); + event.set(); + event.wait(); + } + + // test cross-thread signaling + if (builtin.single_threaded) + return; + + const Context = struct { + value: u128 = 0, + in: AutoResetEvent = AutoResetEvent{}, + out: AutoResetEvent = AutoResetEvent{}, + + const Self = @This(); + + fn sender(self: *Self) void { + testing.expect(self.value == 0); + self.value = 1; + self.out.set(); + + self.in.wait(); + testing.expect(self.value == 2); + self.value = 3; + self.out.set(); + + self.in.wait(); + testing.expect(self.value == 4); + } + + fn receiver(self: *Self) void { + self.out.wait(); + testing.expect(self.value == 1); + self.value = 2; + self.in.set(); + + self.out.wait(); + testing.expect(self.value == 3); + self.value = 4; + self.in.set(); + } + }; + + var context = Context{}; + const send_thread = try std.Thread.spawn(&context, Context.sender); + const recv_thread = try std.Thread.spawn(&context, Context.receiver); + + send_thread.wait(); + recv_thread.wait(); +} diff --git a/lib/std/Thread/Condition.zig b/lib/std/Thread/Condition.zig new file mode 100644 index 0000000000..a14b57f6b4 --- /dev/null +++ b/lib/std/Thread/Condition.zig @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! A condition provides a way for a kernel thread to block until it is signaled +//! to wake up. Spurious wakeups are possible. +//! This API supports static initialization and does not require deinitialization. + +impl: Impl = .{}, + +const std = @import("../std.zig"); +const Condition = @This(); +const windows = std.os.windows; +const linux = std.os.linux; +const Mutex = std.Thread.Mutex; +const assert = std.debug.assert; + +pub fn wait(cond: *Condition, mutex: *Mutex) void { + cond.impl.wait(mutex); +} + +pub fn signal(cond: *Condition) void { + cond.impl.signal(); +} + +pub fn broadcast(cond: *Condition) void { + cond.impl.broadcast(); +} + +const Impl = if (std.builtin.single_threaded) + SingleThreadedCondition +else if (std.Target.current.os.tag == .windows) + WindowsCondition +else if (std.Thread.use_pthreads) + PthreadCondition +else + AtomicCondition; + +pub const SingleThreadedCondition = struct { + pub fn wait(cond: *SingleThreadedCondition, mutex: *Mutex) void { + unreachable; // deadlock detected + } + + pub fn signal(cond: *SingleThreadedCondition) void {} + + pub fn broadcast(cond: *SingleThreadedCondition) void {} +}; + +pub const WindowsCondition = struct { + cond: windows.CONDITION_VARIABLE = windows.CONDITION_VARIABLE_INIT, + + pub fn wait(cond: *WindowsCondition, mutex: *Mutex) void { + const rc = windows.kernel32.SleepConditionVariableSRW( + &cond.cond, + &mutex.srwlock, + windows.INFINITE, + @as(windows.ULONG, 0), + ); + assert(rc != windows.FALSE); + } + + pub fn signal(cond: *WindowsCondition) void { + windows.kernel32.WakeConditionVariable(&cond.cond); + } + + pub fn broadcast(cond: *WindowsCondition) void { + windows.kernel32.WakeAllConditionVariable(&cond.cond); + } +}; + +pub const PthreadCondition = struct { + cond: std.c.pthread_cond_t = .{}, + + pub fn wait(cond: *PthreadCondition, mutex: *Mutex) void { + const rc = std.c.pthread_cond_wait(&cond.cond, &mutex.impl.pthread_mutex); + assert(rc == 0); + } + + pub fn signal(cond: *PthreadCondition) void { + const rc = std.c.pthread_cond_signal(&cond.cond); + assert(rc == 0); + } + + pub fn broadcast(cond: *PthreadCondition) void { + const rc = std.c.pthread_cond_broadcast(&cond.cond); + assert(rc == 0); + } +}; + +pub const AtomicCondition = struct { + pending: bool = false, + queue_mutex: Mutex = .{}, + queue_list: QueueList = .{}, + + pub const QueueList = std.SinglyLinkedList(QueueItem); + + pub const QueueItem = struct { + futex: i32 = 0, + + fn wait(cond: *@This()) void { + while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { + switch (std.Target.current.os.tag) { + .linux => { + switch (linux.getErrno(linux.futex_wait( + &cond.futex, + linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT, + 0, + null, + ))) { + 0 => {}, + std.os.EINTR => {}, + std.os.EAGAIN => {}, + else => unreachable, + } + }, + else => spinLoopHint(), + } + } + } + + fn notify(cond: *@This()) void { + @atomicStore(i32, &cond.futex, 1, .Release); + + switch (std.Target.current.os.tag) { + .linux => { + switch (linux.getErrno(linux.futex_wake( + &cond.futex, + linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE, + 1, + ))) { + 0 => {}, + std.os.EFAULT => {}, + else => unreachable, + } + }, + else => {}, + } + } + }; + + pub fn wait(cond: *AtomicCondition, mutex: *Mutex) void { + var waiter = QueueList.Node{ .data = .{} }; + + { + const held = cond.queue_mutex.acquire(); + defer held.release(); + + cond.queue_list.prepend(&waiter); + @atomicStore(bool, &cond.pending, true, .SeqCst); + } + + mutex.unlock(); + waiter.data.wait(); + mutex.lock(); + } + + pub fn signal(cond: *AtomicCondition) void { + if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) + return; + + const maybe_waiter = blk: { + const held = cond.queue_mutex.acquire(); + defer held.release(); + + const maybe_waiter = cond.queue_list.popFirst(); + @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); + break :blk maybe_waiter; + }; + + if (maybe_waiter) |waiter| + waiter.data.notify(); + } + + pub fn broadcast(cond: *AtomicCondition) void { + if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) + return; + + @atomicStore(bool, &cond.pending, false, .SeqCst); + + var waiters = blk: { + const held = cond.queue_mutex.acquire(); + defer held.release(); + + const waiters = cond.queue_list; + cond.queue_list = .{}; + break :blk waiters; + }; + + while (waiters.popFirst()) |waiter| + waiter.data.notify(); + } +}; diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig new file mode 100644 index 0000000000..94711bcda0 --- /dev/null +++ b/lib/std/Thread/Mutex.zig @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Lock may be held only once. If the same thread tries to acquire +//! the same mutex twice, it deadlocks. This type supports static +//! initialization and is at most `@sizeOf(usize)` in size. When an +//! application is built in single threaded release mode, all the +//! functions are no-ops. In single threaded debug mode, there is +//! deadlock detection. +//! +//! Example usage: +//! var m = Mutex{}; +//! +//! const lock = m.acquire(); +//! defer lock.release(); +//! ... critical code +//! +//! Non-blocking: +//! if (m.tryAcquire) |lock| { +//! defer lock.release(); +//! // ... critical section +//! } else { +//! // ... lock not acquired +//! } + +impl: Impl = .{}, + +const Mutex = @This(); +const std = @import("../std.zig"); +const builtin = std.builtin; +const os = std.os; +const assert = std.debug.assert; +const windows = os.windows; +const linux = os.linux; +const testing = std.testing; +const StaticResetEvent = std.thread.StaticResetEvent; + +/// Try to acquire the mutex without blocking. Returns `null` if the mutex is +/// unavailable. Otherwise returns `Held`. Call `release` on `Held`. +pub fn tryAcquire(m: *Mutex) ?Impl.Held { + return m.impl.tryAcquire(); +} + +/// Acquire the mutex. Deadlocks if the mutex is already +/// held by the calling thread. +pub fn acquire(m: *Mutex) Impl.Held { + return m.impl.acquire(); +} + +const Impl = if (builtin.single_threaded) + Dummy +else if (builtin.os.tag == .windows) + WindowsMutex +else if (std.Thread.use_pthreads) + PthreadMutex +else + AtomicMutex; + +pub const AtomicMutex = struct { + state: State = .unlocked, + + const State = enum(i32) { + unlocked, + locked, + waiting, + }; + + pub const Held = struct { + mutex: *AtomicMutex, + + pub fn release(held: Held) void { + switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) { + .unlocked => unreachable, + .locked => {}, + .waiting => held.mutex.unlockSlow(), + } + } + }; + + pub fn tryAcquire(m: *AtomicMutex) ?Held { + if (@cmpxchgStrong( + State, + &m.state, + .unlocked, + .locked, + .Acquire, + .Monotonic, + ) == null) { + return Held{ .mutex = m }; + } else { + return null; + } + } + + pub fn acquire(m: *AtomicMutex) Held { + switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) { + .unlocked => {}, + else => |s| m.lockSlow(s), + } + return Held{ .mutex = m }; + } + + fn lockSlow(m: *AtomicMutex, current_state: State) void { + @setCold(true); + var new_state = current_state; + + var spin: u8 = 0; + while (spin < 100) : (spin += 1) { + const state = @cmpxchgWeak( + State, + &m.state, + .unlocked, + new_state, + .Acquire, + .Monotonic, + ) orelse return; + + switch (state) { + .unlocked => {}, + .locked => {}, + .waiting => break, + } + + var iter = std.math.min(32, spin + 1); + while (iter > 0) : (iter -= 1) + std.Thread.spinLoopHint(); + } + + new_state = .waiting; + while (true) { + switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) { + .unlocked => return, + else => {}, + } + switch (std.Target.current.os.tag) { + .linux => { + switch (linux.getErrno(linux.futex_wait( + @ptrCast(*const i32, &m.state), + linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT, + @enumToInt(new_state), + null, + ))) { + 0 => {}, + std.os.EINTR => {}, + std.os.EAGAIN => {}, + else => unreachable, + } + }, + else => std.Thread.spinLoopHint(), + } + } + } + + fn unlockSlow(m: *AtomicMutex) void { + @setCold(true); + + switch (std.Target.current.os.tag) { + .linux => { + switch (linux.getErrno(linux.futex_wake( + @ptrCast(*const i32, &m.state), + linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE, + 1, + ))) { + 0 => {}, + std.os.EFAULT => {}, + else => unreachable, + } + }, + else => {}, + } + } +}; + +pub const PthreadMutex = struct { + pthread_mutex: std.c.pthread_mutex_t = .{}, + + pub const Held = struct { + mutex: *PthreadMutex, + + pub fn release(held: Held) void { + switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) { + 0 => return, + std.c.EINVAL => unreachable, + std.c.EAGAIN => unreachable, + std.c.EPERM => unreachable, + else => unreachable, + } + } + }; + + /// Try to acquire the mutex without blocking. Returns null if + /// the mutex is unavailable. Otherwise returns Held. Call + /// release on Held. + pub fn tryAcquire(m: *PthreadMutex) ?Held { + if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == 0) { + return Held{ .mutex = m }; + } else { + return null; + } + } + + /// Acquire the mutex. Will deadlock if the mutex is already + /// held by the calling thread. + pub fn acquire(m: *PthreadMutex) Held { + switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) { + 0 => return Held{ .mutex = m }, + std.c.EINVAL => unreachable, + std.c.EBUSY => unreachable, + std.c.EAGAIN => unreachable, + std.c.EDEADLK => unreachable, + std.c.EPERM => unreachable, + else => unreachable, + } + } +}; + +/// This has the sematics as `Mutex`, however it does not actually do any +/// synchronization. Operations are safety-checked no-ops. +pub const Dummy = struct { + lock: @TypeOf(lock_init) = lock_init, + + const lock_init = if (std.debug.runtime_safety) false else {}; + + pub const Held = struct { + mutex: *Dummy, + + pub fn release(held: Held) void { + if (std.debug.runtime_safety) { + held.mutex.lock = false; + } + } + }; + + /// Try to acquire the mutex without blocking. Returns null if + /// the mutex is unavailable. Otherwise returns Held. Call + /// release on Held. + pub fn tryAcquire(m: *Dummy) ?Held { + if (std.debug.runtime_safety) { + if (m.lock) return null; + m.lock = true; + } + return Held{ .mutex = m }; + } + + /// Acquire the mutex. Will deadlock if the mutex is already + /// held by the calling thread. + pub fn acquire(m: *Dummy) Held { + return m.tryAcquire() orelse @panic("deadlock detected"); + } +}; + +const WindowsMutex = struct { + srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT, + + pub const Held = struct { + mutex: *WindowsMutex, + + pub fn release(held: Held) void { + windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock); + } + }; + + pub fn tryAcquire(m: *WindowsMutex) ?Held { + if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) { + return Held{ .mutex = m }; + } else { + return null; + } + } + + pub fn acquire(m: *WindowsMutex) Held { + windows.kernel32.AcquireSRWLockExclusive(&m.srwlock); + return Held{ .mutex = m }; + } +}; + +const TestContext = struct { + mutex: *Mutex, + data: i128, + + const incr_count = 10000; +}; + +test "basic usage" { + var mutex = Mutex{}; + + var context = TestContext{ + .mutex = &mutex, + .data = 0, + }; + + if (builtin.single_threaded) { + worker(&context); + testing.expect(context.data == TestContext.incr_count); + } else { + const thread_count = 10; + var threads: [thread_count]*std.Thread = undefined; + for (threads) |*t| { + t.* = try std.Thread.spawn(&context, worker); + } + for (threads) |t| + t.wait(); + + testing.expect(context.data == thread_count * TestContext.incr_count); + } +} + +fn worker(ctx: *TestContext) void { + var i: usize = 0; + while (i != TestContext.incr_count) : (i += 1) { + const held = ctx.mutex.acquire(); + defer held.release(); + + ctx.data += 1; + } +} diff --git a/lib/std/Thread/ResetEvent.zig b/lib/std/Thread/ResetEvent.zig new file mode 100644 index 0000000000..622f9be98e --- /dev/null +++ b/lib/std/Thread/ResetEvent.zig @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! A thread-safe resource which supports blocking until signaled. +//! This API is for kernel threads, not evented I/O. +//! This API requires being initialized at runtime, and initialization +//! can fail. Once initialized, the core operations cannot fail. +//! If you need an abstraction that cannot fail to be initialized, see +//! `std.Thread.StaticResetEvent`. However if you can handle initialization failure, +//! it is preferred to use `ResetEvent`. + +const ResetEvent = @This(); +const std = @import("../std.zig"); +const builtin = std.builtin; +const testing = std.testing; +const assert = std.debug.assert; +const c = std.c; +const os = std.os; +const time = std.time; + +impl: Impl, + +pub const Impl = if (builtin.single_threaded) + std.Thread.StaticResetEvent.DebugEvent +else if (std.Target.current.isDarwin()) + DarwinEvent +else if (std.Thread.use_pthreads) + PosixEvent +else + std.Thread.StaticResetEvent.AtomicEvent; + +pub const InitError = error{SystemResources}; + +/// After `init`, it is legal to call any other function. +pub fn init(ev: *ResetEvent) InitError!void { + return ev.impl.init(); +} + +/// This function is not thread-safe. +/// After `deinit`, the only legal function to call is `init`. +pub fn deinit(ev: *ResetEvent) void { + return ev.impl.deinit(); +} + +/// Sets the event if not already set and wakes up all the threads waiting on +/// the event. It is safe to call `set` multiple times before calling `wait`. +/// However it is illegal to call `set` after `wait` is called until the event +/// is `reset`. This function is thread-safe. +pub fn set(ev: *ResetEvent) void { + return ev.impl.set(); +} + +/// Resets the event to its original, unset state. +/// This function is *not* thread-safe. It is equivalent to calling +/// `deinit` followed by `init` but without the possibility of failure. +pub fn reset(ev: *ResetEvent) void { + return ev.impl.reset(); +} + +/// Wait for the event to be set by blocking the current thread. +/// Thread-safe. No spurious wakeups. +/// Upon return from `wait`, the only functions available to be called +/// in `ResetEvent` are `reset` and `deinit`. +pub fn wait(ev: *ResetEvent) void { + return ev.impl.wait(); +} + +pub const TimedWaitResult = enum { event_set, timed_out }; + +/// Wait for the event to be set by blocking the current thread. +/// A timeout in nanoseconds can be provided as a hint for how +/// long the thread should block on the unset event before returning +/// `TimedWaitResult.timed_out`. +/// Thread-safe. No precision of timing is guaranteed. +/// Upon return from `wait`, the only functions available to be called +/// in `ResetEvent` are `reset` and `deinit`. +pub fn timedWait(ev: *ResetEvent, timeout_ns: u64) TimedWaitResult { + return ev.impl.timedWait(timeout_ns); +} + +/// Apple has decided to not support POSIX semaphores, so we go with a +/// different approach using Grand Central Dispatch. This API is exposed +/// by libSystem so it is guaranteed to be available on all Darwin platforms. +pub const DarwinEvent = struct { + sem: c.dispatch_semaphore_t = undefined, + + pub fn init(ev: *DarwinEvent) !void { + ev.* = .{ + .sem = c.dispatch_semaphore_create(0) orelse return error.SystemResources, + }; + } + + pub fn deinit(ev: *DarwinEvent) void { + c.dispatch_release(ev.sem); + ev.* = undefined; + } + + pub fn set(ev: *DarwinEvent) void { + // Empirically this returns the numerical value of the semaphore. + _ = c.dispatch_semaphore_signal(ev.sem); + } + + pub fn wait(ev: *DarwinEvent) void { + assert(c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_FOREVER) == 0); + } + + pub fn timedWait(ev: *DarwinEvent, timeout_ns: u64) TimedWaitResult { + const t = c.dispatch_time(c.DISPATCH_TIME_NOW, @intCast(i64, timeout_ns)); + if (c.dispatch_semaphore_wait(ev.sem, t) != 0) { + return .timed_out; + } else { + return .event_set; + } + } + + pub fn reset(ev: *DarwinEvent) void { + // Keep calling until the semaphore goes back down to 0. + while (c.dispatch_semaphore_wait(ev.sem, c.DISPATCH_TIME_NOW) == 0) {} + } +}; + +/// POSIX semaphores must be initialized at runtime because they are allowed to +/// be implemented as file descriptors, in which case initialization would require +/// a syscall to open the fd. +pub const PosixEvent = struct { + sem: c.sem_t = undefined, + + pub fn init(ev: *PosixEvent) !void { + switch (c.getErrno(c.sem_init(&ev.sem, 0, 0))) { + 0 => return, + else => return error.SystemResources, + } + } + + pub fn deinit(ev: *PosixEvent) void { + assert(c.sem_destroy(&ev.sem) == 0); + ev.* = undefined; + } + + pub fn set(ev: *PosixEvent) void { + assert(c.sem_post(&ev.sem) == 0); + } + + pub fn wait(ev: *PosixEvent) void { + while (true) { + switch (c.getErrno(c.sem_wait(&ev.sem))) { + 0 => return, + c.EINTR => continue, + c.EINVAL => unreachable, + else => unreachable, + } + } + } + + pub fn timedWait(ev: *PosixEvent, timeout_ns: u64) TimedWaitResult { + var ts: os.timespec = undefined; + var timeout_abs = timeout_ns; + os.clock_gettime(os.CLOCK_REALTIME, &ts) catch return .timed_out; + timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; + timeout_abs += @intCast(u64, ts.tv_nsec); + ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); + ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); + while (true) { + switch (c.getErrno(c.sem_timedwait(&ev.sem, &ts))) { + 0 => return .event_set, + c.EINTR => continue, + c.EINVAL => unreachable, + c.ETIMEDOUT => return .timed_out, + else => unreachable, + } + } + } + + pub fn reset(ev: *PosixEvent) void { + while (true) { + switch (c.getErrno(c.sem_trywait(&ev.sem))) { + 0 => continue, // Need to make it go to zero. + c.EINTR => continue, + c.EINVAL => unreachable, + c.EAGAIN => return, // The semaphore currently has the value zero. + else => unreachable, + } + } + } +}; + +test "basic usage" { + var event: ResetEvent = undefined; + try event.init(); + defer event.deinit(); + + // test event setting + event.set(); + + // test event resetting + event.reset(); + + // test event waiting (non-blocking) + event.set(); + event.wait(); + event.reset(); + + event.set(); + testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); + + // test cross-thread signaling + if (builtin.single_threaded) + return; + + const Context = struct { + const Self = @This(); + + value: u128, + in: ResetEvent, + out: ResetEvent, + + fn init(self: *Self) !void { + self.* = .{ + .value = 0, + .in = undefined, + .out = undefined, + }; + try self.in.init(); + try self.out.init(); + } + + fn deinit(self: *Self) void { + self.in.deinit(); + self.out.deinit(); + self.* = undefined; + } + + fn sender(self: *Self) void { + // update value and signal input + testing.expect(self.value == 0); + self.value = 1; + self.in.set(); + + // wait for receiver to update value and signal output + self.out.wait(); + testing.expect(self.value == 2); + + // update value and signal final input + self.value = 3; + self.in.set(); + } + + fn receiver(self: *Self) void { + // wait for sender to update value and signal input + self.in.wait(); + assert(self.value == 1); + + // update value and signal output + self.in.reset(); + self.value = 2; + self.out.set(); + + // wait for sender to update value and signal final input + self.in.wait(); + assert(self.value == 3); + } + + fn sleeper(self: *Self) void { + self.in.set(); + time.sleep(time.ns_per_ms * 2); + self.value = 5; + self.out.set(); + } + + fn timedWaiter(self: *Self) !void { + self.in.wait(); + testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); + try self.out.timedWait(time.ns_per_ms * 100); + testing.expect(self.value == 5); + } + }; + + var context: Context = undefined; + try context.init(); + defer context.deinit(); + const receiver = try std.Thread.spawn(&context, Context.receiver); + defer receiver.wait(); + context.sender(); + + if (false) { + // I have now observed this fail on macOS, Windows, and Linux. + // https://github.com/ziglang/zig/issues/7009 + var timed = Context.init(); + defer timed.deinit(); + const sleeper = try std.Thread.spawn(&timed, Context.sleeper); + defer sleeper.wait(); + try timed.timedWaiter(); + } +} diff --git a/lib/std/Thread/RwLock.zig b/lib/std/Thread/RwLock.zig new file mode 100644 index 0000000000..1d606a9cf1 --- /dev/null +++ b/lib/std/Thread/RwLock.zig @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! A lock that supports one writer or many readers. +//! This API is for kernel threads, not evented I/O. +//! This API requires being initialized at runtime, and initialization +//! can fail. Once initialized, the core operations cannot fail. + +impl: Impl, + +const RwLock = @This(); +const std = @import("../std.zig"); +const builtin = std.builtin; +const assert = std.debug.assert; +const Mutex = std.Thread.Mutex; +const Semaphore = std.Semaphore; +const CondVar = std.CondVar; + +pub const Impl = if (builtin.single_threaded) + SingleThreadedRwLock +else if (std.Thread.use_pthreads) + PthreadRwLock +else + DefaultRwLock; + +pub fn init(rwl: *RwLock) void { + return rwl.impl.init(); +} + +pub fn deinit(rwl: *RwLock) void { + return rwl.impl.deinit(); +} + +/// Attempts to obtain exclusive lock ownership. +/// Returns `true` if the lock is obtained, `false` otherwise. +pub fn tryLock(rwl: *RwLock) bool { + return rwl.impl.tryLock(); +} + +/// Blocks until exclusive lock ownership is acquired. +pub fn lock(rwl: *RwLock) void { + return rwl.impl.lock(); +} + +/// Releases a held exclusive lock. +/// Asserts the lock is held exclusively. +pub fn unlock(rwl: *RwLock) void { + return rwl.impl.unlock(); +} + +/// Attempts to obtain shared lock ownership. +/// Returns `true` if the lock is obtained, `false` otherwise. +pub fn tryLockShared(rwl: *RwLock) bool { + return rwl.impl.tryLockShared(); +} + +/// Blocks until shared lock ownership is acquired. +pub fn lockShared(rwl: *RwLock) void { + return rwl.impl.lockShared(); +} + +/// Releases a held shared lock. +pub fn unlockShared(rwl: *RwLock) void { + return rwl.impl.unlockShared(); +} + +/// Single-threaded applications use this for deadlock checks in +/// debug mode, and no-ops in release modes. +pub const SingleThreadedRwLock = struct { + state: enum { unlocked, locked_exclusive, locked_shared }, + shared_count: usize, + + pub fn init(rwl: *SingleThreadedRwLock) void { + rwl.* = .{ + .state = .unlocked, + .shared_count = 0, + }; + } + + pub fn deinit(rwl: *SingleThreadedRwLock) void { + assert(rwl.state == .unlocked); + assert(rwl.shared_count == 0); + } + + /// Attempts to obtain exclusive lock ownership. + /// Returns `true` if the lock is obtained, `false` otherwise. + pub fn tryLock(rwl: *SingleThreadedRwLock) bool { + switch (rwl.state) { + .unlocked => { + assert(rwl.shared_count == 0); + rwl.state = .locked_exclusive; + return true; + }, + .locked_exclusive, .locked_shared => return false, + } + } + + /// Blocks until exclusive lock ownership is acquired. + pub fn lock(rwl: *SingleThreadedRwLock) void { + assert(rwl.state == .unlocked); // deadlock detected + assert(rwl.shared_count == 0); // corrupted state detected + rwl.state = .locked_exclusive; + } + + /// Releases a held exclusive lock. + /// Asserts the lock is held exclusively. + pub fn unlock(rwl: *SingleThreadedRwLock) void { + assert(rwl.state == .locked_exclusive); + assert(rwl.shared_count == 0); // corrupted state detected + rwl.state = .unlocked; + } + + /// Attempts to obtain shared lock ownership. + /// Returns `true` if the lock is obtained, `false` otherwise. + pub fn tryLockShared(rwl: *SingleThreadedRwLock) bool { + switch (rwl.state) { + .unlocked => { + rwl.state = .locked_shared; + assert(rwl.shared_count == 0); + rwl.shared_count = 1; + return true; + }, + .locked_exclusive, .locked_shared => return false, + } + } + + /// Blocks until shared lock ownership is acquired. + pub fn lockShared(rwl: *SingleThreadedRwLock) void { + switch (rwl.state) { + .unlocked => { + rwl.state = .locked_shared; + assert(rwl.shared_count == 0); + rwl.shared_count = 1; + }, + .locked_shared => { + rwl.shared_count += 1; + }, + .locked_exclusive => unreachable, // deadlock detected + } + } + + /// Releases a held shared lock. + pub fn unlockShared(rwl: *SingleThreadedRwLock) void { + switch (rwl.state) { + .unlocked => unreachable, // too many calls to `unlockShared` + .locked_exclusive => unreachable, // exclusively held lock + .locked_shared => { + rwl.shared_count -= 1; + if (rwl.shared_count == 0) { + rwl.state = .unlocked; + } + }, + } + } +}; + +pub const PthreadRwLock = struct { + rwlock: pthread_rwlock_t, + + pub fn init(rwl: *PthreadRwLock) void { + rwl.* = .{ .rwlock = .{} }; + } + + pub fn deinit(rwl: *PthreadRwLock) void { + const safe_rc = switch (std.builtin.os.tag) { + .dragonfly, .netbsd => std.os.EAGAIN, + else => 0, + }; + + const rc = std.c.pthread_rwlock_destroy(&rwl.rwlock); + assert(rc == 0 or rc == safe_rc); + + rwl.* = undefined; + } + + pub fn tryLock(rwl: *PthreadRwLock) bool { + return pthread_rwlock_trywrlock(&rwl.rwlock) == 0; + } + + pub fn lock(rwl: *PthreadRwLock) void { + const rc = pthread_rwlock_wrlock(&rwl.rwlock); + assert(rc == 0); + } + + pub fn unlock(rwl: *PthreadRwLock) void { + const rc = pthread_rwlock_unlock(&rwl.rwlock); + assert(rc == 0); + } + + pub fn tryLockShared(rwl: *PthreadRwLock) bool { + return pthread_rwlock_tryrdlock(&rwl.rwlock) == 0; + } + + pub fn lockShared(rwl: *PthreadRwLock) void { + const rc = pthread_rwlock_rdlock(&rwl.rwlock); + assert(rc == 0); + } + + pub fn unlockShared(rwl: *PthreadRwLock) void { + const rc = pthread_rwlock_unlock(&rwl.rwlock); + assert(rc == 0); + } +}; + +pub const DefaultRwLock = struct { + state: usize, + mutex: Mutex, + semaphore: Semaphore, + + const IS_WRITING: usize = 1; + const WRITER: usize = 1 << 1; + const READER: usize = 1 << (1 + std.meta.bitCount(Count)); + const WRITER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, WRITER); + const READER_MASK: usize = std.math.maxInt(Count) << @ctz(usize, READER); + const Count = std.meta.Int(.unsigned, @divFloor(std.meta.bitCount(usize) - 1, 2)); + + pub fn init(rwl: *DefaultRwLock) void { + rwl.* = .{ + .state = 0, + .mutex = Mutex.init(), + .semaphore = Semaphore.init(0), + }; + } + + pub fn deinit(rwl: *DefaultRwLock) void { + rwl.semaphore.deinit(); + rwl.mutex.deinit(); + rwl.* = undefined; + } + + pub fn tryLock(rwl: *DefaultRwLock) bool { + if (rwl.mutex.tryLock()) { + const state = @atomicLoad(usize, &rwl.state, .SeqCst); + if (state & READER_MASK == 0) { + _ = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst); + return true; + } + + rwl.mutex.unlock(); + } + + return false; + } + + pub fn lock(rwl: *DefaultRwLock) void { + _ = @atomicRmw(usize, &rwl.state, .Add, WRITER, .SeqCst); + rwl.mutex.lock(); + + const state = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .SeqCst); + if (state & READER_MASK != 0) + rwl.semaphore.wait(); + } + + pub fn unlock(rwl: *DefaultRwLock) void { + _ = @atomicRmw(usize, &rwl.state, .And, ~IS_WRITING, .SeqCst); + rwl.mutex.unlock(); + } + + pub fn tryLockShared(rwl: *DefaultRwLock) bool { + const state = @atomicLoad(usize, &rwl.state, .SeqCst); + if (state & (IS_WRITING | WRITER_MASK) == 0) { + _ = @cmpxchgStrong( + usize, + &rwl.state, + state, + state + READER, + .SeqCst, + .SeqCst, + ) orelse return true; + } + + if (rwl.mutex.tryLock()) { + _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst); + rwl.mutex.unlock(); + return true; + } + + return false; + } + + pub fn lockShared(rwl: *DefaultRwLock) void { + var state = @atomicLoad(usize, &rwl.state, .SeqCst); + while (state & (IS_WRITING | WRITER_MASK) == 0) { + state = @cmpxchgWeak( + usize, + &rwl.state, + state, + state + READER, + .SeqCst, + .SeqCst, + ) orelse return; + } + + rwl.mutex.lock(); + _ = @atomicRmw(usize, &rwl.state, .Add, READER, .SeqCst); + rwl.mutex.unlock(); + } + + pub fn unlockShared(rwl: *DefaultRwLock) void { + const state = @atomicRmw(usize, &rwl.state, .Sub, READER, .SeqCst); + + if ((state & READER_MASK == READER) and (state & IS_WRITING != 0)) + rwl.semaphore.post(); + } +}; diff --git a/lib/std/Thread/Semaphore.zig b/lib/std/Thread/Semaphore.zig new file mode 100644 index 0000000000..169975b362 --- /dev/null +++ b/lib/std/Thread/Semaphore.zig @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! A semaphore is an unsigned integer that blocks the kernel thread if +//! the number would become negative. +//! This API supports static initialization and does not require deinitialization. + +mutex: Mutex = .{}, +cond: Condition = .{}, +/// It is OK to initialize this field to any value. +permits: usize = 0, + +const Semaphore = @This(); +const std = @import("../std.zig"); +const Mutex = std.Thread.Mutex; +const Condition = std.Thread.Condition; + +pub fn wait(sem: *Semaphore) void { + const held = sem.mutex.acquire(); + defer held.release(); + + while (sem.permits == 0) + sem.cond.wait(&sem.mutex); + + sem.permits -= 1; + if (sem.permits > 0) + sem.cond.signal(); +} + +pub fn post(sem: *Semaphore) void { + const held = sem.mutex.acquire(); + defer held.release(); + + sem.permits += 1; + sem.cond.signal(); +} diff --git a/lib/std/reset_event.zig b/lib/std/Thread/StaticResetEvent.zig index 5da53985c6..6d90d7cf9a 100644 --- a/lib/std/reset_event.zig +++ b/lib/std/Thread/StaticResetEvent.zig @@ -1,245 +1,188 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -const std = @import("std.zig"); -const builtin = @import("builtin"); -const testing = std.testing; -const SpinLock = std.SpinLock; + +//! A thread-safe resource which supports blocking until signaled. +//! This API is for kernel threads, not evented I/O. +//! This API is statically initializable. It cannot fail to be initialized +//! and it requires no deinitialization. The downside is that it may not +//! integrate as cleanly into other synchronization APIs, or, in a worst case, +//! may be forced to fall back on spin locking. As a rule of thumb, prefer +//! to use `std.Thread.ResetEvent` when possible, and use `StaticResetEvent` when +//! the logic needs stronger API guarantees. + +const std = @import("../std.zig"); +const StaticResetEvent = @This(); const assert = std.debug.assert; -const c = std.c; const os = std.os; const time = std.time; -const linux = os.linux; -const windows = os.windows; - -/// A resource object which supports blocking until signaled. -/// Once finished, the `deinit()` method should be called for correctness. -pub const ResetEvent = struct { - os_event: OsEvent, - - pub const OsEvent = if (builtin.single_threaded) - DebugEvent - else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux) - PosixEvent - else - AtomicEvent; - - pub fn init() ResetEvent { - return ResetEvent{ .os_event = OsEvent.init() }; - } - - pub fn deinit(self: *ResetEvent) void { - self.os_event.deinit(); - } - - /// Returns whether or not the event is currenetly set - pub fn isSet(self: *ResetEvent) bool { - return self.os_event.isSet(); - } - - /// Sets the event if not already set and - /// wakes up all the threads waiting on the event. - pub fn set(self: *ResetEvent) void { - return self.os_event.set(); - } - - /// Resets the event to its original, unset state. - pub fn reset(self: *ResetEvent) void { - return self.os_event.reset(); - } - - /// Wait for the event to be set by blocking the current thread. - pub fn wait(self: *ResetEvent) void { - return self.os_event.wait(null) catch unreachable; - } - - /// Wait for the event to be set by blocking the current thread. - /// A timeout in nanoseconds can be provided as a hint for how - /// long the thread should block on the unset event before throwing error.TimedOut. - pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { - return self.os_event.wait(timeout_ns); - } -}; +const linux = std.os.linux; +const windows = std.os.windows; +const testing = std.testing; -const DebugEvent = struct { - is_set: bool, +impl: Impl = .{}, - fn init() DebugEvent { - return DebugEvent{ .is_set = false }; - } +pub const Impl = if (std.builtin.single_threaded) + DebugEvent +else + AtomicEvent; - fn deinit(self: *DebugEvent) void { - self.* = undefined; - } +/// Sets the event if not already set and wakes up all the threads waiting on +/// the event. It is safe to call `set` multiple times before calling `wait`. +/// However it is illegal to call `set` after `wait` is called until the event +/// is `reset`. This function is thread-safe. +pub fn set(ev: *StaticResetEvent) void { + return ev.impl.set(); +} - fn isSet(self: *DebugEvent) bool { - return self.is_set; - } +/// Wait for the event to be set by blocking the current thread. +/// Thread-safe. No spurious wakeups. +/// Upon return from `wait`, the only function available to be called +/// in `StaticResetEvent` is `reset`. +pub fn wait(ev: *StaticResetEvent) void { + return ev.impl.wait(); +} - fn reset(self: *DebugEvent) void { - self.is_set = false; - } +/// Resets the event to its original, unset state. +/// This function is *not* thread-safe. It is equivalent to calling +/// `deinit` followed by `init` but without the possibility of failure. +pub fn reset(ev: *StaticResetEvent) void { + return ev.impl.reset(); +} - fn set(self: *DebugEvent) void { - self.is_set = true; - } +pub const TimedWaitResult = std.Thread.ResetEvent.TimedWaitResult; + +/// Wait for the event to be set by blocking the current thread. +/// A timeout in nanoseconds can be provided as a hint for how +/// long the thread should block on the unset event before returning +/// `TimedWaitResult.timed_out`. +/// Thread-safe. No precision of timing is guaranteed. +/// Upon return from `timedWait`, the only function available to be called +/// in `StaticResetEvent` is `reset`. +pub fn timedWait(ev: *StaticResetEvent, timeout_ns: u64) TimedWaitResult { + return ev.impl.timedWait(timeout_ns); +} - fn wait(self: *DebugEvent, timeout: ?u64) !void { - if (self.is_set) - return; - if (timeout != null) - return error.TimedOut; - @panic("deadlock detected"); - } -}; +/// For single-threaded builds, we use this to detect deadlocks. +/// In unsafe modes this ends up being no-ops. +pub const DebugEvent = struct { + state: State = State.unset, -const PosixEvent = struct { - is_set: bool, - cond: c.pthread_cond_t, - mutex: c.pthread_mutex_t, - - fn init() PosixEvent { - return PosixEvent{ - .is_set = false, - .cond = c.PTHREAD_COND_INITIALIZER, - .mutex = c.PTHREAD_MUTEX_INITIALIZER, - }; - } + const State = enum { + unset, + set, + waited, + }; - fn deinit(self: *PosixEvent) void { - // on dragonfly or openbsd, *destroy() functions can return EINVAL - // for statically initialized pthread structures - const err = if (builtin.os.tag == .dragonfly or builtin.os.tag == .openbsd) - os.EINVAL - else - 0; - - const retm = c.pthread_mutex_destroy(&self.mutex); - assert(retm == 0 or retm == err); - const retc = c.pthread_cond_destroy(&self.cond); - assert(retc == 0 or retc == err); + /// This function is provided so that this type can be re-used inside + /// `std.Thread.ResetEvent`. + pub fn init(ev: *DebugEvent) void { + ev.* = .{}; } - fn isSet(self: *PosixEvent) bool { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - - return self.is_set; + /// This function is provided so that this type can be re-used inside + /// `std.Thread.ResetEvent`. + pub fn deinit(ev: *DebugEvent) void { + ev.* = undefined; } - fn reset(self: *PosixEvent) void { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - - self.is_set = false; + pub fn set(ev: *DebugEvent) void { + switch (ev.state) { + .unset => ev.state = .set, + .set => {}, + .waited => unreachable, // Not allowed to call `set` until `reset`. + } } - fn set(self: *PosixEvent) void { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - - if (!self.is_set) { - self.is_set = true; - assert(c.pthread_cond_broadcast(&self.cond) == 0); + pub fn wait(ev: *DebugEvent) void { + switch (ev.state) { + .unset => unreachable, // Deadlock detected. + .set => return, + .waited => unreachable, // Not allowed to call `wait` until `reset`. } } - fn wait(self: *PosixEvent, timeout: ?u64) !void { - assert(c.pthread_mutex_lock(&self.mutex) == 0); - defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); - - // quick guard before possibly calling time syscalls below - if (self.is_set) - return; - - var ts: os.timespec = undefined; - if (timeout) |timeout_ns| { - var timeout_abs = timeout_ns; - if (comptime std.Target.current.isDarwin()) { - var tv: os.darwin.timeval = undefined; - assert(os.darwin.gettimeofday(&tv, null) == 0); - timeout_abs += @intCast(u64, tv.tv_sec) * time.ns_per_s; - timeout_abs += @intCast(u64, tv.tv_usec) * time.ns_per_us; - } else { - os.clock_gettime(os.CLOCK_REALTIME, &ts) catch unreachable; - timeout_abs += @intCast(u64, ts.tv_sec) * time.ns_per_s; - timeout_abs += @intCast(u64, ts.tv_nsec); - } - ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), @divFloor(timeout_abs, time.ns_per_s)); - ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.ns_per_s)); + pub fn timedWait(ev: *DebugEvent, timeout: u64) TimedWaitResult { + switch (ev.state) { + .unset => return .timed_out, + .set => return .event_set, + .waited => unreachable, // Not allowed to call `wait` until `reset`. } + } - while (!self.is_set) { - const rc = switch (timeout == null) { - true => c.pthread_cond_wait(&self.cond, &self.mutex), - else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts), - }; - switch (rc) { - 0 => {}, - os.ETIMEDOUT => return error.TimedOut, - os.EINVAL => unreachable, - os.EPERM => unreachable, - else => unreachable, - } - } + pub fn reset(ev: *DebugEvent) void { + ev.state = .unset; } }; -const AtomicEvent = struct { - waiters: u32, +pub const AtomicEvent = struct { + waiters: u32 = 0, const WAKE = 1 << 0; const WAIT = 1 << 1; - fn init() AtomicEvent { - return AtomicEvent{ .waiters = 0 }; + /// This function is provided so that this type can be re-used inside + /// `std.Thread.ResetEvent`. + pub fn init(ev: *AtomicEvent) void { + ev.* = .{}; } - fn deinit(self: *AtomicEvent) void { - self.* = undefined; + /// This function is provided so that this type can be re-used inside + /// `std.Thread.ResetEvent`. + pub fn deinit(ev: *AtomicEvent) void { + ev.* = undefined; } - fn isSet(self: *const AtomicEvent) bool { - return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE; - } - - fn reset(self: *AtomicEvent) void { - @atomicStore(u32, &self.waiters, 0, .Monotonic); + pub fn set(ev: *AtomicEvent) void { + const waiters = @atomicRmw(u32, &ev.waiters, .Xchg, WAKE, .Release); + if (waiters >= WAIT) { + return Futex.wake(&ev.waiters, waiters >> 1); + } } - fn set(self: *AtomicEvent) void { - const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release); - if (waiters >= WAIT) { - return Futex.wake(&self.waiters, waiters >> 1); + pub fn wait(ev: *AtomicEvent) void { + switch (ev.timedWait(null)) { + .timed_out => unreachable, + .event_set => return, } } - fn wait(self: *AtomicEvent, timeout: ?u64) !void { - var waiters = @atomicLoad(u32, &self.waiters, .Acquire); + pub fn timedWait(ev: *AtomicEvent, timeout: ?u64) TimedWaitResult { + var waiters = @atomicLoad(u32, &ev.waiters, .Acquire); while (waiters != WAKE) { - waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse return Futex.wait(&self.waiters, timeout); + waiters = @cmpxchgWeak(u32, &ev.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) orelse { + if (Futex.wait(&ev.waiters, timeout)) |_| { + return .event_set; + } else |_| { + return .timed_out; + } + }; } + return .event_set; } - pub const Futex = switch (builtin.os.tag) { + pub fn reset(ev: *AtomicEvent) void { + @atomicStore(u32, &ev.waiters, 0, .Monotonic); + } + + pub const Futex = switch (std.Target.current.os.tag) { .windows => WindowsFutex, .linux => LinuxFutex, else => SpinFutex, }; - const SpinFutex = struct { + pub const SpinFutex = struct { fn wake(waiters: *u32, wake_count: u32) void {} fn wait(waiters: *u32, timeout: ?u64) !void { - // TODO: handle platforms where a monotonic timer isnt available var timer: time.Timer = undefined; if (timeout != null) - timer = time.Timer.start() catch unreachable; + timer = time.Timer.start() catch return error.TimedOut; while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { - SpinLock.yield(); + std.os.sched_yield() catch std.Thread.spinLoopHint(); if (timeout) |timeout_ns| { if (timer.read() >= timeout_ns) return error.TimedOut; @@ -248,7 +191,7 @@ const AtomicEvent = struct { } }; - const LinuxFutex = struct { + pub const LinuxFutex = struct { fn wake(waiters: *u32, wake_count: u32) void { const waiting = std.math.maxInt(i32); // wake_count const ptr = @ptrCast(*const i32, waiters); @@ -283,7 +226,7 @@ const AtomicEvent = struct { } }; - const WindowsFutex = struct { + pub const WindowsFutex = struct { pub fn wake(waiters: *u32, wake_count: u32) void { const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); const key = @ptrCast(*const c_void, waiters); @@ -350,7 +293,7 @@ const AtomicEvent = struct { return @intToPtr(?windows.HANDLE, handle); }, LOADING => { - SpinLock.yield(); + std.os.sched_yield() catch std.Thread.spinLoopHint(); handle = @atomicLoad(usize, &event_handle, .Monotonic); }, else => { @@ -362,48 +305,33 @@ const AtomicEvent = struct { }; }; -test "ResetEvent" { - var event = ResetEvent.init(); - defer event.deinit(); +test "basic usage" { + var event = StaticResetEvent{}; // test event setting - testing.expect(event.isSet() == false); event.set(); - testing.expect(event.isSet() == true); // test event resetting event.reset(); - testing.expect(event.isSet() == false); // test event waiting (non-blocking) event.set(); event.wait(); - try event.timedWait(1); + event.reset(); + + event.set(); + testing.expectEqual(TimedWaitResult.event_set, event.timedWait(1)); // test cross-thread signaling - if (builtin.single_threaded) + if (std.builtin.single_threaded) return; const Context = struct { const Self = @This(); - value: u128, - in: ResetEvent, - out: ResetEvent, - - fn init() Self { - return Self{ - .value = 0, - .in = ResetEvent.init(), - .out = ResetEvent.init(), - }; - } - - fn deinit(self: *Self) void { - self.in.deinit(); - self.out.deinit(); - self.* = undefined; - } + value: u128 = 0, + in: StaticResetEvent = .{}, + out: StaticResetEvent = .{}, fn sender(self: *Self) void { // update value and signal input @@ -444,14 +372,13 @@ test "ResetEvent" { fn timedWaiter(self: *Self) !void { self.in.wait(); - testing.expectError(error.TimedOut, self.out.timedWait(time.ns_per_us)); + testing.expectEqual(TimedWaitResult.timed_out, self.out.timedWait(time.ns_per_us)); try self.out.timedWait(time.ns_per_ms * 100); testing.expect(self.value == 5); } }; - var context = Context.init(); - defer context.deinit(); + var context = Context{}; const receiver = try std.Thread.spawn(&context, Context.receiver); defer receiver.wait(); context.sender(); diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index e5ad26cb45..7b0d9ea4dd 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -99,6 +99,16 @@ pub fn ArrayHashMap( }; } + /// `ArrayHashMap` takes ownership of the passed in array list. The array list must have + /// been allocated with `allocator`. + /// Deinitialize with `deinit`. + pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self { + return Self{ + .unmanaged = try Unmanaged.fromOwnedArrayList(allocator, entries), + .allocator = allocator, + }; + } + pub fn deinit(self: *Self) void { self.unmanaged.deinit(self.allocator); self.* = undefined; @@ -214,17 +224,38 @@ pub fn ArrayHashMap( } /// If there is an `Entry` with a matching key, it is deleted from - /// the hash map, and then returned from this function. - pub fn remove(self: *Self, key: K) ?Entry { - return self.unmanaged.remove(key); + /// the hash map, and then returned from this function. The entry is + /// removed from the underlying array by swapping it with the last + /// element. + pub fn swapRemove(self: *Self, key: K) ?Entry { + return self.unmanaged.swapRemove(key); } - /// Asserts there is an `Entry` with matching key, deletes it from the hash map, - /// and discards it. + /// If there is an `Entry` with a matching key, it is deleted from + /// the hash map, and then returned from this function. The entry is + /// removed from the underlying array by shifting all elements forward + /// thereby maintaining the current ordering. + pub fn orderedRemove(self: *Self, key: K) ?Entry { + return self.unmanaged.orderedRemove(key); + } + + /// TODO: deprecated: call swapRemoveAssertDiscard instead. pub fn removeAssertDiscard(self: *Self, key: K) void { return self.unmanaged.removeAssertDiscard(key); } + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by swapping it with the last element, and discards it. + pub fn swapRemoveAssertDiscard(self: *Self, key: K) void { + return self.unmanaged.swapRemoveAssertDiscard(key); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by by shifting all elements forward thereby maintaining the current ordering. + pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void { + return self.unmanaged.orderedRemoveAssertDiscard(key); + } + pub fn items(self: Self) []Entry { return self.unmanaged.items(); } @@ -233,6 +264,29 @@ pub fn ArrayHashMap( var other = try self.unmanaged.clone(self.allocator); return other.promote(self.allocator); } + + /// Rebuilds the key indexes. If the underlying entries has been modified directly, users + /// can call `reIndex` to update the indexes to account for these new entries. + pub fn reIndex(self: *Self) !void { + return self.unmanaged.reIndex(self.allocator); + } + + /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated + /// index entries. Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + return self.unmanaged.shrinkRetainingCapacity(new_len); + } + + /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated + /// index entries. Reduces allocated capacity. + pub fn shrinkAndFree(self: *Self, new_len: usize) void { + return self.unmanaged.shrinkAndFree(self.allocator, new_len); + } + + /// Removes the last inserted `Entry` in the hash map and returns it. + pub fn pop(self: *Self) Entry { + return self.unmanaged.pop(); + } }; } @@ -286,6 +340,7 @@ pub fn ArrayHashMapUnmanaged( pub const GetOrPutResult = struct { entry: *Entry, found_existing: bool, + index: usize, }; pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash); @@ -294,6 +349,12 @@ pub fn ArrayHashMapUnmanaged( const linear_scan_max = 8; + const RemovalType = enum { + swap, + ordered, + index_only, + }; + pub fn promote(self: Self, allocator: *Allocator) Managed { return .{ .unmanaged = self, @@ -301,6 +362,15 @@ pub fn ArrayHashMapUnmanaged( }; } + /// `ArrayHashMapUnmanaged` takes ownership of the passed in array list. The array list must + /// have been allocated with `allocator`. + /// Deinitialize with `deinit`. + pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self { + var array_hash_map = Self{ .entries = entries }; + try array_hash_map.reIndex(allocator); + return array_hash_map; + } + pub fn deinit(self: *Self, allocator: *Allocator) void { self.entries.deinit(allocator); if (self.index_header) |header| { @@ -323,7 +393,7 @@ pub fn ArrayHashMapUnmanaged( } pub fn clearAndFree(self: *Self, allocator: *Allocator) void { - self.entries.shrink(allocator, 0); + self.entries.shrinkAndFree(allocator, 0); if (self.index_header) |header| { header.free(allocator); self.index_header = null; @@ -343,9 +413,11 @@ pub fn ArrayHashMapUnmanaged( pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { // "If key exists this function cannot fail." + const index = self.getIndex(key) orelse return err; return GetOrPutResult{ - .entry = self.getEntry(key) orelse return err, + .entry = &self.entries.items[index], .found_existing = true, + .index = index, }; }; return self.getOrPutAssumeCapacity(key); @@ -362,11 +434,12 @@ pub fn ArrayHashMapUnmanaged( const header = self.index_header orelse { // Linear scan. const h = if (store_hash) hash(key) else {}; - for (self.entries.items) |*item| { + for (self.entries.items) |*item, i| { if (item.hash == h and eql(key, item.key)) { return GetOrPutResult{ .entry = item, .found_existing = true, + .index = i, }; } } @@ -379,6 +452,7 @@ pub fn ArrayHashMapUnmanaged( return GetOrPutResult{ .entry = new_entry, .found_existing = false, + .index = self.entries.items.len - 1, }; }; @@ -524,30 +598,36 @@ pub fn ArrayHashMapUnmanaged( } /// If there is an `Entry` with a matching key, it is deleted from - /// the hash map, and then returned from this function. - pub fn remove(self: *Self, key: K) ?Entry { - const header = self.index_header orelse { - // Linear scan. - const h = if (store_hash) hash(key) else {}; - for (self.entries.items) |item, i| { - if (item.hash == h and eql(key, item.key)) { - return self.entries.swapRemove(i); - } - } - return null; - }; - switch (header.capacityIndexType()) { - .u8 => return self.removeInternal(key, header, u8), - .u16 => return self.removeInternal(key, header, u16), - .u32 => return self.removeInternal(key, header, u32), - .usize => return self.removeInternal(key, header, usize), - } + /// the hash map, and then returned from this function. The entry is + /// removed from the underlying array by swapping it with the last + /// element. + pub fn swapRemove(self: *Self, key: K) ?Entry { + return self.removeInternal(key, .swap); } - /// Asserts there is an `Entry` with matching key, deletes it from the hash map, - /// and discards it. + /// If there is an `Entry` with a matching key, it is deleted from + /// the hash map, and then returned from this function. The entry is + /// removed from the underlying array by shifting all elements forward + /// thereby maintaining the current ordering. + pub fn orderedRemove(self: *Self, key: K) ?Entry { + return self.removeInternal(key, .ordered); + } + + /// TODO deprecated: call swapRemoveAssertDiscard instead. pub fn removeAssertDiscard(self: *Self, key: K) void { - assert(self.remove(key) != null); + return self.swapRemoveAssertDiscard(key); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by swapping it with the last element, and discards it. + pub fn swapRemoveAssertDiscard(self: *Self, key: K) void { + assert(self.swapRemove(key) != null); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by by shifting all elements forward thereby maintaining the current ordering. + pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void { + assert(self.orderedRemove(key) != null); } pub fn items(self: Self) []Entry { @@ -566,9 +646,85 @@ pub fn ArrayHashMapUnmanaged( return other; } - fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { + /// Rebuilds the key indexes. If the underlying entries has been modified directly, users + /// can call `reIndex` to update the indexes to account for these new entries. + pub fn reIndex(self: *Self, allocator: *Allocator) !void { + if (self.entries.capacity <= linear_scan_max) return; + // We're going to rebuild the index header and replace the existing one (if any). The + // indexes should sized such that they will be at most 60% full. + const needed_len = self.entries.capacity * 5 / 3; + const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; + const new_header = try IndexHeader.alloc(allocator, new_indexes_len); + self.insertAllEntriesIntoNewHeader(new_header); + if (self.index_header) |header| + header.free(allocator); + self.index_header = new_header; + } + + /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated + /// index entries. Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + // Remove index entries from the new length onwards. + // Explicitly choose to ONLY remove index entries and not the underlying array list + // entries as we're going to remove them in the subsequent shrink call. + var i: usize = new_len; + while (i < self.entries.items.len) : (i += 1) + _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only); + self.entries.shrinkRetainingCapacity(new_len); + } + + /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated + /// index entries. Reduces allocated capacity. + pub fn shrinkAndFree(self: *Self, allocator: *Allocator, new_len: usize) void { + // Remove index entries from the new length onwards. + // Explicitly choose to ONLY remove index entries and not the underlying array list + // entries as we're going to remove them in the subsequent shrink call. + var i: usize = new_len; + while (i < self.entries.items.len) : (i += 1) + _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only); + self.entries.shrinkAndFree(allocator, new_len); + } + + /// Removes the last inserted `Entry` in the hash map and returns it. + pub fn pop(self: *Self) Entry { + const top = self.entries.pop(); + _ = self.removeWithHash(top.key, top.hash, .index_only); + return top; + } + + fn removeInternal(self: *Self, key: K, comptime removal_type: RemovalType) ?Entry { + const key_hash = if (store_hash) hash(key) else {}; + return self.removeWithHash(key, key_hash, removal_type); + } + + fn removeWithHash(self: *Self, key: K, key_hash: Hash, comptime removal_type: RemovalType) ?Entry { + const header = self.index_header orelse { + // If we're only removing index entries and we have no index header, there's no need + // to continue. + if (removal_type == .index_only) return null; + // Linear scan. + for (self.entries.items) |item, i| { + if (item.hash == key_hash and eql(key, item.key)) { + switch (removal_type) { + .swap => return self.entries.swapRemove(i), + .ordered => return self.entries.orderedRemove(i), + .index_only => unreachable, + } + } + } + return null; + }; + switch (header.capacityIndexType()) { + .u8 => return self.removeWithIndex(key, key_hash, header, u8, removal_type), + .u16 => return self.removeWithIndex(key, key_hash, header, u16, removal_type), + .u32 => return self.removeWithIndex(key, key_hash, header, u32, removal_type), + .usize => return self.removeWithIndex(key, key_hash, header, usize, removal_type), + } + } + + fn removeWithIndex(self: *Self, key: K, key_hash: Hash, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) ?Entry { const indexes = header.indexes(I); - const h = hash(key); + const h = if (store_hash) key_hash else hash(key); const start_index = header.constrainIndex(h); var roll_over: usize = 0; while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { @@ -583,11 +739,26 @@ pub fn ArrayHashMapUnmanaged( if (!hash_match or !eql(key, entry.key)) continue; - const removed_entry = self.entries.swapRemove(index.entry_index); - if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { - // Because of the swap remove, now we need to update the index that was - // pointing to the last entry and is now pointing to this removed item slot. - self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); + var removed_entry: ?Entry = undefined; + switch (removal_type) { + .swap => { + removed_entry = self.entries.swapRemove(index.entry_index); + if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { + // Because of the swap remove, now we need to update the index that was + // pointing to the last entry and is now pointing to this removed item slot. + self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); + } + }, + .ordered => { + removed_entry = self.entries.orderedRemove(index.entry_index); + var i: usize = index.entry_index; + while (i < self.entries.items.len) : (i += 1) { + // Because of the ordered remove, everything from the entry index onwards has + // been shifted forward so we'll need to update the index entries. + self.updateEntryIndex(header, i + 1, i, I, indexes); + } + }, + .index_only => removed_entry = null, } // Now we have to shift over the following indexes. @@ -658,6 +829,7 @@ pub fn ArrayHashMapUnmanaged( return .{ .found_existing = false, .entry = new_entry, + .index = self.entries.items.len - 1, }; } @@ -669,6 +841,7 @@ pub fn ArrayHashMapUnmanaged( return .{ .found_existing = true, .entry = entry, + .index = index.entry_index, }; } if (index.distance_from_start_index < distance_from_start_index) { @@ -710,6 +883,7 @@ pub fn ArrayHashMapUnmanaged( return .{ .found_existing = false, .entry = new_entry, + .index = self.entries.items.len - 1, }; } if (next_index.distance_from_start_index < distance_from_start_index) { @@ -901,11 +1075,13 @@ test "basic hash map usage" { const gop1 = try map.getOrPut(5); testing.expect(gop1.found_existing == true); testing.expect(gop1.entry.value == 55); + testing.expect(gop1.index == 4); gop1.entry.value = 77; testing.expect(map.getEntry(5).?.value == 77); const gop2 = try map.getOrPut(99); testing.expect(gop2.found_existing == false); + testing.expect(gop2.index == 5); gop2.entry.value = 42; testing.expect(map.getEntry(99).?.value == 42); @@ -919,13 +1095,32 @@ test "basic hash map usage" { testing.expect(map.getEntry(2).?.value == 22); testing.expect(map.get(2).? == 22); - const rmv1 = map.remove(2); + const rmv1 = map.swapRemove(2); testing.expect(rmv1.?.key == 2); testing.expect(rmv1.?.value == 22); - testing.expect(map.remove(2) == null); + testing.expect(map.swapRemove(2) == null); testing.expect(map.getEntry(2) == null); testing.expect(map.get(2) == null); + // Since we've used `swapRemove` above, the index of this entry should remain unchanged. + testing.expect(map.getIndex(100).? == 1); + const gop5 = try map.getOrPut(5); + testing.expect(gop5.found_existing == true); + testing.expect(gop5.entry.value == 77); + testing.expect(gop5.index == 4); + + // Whereas, if we do an `orderedRemove`, it should move the index forward one spot. + const rmv2 = map.orderedRemove(100); + testing.expect(rmv2.?.key == 100); + testing.expect(rmv2.?.value == 41); + testing.expect(map.orderedRemove(100) == null); + testing.expect(map.getEntry(100) == null); + testing.expect(map.get(100) == null); + const gop6 = try map.getOrPut(5); + testing.expect(gop6.found_existing == true); + testing.expect(gop6.entry.value == 77); + testing.expect(gop6.index == 3); + map.removeAssertDiscard(3); } @@ -1019,6 +1214,130 @@ test "clone" { } } +test "shrink" { + var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); + defer map.deinit(); + + // This test is more interesting if we insert enough entries to allocate the index header. + const num_entries = 20; + var i: i32 = 0; + while (i < num_entries) : (i += 1) + testing.expect((try map.fetchPut(i, i * 10)) == null); + + testing.expect(map.unmanaged.index_header != null); + testing.expect(map.count() == num_entries); + + // Test `shrinkRetainingCapacity`. + map.shrinkRetainingCapacity(17); + testing.expect(map.count() == 17); + testing.expect(map.capacity() == 20); + i = 0; + while (i < num_entries) : (i += 1) { + const gop = try map.getOrPut(i); + if (i < 17) { + testing.expect(gop.found_existing == true); + testing.expect(gop.entry.value == i * 10); + } else testing.expect(gop.found_existing == false); + } + + // Test `shrinkAndFree`. + map.shrinkAndFree(15); + testing.expect(map.count() == 15); + testing.expect(map.capacity() == 15); + i = 0; + while (i < num_entries) : (i += 1) { + const gop = try map.getOrPut(i); + if (i < 15) { + testing.expect(gop.found_existing == true); + testing.expect(gop.entry.value == i * 10); + } else testing.expect(gop.found_existing == false); + } +} + +test "pop" { + var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); + defer map.deinit(); + + testing.expect((try map.fetchPut(1, 11)) == null); + testing.expect((try map.fetchPut(2, 22)) == null); + testing.expect((try map.fetchPut(3, 33)) == null); + testing.expect((try map.fetchPut(4, 44)) == null); + + const pop1 = map.pop(); + testing.expect(pop1.key == 4 and pop1.value == 44); + const pop2 = map.pop(); + testing.expect(pop2.key == 3 and pop2.value == 33); + const pop3 = map.pop(); + testing.expect(pop3.key == 2 and pop3.value == 22); + const pop4 = map.pop(); + testing.expect(pop4.key == 1 and pop4.value == 11); +} + +test "reIndex" { + var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); + defer map.deinit(); + + // Populate via the API. + const num_indexed_entries = 20; + var i: i32 = 0; + while (i < num_indexed_entries) : (i += 1) + testing.expect((try map.fetchPut(i, i * 10)) == null); + + // Make sure we allocated an index header. + testing.expect(map.unmanaged.index_header != null); + + // Now write to the underlying array list directly. + const num_unindexed_entries = 20; + const hash = getAutoHashFn(i32); + var al = &map.unmanaged.entries; + while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { + try al.append(std.testing.allocator, .{ + .key = i, + .value = i * 10, + .hash = hash(i), + }); + } + + // After reindexing, we should see everything. + try map.reIndex(); + i = 0; + while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) { + const gop = try map.getOrPut(i); + testing.expect(gop.found_existing == true); + testing.expect(gop.entry.value == i * 10); + testing.expect(gop.index == i); + } +} + +test "fromOwnedArrayList" { + comptime const array_hash_map_type = AutoArrayHashMap(i32, i32); + var al = std.ArrayListUnmanaged(array_hash_map_type.Entry){}; + const hash = getAutoHashFn(i32); + + // Populate array list. + const num_entries = 20; + var i: i32 = 0; + while (i < num_entries) : (i += 1) { + try al.append(std.testing.allocator, .{ + .key = i, + .value = i * 10, + .hash = hash(i), + }); + } + + // Now instantiate using `fromOwnedArrayList`. + var map = try array_hash_map_type.fromOwnedArrayList(std.testing.allocator, al); + defer map.deinit(); + + i = 0; + while (i < num_entries) : (i += 1) { + const gop = try map.getOrPut(i); + testing.expect(gop.found_existing == true); + testing.expect(gop.entry.value == i * 10); + testing.expect(gop.index == i); + } +} + pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { return struct { fn hash(key: K) u32 { diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index ab47510cb7..f30a86d8f7 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -12,10 +12,20 @@ const Allocator = mem.Allocator; /// A contiguous, growable list of items in memory. /// This is a wrapper around an array of T values. Initialize with `init`. +/// +/// This struct internally stores a `std.mem.Allocator` for memory management. +/// To manually specify an allocator with each method call see `ArrayListUnmanaged`. pub fn ArrayList(comptime T: type) type { return ArrayListAligned(T, null); } +/// A contiguous, growable list of arbitrarily aligned items in memory. +/// This is a wrapper around an array of T values aligned to `alignment`-byte +/// addresses. If the specified alignment is `null`, then `@alignOf(T)` is used. +/// Initialize with `init`. +/// +/// This struct internally stores a `std.mem.Allocator` for memory management. +/// To manually specify an allocator with each method call see `ArrayListAlignedUnmanaged`. pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { if (alignment) |a| { if (a == @alignOf(T)) { @@ -24,9 +34,18 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } return struct { const Self = @This(); - - /// Content of the ArrayList + /// Contents of the list. Pointers to elements in this slice are + /// **invalid after resizing operations** on the ArrayList, unless the + /// operation explicitly either: (1) states otherwise or (2) lists the + /// invalidated pointers. + /// + /// The allocator used determines how element pointers are + /// invalidated, so the behavior may vary between lists. To avoid + /// illegal behavior, take into account the above paragraph plus the + /// explicit statements given in each method. items: Slice, + /// How many T values this list can hold without allocating + /// additional memory. capacity: usize, allocator: *Allocator, @@ -42,7 +61,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { }; } - /// Initialize with capacity to hold at least num elements. + /// Initialize with capacity to hold at least `num` elements. /// Deinitialize with `deinit` or use `toOwnedSlice`. pub fn initCapacity(allocator: *Allocator, num: usize) !Self { var self = Self.init(allocator); @@ -79,11 +98,23 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { }; } + /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields + /// of this ArrayList. This ArrayList retains ownership of underlying memory. + /// Deprecated: use `moveToUnmanaged` which has different semantics. pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { return .{ .items = self.items, .capacity = self.capacity }; } - /// The caller owns the returned memory. ArrayList becomes empty. + /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields + /// of this ArrayList. Empties this ArrayList. + pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) { + const allocator = self.allocator; + const result = .{ .items = self.items, .capacity = self.capacity }; + self.* = init(allocator); + return result; + } + + /// The caller owns the returned memory. Empties this ArrayList. pub fn toOwnedSlice(self: *Self) Slice { const allocator = self.allocator; const result = allocator.shrink(self.allocatedSlice(), self.items.len); @@ -91,6 +122,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return result; } + /// The caller owns the returned memory. Empties this ArrayList. + pub fn toOwnedSliceSentinel(self: *Self, comptime sentinel: T) ![:sentinel]T { + try self.append(sentinel); + const result = self.toOwnedSlice(); + return result[0 .. result.len - 1 :sentinel]; + } + /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room. /// This operation is O(N). pub fn insert(self: *Self, n: usize, item: T) !void { @@ -111,9 +149,10 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { mem.copy(T, self.items[i .. i + items.len], items); } - /// Replace range of elements `list[start..start+len]` with `new_items` - /// grows list if `len < new_items.len`. may allocate - /// shrinks list if `len > new_items.len` + /// Replace range of elements `list[start..start+len]` with `new_items`. + /// Grows list if `len < new_items.len`. + /// Shrinks list if `len > new_items.len`. + /// Invalidates pointers if this ArrayList is resized. pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void { const after_range = start + len; const range = self.items[start..after_range]; @@ -144,15 +183,18 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { new_item_ptr.* = item; } - /// Extend the list by 1 element, but asserting `self.capacity` - /// is sufficient to hold an additional item. + /// Extend the list by 1 element, but assert `self.capacity` + /// is sufficient to hold an additional item. **Does not** + /// invalidate pointers. pub fn appendAssumeCapacity(self: *Self, item: T) void { const new_item_ptr = self.addOneAssumeCapacity(); new_item_ptr.* = item; } - /// Remove the element at index `i` from the list and return its value. + /// Remove the element at index `i`, shift elements after index + /// `i` forward, and return the removed element. /// Asserts the array has at least one item. + /// Invalidates pointers to end of list. /// This operation is O(N). pub fn orderedRemove(self: *Self, i: usize) T { const newlen = self.items.len - 1; @@ -184,7 +226,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Append the slice of items to the list, asserting the capacity is already - /// enough to store the new items. + /// enough to store the new items. **Does not** invalidate pointers. pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void { const oldlen = self.items.len; const newlen = self.items.len + items.len; @@ -200,9 +242,6 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return .{ .context = self }; } - /// Deprecated: use `writer` - pub const outStream = writer; - /// Same as `append` except it returns the number of bytes written, which is always the same /// as `m.len`. The purpose of this function existing is to match `std.io.Writer` API. fn appendWrite(self: *Self, m: []const u8) !usize { @@ -220,7 +259,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Append a value to the list `n` times. - /// Asserts the capacity is enough. + /// Asserts the capacity is enough. **Does not** invalidate pointers. pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void { const new_len = self.items.len + n; assert(new_len <= self.capacity); @@ -236,8 +275,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Reduce allocated capacity to `new_len`. - /// Invalidates element pointers. - pub fn shrink(self: *Self, new_len: usize) void { + /// May invalidate element pointers. + pub fn shrinkAndFree(self: *Self, new_len: usize) void { assert(new_len <= self.items.len); self.items = self.allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) { @@ -250,13 +289,14 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Reduce length to `new_len`. - /// Invalidates element pointers. - /// Keeps capacity the same. + /// Invalidates pointers for the elements `items[new_len..]`. pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { assert(new_len <= self.items.len); self.items.len = new_len; } + /// Modify the array so that it can hold at least `new_capacity` items. + /// Invalidates pointers if additional memory is needed. pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { var better_capacity = self.capacity; if (better_capacity >= new_capacity) return; @@ -273,14 +313,13 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } /// Increases the array's length to match the full capacity that is already allocated. - /// The new elements have `undefined` values. This operation does not invalidate any - /// element pointers. + /// The new elements have `undefined` values. **Does not** invalidate pointers. pub fn expandToCapacity(self: *Self) void { self.items.len = self.capacity; } /// Increase length by 1, returning pointer to the new item. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer becomes invalid when the list resized. pub fn addOne(self: *Self) !*T { const newlen = self.items.len + 1; try self.ensureCapacity(newlen); @@ -290,6 +329,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Increase length by 1, returning pointer to the new item. /// Asserts that there is already space for the new item without allocating more. /// The returned pointer becomes invalid when the list is resized. + /// **Does not** invalidate element pointers. pub fn addOneAssumeCapacity(self: *Self) *T { assert(self.items.len < self.capacity); @@ -299,6 +339,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. + /// The returned pointer becomes invalid when the list is resized. + /// Resizes list if `self.capacity` is not large enough. pub fn addManyAsArray(self: *Self, comptime n: usize) !*[n]T { const prev_len = self.items.len; try self.resize(self.items.len + n); @@ -308,6 +350,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. /// Asserts that there is already space for the new item without allocating more. + /// **Does not** invalidate element pointers. + /// The returned pointer becomes invalid when the list is resized. pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { assert(self.items.len + n <= self.capacity); const prev_len = self.items.len; @@ -317,33 +361,51 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Remove and return the last element from the list. /// Asserts the list has at least one item. + /// Invalidates pointers to the removed element. pub fn pop(self: *Self) T { const val = self.items[self.items.len - 1]; self.items.len -= 1; return val; } - /// Remove and return the last element from the list. - /// If the list is empty, returns `null`. + /// Remove and return the last element from the list, or + /// return `null` if list is empty. + /// Invalidates pointers to the removed element, if any. pub fn popOrNull(self: *Self) ?T { if (self.items.len == 0) return null; return self.pop(); } - // For a nicer API, `items.len` is the length, not the capacity. - // This requires "unsafe" slicing. - fn allocatedSlice(self: Self) Slice { + /// Returns a slice of all the items plus the extra capacity, whose memory + /// contents are `undefined`. + pub fn allocatedSlice(self: Self) Slice { + // For a nicer API, `items.len` is the length, not the capacity. + // This requires "unsafe" slicing. return self.items.ptr[0..self.capacity]; } + + /// Returns a slice of only the extra capacity after items. + /// This can be useful for writing directly into an ArrayList. + /// Note that such an operation must be followed up with a direct + /// modification of `self.items.len`. + pub fn unusedCapacitySlice(self: Self) Slice { + return self.allocatedSlice()[self.items.len..]; + } }; } -/// Bring-your-own allocator with every function call. -/// Initialize directly and deinitialize with `deinit` or use `toOwnedSlice`. +/// An ArrayList, but the allocator is passed as a parameter to the relevant functions +/// rather than stored in the struct itself. The same allocator **must** be used throughout +/// the entire lifetime of an ArrayListUnmanaged. Initialize directly or with +/// `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`. pub fn ArrayListUnmanaged(comptime T: type) type { return ArrayListAlignedUnmanaged(T, null); } +/// An ArrayListAligned, but the allocator is passed as a parameter to the relevant +/// functions rather than stored in the struct itself. The same allocator **must** +/// be used throughout the entire lifetime of an ArrayListAlignedUnmanaged. +/// Initialize directly or with `initCapacity`, and deinitialize with `deinit` or use `toOwnedSlice`. pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) type { if (alignment) |a| { if (a == @alignOf(T)) { @@ -352,9 +414,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } return struct { const Self = @This(); - - /// Content of the ArrayList. + /// Contents of the list. Pointers to elements in this slice are + /// **invalid after resizing operations** on the ArrayList, unless the + /// operation explicitly either: (1) states otherwise or (2) lists the + /// invalidated pointers. + /// + /// The allocator used determines how element pointers are + /// invalidated, so the behavior may vary between lists. To avoid + /// illegal behavior, take into account the above paragraph plus the + /// explicit statements given in each method. items: Slice = &[_]T{}, + /// How many T values this list can hold without allocating + /// additional memory. capacity: usize = 0, pub const Slice = if (alignment) |a| ([]align(a) T) else []T; @@ -378,6 +449,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ self.* = undefined; } + /// Convert this list into an analogous memory-managed one. + /// The returned list has ownership of the underlying memory. pub fn toManaged(self: *Self, allocator: *Allocator) ArrayListAligned(T, alignment) { return .{ .items = self.items, .capacity = self.capacity, .allocator = allocator }; } @@ -389,8 +462,16 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ return result; } + /// The caller owns the returned memory. ArrayList becomes empty. + pub fn toOwnedSliceSentinel(self: *Self, allocator: *Allocator, comptime sentinel: T) ![:sentinel]T { + try self.append(allocator, sentinel); + const result = self.toOwnedSlice(allocator); + return result[0 .. result.len - 1 :sentinel]; + } + /// Insert `item` at index `n`. Moves `list[n .. list.len]` - /// to make room. + /// to higher indices to make room. + /// This operation is O(N). pub fn insert(self: *Self, allocator: *Allocator, n: usize, item: T) !void { try self.ensureCapacity(allocator, self.items.len + 1); self.items.len += 1; @@ -399,8 +480,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ self.items[n] = item; } - /// Insert slice `items` at index `i`. Moves - /// `list[i .. list.len]` to make room. + /// Insert slice `items` at index `i`. Moves `list[i .. list.len]` to + /// higher indicices make room. /// This operation is O(N). pub fn insertSlice(self: *Self, allocator: *Allocator, i: usize, items: SliceConst) !void { try self.ensureCapacity(allocator, self.items.len + items.len); @@ -411,8 +492,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// Replace range of elements `list[start..start+len]` with `new_items` - /// grows list if `len < new_items.len`. may allocate - /// shrinks list if `len > new_items.len` + /// Grows list if `len < new_items.len`. + /// Shrinks list if `len > new_items.len` + /// Invalidates pointers if this ArrayList is resized. pub fn replaceRange(self: *Self, allocator: *Allocator, start: usize, len: usize, new_items: SliceConst) !void { var managed = self.toManaged(allocator); try managed.replaceRange(start, len, new_items); @@ -433,7 +515,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// Remove the element at index `i` from the list and return its value. - /// Asserts the array has at least one item. + /// Asserts the array has at least one item. Invalidates pointers to + /// last element. /// This operation is O(N). pub fn orderedRemove(self: *Self, i: usize) T { const newlen = self.items.len - 1; @@ -448,6 +531,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Removes the element at the specified index and returns it. /// The empty slot is filled from the end of the list. + /// Invalidates pointers to last element. /// This operation is O(1). pub fn swapRemove(self: *Self, i: usize) T { if (self.items.len - 1 == i) return self.pop(); @@ -474,14 +558,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ mem.copy(T, self.items[oldlen..], items); } - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize { - try self.appendSlice(allocator, m); - return m.len; - } - /// Append a value to the list `n` times. /// Allocates more memory as necessary. pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void { @@ -491,6 +567,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// Append a value to the list `n` times. + /// **Does not** invalidate pointers. /// Asserts the capacity is enough. pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void { const new_len = self.items.len + n; @@ -500,15 +577,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// Adjust the list's length to `new_len`. - /// Does not initialize added items if any. + /// Does not initialize added items, if any. pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void { try self.ensureCapacity(allocator, new_len); self.items.len = new_len; } /// Reduce allocated capacity to `new_len`. - /// Invalidates element pointers. - pub fn shrink(self: *Self, allocator: *Allocator, new_len: usize) void { + pub fn shrinkAndFree(self: *Self, allocator: *Allocator, new_len: usize) void { assert(new_len <= self.items.len); self.items = allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) { @@ -521,13 +597,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } /// Reduce length to `new_len`. - /// Invalidates element pointers. + /// Invalidates pointers to elements `items[new_len..]`. /// Keeps capacity the same. pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { assert(new_len <= self.items.len); self.items.len = new_len; } + /// Modify the array so that it can hold at least `new_capacity` items. + /// Invalidates pointers if additional memory is needed. pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { var better_capacity = self.capacity; if (better_capacity >= new_capacity) return; @@ -544,13 +622,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Increases the array's length to match the full capacity that is already allocated. /// The new elements have `undefined` values. - /// This operation does not invalidate any element pointers. + /// **Does not** invalidate pointers. pub fn expandToCapacity(self: *Self) void { self.items.len = self.capacity; } /// Increase length by 1, returning pointer to the new item. - /// The returned pointer becomes invalid when the list is resized. + /// The returned pointer becomes invalid when the list resized. pub fn addOne(self: *Self, allocator: *Allocator) !*T { const newlen = self.items.len + 1; try self.ensureCapacity(allocator, newlen); @@ -559,8 +637,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Increase length by 1, returning pointer to the new item. /// Asserts that there is already space for the new item without allocating more. - /// The returned pointer becomes invalid when the list is resized. - /// This operation does not invalidate any element pointers. + /// **Does not** invalidate pointers. + /// The returned pointer becomes invalid when the list resized. pub fn addOneAssumeCapacity(self: *Self) *T { assert(self.items.len < self.capacity); @@ -570,6 +648,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. + /// The returned pointer becomes invalid when the list is resized. pub fn addManyAsArray(self: *Self, allocator: *Allocator, comptime n: usize) !*[n]T { const prev_len = self.items.len; try self.resize(allocator, self.items.len + n); @@ -579,6 +658,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Resize the array, adding `n` new elements, which have `undefined` values. /// The return value is an array pointing to the newly allocated elements. /// Asserts that there is already space for the new item without allocating more. + /// **Does not** invalidate pointers. + /// The returned pointer becomes invalid when the list is resized. pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { assert(self.items.len + n <= self.capacity); const prev_len = self.items.len; @@ -588,7 +669,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Remove and return the last element from the list. /// Asserts the list has at least one item. - /// This operation does not invalidate any element pointers. + /// Invalidates pointers to last element. pub fn pop(self: *Self) T { const val = self.items[self.items.len - 1]; self.items.len -= 1; @@ -597,7 +678,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ /// Remove and return the last element from the list. /// If the list is empty, returns `null`. - /// This operation does not invalidate any element pointers. + /// Invalidates pointers to last element. pub fn popOrNull(self: *Self) ?T { if (self.items.len == 0) return null; return self.pop(); @@ -1047,13 +1128,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" { } } -test "std.ArrayList(u8) implements outStream" { +test "std.ArrayList(u8) implements writer" { var buffer = ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const x: i32 = 42; const y: i32 = 1234; - try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y }); + try buffer.writer().print("x: {}\ny: {}\n", .{ x, y }); testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); } @@ -1071,7 +1152,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe try list.append(2); try list.append(3); - list.shrink(1); + list.shrinkAndFree(1); testing.expect(list.items.len == 1); } { @@ -1081,7 +1162,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe try list.append(a, 2); try list.append(a, 3); - list.shrink(a, 1); + list.shrinkAndFree(a, 1); testing.expect(list.items.len == 1); } } @@ -1121,3 +1202,27 @@ test "std.ArrayList/ArrayListUnmanaged.addManyAsArray" { testing.expectEqualSlices(u8, list.items, "aoeuasdf"); } } + +test "std.ArrayList/ArrayListUnmanaged.toOwnedSliceSentinel" { + const a = testing.allocator; + { + var list = ArrayList(u8).init(a); + defer list.deinit(); + + try list.appendSlice("foobar"); + + const result = try list.toOwnedSliceSentinel(0); + defer a.free(result); + testing.expectEqualStrings(result, mem.spanZ(result.ptr)); + } + { + var list = ArrayListUnmanaged(u8){}; + defer list.deinit(a); + + try list.appendSlice(a, "foobar"); + + const result = try list.toOwnedSliceSentinel(a, 0); + defer a.free(result); + testing.expectEqualStrings(result, mem.spanZ(result.ptr)); + } +} diff --git a/lib/std/array_list_sentineled.zig b/lib/std/array_list_sentineled.zig deleted file mode 100644 index 31f8f4fd80..0000000000 --- a/lib/std/array_list_sentineled.zig +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const debug = std.debug; -const mem = std.mem; -const Allocator = mem.Allocator; -const assert = debug.assert; -const testing = std.testing; -const ArrayList = std.ArrayList; - -/// A contiguous, growable list of items in memory, with a sentinel after them. -/// The sentinel is maintained when appending, resizing, etc. -/// If you do not need a sentinel, consider using `ArrayList` instead. -pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type { - return struct { - list: ArrayList(T), - - const Self = @This(); - - /// Must deinitialize with deinit. - pub fn init(allocator: *Allocator, m: []const T) !Self { - var self = try initSize(allocator, m.len); - mem.copy(T, self.list.items, m); - return self; - } - - /// Initialize memory to size bytes of undefined values. - /// Must deinitialize with deinit. - pub fn initSize(allocator: *Allocator, size: usize) !Self { - var self = initNull(allocator); - try self.resize(size); - return self; - } - - /// Initialize with capacity to hold at least num bytes. - /// Must deinitialize with deinit. - pub fn initCapacity(allocator: *Allocator, num: usize) !Self { - var self = Self{ .list = try ArrayList(T).initCapacity(allocator, num + 1) }; - self.list.appendAssumeCapacity(sentinel); - return self; - } - - /// Must deinitialize with deinit. - /// None of the other operations are valid until you do one of these: - /// * `replaceContents` - /// * `resize` - pub fn initNull(allocator: *Allocator) Self { - return Self{ .list = ArrayList(T).init(allocator) }; - } - - /// Must deinitialize with deinit. - pub fn initFromBuffer(buffer: Self) !Self { - return Self.init(buffer.list.allocator, buffer.span()); - } - - /// Takes ownership of the passed in slice. The slice must have been - /// allocated with `allocator`. - /// Must deinitialize with deinit. - pub fn fromOwnedSlice(allocator: *Allocator, slice: []T) !Self { - var self = Self{ .list = ArrayList(T).fromOwnedSlice(allocator, slice) }; - try self.list.append(sentinel); - return self; - } - - /// The caller owns the returned memory. The list becomes null and is safe to `deinit`. - pub fn toOwnedSlice(self: *Self) [:sentinel]T { - const allocator = self.list.allocator; - const result = self.list.toOwnedSlice(); - self.* = initNull(allocator); - return result[0 .. result.len - 1 :sentinel]; - } - - /// Only works when `T` is `u8`. - pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: anytype) !Self { - const size = std.math.cast(usize, std.fmt.count(format, args)) catch |err| switch (err) { - error.Overflow => return error.OutOfMemory, - }; - var self = try Self.initSize(allocator, size); - assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); - return self; - } - - pub fn deinit(self: *Self) void { - self.list.deinit(); - } - - pub fn span(self: anytype) @TypeOf(self.list.items[0..:sentinel]) { - return self.list.items[0..self.len() :sentinel]; - } - - pub fn shrink(self: *Self, new_len: usize) void { - assert(new_len <= self.len()); - self.list.shrink(new_len + 1); - self.list.items[self.len()] = sentinel; - } - - pub fn resize(self: *Self, new_len: usize) !void { - try self.list.resize(new_len + 1); - self.list.items[self.len()] = sentinel; - } - - pub fn isNull(self: Self) bool { - return self.list.items.len == 0; - } - - pub fn len(self: Self) usize { - return self.list.items.len - 1; - } - - pub fn capacity(self: Self) usize { - return if (self.list.capacity > 0) - self.list.capacity - 1 - else - 0; - } - - pub fn appendSlice(self: *Self, m: []const T) !void { - const old_len = self.len(); - try self.resize(old_len + m.len); - mem.copy(T, self.list.items[old_len..], m); - } - - pub fn append(self: *Self, byte: T) !void { - const old_len = self.len(); - try self.resize(old_len + 1); - self.list.items[old_len] = byte; - } - - pub fn eql(self: Self, m: []const T) bool { - return mem.eql(T, self.span(), m); - } - - pub fn startsWith(self: Self, m: []const T) bool { - if (self.len() < m.len) return false; - return mem.eql(T, self.list.items[0..m.len], m); - } - - pub fn endsWith(self: Self, m: []const T) bool { - const l = self.len(); - if (l < m.len) return false; - const start = l - m.len; - return mem.eql(T, self.list.items[start..l], m); - } - - pub fn replaceContents(self: *Self, m: []const T) !void { - try self.resize(m.len); - mem.copy(T, self.list.items, m); - } - - /// Initializes an OutStream which will append to the list. - /// This function may be called only when `T` is `u8`. - pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { - return .{ .context = self }; - } - - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - pub fn appendWrite(self: *Self, m: []const u8) !usize { - try self.appendSlice(m); - return m.len; - } - }; -} - -test "simple" { - var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); - defer buf.deinit(); - - testing.expect(buf.len() == 0); - try buf.appendSlice("hello"); - try buf.appendSlice(" "); - try buf.appendSlice("world"); - testing.expect(buf.eql("hello world")); - testing.expect(mem.eql(u8, mem.spanZ(buf.span().ptr), buf.span())); - - var buf2 = try ArrayListSentineled(u8, 0).initFromBuffer(buf); - defer buf2.deinit(); - testing.expect(buf.eql(buf2.span())); - - testing.expect(buf.startsWith("hell")); - testing.expect(buf.endsWith("orld")); - - try buf2.resize(4); - testing.expect(buf.startsWith(buf2.span())); -} - -test "initSize" { - var buf = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 3); - defer buf.deinit(); - testing.expect(buf.len() == 3); - try buf.appendSlice("hello"); - testing.expect(mem.eql(u8, buf.span()[3..], "hello")); -} - -test "initCapacity" { - var buf = try ArrayListSentineled(u8, 0).initCapacity(testing.allocator, 10); - defer buf.deinit(); - testing.expect(buf.len() == 0); - testing.expect(buf.capacity() >= 10); - const old_cap = buf.capacity(); - try buf.appendSlice("hello"); - testing.expect(buf.len() == 5); - testing.expect(buf.capacity() == old_cap); - testing.expect(mem.eql(u8, buf.span(), "hello")); -} - -test "print" { - var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); - defer buf.deinit(); - - try buf.outStream().print("Hello {} the {}", .{ 2, "world" }); - testing.expect(buf.eql("Hello 2 the world")); -} - -test "outStream" { - var buffer = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 0); - defer buffer.deinit(); - const buf_stream = buffer.outStream(); - - const x: i32 = 42; - const y: i32 = 1234; - try buf_stream.print("x: {}\ny: {}\n", .{ x, y }); - - testing.expect(mem.eql(u8, buffer.span(), "x: 42\ny: 1234\n")); -} diff --git a/lib/std/ascii.zig b/lib/std/ascii.zig index c8dc37c99c..7d391b7c4b 100644 --- a/lib/std/ascii.zig +++ b/lib/std/ascii.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/atomic.zig b/lib/std/atomic.zig index 4402ca462b..ab80fce872 100644 --- a/lib/std/atomic.zig +++ b/lib/std/atomic.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/atomic/bool.zig b/lib/std/atomic/bool.zig index 27a265bbc1..c968b862b9 100644 --- a/lib/std/atomic/bool.zig +++ b/lib/std/atomic/bool.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/atomic/int.zig b/lib/std/atomic/int.zig index b06575e05f..1a3bead2df 100644 --- a/lib/std/atomic/int.zig +++ b/lib/std/atomic/int.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/atomic/queue.zig b/lib/std/atomic/queue.zig index dd139106b4..f5f63944ab 100644 --- a/lib/std/atomic/queue.zig +++ b/lib/std/atomic/queue.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -16,7 +16,7 @@ pub fn Queue(comptime T: type) type { return struct { head: ?*Node, tail: ?*Node, - mutex: std.Mutex, + mutex: std.Thread.Mutex, pub const Self = @This(); pub const Node = std.TailQueue(T).Node; @@ -27,7 +27,7 @@ pub fn Queue(comptime T: type) type { return Self{ .head = null, .tail = null, - .mutex = std.Mutex{}, + .mutex = std.Thread.Mutex{}, }; } @@ -122,7 +122,7 @@ pub fn Queue(comptime T: type) type { /// Dumps the contents of the queue to `stderr`. pub fn dump(self: *Self) void { - self.dumpToStream(std.io.getStdErr().outStream()) catch return; + self.dumpToStream(std.io.getStdErr().writer()) catch return; } /// Dumps the contents of the queue to `stream`. @@ -351,7 +351,7 @@ test "std.atomic.Queue dump" { // Test empty stream fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); expect(mem.eql(u8, buffer[0..fbs.pos], \\head: (null) \\tail: (null) @@ -367,7 +367,7 @@ test "std.atomic.Queue dump" { queue.put(&node_0); fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); var expected = try std.fmt.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 @@ -387,7 +387,7 @@ test "std.atomic.Queue dump" { queue.put(&node_1); fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); expected = try std.fmt.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 diff --git a/lib/std/atomic/stack.zig b/lib/std/atomic/stack.zig index 1e289bae70..d55a8f81a3 100644 --- a/lib/std/atomic/stack.zig +++ b/lib/std/atomic/stack.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/auto_reset_event.zig b/lib/std/auto_reset_event.zig deleted file mode 100644 index 7e13dc1aba..0000000000 --- a/lib/std/auto_reset_event.zig +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const builtin = @import("builtin"); -const testing = std.testing; -const assert = std.debug.assert; - -/// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`. -/// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like). -pub const AutoResetEvent = struct { - // AutoResetEvent has 3 possible states: - // - UNSET: the AutoResetEvent is currently unset - // - SET: the AutoResetEvent was notified before a wait() was called - // - <std.ResetEvent pointer>: there is an active waiter waiting for a notification. - // - // When attempting to wait: - // if the event is unset, it registers a ResetEvent pointer to be notified when the event is set - // if the event is already set, then it consumes the notification and resets the event. - // - // When attempting to notify: - // if the event is unset, then we set the event - // if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent - // - // This ensures that the event is automatically reset after a wait() has been issued - // and avoids the race condition when using std.ResetEvent in the following scenario: - // thread 1 | thread 2 - // std.ResetEvent.wait() | - // | std.ResetEvent.set() - // | std.ResetEvent.set() - // std.ResetEvent.reset() | - // std.ResetEvent.wait() | (missed the second .set() notification above) - state: usize = UNSET, - - const UNSET = 0; - const SET = 1; - - // the minimum alignment for the `*std.ResetEvent` created by wait*() - const event_align = std.math.max(@alignOf(std.ResetEvent), 2); - - pub fn wait(self: *AutoResetEvent) void { - self.waitFor(null) catch unreachable; - } - - pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void { - return self.waitFor(timeout); - } - - fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void { - // lazily initialized std.ResetEvent - var reset_event: std.ResetEvent align(event_align) = undefined; - var has_reset_event = false; - defer if (has_reset_event) { - reset_event.deinit(); - }; - - var state = @atomicLoad(usize, &self.state, .SeqCst); - while (true) { - // consume a notification if there is any - if (state == SET) { - @atomicStore(usize, &self.state, UNSET, .SeqCst); - return; - } - - // check if theres currently a pending ResetEvent pointer already registered - if (state != UNSET) { - unreachable; // multiple waiting threads on the same AutoResetEvent - } - - // lazily initialize the ResetEvent if it hasn't been already - if (!has_reset_event) { - has_reset_event = true; - reset_event = std.ResetEvent.init(); - } - - // Since the AutoResetEvent currently isnt set, - // try to register our ResetEvent on it to wait - // for a set() call from another thread. - if (@cmpxchgWeak( - usize, - &self.state, - UNSET, - @ptrToInt(&reset_event), - .SeqCst, - .SeqCst, - )) |new_state| { - state = new_state; - continue; - } - - // if no timeout was specified, then just wait forever - const timeout_ns = timeout orelse { - reset_event.wait(); - return; - }; - - // wait with a timeout and return if signalled via set() - if (reset_event.timedWait(timeout_ns)) |_| { - return; - } else |timed_out| {} - - // If we timed out, we need to transition the AutoResetEvent back to UNSET. - // If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent. - state = @cmpxchgStrong( - usize, - &self.state, - @ptrToInt(&reset_event), - UNSET, - .SeqCst, - .SeqCst, - ) orelse return error.TimedOut; - - // We didn't manage to unregister ourselves from the state. - if (state == SET) { - unreachable; // AutoResetEvent notified without waking up the waiting thread - } else if (state != UNSET) { - unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out - } - - // This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up. - // We need to wait for it to wake up our ResetEvent before we can return and invalidate it. - // We don't return error.TimedOut here as it technically notified us while we were "timing out". - reset_event.wait(); - return; - } - } - - pub fn set(self: *AutoResetEvent) void { - var state = @atomicLoad(usize, &self.state, .SeqCst); - while (true) { - // If the AutoResetEvent is already set, there is nothing else left to do - if (state == SET) { - return; - } - - // If the AutoResetEvent isn't set, - // then try to leave a notification for the wait() thread that we set() it. - if (state == UNSET) { - state = @cmpxchgWeak( - usize, - &self.state, - UNSET, - SET, - .SeqCst, - .SeqCst, - ) orelse return; - continue; - } - - // There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting. - // Try to acquire ownership of it so that we can wake it up. - // This also resets the AutoResetEvent so that there is no race condition as defined above. - if (@cmpxchgWeak( - usize, - &self.state, - state, - UNSET, - .SeqCst, - .SeqCst, - )) |new_state| { - state = new_state; - continue; - } - - const reset_event = @intToPtr(*align(event_align) std.ResetEvent, state); - reset_event.set(); - return; - } - } -}; - -test "std.AutoResetEvent" { - // test local code paths - { - var event = AutoResetEvent{}; - testing.expectError(error.TimedOut, event.timedWait(1)); - event.set(); - event.wait(); - } - - // test cross-thread signaling - if (builtin.single_threaded) - return; - - const Context = struct { - value: u128 = 0, - in: AutoResetEvent = AutoResetEvent{}, - out: AutoResetEvent = AutoResetEvent{}, - - const Self = @This(); - - fn sender(self: *Self) void { - testing.expect(self.value == 0); - self.value = 1; - self.out.set(); - - self.in.wait(); - testing.expect(self.value == 2); - self.value = 3; - self.out.set(); - - self.in.wait(); - testing.expect(self.value == 4); - } - - fn receiver(self: *Self) void { - self.out.wait(); - testing.expect(self.value == 1); - self.value = 2; - self.in.set(); - - self.out.wait(); - testing.expect(self.value == 3); - self.value = 4; - self.in.set(); - } - }; - - var context = Context{}; - const send_thread = try std.Thread.spawn(&context, Context.sender); - const recv_thread = try std.Thread.spawn(&context, Context.receiver); - - send_thread.wait(); - recv_thread.wait(); -} diff --git a/lib/std/base64.zig b/lib/std/base64.zig index 0b91a1cf0a..e6a780c239 100644 --- a/lib/std/base64.zig +++ b/lib/std/base64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -38,8 +38,8 @@ pub const Base64Encoder = struct { } /// dest.len must be what you get from ::calcSize. - pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) void { - assert(dest.len == Base64Encoder.calcSize(source.len)); + pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 { + assert(dest.len >= Base64Encoder.calcSize(source.len)); var i: usize = 0; var out_index: usize = 0; @@ -78,6 +78,7 @@ pub const Base64Encoder = struct { dest[out_index] = encoder.pad_char; out_index += 1; } + return dest[0..out_index]; } }; @@ -221,12 +222,10 @@ pub const Base64DecoderWithIgnore = struct { } else if (decoder_with_ignore.char_is_ignored[c]) { // we can even ignore chars during the padding continue; - } else - return error.InvalidCharacter; + } else return error.InvalidCharacter; } break; - } else - return error.InvalidCharacter; + } else return error.InvalidCharacter; } switch (available_chars) { @@ -398,8 +397,7 @@ fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) !void // Base64Encoder { var buffer: [0x100]u8 = undefined; - var encoded = buffer[0..Base64Encoder.calcSize(expected_decoded.len)]; - standard_encoder.encode(encoded, expected_decoded); + const encoded = standard_encoder.encode(&buffer, expected_decoded); testing.expectEqualSlices(u8, expected_encoded, encoded); } diff --git a/lib/std/buf_map.zig b/lib/std/buf_map.zig index 3be724e10e..29d1ac4876 100644 --- a/lib/std/buf_map.zig +++ b/lib/std/buf_map.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/buf_set.zig b/lib/std/buf_set.zig index f48e6c594c..75c5ae742d 100644 --- a/lib/std/buf_set.zig +++ b/lib/std/buf_set.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/build.zig b/lib/std/build.zig index dacfaf5f75..e11402e493 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -272,15 +272,57 @@ pub const Builder = struct { return LibExeObjStep.createSharedLibrary(self, name, root_src_param, kind); } + pub fn addSharedLibraryFromWriteFileStep( + self: *Builder, + name: []const u8, + wfs: *WriteFileStep, + basename: []const u8, + kind: LibExeObjStep.SharedLibKind, + ) *LibExeObjStep { + return LibExeObjStep.createSharedLibrary(self, name, @as(FileSource, .{ + .write_file = .{ + .step = wfs, + .basename = basename, + }, + }), kind); + } + pub fn addStaticLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { const root_src_param = if (root_src) |p| @as(FileSource, .{ .path = p }) else null; return LibExeObjStep.createStaticLibrary(self, name, root_src_param); } + pub fn addStaticLibraryFromWriteFileStep( + self: *Builder, + name: []const u8, + wfs: *WriteFileStep, + basename: []const u8, + ) *LibExeObjStep { + return LibExeObjStep.createStaticLibrary(self, name, @as(FileSource, .{ + .write_file = .{ + .step = wfs, + .basename = basename, + }, + })); + } + pub fn addTest(self: *Builder, root_src: []const u8) *LibExeObjStep { return LibExeObjStep.createTest(self, "test", .{ .path = root_src }); } + pub fn addTestFromWriteFileStep( + self: *Builder, + wfs: *WriteFileStep, + basename: []const u8, + ) *LibExeObjStep { + return LibExeObjStep.createTest(self, "test", @as(FileSource, .{ + .write_file = .{ + .step = wfs, + .basename = basename, + }, + })); + } + pub fn addAssemble(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep { const obj_step = LibExeObjStep.createObject(self, name, null); obj_step.addAssemblyFile(src); @@ -294,7 +336,7 @@ pub const Builder = struct { /// To run an executable built with zig build, see `LibExeObjStep.run`. pub fn addSystemCommand(self: *Builder, argv: []const []const u8) *RunStep { assert(argv.len >= 1); - const run_step = RunStep.create(self, self.fmt("run {}", .{argv[0]})); + const run_step = RunStep.create(self, self.fmt("run {s}", .{argv[0]})); run_step.addArgs(argv); return run_step; } @@ -303,6 +345,14 @@ pub const Builder = struct { return self.allocator.dupe(u8, bytes) catch unreachable; } + pub fn dupeStrings(self: *Builder, strings: []const []const u8) [][]u8 { + const array = self.allocator.alloc([]u8, strings.len) catch unreachable; + for (strings) |s, i| { + array[i] = self.dupe(s); + } + return array; + } + pub fn dupePath(self: *Builder, bytes: []const u8) []u8 { const the_copy = self.dupe(bytes); for (the_copy) |*byte| { @@ -409,7 +459,7 @@ pub const Builder = struct { for (self.installed_files.items) |installed_file| { const full_path = self.getInstallPath(installed_file.dir, installed_file.path); if (self.verbose) { - warn("rm {}\n", .{full_path}); + warn("rm {s}\n", .{full_path}); } fs.cwd().deleteTree(full_path) catch {}; } @@ -419,7 +469,7 @@ pub const Builder = struct { fn makeOneStep(self: *Builder, s: *Step) anyerror!void { if (s.loop_flag) { - warn("Dependency loop detected:\n {}\n", .{s.name}); + warn("Dependency loop detected:\n {s}\n", .{s.name}); return error.DependencyLoopDetected; } s.loop_flag = true; @@ -427,7 +477,7 @@ pub const Builder = struct { for (s.dependencies.items) |dep| { self.makeOneStep(dep) catch |err| { if (err == error.DependencyLoopDetected) { - warn(" {}\n", .{s.name}); + warn(" {s}\n", .{s.name}); } return err; }; @@ -444,11 +494,13 @@ pub const Builder = struct { return &top_level_step.step; } } - warn("Cannot run step '{}' because it does not exist\n", .{name}); + warn("Cannot run step '{s}' because it does not exist\n", .{name}); return error.InvalidStepName; } - pub fn option(self: *Builder, comptime T: type, name: []const u8, description: []const u8) ?T { + pub fn option(self: *Builder, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { + const name = self.dupe(name_raw); + const description = self.dupe(description_raw); const type_id = comptime typeToEnum(T); const available_option = AvailableOption{ .name = name, @@ -456,7 +508,7 @@ pub const Builder = struct { .description = description, }; if ((self.available_options_map.fetchPut(name, available_option) catch unreachable) != null) { - panic("Option '{}' declared twice", .{name}); + panic("Option '{s}' declared twice", .{name}); } self.available_options_list.append(available_option) catch unreachable; @@ -471,32 +523,32 @@ pub const Builder = struct { } else if (mem.eql(u8, s, "false")) { return false; } else { - warn("Expected -D{} to be a boolean, but received '{}'\n", .{ name, s }); + warn("Expected -D{s} to be a boolean, but received '{s}'\n\n", .{ name, s }); self.markInvalidUserInput(); return null; } }, .List => { - warn("Expected -D{} to be a boolean, but received a list.\n", .{name}); + warn("Expected -D{s} to be a boolean, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .Int => switch (entry.value.value) { .Flag => { - warn("Expected -D{} to be an integer, but received a boolean.\n", .{name}); + warn("Expected -D{s} to be an integer, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .Scalar => |s| { const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { error.Overflow => { - warn("-D{} value {} cannot fit into type {}.\n", .{ name, s, @typeName(T) }); + warn("-D{s} value {s} cannot fit into type {s}.\n\n", .{ name, s, @typeName(T) }); self.markInvalidUserInput(); return null; }, else => { - warn("Expected -D{} to be an integer of type {}.\n", .{ name, @typeName(T) }); + warn("Expected -D{s} to be an integer of type {s}.\n\n", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; }, @@ -504,15 +556,34 @@ pub const Builder = struct { return n; }, .List => { - warn("Expected -D{} to be an integer, but received a list.\n", .{name}); + warn("Expected -D{s} to be an integer, but received a list.\n\n", .{name}); + self.markInvalidUserInput(); + return null; + }, + }, + .Float => switch (entry.value.value) { + .Flag => { + warn("Expected -D{s} to be a float, but received a boolean.\n\n", .{name}); + self.markInvalidUserInput(); + return null; + }, + .Scalar => |s| { + const n = std.fmt.parseFloat(T, s) catch |err| { + warn("Expected -D{s} to be a float of type {s}.\n\n", .{ name, @typeName(T) }); + self.markInvalidUserInput(); + return null; + }; + return n; + }, + .List => { + warn("Expected -D{s} to be a float, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, - .Float => panic("TODO float options to build script", .{}), .Enum => switch (entry.value.value) { .Flag => { - warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); + warn("Expected -D{s} to be a string, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, @@ -520,25 +591,25 @@ pub const Builder = struct { if (std.meta.stringToEnum(T, s)) |enum_lit| { return enum_lit; } else { - warn("Expected -D{} to be of type {}.\n", .{ name, @typeName(T) }); + warn("Expected -D{s} to be of type {s}.\n\n", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; } }, .List => { - warn("Expected -D{} to be a string, but received a list.\n", .{name}); + warn("Expected -D{s} to be a string, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, }, .String => switch (entry.value.value) { .Flag => { - warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); + warn("Expected -D{s} to be a string, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, .List => { - warn("Expected -D{} to be a string, but received a list.\n", .{name}); + warn("Expected -D{s} to be a string, but received a list.\n\n", .{name}); self.markInvalidUserInput(); return null; }, @@ -546,7 +617,7 @@ pub const Builder = struct { }, .List => switch (entry.value.value) { .Flag => { - warn("Expected -D{} to be a list, but received a boolean.\n", .{name}); + warn("Expected -D{s} to be a list, but received a boolean.\n\n", .{name}); self.markInvalidUserInput(); return null; }, @@ -562,7 +633,7 @@ pub const Builder = struct { const step_info = self.allocator.create(TopLevelStep) catch unreachable; step_info.* = TopLevelStep{ .step = Step.initNoOp(.TopLevel, name, self.allocator), - .description = description, + .description = self.dupe(description), }; self.top_level_steps.append(step_info) catch unreachable; return &step_info.step; @@ -573,7 +644,7 @@ pub const Builder = struct { if (self.release_mode != null) { @panic("setPreferredReleaseMode must be called before standardReleaseOptions and may not be called twice"); } - const description = self.fmt("Create a release build ({})", .{@tagName(mode)}); + const description = self.fmt("Create a release build ({s})", .{@tagName(mode)}); self.is_release = self.option(bool, "release", description) orelse false; self.release_mode = if (self.is_release) mode else builtin.Mode.Debug; } @@ -596,7 +667,7 @@ pub const Builder = struct { else if (!release_fast and !release_safe and !release_small) builtin.Mode.Debug else x: { - warn("Multiple release modes (of -Drelease-safe, -Drelease-fast and -Drelease-small)", .{}); + warn("Multiple release modes (of -Drelease-safe, -Drelease-fast and -Drelease-small)\n\n", .{}); self.markInvalidUserInput(); break :x builtin.Mode.Debug; }; @@ -627,43 +698,50 @@ pub const Builder = struct { .diagnostics = &diags, }) catch |err| switch (err) { error.UnknownCpuModel => { - std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ + warn("Unknown CPU: '{s}'\nAvailable CPUs for architecture '{s}':\n", .{ diags.cpu_name.?, @tagName(diags.arch.?), }); for (diags.arch.?.allCpuModels()) |cpu| { - std.debug.warn(" {}\n", .{cpu.name}); + warn(" {s}\n", .{cpu.name}); } - process.exit(1); + warn("\n", .{}); + self.markInvalidUserInput(); + return args.default_target; }, error.UnknownCpuFeature => { - std.debug.warn( - \\Unknown CPU feature: '{}' - \\Available CPU features for architecture '{}': + warn( + \\Unknown CPU feature: '{s}' + \\Available CPU features for architecture '{s}': \\ , .{ diags.unknown_feature_name, @tagName(diags.arch.?), }); for (diags.arch.?.allFeaturesList()) |feature| { - std.debug.warn(" {}: {}\n", .{ feature.name, feature.description }); + warn(" {s}: {s}\n", .{ feature.name, feature.description }); } - process.exit(1); + warn("\n", .{}); + self.markInvalidUserInput(); + return args.default_target; }, error.UnknownOperatingSystem => { - std.debug.warn( - \\Unknown OS: '{}' + warn( + \\Unknown OS: '{s}' \\Available operating systems: \\ , .{diags.os_name}); inline for (std.meta.fields(std.Target.Os.Tag)) |field| { - std.debug.warn(" {}\n", .{field.name}); + warn(" {s}\n", .{field.name}); } - process.exit(1); + warn("\n", .{}); + self.markInvalidUserInput(); + return args.default_target; }, else => |e| { - std.debug.warn("Unable to parse target '{}': {}\n", .{ triple, @errorName(e) }); - process.exit(1); + warn("Unable to parse target '{s}': {s}\n\n", .{ triple, @errorName(e) }); + self.markInvalidUserInput(); + return args.default_target; }, }; @@ -677,22 +755,24 @@ pub const Builder = struct { break :whitelist_check; } } - std.debug.warn("Chosen target '{}' does not match one of the supported targets:\n", .{ + warn("Chosen target '{s}' does not match one of the supported targets:\n", .{ selected_canonicalized_triple, }); for (list) |t| { const t_triple = t.zigTriple(self.allocator) catch unreachable; - std.debug.warn(" {}\n", .{t_triple}); + warn(" {s}\n", .{t_triple}); } - // TODO instead of process exit, return error and have a zig build flag implemented by - // the build runner that turns process exits into error return traces - process.exit(1); + warn("\n", .{}); + self.markInvalidUserInput(); + return args.default_target; } return selected_target; } - pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool { + pub fn addUserInputOption(self: *Builder, name_raw: []const u8, value_raw: []const u8) !bool { + const name = self.dupe(name_raw); + const value = self.dupe(value_raw); const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { gop.entry.value = UserInputOption{ @@ -726,14 +806,15 @@ pub const Builder = struct { }) catch unreachable; }, UserValue.Flag => { - warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", .{ name, value, name }); + warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.\n", .{ name, value, name }); return true; }, } return false; } - pub fn addUserInputFlag(self: *Builder, name: []const u8) !bool { + pub fn addUserInputFlag(self: *Builder, name_raw: []const u8) !bool { + const name = self.dupe(name_raw); const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { gop.entry.value = UserInputOption{ @@ -747,11 +828,11 @@ pub const Builder = struct { // option already exists switch (gop.entry.value.value) { UserValue.Scalar => |s| { - warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", .{ name, name, s }); + warn("Flag '-D{s}' conflicts with option '-D{s}={s}'.\n", .{ name, name, s }); return true; }, UserValue.List => { - warn("Flag '-D{}' conflicts with multiple options of the same name.\n", .{name}); + warn("Flag '-D{s}' conflicts with multiple options of the same name.\n", .{name}); return true; }, UserValue.Flag => {}, @@ -794,7 +875,7 @@ pub const Builder = struct { while (true) { const entry = it.next() orelse break; if (!entry.value.used) { - warn("Invalid option: -D{}\n\n", .{entry.key}); + warn("Invalid option: -D{s}\n\n", .{entry.key}); self.markInvalidUserInput(); } } @@ -807,9 +888,9 @@ pub const Builder = struct { } fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| warn("cd {} && ", .{yes_cwd}); + if (cwd) |yes_cwd| warn("cd {s} && ", .{yes_cwd}); for (argv) |arg| { - warn("{} ", .{arg}); + warn("{s} ", .{arg}); } warn("\n", .{}); } @@ -826,7 +907,7 @@ pub const Builder = struct { child.env_map = env_map; const term = child.spawnAndWait() catch |err| { - warn("Unable to spawn {}: {}\n", .{ argv[0], @errorName(err) }); + warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); return err; }; @@ -849,7 +930,7 @@ pub const Builder = struct { pub fn makePath(self: *Builder, path: []const u8) !void { fs.cwd().makePath(self.pathFromRoot(path)) catch |err| { - warn("Unable to create path {}: {}\n", .{ path, @errorName(err) }); + warn("Unable to create path {s}: {s}\n", .{ path, @errorName(err) }); return err; }; } @@ -925,15 +1006,16 @@ pub const Builder = struct { } pub fn pushInstalledFile(self: *Builder, dir: InstallDir, dest_rel_path: []const u8) void { - self.installed_files.append(InstalledFile{ + const file = InstalledFile{ .dir = dir, .path = dest_rel_path, - }) catch unreachable; + }; + self.installed_files.append(file.dupe(self)) catch unreachable; } pub fn updateFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void { if (self.verbose) { - warn("cp {} {} ", .{ source_path, dest_path }); + warn("cp {s} {s} ", .{ source_path, dest_path }); } const cwd = fs.cwd(); const prev_status = try fs.Dir.updateFile(cwd, source_path, cwd, dest_path, .{}); @@ -962,7 +1044,7 @@ pub const Builder = struct { const full_path = try fs.path.join(self.allocator, &[_][]const u8{ search_prefix, "bin", - self.fmt("{}{}", .{ name, exe_extension }), + self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } @@ -976,7 +1058,7 @@ pub const Builder = struct { while (it.next()) |path| { const full_path = try fs.path.join(self.allocator, &[_][]const u8{ path, - self.fmt("{}{}", .{ name, exe_extension }), + self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } @@ -989,7 +1071,7 @@ pub const Builder = struct { for (paths) |path| { const full_path = try fs.path.join(self.allocator, &[_][]const u8{ path, - self.fmt("{}{}", .{ name, exe_extension }), + self.fmt("{s}{s}", .{ name, exe_extension }), }); return fs.realpathAlloc(self.allocator, full_path) catch continue; } @@ -1012,10 +1094,11 @@ pub const Builder = struct { child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = stderr_behavior; + child.env_map = self.env_map; try child.spawn(); - const stdout = try child.stdout.?.inStream().readAllAlloc(self.allocator, max_output_size); + const stdout = try child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size); errdefer self.allocator.free(stdout); const term = try child.wait(); @@ -1044,19 +1127,19 @@ pub const Builder = struct { var code: u8 = undefined; return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) { error.FileNotFound => { - if (src_step) |s| warn("{}...", .{s.name}); + if (src_step) |s| warn("{s}...", .{s.name}); warn("Unable to spawn the following command: file not found\n", .{}); printCmd(null, argv); std.os.exit(@truncate(u8, code)); }, error.ExitCodeFailure => { - if (src_step) |s| warn("{}...", .{s.name}); - warn("The following command exited with error code {}:\n", .{code}); + if (src_step) |s| warn("{s}...", .{s.name}); + warn("The following command exited with error code {d}:\n", .{code}); printCmd(null, argv); std.os.exit(@truncate(u8, code)); }, error.ProcessTerminated => { - if (src_step) |s| warn("{}...", .{s.name}); + if (src_step) |s| warn("{s}...", .{s.name}); warn("The following command terminated unexpectedly:\n", .{}); printCmd(null, argv); std.os.exit(@truncate(u8, code)); @@ -1070,10 +1153,11 @@ pub const Builder = struct { } pub fn addSearchPrefix(self: *Builder, search_prefix: []const u8) void { - self.search_prefixes.append(search_prefix) catch unreachable; + self.search_prefixes.append(self.dupePath(search_prefix)) catch unreachable; } pub fn getInstallPath(self: *Builder, dir: InstallDir, dest_rel_path: []const u8) []const u8 { + assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix const base_dir = switch (dir) { .Prefix => self.install_path, .Bin => self.exe_dir, @@ -1090,6 +1174,7 @@ pub const Builder = struct { fn execPkgConfigList(self: *Builder, out_code: *u8) ![]const PkgConfigPkg { const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); var list = ArrayList(PkgConfigPkg).init(self.allocator); + errdefer list.deinit(); var line_it = mem.tokenize(stdout, "\r\n"); while (line_it.next()) |line| { if (mem.trim(u8, line, " \t").len == 0) continue; @@ -1099,7 +1184,7 @@ pub const Builder = struct { .desc = tok_it.rest(), }); } - return list.items; + return list.toOwnedSlice(); } fn getPkgConfigList(self: *Builder) ![]const PkgConfigPkg { @@ -1154,9 +1239,16 @@ pub const Pkg = struct { dependencies: ?[]const Pkg = null, }; -const CSourceFile = struct { +pub const CSourceFile = struct { source: FileSource, args: []const []const u8, + + fn dupe(self: CSourceFile, b: *Builder) CSourceFile { + return .{ + .source = self.source.dupe(b), + .args = b.dupeStrings(self.args), + }; + } }; const CSourceFiles = struct { @@ -1198,6 +1290,17 @@ pub const FileSource = union(enum) { .translate_c => |tc| tc.getOutputPath(), }; } + + pub fn dupe(self: FileSource, b: *Builder) FileSource { + return switch (self) { + .path => |p| .{ .path = b.dupe(p) }, + .write_file => |wf| .{ .write_file = .{ + .step = wf.step, + .basename = b.dupe(wf.basename), + } }, + .translate_c => |tc| .{ .translate_c = tc }, + }; + } }; const BuildOptionArtifactArg = struct { @@ -1205,6 +1308,12 @@ const BuildOptionArtifactArg = struct { artifact: *LibExeObjStep, }; +const BuildOptionWriteFileArg = struct { + name: []const u8, + write_file: *WriteFileStep, + basename: []const u8, +}; + pub const LibExeObjStep = struct { step: Step, builder: *Builder, @@ -1233,6 +1342,7 @@ pub const LibExeObjStep = struct { bundle_compiler_rt: ?bool = null, disable_stack_probing: bool, disable_sanitize_c: bool, + sanitize_thread: bool, rdynamic: bool, c_std: Builder.CStd, override_lib_dir: ?[]const u8, @@ -1251,6 +1361,7 @@ pub const LibExeObjStep = struct { packages: ArrayList(Pkg), build_options_contents: std.ArrayList(u8), build_options_artifact_args: std.ArrayList(BuildOptionArtifactArg), + build_options_write_file_args: std.ArrayList(BuildOptionWriteFileArg), object_src: []const u8, @@ -1303,11 +1414,15 @@ pub const LibExeObjStep = struct { /// Position Independent Executable pie: ?bool = null, + red_zone: ?bool = null, + subsystem: ?builtin.SubSystem = null, /// Overrides the default stack size stack_size: ?u64 = null, + want_lto: ?bool = null, + const LinkObject = union(enum) { StaticPath: []const u8, OtherStep: *LibExeObjStep, @@ -1370,14 +1485,16 @@ pub const LibExeObjStep = struct { fn initExtraArgs( builder: *Builder, - name: []const u8, - root_src: ?FileSource, + name_raw: []const u8, + root_src_raw: ?FileSource, kind: Kind, is_dynamic: bool, ver: ?Version, ) LibExeObjStep { + const name = builder.dupe(name_raw); + const root_src: ?FileSource = if (root_src_raw) |rsrc| rsrc.dupe(builder) else null; if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { - panic("invalid name: '{}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); + panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } var self = LibExeObjStep{ .strip = false, @@ -1393,9 +1510,9 @@ pub const LibExeObjStep = struct { .step = Step.init(.LibExeObj, name, builder.allocator, make), .version = ver, .out_filename = undefined, - .out_h_filename = builder.fmt("{}.h", .{name}), + .out_h_filename = builder.fmt("{s}.h", .{name}), .out_lib_filename = undefined, - .out_pdb_filename = builder.fmt("{}.pdb", .{name}), + .out_pdb_filename = builder.fmt("{s}.pdb", .{name}), .major_only_filename = undefined, .name_only_filename = undefined, .packages = ArrayList(Pkg).init(builder.allocator), @@ -1407,6 +1524,7 @@ pub const LibExeObjStep = struct { .object_src = undefined, .build_options_contents = std.ArrayList(u8).init(builder.allocator), .build_options_artifact_args = std.ArrayList(BuildOptionArtifactArg).init(builder.allocator), + .build_options_write_file_args = std.ArrayList(BuildOptionWriteFileArg).init(builder.allocator), .c_std = Builder.CStd.C99, .override_lib_dir = null, .main_pkg_path = null, @@ -1415,6 +1533,7 @@ pub const LibExeObjStep = struct { .filter = null, .disable_stack_probing = false, .disable_sanitize_c = false, + .sanitize_thread = false, .rdynamic = false, .output_dir = null, .single_threaded = false, @@ -1500,7 +1619,7 @@ pub const LibExeObjStep = struct { // It doesn't have to be native. We catch that if you actually try to run it. // Consider that this is declarative; the run step may not be run unless a user // option is supplied. - const run_step = RunStep.create(exe.builder, exe.builder.fmt("run {}", .{exe.step.name})); + const run_step = RunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name})); run_step.addArtifactArg(exe); if (exe.vcpkg_bin_path) |path| { @@ -1511,12 +1630,12 @@ pub const LibExeObjStep = struct { } pub fn setLinkerScriptPath(self: *LibExeObjStep, path: []const u8) void { - self.linker_script = path; + self.linker_script = self.builder.dupePath(path); } pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void { assert(self.target.isDarwin()); - self.frameworks.put(framework_name) catch unreachable; + self.frameworks.put(self.builder.dupe(framework_name)) catch unreachable; } /// Returns whether the library, executable, or object depends on a particular system library. @@ -1651,7 +1770,7 @@ pub const LibExeObjStep = struct { } else if (mem.eql(u8, tok, "-pthread")) { self.linkLibC(); } else if (self.builder.verbose) { - warn("Ignoring pkg-config flag '{}'\n", .{tok}); + warn("Ignoring pkg-config flag '{s}'\n", .{tok}); } } } @@ -1680,25 +1799,23 @@ pub const LibExeObjStep = struct { pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void { assert(self.kind == Kind.Test); - self.name_prefix = text; + self.name_prefix = self.builder.dupe(text); } pub fn setFilter(self: *LibExeObjStep, text: ?[]const u8) void { assert(self.kind == Kind.Test); - self.filter = text; + self.filter = if (text) |t| self.builder.dupe(t) else null; } /// Handy when you have many C/C++ source files and want them all to have the same flags. pub fn addCSourceFiles(self: *LibExeObjStep, files: []const []const u8, flags: []const []const u8) void { const c_source_files = self.builder.allocator.create(CSourceFiles) catch unreachable; - const flags_copy = self.builder.allocator.alloc([]u8, flags.len) catch unreachable; - for (flags) |flag, i| { - flags_copy[i] = self.builder.dupe(flag); - } + const files_copy = self.builder.dupeStrings(files); + const flags_copy = self.builder.dupeStrings(flags); c_source_files.* = .{ - .files = files, + .files = files_copy, .flags = flags_copy, }; self.link_objects.append(LinkObject{ .CSourceFiles = c_source_files }) catch unreachable; @@ -1713,14 +1830,7 @@ pub const LibExeObjStep = struct { pub fn addCSourceFileSource(self: *LibExeObjStep, source: CSourceFile) void { const c_source_file = self.builder.allocator.create(CSourceFile) catch unreachable; - - const args_copy = self.builder.allocator.alloc([]u8, source.args.len) catch unreachable; - for (source.args) |arg, i| { - args_copy[i] = self.builder.dupe(arg); - } - - c_source_file.* = source; - c_source_file.args = args_copy; + c_source_file.* = source.dupe(self.builder); self.link_objects.append(LinkObject{ .CSourceFile = c_source_file }) catch unreachable; } @@ -1737,15 +1847,15 @@ pub const LibExeObjStep = struct { } pub fn overrideZigLibDir(self: *LibExeObjStep, dir_path: []const u8) void { - self.override_lib_dir = self.builder.dupe(dir_path); + self.override_lib_dir = self.builder.dupePath(dir_path); } pub fn setMainPkgPath(self: *LibExeObjStep, dir_path: []const u8) void { - self.main_pkg_path = dir_path; + self.main_pkg_path = self.builder.dupePath(dir_path); } pub fn setLibCFile(self: *LibExeObjStep, libc_file: ?[]const u8) void { - self.libc_file = libc_file; + self.libc_file = if (libc_file) |f| self.builder.dupe(f) else null; } /// Unless setOutputDir was called, this function must be called only in @@ -1805,8 +1915,9 @@ pub const LibExeObjStep = struct { } pub fn addAssemblyFileSource(self: *LibExeObjStep, source: FileSource) void { - self.link_objects.append(LinkObject{ .AssemblyFile = source }) catch unreachable; - source.addStepDependencies(&self.step); + const source_duped = source.dupe(self.builder); + self.link_objects.append(LinkObject{ .AssemblyFile = source_duped }) catch unreachable; + source_duped.addStepDependencies(&self.step); } pub fn addObjectFile(self: *LibExeObjStep, path: []const u8) void { @@ -1819,28 +1930,28 @@ pub const LibExeObjStep = struct { } pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void { - const out = self.build_options_contents.outStream(); + const out = self.build_options_contents.writer(); switch (T) { []const []const u8 => { - out.print("pub const {z}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable; + out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; for (value) |slice| { - out.print(" \"{Z}\",\n", .{slice}) catch unreachable; + out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; }, [:0]const u8 => { - out.print("pub const {z}: [:0]const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, []const u8 => { - out.print("pub const {z}: []const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, ?[]const u8 => { - out.print("pub const {z}: ?[]const u8 = ", .{name}) catch unreachable; + out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; if (value) |payload| { - out.print("\"{Z}\";\n", .{payload}) catch unreachable; + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; } else { out.writeAll("null;\n") catch unreachable; } @@ -1848,14 +1959,14 @@ pub const LibExeObjStep = struct { }, std.builtin.Version => { out.print( - \\pub const {z}: @import("builtin").Version = .{{ + \\pub const {}: @import("builtin").Version = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\}}; \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, @@ -1864,23 +1975,23 @@ pub const LibExeObjStep = struct { }, std.SemanticVersion => { out.print( - \\pub const {z}: @import("std").SemanticVersion = .{{ + \\pub const {}: @import("std").SemanticVersion = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, value.patch, }) catch unreachable; if (value.pre) |some| { - out.print(" .pre = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } if (value.build) |some| { - out.print(" .build = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; @@ -1889,24 +2000,41 @@ pub const LibExeObjStep = struct { } switch (@typeInfo(T)) { .Enum => |enum_info| { - out.print("pub const {z} = enum {{\n", .{@typeName(T)}) catch unreachable; + out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; inline for (enum_info.fields) |field| { - out.print(" {z},\n", .{field.name}) catch unreachable; + out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; } out.writeAll("};\n") catch unreachable; }, else => {}, } - out.print("pub const {z}: {} = {};\n", .{ name, @typeName(T), value }) catch unreachable; + out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable; } /// The value is the path in the cache dir. /// Adds a dependency automatically. pub fn addBuildOptionArtifact(self: *LibExeObjStep, name: []const u8, artifact: *LibExeObjStep) void { - self.build_options_artifact_args.append(.{ .name = name, .artifact = artifact }) catch unreachable; + self.build_options_artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable; self.step.dependOn(&artifact.step); } + /// The value is the path in the cache dir. + /// Adds a dependency automatically. + /// basename refers to the basename of the WriteFileStep + pub fn addBuildOptionWriteFile( + self: *LibExeObjStep, + name: []const u8, + write_file: *WriteFileStep, + basename: []const u8, + ) void { + self.build_options_write_file_args.append(.{ + .name = name, + .write_file = write_file, + .basename = basename, + }) catch unreachable; + self.step.dependOn(&write_file.step); + } + pub fn addSystemIncludeDir(self: *LibExeObjStep, path: []const u8) void { self.include_dirs.append(IncludeDir{ .RawPathSystem = self.builder.dupe(path) }) catch unreachable; } @@ -1973,7 +2101,11 @@ pub const LibExeObjStep = struct { pub fn setExecCmd(self: *LibExeObjStep, args: []const ?[]const u8) void { assert(self.kind == Kind.Test); - self.exec_cmd_args = args; + const duped_args = self.builder.allocator.alloc(?[]u8, args.len) catch unreachable; + for (args) |arg, i| { + duped_args[i] = if (arg) |a| self.builder.dupe(a) else null; + } + self.exec_cmd_args = duped_args; } fn linkLibraryOrObject(self: *LibExeObjStep, other: *LibExeObjStep) void { @@ -2019,7 +2151,7 @@ pub const LibExeObjStep = struct { const builder = self.builder; if (self.root_src == null and self.link_objects.items.len == 0) { - warn("{}: linker needs 1 or more objects to link\n", .{self.step.name}); + warn("{s}: linker needs 1 or more objects to link\n", .{self.step.name}); return error.NeedAnObject; } @@ -2123,16 +2255,32 @@ pub const LibExeObjStep = struct { } } - if (self.build_options_contents.items.len > 0 or self.build_options_artifact_args.items.len > 0) { - // Render build artifact options at the last minute, now that the path is known. + if (self.build_options_contents.items.len > 0 or + self.build_options_artifact_args.items.len > 0 or + self.build_options_write_file_args.items.len > 0) + { + // Render build artifact and write file options at the last minute, now that the path is known. + // + // Note that pathFromRoot uses resolve path, so this will have + // correct behavior even if getOutputPath is already absolute. for (self.build_options_artifact_args.items) |item| { - const out = self.build_options_contents.writer(); - out.print("pub const {}: []const u8 = \"{Z}\";\n", .{ item.name, item.artifact.getOutputPath() }) catch unreachable; + self.addBuildOption( + []const u8, + item.name, + self.builder.pathFromRoot(item.artifact.getOutputPath()), + ); + } + for (self.build_options_write_file_args.items) |item| { + self.addBuildOption( + []const u8, + item.name, + self.builder.pathFromRoot(item.write_file.getOutputPath(item.basename)), + ); } const build_options_file = try fs.path.join( builder.allocator, - &[_][]const u8{ builder.cache_root, builder.fmt("{}_build_options.zig", .{self.name}) }, + &[_][]const u8{ builder.cache_root, builder.fmt("{s}_build_options.zig", .{self.name}) }, ); const path_from_root = builder.pathFromRoot(build_options_file); try fs.cwd().writeFile(path_from_root, self.build_options_contents.items); @@ -2230,9 +2378,19 @@ pub const LibExeObjStep = struct { if (self.disable_stack_probing) { try zig_args.append("-fno-stack-check"); } + if (self.red_zone) |red_zone| { + if (red_zone) { + try zig_args.append("-mred-zone"); + } else { + try zig_args.append("-mno-red-zone"); + } + } if (self.disable_sanitize_c) { try zig_args.append("-fno-sanitize-c"); } + if (self.sanitize_thread) { + try zig_args.append("-fsanitize-thread"); + } if (self.rdynamic) { try zig_args.append("-rdynamic"); } @@ -2262,16 +2420,16 @@ pub const LibExeObjStep = struct { } else { var mcpu_buffer = std.ArrayList(u8).init(builder.allocator); - try mcpu_buffer.outStream().print("-mcpu={}", .{cross.cpu.model.name}); + try mcpu_buffer.writer().print("-mcpu={s}", .{cross.cpu.model.name}); for (all_features) |feature, i_usize| { const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); const in_actual_set = cross.cpu.features.isEnabled(i); if (in_cpu_set and !in_actual_set) { - try mcpu_buffer.outStream().print("-{}", .{feature.name}); + try mcpu_buffer.writer().print("-{s}", .{feature.name}); } else if (!in_cpu_set and in_actual_set) { - try mcpu_buffer.outStream().print("+{}", .{feature.name}); + try mcpu_buffer.writer().print("+{s}", .{feature.name}); } } @@ -2433,6 +2591,14 @@ pub const LibExeObjStep = struct { } } + if (self.want_lto) |lto| { + if (lto) { + try zig_args.append("-flto"); + } else { + try zig_args.append("-fno-lto"); + } + } + if (self.subsystem) |subsystem| { try zig_args.append("--subsystem"); try zig_args.append(switch (subsystem) { @@ -2504,7 +2670,7 @@ pub const InstallArtifactStep = struct { const self = builder.allocator.create(Self) catch unreachable; self.* = Self{ .builder = builder, - .step = Step.init(.InstallArtifact, builder.fmt("install {}", .{artifact.step.name}), builder.allocator, make), + .step = Step.init(.InstallArtifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make), .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .Obj => unreachable, @@ -2580,10 +2746,10 @@ pub const InstallFileStep = struct { builder.pushInstalledFile(dir, dest_rel_path); return InstallFileStep{ .builder = builder, - .step = Step.init(.InstallFile, builder.fmt("install {}", .{src_path}), builder.allocator, make), - .src_path = src_path, - .dir = dir, - .dest_rel_path = dest_rel_path, + .step = Step.init(.InstallFile, builder.fmt("install {s}", .{src_path}), builder.allocator, make), + .src_path = builder.dupePath(src_path), + .dir = dir.dupe(builder), + .dest_rel_path = builder.dupePath(dest_rel_path), }; } @@ -2600,6 +2766,18 @@ pub const InstallDirectoryOptions = struct { install_dir: InstallDir, install_subdir: []const u8, exclude_extensions: ?[]const []const u8 = null, + + fn dupe(self: InstallDirectoryOptions, b: *Builder) InstallDirectoryOptions { + return .{ + .source_dir = b.dupe(self.source_dir), + .install_dir = self.install_dir.dupe(b), + .install_subdir = b.dupe(self.install_subdir), + .exclude_extensions = if (self.exclude_extensions) |extensions| + b.dupeStrings(extensions) + else + null, + }; + } }; pub const InstallDirStep = struct { @@ -2614,8 +2792,8 @@ pub const InstallDirStep = struct { builder.pushInstalledFile(options.install_dir, options.install_subdir); return InstallDirStep{ .builder = builder, - .step = Step.init(.InstallDir, builder.fmt("install {}/", .{options.source_dir}), builder.allocator, make), - .options = options, + .step = Step.init(.InstallDir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make), + .options = options.dupe(builder), }; } @@ -2650,14 +2828,14 @@ pub const LogStep = struct { pub fn init(builder: *Builder, data: []const u8) LogStep { return LogStep{ .builder = builder, - .step = Step.init(.Log, builder.fmt("log {}", .{data}), builder.allocator, make), - .data = data, + .step = Step.init(.Log, builder.fmt("log {s}", .{data}), builder.allocator, make), + .data = builder.dupe(data), }; } fn make(step: *Step) anyerror!void { const self = @fieldParentPtr(LogStep, "step", step); - warn("{}", .{self.data}); + warn("{s}", .{self.data}); } }; @@ -2669,8 +2847,8 @@ pub const RemoveDirStep = struct { pub fn init(builder: *Builder, dir_path: []const u8) RemoveDirStep { return RemoveDirStep{ .builder = builder, - .step = Step.init(.RemoveDir, builder.fmt("RemoveDir {}", .{dir_path}), builder.allocator, make), - .dir_path = dir_path, + .step = Step.init(.RemoveDir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make), + .dir_path = builder.dupePath(dir_path), }; } @@ -2679,7 +2857,7 @@ pub const RemoveDirStep = struct { const full_path = self.builder.pathFromRoot(self.dir_path); fs.cwd().deleteTree(full_path) catch |err| { - warn("Unable to remove {}: {}\n", .{ full_path, @errorName(err) }); + warn("Unable to remove {s}: {s}\n", .{ full_path, @errorName(err) }); return err; }; } @@ -2714,7 +2892,7 @@ pub const Step = struct { pub fn init(id: Id, name: []const u8, allocator: *Allocator, makeFn: fn (*Step) anyerror!void) Step { return Step{ .id = id, - .name = name, + .name = allocator.dupe(u8, name) catch unreachable, .makeFn = makeFn, .dependencies = ArrayList(*Step).init(allocator), .loop_flag = false, @@ -2767,7 +2945,7 @@ fn doAtomicSymLinks(allocator: *Allocator, output_path: []const u8, filename_maj &[_][]const u8{ out_dir, filename_major_only }, ) catch unreachable; fs.atomicSymLink(allocator, out_basename, major_only_path) catch |err| { - warn("Unable to symlink {} -> {}\n", .{ major_only_path, out_basename }); + warn("Unable to symlink {s} -> {s}\n", .{ major_only_path, out_basename }); return err; }; // sym link for libfoo.so to libfoo.so.1 @@ -2776,7 +2954,7 @@ fn doAtomicSymLinks(allocator: *Allocator, output_path: []const u8, filename_maj &[_][]const u8{ out_dir, filename_name_only }, ) catch unreachable; fs.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| { - warn("Unable to symlink {} -> {}\n", .{ name_only_path, filename_major_only }); + warn("Unable to symlink {s} -> {s}\n", .{ name_only_path, filename_major_only }); return err; }; } @@ -2821,11 +2999,28 @@ pub const InstallDir = union(enum) { Header: void, /// A path relative to the prefix Custom: []const u8, + + fn dupe(self: InstallDir, builder: *Builder) InstallDir { + if (self == .Custom) { + // Written with this temporary to avoid RLS problems + const duped_path = builder.dupe(self.Custom); + return .{ .Custom = duped_path }; + } else { + return self; + } + } }; pub const InstalledFile = struct { dir: InstallDir, path: []const u8, + + pub fn dupe(self: InstalledFile, builder: *Builder) InstalledFile { + return .{ + .dir = self.dir.dupe(builder), + .path = builder.dupe(self.path), + }; + } }; test "Builder.dupePkg()" { @@ -2943,7 +3138,7 @@ test "LibExeObjStep.addPackage" { std.testing.expectEqualStrings(pkg_top.name, dupe.name); } -test "" { +test { // The only purpose of this test is to get all these untested functions // to be referenced to avoid regression so it is okay to skip some targets. if (comptime std.Target.current.cpu.arch.ptrBitWidth() == 64) { diff --git a/lib/std/build/check_file.zig b/lib/std/build/check_file.zig index e1372c0fe9..28c98547b7 100644 --- a/lib/std/build/check_file.zig +++ b/lib/std/build/check_file.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -27,8 +27,8 @@ pub const CheckFileStep = struct { self.* = CheckFileStep{ .builder = builder, .step = Step.init(.CheckFile, "CheckFile", builder.allocator, make), - .source = source, - .expected_matches = expected_matches, + .source = source.dupe(builder), + .expected_matches = builder.dupeStrings(expected_matches), }; self.source.addStepDependencies(&self.step); return self; @@ -45,9 +45,9 @@ pub const CheckFileStep = struct { warn( \\ \\========= Expected to find: =================== - \\{} + \\{s} \\========= But file does not contain it: ======= - \\{} + \\{s} \\ , .{ expected_match, contents }); return error.TestFailed; diff --git a/lib/std/build/emit_raw.zig b/lib/std/build/emit_raw.zig index ce2b6dfe2a..721b38b7a2 100644 --- a/lib/std/build/emit_raw.zig +++ b/lib/std/build/emit_raw.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -189,7 +189,7 @@ pub const InstallRawStep = struct { pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8) *Self { const self = builder.allocator.create(Self) catch unreachable; self.* = Self{ - .step = Step.init(.InstallRaw, builder.fmt("install raw binary {}", .{artifact.step.name}), builder.allocator, make), + .step = Step.init(.InstallRaw, builder.fmt("install raw binary {s}", .{artifact.step.name}), builder.allocator, make), .builder = builder, .artifact = artifact, .dest_dir = switch (artifact.kind) { @@ -223,6 +223,6 @@ pub const InstallRawStep = struct { } }; -test "" { +test { std.testing.refAllDecls(InstallRawStep); } diff --git a/lib/std/build/fmt.zig b/lib/std/build/fmt.zig index 8f0176c00e..069cd348bc 100644 --- a/lib/std/build/fmt.zig +++ b/lib/std/build/fmt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/build/run.zig b/lib/std/build/run.zig index 137d11f9b5..ca39b0216e 100644 --- a/lib/std/build/run.zig +++ b/lib/std/build/run.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -76,7 +76,7 @@ pub const RunStep = struct { self.argv.append(Arg{ .WriteFile = .{ .step = write_file, - .file_name = file_name, + .file_name = self.builder.dupePath(file_name), }, }) catch unreachable; self.step.dependOn(&write_file.step); @@ -116,10 +116,10 @@ pub const RunStep = struct { } if (prev_path) |pp| { - const new_path = self.builder.fmt("{}" ++ [1]u8{fs.path.delimiter} ++ "{}", .{ pp, search_path }); + const new_path = self.builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); env_map.set(key, new_path) catch unreachable; } else { - env_map.set(key, search_path) catch unreachable; + env_map.set(key, self.builder.dupePath(search_path)) catch unreachable; } } @@ -134,15 +134,18 @@ pub const RunStep = struct { pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { const env_map = self.getEnvMap(); - env_map.set(key, value) catch unreachable; + env_map.set( + self.builder.dupe(key), + self.builder.dupe(value), + ) catch unreachable; } pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { - self.stderr_action = .{ .expect_exact = bytes }; + self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; } pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { - self.stdout_action = .{ .expect_exact = bytes }; + self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; } fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { @@ -189,7 +192,7 @@ pub const RunStep = struct { child.stderr_behavior = stdIoActionToBehavior(self.stderr_action); child.spawn() catch |err| { - warn("Unable to spawn {}: {}\n", .{ argv[0], @errorName(err) }); + warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); return err; }; @@ -200,7 +203,7 @@ pub const RunStep = struct { switch (self.stdout_action) { .expect_exact, .expect_matches => { - stdout = child.stdout.?.inStream().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } @@ -210,13 +213,13 @@ pub const RunStep = struct { switch (self.stderr_action) { .expect_exact, .expect_matches => { - stderr = child.stderr.?.inStream().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } const term = child.wait() catch |err| { - warn("Unable to spawn {}: {}\n", .{ argv[0], @errorName(err) }); + warn("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); return err; }; @@ -245,9 +248,9 @@ pub const RunStep = struct { warn( \\ \\========= Expected this stderr: ========= - \\{} + \\{s} \\========= But found: ==================== - \\{} + \\{s} \\ , .{ expected_bytes, stderr.? }); printCmd(cwd, argv); @@ -259,9 +262,9 @@ pub const RunStep = struct { warn( \\ \\========= Expected to find in stderr: ========= - \\{} + \\{s} \\========= But stderr does not contain it: ===== - \\{} + \\{s} \\ , .{ match, stderr.? }); printCmd(cwd, argv); @@ -277,9 +280,9 @@ pub const RunStep = struct { warn( \\ \\========= Expected this stdout: ========= - \\{} + \\{s} \\========= But found: ==================== - \\{} + \\{s} \\ , .{ expected_bytes, stdout.? }); printCmd(cwd, argv); @@ -291,9 +294,9 @@ pub const RunStep = struct { warn( \\ \\========= Expected to find in stdout: ========= - \\{} + \\{s} \\========= But stdout does not contain it: ===== - \\{} + \\{s} \\ , .{ match, stdout.? }); printCmd(cwd, argv); @@ -304,9 +307,9 @@ pub const RunStep = struct { } fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| warn("cd {} && ", .{yes_cwd}); + if (cwd) |yes_cwd| warn("cd {s} && ", .{yes_cwd}); for (argv) |arg| { - warn("{} ", .{arg}); + warn("{s} ", .{arg}); } warn("\n", .{}); } diff --git a/lib/std/build/translate_c.zig b/lib/std/build/translate_c.zig index 688a7df419..4009079e3d 100644 --- a/lib/std/build/translate_c.zig +++ b/lib/std/build/translate_c.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -57,11 +57,11 @@ pub const TranslateCStep = struct { } pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { - self.include_dirs.append(include_dir) catch unreachable; + self.include_dirs.append(self.builder.dupePath(include_dir)) catch unreachable; } pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { - return CheckFileStep.create(self.builder, .{ .translate_c = self }, expected_matches); + return CheckFileStep.create(self.builder, .{ .translate_c = self }, self.builder.dupeStrings(expected_matches)); } fn make(step: *Step) !void { diff --git a/lib/std/build/write_file.zig b/lib/std/build/write_file.zig index 923df960de..6e88aa5633 100644 --- a/lib/std/build/write_file.zig +++ b/lib/std/build/write_file.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -32,7 +32,10 @@ pub const WriteFileStep = struct { } pub fn add(self: *WriteFileStep, basename: []const u8, bytes: []const u8) void { - self.files.append(.{ .basename = basename, .bytes = bytes }) catch unreachable; + self.files.append(.{ + .basename = self.builder.dupePath(basename), + .bytes = self.builder.dupe(bytes), + }) catch unreachable; } /// Unless setOutputDir was called, this function must be called only in @@ -72,7 +75,7 @@ pub const WriteFileStep = struct { var digest: [48]u8 = undefined; hash.final(&digest); var hash_basename: [64]u8 = undefined; - fs.base64_encoder.encode(&hash_basename, &digest); + _ = fs.base64_encoder.encode(&hash_basename, &digest); self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{ self.builder.cache_root, "o", @@ -80,14 +83,14 @@ pub const WriteFileStep = struct { }); // TODO replace with something like fs.makePathAndOpenDir fs.cwd().makePath(self.output_dir) catch |err| { - warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) }); + warn("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) }); return err; }; var dir = try fs.cwd().openDir(self.output_dir, .{}); defer dir.close(); for (self.files.items) |file| { dir.writeFile(file.basename, file.bytes) catch |err| { - warn("unable to write {} into {}: {}\n", .{ + warn("unable to write {s} into {s}: {s}\n", .{ file.basename, self.output_dir, @errorName(err), diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index fa6b2ab6b4..93de8ae3b9 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -67,12 +67,12 @@ pub const StackTrace = struct { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const debug_info = std.debug.getSelfDebugInfo() catch |err| { - return writer.print("\nUnable to print stack trace: Unable to open debug info: {}\n", .{@errorName(err)}); + return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); }; const tty_config = std.debug.detectTTYConfig(); try writer.writeAll("\n"); std.debug.writeStackTrace(self, writer, &arena.allocator, debug_info, tty_config) catch |err| { - try writer.print("Unable to print stack trace: {}\n", .{@errorName(err)}); + try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); }; try writer.writeAll("\n"); } @@ -155,6 +155,7 @@ pub const CallingConvention = enum { C, Naked, Async, + Inline, Interrupt, Signal, Stdcall, @@ -175,7 +176,7 @@ pub const SourceLocation = struct { column: u32, }; -pub const TypeId = @TagType(TypeInfo); +pub const TypeId = std.meta.Tag(TypeInfo); /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. @@ -404,21 +405,13 @@ pub const TypeInfo = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const FnDecl = struct { fn_type: type, - inline_type: Inline, + is_noinline: bool, is_var_args: bool, is_extern: bool, is_export: bool, lib_name: ?[]const u8, return_type: type, arg_names: []const []const u8, - - /// This data structure is used by the Zig language code generation and - /// therefore must be kept in sync with the compiler implementation. - pub const Inline = enum { - Auto, - Always, - Never, - }; }; }; }; @@ -529,12 +522,12 @@ pub const Version = struct { if (fmt.len == 0) { if (self.patch == 0) { if (self.minor == 0) { - return std.fmt.format(out_stream, "{}", .{self.major}); + return std.fmt.format(out_stream, "{d}", .{self.major}); } else { - return std.fmt.format(out_stream, "{}.{}", .{ self.major, self.minor }); + return std.fmt.format(out_stream, "{d}.{d}", .{ self.major, self.minor }); } } else { - return std.fmt.format(out_stream, "{}.{}.{}", .{ self.major, self.minor, self.patch }); + return std.fmt.format(out_stream, "{d}.{d}.{d}", .{ self.major, self.minor, self.patch }); } } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); @@ -683,7 +676,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn } }, .wasi => { - std.debug.warn("{}", .{msg}); + std.debug.warn("{s}", .{msg}); std.os.abort(); }, .uefi => { @@ -692,7 +685,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn }, else => { const first_trace_addr = @returnAddress(); - std.debug.panicExtra(error_return_trace, first_trace_addr, "{}", .{msg}); + std.debug.panicExtra(error_return_trace, first_trace_addr, "{s}", .{msg}); }, } } diff --git a/lib/std/c.zig b/lib/std/c.zig index a8ac19053d..1688824dd9 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -12,8 +12,9 @@ pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const parse = @import("c/parse.zig").parse; pub const ast = @import("c/ast.zig"); +pub const builtins = @import("c/builtins.zig"); -test "" { +test { _ = tokenizer; } @@ -72,14 +73,15 @@ pub fn versionCheck(glibc_version: builtin.Version) type { pub extern "c" var environ: [*:null]?[*:0]u8; -pub extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; +pub extern "c" fn fopen(noalias filename: [*:0]const u8, noalias modes: [*:0]const u8) ?*FILE; pub extern "c" fn fclose(stream: *FILE) c_int; -pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; -pub extern "c" fn fread(ptr: [*]u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; +pub extern "c" fn fwrite(noalias ptr: [*]const u8, size_of_type: usize, item_count: usize, noalias stream: *FILE) usize; +pub extern "c" fn fread(noalias ptr: [*]u8, size_of_type: usize, item_count: usize, noalias stream: *FILE) usize; pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; +pub extern "c" fn _exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn close(fd: fd_t) c_int; pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t; @@ -98,6 +100,8 @@ pub extern "c" fn pwrite(fd: fd_t, buf: [*]const u8, nbyte: usize, offset: u64) pub extern "c" fn mmap(addr: ?*align(page_size) c_void, len: usize, prot: c_uint, flags: c_uint, fd: fd_t, offset: u64) *c_void; pub extern "c" fn munmap(addr: *align(page_size) c_void, len: usize) c_int; pub extern "c" fn mprotect(addr: *align(page_size) c_void, len: usize, prot: c_uint) c_int; +pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: c_int) c_int; +pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_int) c_int; pub extern "c" fn unlink(path: [*:0]const u8) c_int; pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int; pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8; @@ -106,7 +110,6 @@ pub extern "c" fn fork() c_int; pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int; pub extern "c" fn pipe(fds: *[2]fd_t) c_int; -pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; @@ -151,9 +154,9 @@ pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int; pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int; pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int; -pub extern "c" fn accept(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t) c_int; -pub extern "c" fn accept4(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t, flags: c_uint) c_int; -pub extern "c" fn getsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*c_void, optlen: *socklen_t) c_int; +pub extern "c" fn accept(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t) c_int; +pub extern "c" fn accept4(sockfd: fd_t, noalias addr: ?*sockaddr, noalias addrlen: ?*socklen_t, flags: c_uint) c_int; +pub extern "c" fn getsockopt(sockfd: fd_t, level: u32, optname: u32, noalias optval: ?*c_void, noalias optlen: *socklen_t) c_int; pub extern "c" fn setsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*const c_void, optlen: socklen_t) c_int; pub extern "c" fn send(sockfd: fd_t, buf: *const c_void, len: usize, flags: u32) isize; pub extern "c" fn sendto( @@ -264,6 +267,22 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int; pub extern "c" fn pthread_self() pthread_t; pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int; +pub extern "c" fn pthread_atfork( + prepare: ?fn () callconv(.C) void, + parent: ?fn () callconv(.C) void, + child: ?fn () callconv(.C) void, +) c_int; +pub extern "c" fn pthread_key_create(key: *pthread_key_t, destructor: ?fn (value: *c_void) callconv(.C) void) c_int; +pub extern "c" fn pthread_key_delete(key: pthread_key_t) c_int; +pub extern "c" fn pthread_getspecific(key: pthread_key_t) ?*c_void; +pub extern "c" fn pthread_setspecific(key: pthread_key_t, value: ?*c_void) c_int; +pub extern "c" fn sem_init(sem: *sem_t, pshared: c_int, value: c_uint) c_int; +pub extern "c" fn sem_destroy(sem: *sem_t) c_int; +pub extern "c" fn sem_post(sem: *sem_t) c_int; +pub extern "c" fn sem_wait(sem: *sem_t) c_int; +pub extern "c" fn sem_trywait(sem: *sem_t) c_int; +pub extern "c" fn sem_timedwait(sem: *sem_t, abs_timeout: *const timespec) c_int; +pub extern "c" fn sem_getvalue(sem: *sem_t, sval: *c_int) c_int; pub extern "c" fn kqueue() c_int; pub extern "c" fn kevent( @@ -310,6 +329,7 @@ pub extern "c" fn dn_expand( pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{}; pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int; pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int; +pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) c_int; pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int; pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{}; @@ -319,6 +339,13 @@ pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int; pub extern "c" fn pthread_cond_broadcast(cond: *pthread_cond_t) c_int; pub extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int; +pub extern "c" fn pthread_rwlock_destroy(rwl: *pthread_rwlock_t) callconv(.C) c_int; +pub extern "c" fn pthread_rwlock_rdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; +pub extern "c" fn pthread_rwlock_wrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; +pub extern "c" fn pthread_rwlock_tryrdlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; +pub extern "c" fn pthread_rwlock_trywrlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; +pub extern "c" fn pthread_rwlock_unlock(rwl: *pthread_rwlock_t) callconv(.C) c_int; + pub const pthread_t = *opaque {}; pub const FILE = opaque {}; @@ -336,6 +363,13 @@ pub extern "c" fn prctl(option: c_int, ...) c_int; pub extern "c" fn getrlimit(resource: rlimit_resource, rlim: *rlimit) c_int; pub extern "c" fn setrlimit(resource: rlimit_resource, rlim: *const rlimit) c_int; +pub extern "c" fn fmemopen(noalias buf: ?*c_void, size: usize, noalias mode: [*:0]const u8) ?*FILE; + +pub extern "c" fn syslog(priority: c_int, message: [*:0]const u8, ...) void; +pub extern "c" fn openlog(ident: [*:0]const u8, logopt: c_int, facility: c_int) void; +pub extern "c" fn closelog() void; +pub extern "c" fn setlogmask(maskpri: c_int) c_int; + pub const max_align_t = if (std.Target.current.abi == .msvc) f64 else if (std.Target.current.isDarwin()) diff --git a/lib/std/c/ast.zig b/lib/std/c/ast.zig index 274481f66e..71455c0ea3 100644 --- a/lib/std/c/ast.zig +++ b/lib/std/c/ast.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -110,15 +110,15 @@ pub const Error = union(enum) { pub const ExpectedToken = struct { token: TokenIndex, - expected_id: @TagType(Token.Id), + expected_id: std.meta.Tag(Token.Id), pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { const found_token = tree.tokens.at(self.token); if (found_token.id == .Invalid) { - return stream.print("expected '{}', found invalid bytes", .{self.expected_id.symbol()}); + return stream.print("expected '{s}', found invalid bytes", .{self.expected_id.symbol()}); } else { const token_name = found_token.id.symbol(); - return stream.print("expected '{}', found '{}'", .{ self.expected_id.symbol(), token_name }); + return stream.print("expected '{s}', found '{s}'", .{ self.expected_id.symbol(), token_name }); } } }; @@ -131,7 +131,7 @@ pub const Error = union(enum) { try stream.write("invalid type specifier '"); try type_spec.spec.print(tree, stream); const token_name = tree.tokens.at(self.token).id.symbol(); - return stream.print("{}'", .{token_name}); + return stream.print("{s}'", .{token_name}); } }; @@ -140,7 +140,7 @@ pub const Error = union(enum) { name: TokenIndex, pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { - return stream.print("must use '{}' tag to refer to type '{}'", .{ tree.slice(kw), tree.slice(name) }); + return stream.print("must use '{s}' tag to refer to type '{s}'", .{ tree.slice(kw), tree.slice(name) }); } }; diff --git a/lib/std/c/builtins.zig b/lib/std/c/builtins.zig new file mode 100644 index 0000000000..c11bf0a391 --- /dev/null +++ b/lib/std/c/builtins.zig @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("std"); + +pub fn __builtin_bswap16(val: u16) callconv(.Inline) u16 { + return @byteSwap(u16, val); +} +pub fn __builtin_bswap32(val: u32) callconv(.Inline) u32 { + return @byteSwap(u32, val); +} +pub fn __builtin_bswap64(val: u64) callconv(.Inline) u64 { + return @byteSwap(u64, val); +} + +pub fn __builtin_signbit(val: f64) callconv(.Inline) c_int { + return @boolToInt(std.math.signbit(val)); +} +pub fn __builtin_signbitf(val: f32) callconv(.Inline) c_int { + return @boolToInt(std.math.signbit(val)); +} + +pub fn __builtin_popcount(val: c_uint) callconv(.Inline) c_int { + // popcount of a c_uint will never exceed the capacity of a c_int + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @popCount(c_uint, val))); +} +pub fn __builtin_ctz(val: c_uint) callconv(.Inline) c_int { + // Returns the number of trailing 0-bits in val, starting at the least significant bit position. + // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @ctz(c_uint, val))); +} +pub fn __builtin_clz(val: c_uint) callconv(.Inline) c_int { + // Returns the number of leading 0-bits in x, starting at the most significant bit position. + // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint + @setRuntimeSafety(false); + return @bitCast(c_int, @as(c_uint, @clz(c_uint, val))); +} + +pub fn __builtin_sqrt(val: f64) callconv(.Inline) f64 { + return @sqrt(val); +} +pub fn __builtin_sqrtf(val: f32) callconv(.Inline) f32 { + return @sqrt(val); +} + +pub fn __builtin_sin(val: f64) callconv(.Inline) f64 { + return @sin(val); +} +pub fn __builtin_sinf(val: f32) callconv(.Inline) f32 { + return @sin(val); +} +pub fn __builtin_cos(val: f64) callconv(.Inline) f64 { + return @cos(val); +} +pub fn __builtin_cosf(val: f32) callconv(.Inline) f32 { + return @cos(val); +} + +pub fn __builtin_exp(val: f64) callconv(.Inline) f64 { + return @exp(val); +} +pub fn __builtin_expf(val: f32) callconv(.Inline) f32 { + return @exp(val); +} +pub fn __builtin_exp2(val: f64) callconv(.Inline) f64 { + return @exp2(val); +} +pub fn __builtin_exp2f(val: f32) callconv(.Inline) f32 { + return @exp2(val); +} +pub fn __builtin_log(val: f64) callconv(.Inline) f64 { + return @log(val); +} +pub fn __builtin_logf(val: f32) callconv(.Inline) f32 { + return @log(val); +} +pub fn __builtin_log2(val: f64) callconv(.Inline) f64 { + return @log2(val); +} +pub fn __builtin_log2f(val: f32) callconv(.Inline) f32 { + return @log2(val); +} +pub fn __builtin_log10(val: f64) callconv(.Inline) f64 { + return @log10(val); +} +pub fn __builtin_log10f(val: f32) callconv(.Inline) f32 { + return @log10(val); +} + +// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub fn __builtin_abs(val: c_int) callconv(.Inline) c_int { + return std.math.absInt(val) catch std.math.minInt(c_int); +} +pub fn __builtin_fabs(val: f64) callconv(.Inline) f64 { + return @fabs(val); +} +pub fn __builtin_fabsf(val: f32) callconv(.Inline) f32 { + return @fabs(val); +} + +pub fn __builtin_floor(val: f64) callconv(.Inline) f64 { + return @floor(val); +} +pub fn __builtin_floorf(val: f32) callconv(.Inline) f32 { + return @floor(val); +} +pub fn __builtin_ceil(val: f64) callconv(.Inline) f64 { + return @ceil(val); +} +pub fn __builtin_ceilf(val: f32) callconv(.Inline) f32 { + return @ceil(val); +} +pub fn __builtin_trunc(val: f64) callconv(.Inline) f64 { + return @trunc(val); +} +pub fn __builtin_truncf(val: f32) callconv(.Inline) f32 { + return @trunc(val); +} +pub fn __builtin_round(val: f64) callconv(.Inline) f64 { + return @round(val); +} +pub fn __builtin_roundf(val: f32) callconv(.Inline) f32 { + return @round(val); +} + +pub fn __builtin_strlen(s: [*c]const u8) callconv(.Inline) usize { + return std.mem.lenZ(s); +} +pub fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) callconv(.Inline) c_int { + return @as(c_int, std.cstr.cmp(s1, s2)); +} + +pub fn __builtin_object_size(ptr: ?*const c_void, ty: c_int) callconv(.Inline) usize { + // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html + // If it is not possible to determine which objects ptr points to at compile time, + // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 + // for type 2 or 3. + if (ty == 0 or ty == 1) return @bitCast(usize, -@as(c_long, 1)); + if (ty == 2 or ty == 3) return 0; + unreachable; +} + +pub fn __builtin___memset_chk( + dst: ?*c_void, + val: c_int, + len: usize, + remaining: usize, +) callconv(.Inline) ?*c_void { + if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining"); + return __builtin_memset(dst, val, len); +} + +pub fn __builtin_memset(dst: ?*c_void, val: c_int, len: usize) callconv(.Inline) ?*c_void { + const dst_cast = @ptrCast([*c]u8, dst); + @memset(dst_cast, @bitCast(u8, @truncate(i8, val)), len); + return dst; +} + +pub fn __builtin___memcpy_chk( + noalias dst: ?*c_void, + noalias src: ?*const c_void, + len: usize, + remaining: usize, +) callconv(.Inline) ?*c_void { + if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); + return __builtin_memcpy(dst, src, len); +} + +pub fn __builtin_memcpy( + noalias dst: ?*c_void, + noalias src: ?*const c_void, + len: usize, +) callconv(.Inline) ?*c_void { + const dst_cast = @ptrCast([*c]u8, dst); + const src_cast = @ptrCast([*c]const u8, src); + + @memcpy(dst_cast, src_cast, len); + return dst; +} diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 635e0f97d4..b5c3fbf977 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -177,6 +177,11 @@ pub const pthread_cond_t = extern struct { __sig: c_long = 0x3CB0B1BB, __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__, }; +pub const pthread_rwlock_t = extern struct { + __sig: c_long = 0x2DA8B3B4, + __opaque: [192]u8 = [_]u8{0} ** 192, +}; +pub const sem_t = c_int; const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40; const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24; @@ -185,4 +190,19 @@ pub const pthread_attr_t = extern struct { __opaque: [56]u8, }; +const pthread_t = std.c.pthread_t; +pub extern "c" fn pthread_threadid_np(thread: ?pthread_t, thread_id: *u64) c_int; + pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; + +// Grand Central Dispatch is exposed by libSystem. +pub const dispatch_semaphore_t = *opaque {}; +pub const dispatch_time_t = u64; +pub const DISPATCH_TIME_NOW = @as(dispatch_time_t, 0); +pub const DISPATCH_TIME_FOREVER = ~@as(dispatch_time_t, 0); +pub extern "c" fn dispatch_semaphore_create(value: isize) ?dispatch_semaphore_t; +pub extern "c" fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) isize; +pub extern "c" fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) isize; + +pub extern "c" fn dispatch_release(object: *c_void) void; +pub extern "c" fn dispatch_time(when: dispatch_time_t, delta: i64) dispatch_time_t; diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 3261d34b78..4e6650094b 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -13,6 +13,7 @@ pub fn _errno() *c_int { pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub const dl_iterate_phdr_callback = fn (info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int; pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; diff --git a/lib/std/c/emscripten.zig b/lib/std/c/emscripten.zig index e94a6f1004..526eb9e99c 100644 --- a/lib/std/c/emscripten.zig +++ b/lib/std/c/emscripten.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, }; +pub const pthread_rwlock_t = extern struct { + size: [32]u8 align(4) = [_]u8{0} ** 32, +}; const __SIZEOF_PTHREAD_COND_T = 48; const __SIZEOF_PTHREAD_MUTEX_T = 28; diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index 8fa78b0d6f..795b36dc68 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -13,6 +13,9 @@ pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; +pub extern "c" fn pthread_getthreadid_np() c_int; +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; + pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int; pub extern "c" fn malloc_usable_size(?*const c_void) usize; @@ -41,12 +44,24 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { inner: ?*c_void = null, }; +pub const pthread_rwlock_t = extern struct { + ptr: ?*c_void = null, +}; pub const pthread_attr_t = extern struct { __size: [56]u8, __align: c_long, }; +pub const sem_t = extern struct { + _magic: u32, + _kern: extern struct { + _count: u32, + _flags: u32, + }, + _padding: u32, +}; + pub const EAI = extern enum(c_int) { /// address family for hostname not supported ADDRFAMILY = 1, diff --git a/lib/std/c/fuchsia.zig b/lib/std/c/fuchsia.zig index ceeb34a763..fc34f49d22 100644 --- a/lib/std/c/fuchsia.zig +++ b/lib/std/c/fuchsia.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,5 +9,8 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, }; +pub const pthread_rwlock_t = extern struct { + size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56, +}; const __SIZEOF_PTHREAD_COND_T = 48; const __SIZEOF_PTHREAD_MUTEX_T = 40; diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 6b56e163c8..e361a7520e 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -1,8 +1,51 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. + +// +const std = @import("../std.zig"); +const builtin = std.builtin; + +usingnamespace std.c; + +extern "c" fn _errnop() *c_int; + +pub const _errno = _errnop; + +pub extern "c" fn find_directory(which: c_int, volume: i32, createIt: bool, path_ptr: [*]u8, length: i32) u64; + +pub extern "c" fn find_thread(thread_name: ?*c_void) i32; + +pub extern "c" fn get_system_info(system_info: *system_info) usize; + +// TODO revisit if abi changes or better option becomes apparent +pub extern "c" fn _get_next_image_info(team: c_int, cookie: *i32, image_info: *image_info) usize; + +pub extern "c" fn _kern_read_dir(fd: c_int, buf_ptr: [*]u8, nbytes: usize, maxcount: u32) usize; + +pub extern "c" fn _kern_read_stat(fd: c_int, path_ptr: [*]u8, traverse_link: bool, libc_stat: *libc_stat, stat_size: i32) usize; + +pub extern "c" fn _kern_get_current_team() i32; + +pub const sem_t = extern struct { + _magic: u32, + _kern: extern struct { + _count: u32, + _flags: u32, + }, + _padding: u32, +}; + +pub const pthread_attr_t = extern struct { + __detach_state: i32, + __sched_priority: i32, + __stack_size: i32, + __guard_size: i32, + __stack_address: ?*c_void, +}; + pub const pthread_mutex_t = extern struct { flags: u32 = 0, lock: i32 = 0, @@ -17,3 +60,12 @@ pub const pthread_cond_t = extern struct { waiter_count: i32 = 0, lock: i32 = 0, }; +pub const pthread_rwlock_t = extern struct { + flags: u32 = 0, + owner: i32 = -1, + lock_sem: i32 = 0, + lock_count: i32 = 0, + reader_count: i32 = 0, + writer_count: i32 = 0, + waiters: [2]?*c_void = [_]?*c_void{ null, null }, +}; diff --git a/lib/std/c/hermit.zig b/lib/std/c/hermit.zig index 6762e60962..a159395ab3 100644 --- a/lib/std/c/hermit.zig +++ b/lib/std/c/hermit.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,3 +9,6 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { inner: usize = ~@as(usize, 0), }; +pub const pthread_rwlock_t = extern struct { + ptr: usize = std.math.maxInt(usize), +}; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 21124d1030..d2018f6f79 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -86,6 +86,7 @@ pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int; +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn fallocate(fd: fd_t, mode: c_int, offset: off_t, len: off_t) c_int; @@ -106,6 +107,12 @@ pub extern "c" fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: *con pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int; pub extern "c" fn malloc_usable_size(?*const c_void) usize; +pub extern "c" fn madvise( + addr: *align(std.mem.page_size) c_void, + length: usize, + advice: c_uint, +) c_int; + pub const pthread_attr_t = extern struct { __size: [56]u8, __align: c_long, @@ -117,6 +124,36 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, }; +pub const pthread_rwlock_t = switch (std.builtin.abi) { + .android => switch (@sizeOf(usize)) { + 4 => extern struct { + lock: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER, + cond: std.c.pthread_cond_t = std.c.PTHREAD_COND_INITIALIZER, + numLocks: c_int = 0, + writerThreadId: c_int = 0, + pendingReaders: c_int = 0, + pendingWriters: c_int = 0, + attr: i32 = 0, + __reserved: [12]u8 = [_]u8{0} ** 2, + }, + 8 => extern struct { + numLocks: c_int = 0, + writerThreadId: c_int = 0, + pendingReaders: c_int = 0, + pendingWriters: c_int = 0, + attr: i32 = 0, + __reserved: [36]u8 = [_]u8{0} ** 36, + }, + else => unreachable, + }, + else => extern struct { + size: [56]u8 align(@alignOf(usize)) = [_]u8{0} ** 56, + }, +}; +pub const sem_t = extern struct { + __size: [__SIZEOF_SEM_T]u8 align(@alignOf(usize)), +}; + const __SIZEOF_PTHREAD_COND_T = 48; const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) { .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24, @@ -128,6 +165,7 @@ const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch }, else => unreachable, }; +const __SIZEOF_SEM_T = 4 * @sizeOf(usize); pub const RTLD_LAZY = 1; pub const RTLD_NOW = 2; diff --git a/lib/std/c/minix.zig b/lib/std/c/minix.zig index 2bc1bac47a..6cf5684079 100644 --- a/lib/std/c/minix.zig +++ b/lib/std/c/minix.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index e6d7e86bee..7169095197 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -14,6 +14,9 @@ pub const _errno = __errno; pub const dl_iterate_phdr_callback = fn (info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int; pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; +pub extern "c" fn _lwp_self() lwpid_t; + +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; pub extern "c" fn __fstat50(fd: fd_t, buf: *Stat) c_int; pub extern "c" fn __stat50(path: [*:0]const u8, buf: *Stat) c_int; @@ -52,6 +55,22 @@ pub const pthread_cond_t = extern struct { ptc_private: ?*c_void = null, }; +pub const pthread_rwlock_t = extern struct { + ptr_magic: c_uint = 0x99990009, + ptr_interlock: switch (std.builtin.arch) { + .aarch64, .sparc, .x86_64, .i386 => u8, + .arm, .powerpc => c_int, + else => unreachable, + } = 0, + ptr_rblocked_first: ?*u8 = null, + ptr_rblocked_last: ?*u8 = null, + ptr_wblocked_first: ?*u8 = null, + ptr_wblocked_last: ?*u8 = null, + ptr_nreaders: c_uint = 0, + ptr_owner: std.c.pthread_t = null, + ptr_private: ?*c_void = null, +}; + const pthread_spin_t = switch (builtin.arch) { .aarch64, .aarch64_be, .aarch64_32 => u8, .mips, .mipsel, .mips64, .mips64el => u32, diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index ab193abb6b..cac3df867d 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -16,6 +16,9 @@ pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_ pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; +pub extern "c" fn getthrid() pid_t; +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; + pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; @@ -25,12 +28,20 @@ pub const pthread_mutex_t = extern struct { pub const pthread_cond_t = extern struct { inner: ?*c_void = null, }; +pub const pthread_rwlock_t = extern struct { + ptr: ?*c_void = null, +}; pub const pthread_spinlock_t = extern struct { inner: ?*c_void = null, }; - pub const pthread_attr_t = extern struct { inner: ?*c_void = null, }; +pub const pthread_key_t = c_int; + +pub const sem_t = ?*opaque {}; pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int; + +pub extern "c" fn pledge(promises: ?[*:0]const u8, execpromises: ?[*:0]const u8) c_int; +pub extern "c" fn unveil(path: ?[*:0]const u8, permissions: ?[*:0]const u8) c_int; diff --git a/lib/std/c/parse.zig b/lib/std/c/parse.zig index d5b1a4a01e..29d4ba2fe1 100644 --- a/lib/std/c/parse.zig +++ b/lib/std/c/parse.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -26,7 +26,7 @@ pub const Options = struct { None, /// Some warnings are errors - Some: []@TagType(ast.Error), + Some: []std.meta.Tag(ast.Error), /// All warnings are errors All, @@ -300,8 +300,7 @@ const Parser = struct { try node.initializers.push((try parser.initializer(dr)) orelse return parser.err(.{ .ExpectedInitializer = .{ .token = parser.it.index }, })); - } else - try node.initializers.push(&dr.base); + } else try node.initializers.push(&dr.base); if (parser.eatToken(.Comma) != null) break; dr = @fieldParentPtr(Node.Declarator, "base", (try parser.declarator(.Must)) orelse return parser.err(.{ .ExpectedDeclarator = .{ .token = parser.it.index }, @@ -1363,7 +1362,7 @@ const Parser = struct { return &node.base; } - fn eatToken(parser: *Parser, id: @TagType(Token.Id)) ?TokenIndex { + fn eatToken(parser: *Parser, id: std.meta.Tag(Token.Id)) ?TokenIndex { while (true) { switch ((parser.it.next() orelse return null).id) { .LineComment, .MultiLineComment, .Nl => continue, @@ -1377,7 +1376,7 @@ const Parser = struct { } } - fn expectToken(parser: *Parser, id: @TagType(Token.Id)) Error!TokenIndex { + fn expectToken(parser: *Parser, id: std.meta.Tag(Token.Id)) Error!TokenIndex { while (true) { switch ((parser.it.next() orelse return error.ParseError).id) { .LineComment, .MultiLineComment, .Nl => continue, diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 49ce0886f7..ed043018d0 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/c/tokenizer.zig b/lib/std/c/tokenizer.zig index 9e9b5f4147..2e1969e269 100644 --- a/lib/std/c/tokenizer.zig +++ b/lib/std/c/tokenizer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -131,7 +131,7 @@ pub const Token = struct { Keyword_error, Keyword_pragma, - pub fn symbol(id: @TagType(Id)) []const u8 { + pub fn symbol(id: std.meta.TagType(Id)) []const u8 { return switch (id) { .Invalid => "Invalid", .Eof => "Eof", @@ -347,7 +347,7 @@ pub const Token = struct { pub const Tokenizer = struct { buffer: []const u8, index: usize = 0, - prev_tok_id: @TagType(Token.Id) = .Invalid, + prev_tok_id: std.meta.TagType(Token.Id) = .Invalid, pp_directive: bool = false, pub fn next(self: *Tokenizer) Token { @@ -446,7 +446,7 @@ pub const Tokenizer = struct { 'L' => { state = .L; }, - 'a'...'t', 'v'...'z', 'A'...'K', 'M'...'T', 'V'...'Z', '_' => { + 'a'...'t', 'v'...'z', 'A'...'K', 'M'...'T', 'V'...'Z', '_', '$' => { state = .Identifier; }, '=' => { @@ -776,7 +776,7 @@ pub const Tokenizer = struct { }, }, .Identifier => switch (c) { - 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + 'a'...'z', 'A'...'Z', '_', '0'...'9', '$' => {}, else => { result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier; if (self.prev_tok_id == .Hash) @@ -1552,7 +1552,7 @@ fn expectTokens(source: []const u8, expected_tokens: []const Token.Id) void { for (expected_tokens) |expected_token_id| { const token = tokenizer.next(); if (!std.meta.eql(token.id, expected_token_id)) { - std.debug.panic("expected {}, found {}\n", .{ @tagName(expected_token_id), @tagName(token.id) }); + std.debug.panic("expected {s}, found {s}\n", .{ @tagName(expected_token_id), @tagName(token.id) }); } } const last_token = tokenizer.next(); diff --git a/lib/std/c/windows.zig b/lib/std/c/windows.zig index f96da56c1f..bed2e421ff 100644 --- a/lib/std/c/windows.zig +++ b/lib/std/c/windows.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index b61fe9470d..d37dd9fdf5 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,11 +15,11 @@ const windows = os.windows; const mem = std.mem; const debug = std.debug; const BufMap = std.BufMap; -const ArrayListSentineled = std.ArrayListSentineled; const builtin = @import("builtin"); const Os = builtin.Os; const TailQueue = std.TailQueue; const maxInt = std.math.maxInt; +const assert = std.debug.assert; pub const ChildProcess = struct { pid: if (builtin.os.tag == .windows) void else i32, @@ -186,6 +186,58 @@ pub const ChildProcess = struct { pub const exec2 = @compileError("deprecated: exec2 is renamed to exec"); + fn collectOutputPosix( + child: *const ChildProcess, + stdout: *std.ArrayList(u8), + stderr: *std.ArrayList(u8), + max_output_bytes: usize, + ) !void { + var poll_fds = [_]os.pollfd{ + .{ .fd = child.stdout.?.handle, .events = os.POLLIN, .revents = undefined }, + .{ .fd = child.stderr.?.handle, .events = os.POLLIN, .revents = undefined }, + }; + + var dead_fds: usize = 0; + // We ask for ensureCapacity with this much extra space. This has more of an + // effect on small reads because once the reads start to get larger the amount + // of space an ArrayList will allocate grows exponentially. + const bump_amt = 512; + + while (dead_fds < poll_fds.len) { + const events = try os.poll(&poll_fds, std.math.maxInt(i32)); + if (events == 0) continue; + + // Try reading whatever is available before checking the error + // conditions. + if (poll_fds[0].revents & os.POLLIN != 0) { + // stdout is ready. + const new_capacity = std.math.min(stdout.items.len + bump_amt, max_output_bytes); + try stdout.ensureCapacity(new_capacity); + const buf = stdout.unusedCapacitySlice(); + if (buf.len == 0) return error.StdoutStreamTooLong; + stdout.items.len += try os.read(poll_fds[0].fd, buf); + } + if (poll_fds[1].revents & os.POLLIN != 0) { + // stderr is ready. + const new_capacity = std.math.min(stderr.items.len + bump_amt, max_output_bytes); + try stderr.ensureCapacity(new_capacity); + const buf = stderr.unusedCapacitySlice(); + if (buf.len == 0) return error.StderrStreamTooLong; + stderr.items.len += try os.read(poll_fds[1].fd, buf); + } + + // Exclude the fds that signaled an error. + if (poll_fds[0].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { + poll_fds[0].fd = -1; + dead_fds += 1; + } + if (poll_fds[1].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { + poll_fds[1].fd = -1; + dead_fds += 1; + } + } + } + /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn exec(args: struct { @@ -210,19 +262,33 @@ pub const ChildProcess = struct { try child.spawn(); - const stdout_in = child.stdout.?.reader(); - const stderr_in = child.stderr.?.reader(); + // TODO collect output in a deadlock-avoiding way on Windows. + // https://github.com/ziglang/zig/issues/6343 + if (builtin.os.tag == .windows) { + const stdout_in = child.stdout.?.reader(); + const stderr_in = child.stderr.?.reader(); + + const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); + errdefer args.allocator.free(stdout); + const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); + errdefer args.allocator.free(stderr); + + return ExecResult{ + .term = try child.wait(), + .stdout = stdout, + .stderr = stderr, + }; + } + + var stdout = std.ArrayList(u8).init(args.allocator); + var stderr = std.ArrayList(u8).init(args.allocator); - // TODO https://github.com/ziglang/zig/issues/6343 - const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); - errdefer args.allocator.free(stdout); - const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); - errdefer args.allocator.free(stderr); + try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); return ExecResult{ .term = try child.wait(), - .stdout = stdout, - .stderr = stderr, + .stdout = stdout.toOwnedSlice(), + .stderr = stderr.toOwnedSlice(), }; } @@ -377,19 +443,37 @@ pub const ChildProcess = struct { if (any_ignore) os.close(dev_null_fd); } - var env_map_owned: BufMap = undefined; - var we_own_env_map: bool = undefined; - const env_map = if (self.env_map) |env_map| x: { - we_own_env_map = false; - break :x env_map; - } else x: { - we_own_env_map = true; - env_map_owned = try process.getEnvMap(self.allocator); - break :x &env_map_owned; + var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); + defer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + // The POSIX standard does not allow malloc() between fork() and execve(), + // and `self.allocator` may be a libc allocator. + // I have personally observed the child process deadlocking when it tries + // to call malloc() due to a heap allocation between fork() and execve(), + // in musl v1.1.24. + // Additionally, we want to reduce the number of possible ways things + // can fail between fork() and execve(). + // Therefore, we do all the allocation for the execve() before the fork(). + // This means we must do the null-termination of argv and env vars here. + const argv_buf = try arena.allocSentinel(?[*:0]u8, self.argv.len, null); + for (self.argv) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const envp = m: { + if (self.env_map) |env_map| { + const envp_buf = try createNullDelimitedEnvMap(arena, env_map); + break :m envp_buf.ptr; + } else if (std.builtin.link_libc) { + break :m std.c.environ; + } else if (std.builtin.output_mode == .Exe) { + // Then we have Zig start code and this works. + // TODO type-safety for null-termination of `os.environ`. + break :m @ptrCast([*:null]?[*:0]u8, os.environ.ptr); + } else { + // TODO come up with a solution for this. + @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process"); + } }; - defer { - if (we_own_env_map) env_map_owned.deinit(); - } // This pipe is used to communicate errors between the time of fork // and execve from the child process to the parent process. @@ -439,7 +523,10 @@ pub const ChildProcess = struct { os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err); } - const err = os.execvpe_expandArg0(self.allocator, self.expand_arg0, self.argv, env_map); + const err = switch (self.expand_arg0) { + .expand => os.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + .no_expand => os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + }; forkChildErrReport(err_pipe[1], err); } @@ -749,38 +836,37 @@ fn windowsCreateProcess(app_name: [*:0]u16, cmd_line: [*:0]u16, envp_ptr: ?[*]u1 /// Caller must dealloc. fn windowsCreateCommandLine(allocator: *mem.Allocator, argv: []const []const u8) ![:0]u8 { - var buf = try ArrayListSentineled(u8, 0).initSize(allocator, 0); + var buf = std.ArrayList(u8).init(allocator); defer buf.deinit(); - const buf_stream = buf.outStream(); for (argv) |arg, arg_i| { - if (arg_i != 0) try buf_stream.writeByte(' '); + if (arg_i != 0) try buf.append(' '); if (mem.indexOfAny(u8, arg, " \t\n\"") == null) { - try buf_stream.writeAll(arg); + try buf.appendSlice(arg); continue; } - try buf_stream.writeByte('"'); + try buf.append('"'); var backslash_count: usize = 0; for (arg) |byte| { switch (byte) { '\\' => backslash_count += 1, '"' => { - try buf_stream.writeByteNTimes('\\', backslash_count * 2 + 1); - try buf_stream.writeByte('"'); + try buf.appendNTimes('\\', backslash_count * 2 + 1); + try buf.append('"'); backslash_count = 0; }, else => { - try buf_stream.writeByteNTimes('\\', backslash_count); - try buf_stream.writeByte(byte); + try buf.appendNTimes('\\', backslash_count); + try buf.append(byte); backslash_count = 0; }, } } - try buf_stream.writeByteNTimes('\\', backslash_count * 2); - try buf_stream.writeByte('"'); + try buf.appendNTimes('\\', backslash_count * 2); + try buf.append('"'); } - return buf.toOwnedSlice(); + return buf.toOwnedSliceSentinel(0); } fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { @@ -821,8 +907,9 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { // which we really do not want to run in the fork child. I caught LLVM doing this and // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, // "Why'd you have to go and make things so complicated?" - if (std.Target.current.os.tag == .linux) { - std.os.linux.exit(1); // By-pass libc regardless of whether it is linked. + if (builtin.link_libc) { + // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) + std.c._exit(1); } os.exit(1); } @@ -835,7 +922,7 @@ fn writeIntFd(fd: i32, value: ErrInt) !void { .capable_io_mode = .blocking, .intended_io_mode = .blocking, }; - file.outStream().writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; + file.writer().writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; } fn readIntFd(fd: i32) !ErrInt { @@ -883,3 +970,54 @@ pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) i += 1; return allocator.shrink(result, i); } + +pub fn createNullDelimitedEnvMap(arena: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { + const envp_count = env_map.count(); + const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); + { + var it = env_map.iterator(); + var i: usize = 0; + while (it.next()) |pair| : (i += 1) { + const env_buf = try arena.allocSentinel(u8, pair.key.len + pair.value.len + 1, 0); + mem.copy(u8, env_buf, pair.key); + env_buf[pair.key.len] = '='; + mem.copy(u8, env_buf[pair.key.len + 1 ..], pair.value); + envp_buf[i] = env_buf.ptr; + } + assert(i == envp_count); + } + return envp_buf; +} + +test "createNullDelimitedEnvMap" { + const testing = std.testing; + const allocator = testing.allocator; + var envmap = BufMap.init(allocator); + defer envmap.deinit(); + + try envmap.set("HOME", "/home/ifreund"); + try envmap.set("WAYLAND_DISPLAY", "wayland-1"); + try envmap.set("DISPLAY", ":1"); + try envmap.set("DEBUGINFOD_URLS", " "); + try envmap.set("XCURSOR_SIZE", "24"); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const environ = try createNullDelimitedEnvMap(&arena.allocator, &envmap); + + testing.expectEqual(@as(usize, 5), environ.len); + + inline for (.{ + "HOME=/home/ifreund", + "WAYLAND_DISPLAY=wayland-1", + "DISPLAY=:1", + "DEBUGINFOD_URLS= ", + "XCURSOR_SIZE=24", + }) |target| { + for (environ) |variable| { + if (mem.eql(u8, mem.span(variable orelse continue), target)) break; + } else { + testing.expect(false); // Environment variable not found + } + } +} diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 1fdf3f8893..edeff89cc5 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -127,7 +127,7 @@ pub const Coff = struct { pub fn loadHeader(self: *Coff) !void { const pe_pointer_offset = 0x3C; - const in = self.in_file.inStream(); + const in = self.in_file.reader(); var magic: [2]u8 = undefined; try in.readNoEof(magic[0..]); @@ -163,7 +163,7 @@ pub const Coff = struct { } fn loadOptionalHeader(self: *Coff) !void { - const in = self.in_file.inStream(); + const in = self.in_file.reader(); self.pe_header.magic = try in.readIntLittle(u16); // For now we're only interested in finding the reference to the .pdb, // so we'll skip most of this header, which size is different in 32 @@ -173,8 +173,7 @@ pub const Coff = struct { skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 18 * @sizeOf(u32); } else if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 12 * @sizeOf(u32) + 5 * @sizeOf(u64); - } else - return error.InvalidPEMagic; + } else return error.InvalidPEMagic; try self.in_file.seekBy(skip_size); @@ -206,7 +205,7 @@ pub const Coff = struct { const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY]; const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data; - const in = self.in_file.inStream(); + const in = self.in_file.reader(); try self.in_file.seekTo(file_offset); // Find the correct DebugDirectoryEntry, and where its data is stored. @@ -257,7 +256,7 @@ pub const Coff = struct { try self.sections.ensureCapacity(self.coff_header.number_of_sections); - const in = self.in_file.inStream(); + const in = self.in_file.reader(); var name: [8]u8 = undefined; diff --git a/lib/std/compress.zig b/lib/std/compress.zig index 95f496021e..972031c182 100644 --- a/lib/std/compress.zig +++ b/lib/std/compress.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,7 +9,7 @@ pub const deflate = @import("compress/deflate.zig"); pub const gzip = @import("compress/gzip.zig"); pub const zlib = @import("compress/zlib.zig"); -test "" { +test { _ = gzip; _ = zlib; } diff --git a/lib/std/compress/deflate.zig b/lib/std/compress/deflate.zig index addd1b1a27..e680dc9e6f 100644 --- a/lib/std/compress/deflate.zig +++ b/lib/std/compress/deflate.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -209,7 +209,7 @@ pub fn InflateStream(comptime ReaderType: type) type { // Insert a single byte into the window. // Assumes there's enough space. - inline fn appendUnsafe(self: *WSelf, value: u8) void { + fn appendUnsafe(self: *WSelf, value: u8) callconv(.Inline) void { self.buf[self.wi] = value; self.wi = (self.wi + 1) & (self.buf.len - 1); self.el += 1; diff --git a/lib/std/compress/gzip.zig b/lib/std/compress/gzip.zig index aad1731393..89aa12207b 100644 --- a/lib/std/compress/gzip.zig +++ b/lib/std/compress/gzip.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/compress/zlib.zig b/lib/std/compress/zlib.zig index 63ef6c2aee..7ef644ef6d 100644 --- a/lib/std/compress/zlib.zig +++ b/lib/std/compress/zlib.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/comptime_string_map.zig b/lib/std/comptime_string_map.zig index ed647124a8..4882924ae5 100644 --- a/lib/std/comptime_string_map.zig +++ b/lib/std/comptime_string_map.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 6eb934473f..da6ec2edf0 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -134,10 +134,15 @@ pub const nacl = struct { pub const utils = @import("crypto/utils.zig"); +/// This is a thread-local, cryptographically secure pseudo random number generator. +pub const random = &@import("crypto/tlcsprng.zig").interface; + const std = @import("std.zig"); -pub const randomBytes = std.os.getrandom; test "crypto" { + const please_windows_dont_oom = std.Target.current.os.tag == .windows; + if (please_windows_dont_oom) return error.SkipZigTest; + inline for (std.meta.declarations(@This())) |decl| { switch (decl.data) { .Type => |t| { @@ -178,6 +183,13 @@ test "crypto" { _ = @import("crypto/25519/ristretto255.zig"); } +test "CSPRNG" { + const a = random.int(u64); + const b = random.int(u64); + const c = random.int(u64); + std.testing.expect(a ^ b ^ c != 0); +} + test "issue #4532: no index out of bounds" { const types = [_]type{ hash.Md5, diff --git a/lib/std/crypto/25519/curve25519.zig b/lib/std/crypto/25519/curve25519.zig index 3ca7af7a41..765ffa1629 100644 --- a/lib/std/crypto/25519/curve25519.zig +++ b/lib/std/crypto/25519/curve25519.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,12 +15,12 @@ pub const Curve25519 = struct { x: Fe, /// Decode a Curve25519 point from its compressed (X) coordinates. - pub inline fn fromBytes(s: [32]u8) Curve25519 { + pub fn fromBytes(s: [32]u8) callconv(.Inline) Curve25519 { return .{ .x = Fe.fromBytes(s) }; } /// Encode a Curve25519 point. - pub inline fn toBytes(p: Curve25519) [32]u8 { + pub fn toBytes(p: Curve25519) callconv(.Inline) [32]u8 { return p.x.toBytes(); } diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 842b08d706..5c7ec0cdac 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -43,7 +43,7 @@ pub const Ed25519 = struct { pub fn create(seed: ?[seed_length]u8) !KeyPair { const ss = seed orelse ss: { var random_seed: [seed_length]u8 = undefined; - try crypto.randomBytes(&random_seed); + crypto.random.bytes(&random_seed); break :ss random_seed; }; var az: [Sha512.digest_length]u8 = undefined; @@ -179,7 +179,7 @@ pub const Ed25519 = struct { var z_batch: [count]Curve.scalar.CompressedScalar = undefined; for (z_batch) |*z| { - try std.crypto.randomBytes(z[0..16]); + std.crypto.random.bytes(z[0..16]); mem.set(u8, z[16..], 0); } @@ -207,7 +207,7 @@ pub const Ed25519 = struct { test "ed25519 key pair creation" { var seed: [32]u8 = undefined; - try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); const key_pair = try Ed25519.KeyPair.create(seed); var buf: [256]u8 = undefined; std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); @@ -216,7 +216,7 @@ test "ed25519 key pair creation" { test "ed25519 signature" { var seed: [32]u8 = undefined; - try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); const key_pair = try Ed25519.KeyPair.create(seed); const sig = try Ed25519.sign("test", key_pair, null); @@ -232,8 +232,8 @@ test "ed25519 batch verification" { const key_pair = try Ed25519.KeyPair.create(null); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; - try std.crypto.randomBytes(&msg1); - try std.crypto.randomBytes(&msg2); + std.crypto.random.bytes(&msg1); + std.crypto.random.bytes(&msg2); const sig1 = try Ed25519.sign(&msg1, key_pair, null); const sig2 = try Ed25519.sign(&msg2, key_pair, null); var signature_batch = [_]Ed25519.BatchElement{ @@ -339,11 +339,11 @@ test "ed25519 test vectors" { }; for (entries) |entry, i| { var msg: [entry.msg_hex.len / 2]u8 = undefined; - try fmt.hexToBytes(&msg, entry.msg_hex); + _ = try fmt.hexToBytes(&msg, entry.msg_hex); var public_key: [32]u8 = undefined; - try fmt.hexToBytes(&public_key, entry.public_key_hex); + _ = try fmt.hexToBytes(&public_key, entry.public_key_hex); var sig: [64]u8 = undefined; - try fmt.hexToBytes(&sig, entry.sig_hex); + _ = try fmt.hexToBytes(&sig, entry.sig_hex); if (entry.expected) |error_type| { std.testing.expectError(error_type, Ed25519.verify(sig, &msg, public_key)); } else { diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 008a4535b3..d4238f87bb 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -92,7 +92,7 @@ pub const Edwards25519 = struct { } /// Flip the sign of the X coordinate. - pub inline fn neg(p: Edwards25519) Edwards25519 { + pub fn neg(p: Edwards25519) callconv(.Inline) Edwards25519 { return .{ .x = p.x.neg(), .y = p.y, .z = p.z, .t = p.t.neg() }; } @@ -137,14 +137,14 @@ pub const Edwards25519 = struct { return p.add(q.neg()); } - inline fn cMov(p: *Edwards25519, a: Edwards25519, c: u64) void { + fn cMov(p: *Edwards25519, a: Edwards25519, c: u64) callconv(.Inline) void { p.x.cMov(a.x, c); p.y.cMov(a.y, c); p.z.cMov(a.z, c); p.t.cMov(a.t, c); } - inline fn pcSelect(comptime n: usize, pc: [n]Edwards25519, b: u8) Edwards25519 { + fn pcSelect(comptime n: usize, pc: [n]Edwards25519, b: u8) callconv(.Inline) Edwards25519 { var t = Edwards25519.identityElement; comptime var i: u8 = 1; inline while (i < pc.len) : (i += 1) { @@ -484,8 +484,8 @@ test "edwards25519 packing/unpacking" { test "edwards25519 point addition/substraction" { var s1: [32]u8 = undefined; var s2: [32]u8 = undefined; - try std.crypto.randomBytes(&s1); - try std.crypto.randomBytes(&s2); + std.crypto.random.bytes(&s1); + std.crypto.random.bytes(&s2); const p = try Edwards25519.basePoint.clampedMul(s1); const q = try Edwards25519.basePoint.clampedMul(s2); const r = p.add(q).add(q).sub(q).sub(q); diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig index d2002ce52d..320cb1bb51 100644 --- a/lib/std/crypto/25519/field.zig +++ b/lib/std/crypto/25519/field.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -52,7 +52,7 @@ pub const Fe = struct { pub const edwards25519sqrtam2 = Fe{ .limbs = .{ 1693982333959686, 608509411481997, 2235573344831311, 947681270984193, 266558006233600 } }; /// Return true if the field element is zero - pub inline fn isZero(fe: Fe) bool { + pub fn isZero(fe: Fe) callconv(.Inline) bool { var reduced = fe; reduced.reduce(); const limbs = reduced.limbs; @@ -60,7 +60,7 @@ pub const Fe = struct { } /// Return true if both field elements are equivalent - pub inline fn equivalent(a: Fe, b: Fe) bool { + pub fn equivalent(a: Fe, b: Fe) callconv(.Inline) bool { return a.sub(b).isZero(); } @@ -164,7 +164,7 @@ pub const Fe = struct { } /// Add a field element - pub inline fn add(a: Fe, b: Fe) Fe { + pub fn add(a: Fe, b: Fe) callconv(.Inline) Fe { var fe: Fe = undefined; comptime var i = 0; inline while (i < 5) : (i += 1) { @@ -174,7 +174,7 @@ pub const Fe = struct { } /// Substract a field elememnt - pub inline fn sub(a: Fe, b: Fe) Fe { + pub fn sub(a: Fe, b: Fe) callconv(.Inline) Fe { var fe = b; comptime var i = 0; inline while (i < 4) : (i += 1) { @@ -193,17 +193,17 @@ pub const Fe = struct { } /// Negate a field element - pub inline fn neg(a: Fe) Fe { + pub fn neg(a: Fe) callconv(.Inline) Fe { return zero.sub(a); } /// Return true if a field element is negative - pub inline fn isNegative(a: Fe) bool { + pub fn isNegative(a: Fe) callconv(.Inline) bool { return (a.toBytes()[0] & 1) != 0; } /// Conditonally replace a field element with `a` if `c` is positive - pub inline fn cMov(fe: *Fe, a: Fe, c: u64) void { + pub fn cMov(fe: *Fe, a: Fe, c: u64) callconv(.Inline) void { const mask: u64 = 0 -% c; var x = fe.*; comptime var i = 0; @@ -244,7 +244,7 @@ pub const Fe = struct { } } - inline fn _carry128(r: *[5]u128) Fe { + fn _carry128(r: *[5]u128) callconv(.Inline) Fe { var rs: [5]u64 = undefined; comptime var i = 0; inline while (i < 4) : (i += 1) { @@ -265,7 +265,7 @@ pub const Fe = struct { } /// Multiply two field elements - pub inline fn mul(a: Fe, b: Fe) Fe { + pub fn mul(a: Fe, b: Fe) callconv(.Inline) Fe { var ax: [5]u128 = undefined; var bx: [5]u128 = undefined; var a19: [5]u128 = undefined; @@ -288,7 +288,7 @@ pub const Fe = struct { return _carry128(&r); } - inline fn _sq(a: Fe, double: comptime bool) Fe { + fn _sq(a: Fe, double: comptime bool) callconv(.Inline) Fe { var ax: [5]u128 = undefined; var r: [5]u128 = undefined; comptime var i = 0; @@ -317,17 +317,17 @@ pub const Fe = struct { } /// Square a field element - pub inline fn sq(a: Fe) Fe { + pub fn sq(a: Fe) callconv(.Inline) Fe { return _sq(a, false); } /// Square and double a field element - pub inline fn sq2(a: Fe) Fe { + pub fn sq2(a: Fe) callconv(.Inline) Fe { return _sq(a, true); } /// Multiply a field element with a small (32-bit) integer - pub inline fn mul32(a: Fe, comptime n: u32) Fe { + pub fn mul32(a: Fe, comptime n: u32) callconv(.Inline) Fe { const sn = @intCast(u128, n); var fe: Fe = undefined; var x: u128 = 0; @@ -342,7 +342,7 @@ pub const Fe = struct { } /// Square a field element `n` times - inline fn sqn(a: Fe, comptime n: comptime_int) Fe { + fn sqn(a: Fe, comptime n: comptime_int) callconv(.Inline) Fe { var i: usize = 0; var fe = a; while (i < n) : (i += 1) { diff --git a/lib/std/crypto/25519/ristretto255.zig b/lib/std/crypto/25519/ristretto255.zig index 16d301592a..df85422f65 100644 --- a/lib/std/crypto/25519/ristretto255.zig +++ b/lib/std/crypto/25519/ristretto255.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -42,7 +42,7 @@ pub const Ristretto255 = struct { } /// Reject the neutral element. - pub inline fn rejectIdentity(p: Ristretto255) !void { + pub fn rejectIdentity(p: Ristretto255) callconv(.Inline) !void { return p.p.rejectIdentity(); } @@ -141,19 +141,19 @@ pub const Ristretto255 = struct { } /// Double a Ristretto255 element. - pub inline fn dbl(p: Ristretto255) Ristretto255 { + pub fn dbl(p: Ristretto255) callconv(.Inline) Ristretto255 { return .{ .p = p.p.dbl() }; } /// Add two Ristretto255 elements. - pub inline fn add(p: Ristretto255, q: Ristretto255) Ristretto255 { + pub fn add(p: Ristretto255, q: Ristretto255) callconv(.Inline) Ristretto255 { return .{ .p = p.p.add(q.p) }; } /// Multiply a Ristretto255 element with a scalar. /// Return error.WeakPublicKey if the resulting element is /// the identity element. - pub inline fn mul(p: Ristretto255, s: [encoded_length]u8) !Ristretto255 { + pub fn mul(p: Ristretto255, s: [encoded_length]u8) callconv(.Inline) !Ristretto255 { return Ristretto255{ .p = try p.p.mul(s) }; } @@ -173,7 +173,7 @@ test "ristretto255" { std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76"); var r: [Ristretto255.encoded_length]u8 = undefined; - try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); + _ = try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); var q = try Ristretto255.fromBytes(r); q = q.dbl().add(p); std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{q.toBytes()}), "E882B131016B52C1D3337080187CF768423EFCCBB517BB495AB812C4160FF44E"); diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index c5e460d29e..ceff153bff 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -46,7 +46,7 @@ pub fn reduce64(s: [64]u8) [32]u8 { /// Perform the X25519 "clamping" operation. /// The scalar is then guaranteed to be a multiple of the cofactor. -pub inline fn clamp(s: *[32]u8) void { +pub fn clamp(s: *[32]u8) callconv(.Inline) void { s[0] &= 248; s[31] = (s[31] & 127) | 64; } diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index 3b3ff551fe..5d0479bd4d 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -34,7 +34,7 @@ pub const X25519 = struct { pub fn create(seed: ?[seed_length]u8) !KeyPair { const sk = seed orelse sk: { var random_seed: [seed_length]u8 = undefined; - try crypto.randomBytes(&random_seed); + crypto.random.bytes(&random_seed); break :sk random_seed; }; var kp: KeyPair = undefined; @@ -85,8 +85,8 @@ const htest = @import("../test.zig"); test "x25519 public key calculation from secret key" { var sk: [32]u8 = undefined; var pk_expected: [32]u8 = undefined; - try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); + _ = try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); const pk_calculated = try X25519.recoverPublicKey(sk); std.testing.expectEqual(pk_calculated, pk_expected); } diff --git a/lib/std/crypto/aegis.zig b/lib/std/crypto/aegis.zig index f3060ef615..2983f68ce8 100644 --- a/lib/std/crypto/aegis.zig +++ b/lib/std/crypto/aegis.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -35,7 +35,7 @@ const State128L = struct { return state; } - inline fn update(state: *State128L, d1: AesBlock, d2: AesBlock) void { + fn update(state: *State128L, d1: AesBlock, d2: AesBlock) callconv(.Inline) void { const blocks = &state.blocks; const tmp = blocks[7]; comptime var i: usize = 7; @@ -81,8 +81,8 @@ const State128L = struct { while (i < 7) : (i += 1) { state.update(tmp, tmp); } - return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]). - xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(); + return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]) + .xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(); } }; @@ -207,7 +207,7 @@ const State256 = struct { return state; } - inline fn update(state: *State256, d: AesBlock) void { + fn update(state: *State256, d: AesBlock) callconv(.Inline) void { const blocks = &state.blocks; const tmp = blocks[5].encrypt(blocks[0]); comptime var i: usize = 5; @@ -244,8 +244,8 @@ const State256 = struct { while (i < 7) : (i += 1) { state.update(tmp); } - return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]). - xorBlocks(blocks[5]).toBytes(); + return blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]) + .xorBlocks(blocks[5]).toBytes(); } }; diff --git a/lib/std/crypto/aes.zig b/lib/std/crypto/aes.zig index ada55fa975..2a81492c8a 100644 --- a/lib/std/crypto/aes.zig +++ b/lib/std/crypto/aes.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -122,11 +122,11 @@ test "expand 128-bit key" { var exp: [16]u8 = undefined; for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_enc[i]); + _ = try std.fmt.hexToBytes(&exp, exp_enc[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_dec[i]); + _ = try std.fmt.hexToBytes(&exp, exp_dec[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } } @@ -144,11 +144,11 @@ test "expand 256-bit key" { var exp: [16]u8 = undefined; for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_enc[i]); + _ = try std.fmt.hexToBytes(&exp, exp_enc[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } for (dec.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_dec[i]); + _ = try std.fmt.hexToBytes(&exp, exp_dec[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } } diff --git a/lib/std/crypto/aes/aesni.zig b/lib/std/crypto/aes/aesni.zig index 3d694875bf..13b3f8e527 100644 --- a/lib/std/crypto/aes/aesni.zig +++ b/lib/std/crypto/aes/aesni.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -19,24 +19,24 @@ pub const Block = struct { repr: BlockVec, /// Convert a byte sequence into an internal representation. - pub inline fn fromBytes(bytes: *const [16]u8) Block { + pub fn fromBytes(bytes: *const [16]u8) callconv(.Inline) Block { const repr = mem.bytesToValue(BlockVec, bytes); return Block{ .repr = repr }; } /// Convert the internal representation of a block into a byte sequence. - pub inline fn toBytes(block: Block) [16]u8 { + pub fn toBytes(block: Block) callconv(.Inline) [16]u8 { return mem.toBytes(block.repr); } /// XOR the block with a byte sequence. - pub inline fn xorBytes(block: Block, bytes: *const [16]u8) [16]u8 { + pub fn xorBytes(block: Block, bytes: *const [16]u8) callconv(.Inline) [16]u8 { const x = block.repr ^ fromBytes(bytes).repr; return mem.toBytes(x); } /// Encrypt a block with a round key. - pub inline fn encrypt(block: Block, round_key: Block) Block { + pub fn encrypt(block: Block, round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ vaesenc %[rk], %[in], %[out] @@ -48,7 +48,7 @@ pub const Block = struct { } /// Encrypt a block with the last round key. - pub inline fn encryptLast(block: Block, round_key: Block) Block { + pub fn encryptLast(block: Block, round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ vaesenclast %[rk], %[in], %[out] @@ -60,7 +60,7 @@ pub const Block = struct { } /// Decrypt a block with a round key. - pub inline fn decrypt(block: Block, inv_round_key: Block) Block { + pub fn decrypt(block: Block, inv_round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ vaesdec %[rk], %[in], %[out] @@ -72,7 +72,7 @@ pub const Block = struct { } /// Decrypt a block with the last round key. - pub inline fn decryptLast(block: Block, inv_round_key: Block) Block { + pub fn decryptLast(block: Block, inv_round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ vaesdeclast %[rk], %[in], %[out] @@ -84,17 +84,17 @@ pub const Block = struct { } /// Apply the bitwise XOR operation to the content of two blocks. - pub inline fn xorBlocks(block1: Block, block2: Block) Block { + pub fn xorBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr ^ block2.repr }; } /// Apply the bitwise AND operation to the content of two blocks. - pub inline fn andBlocks(block1: Block, block2: Block) Block { + pub fn andBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr & block2.repr }; } /// Apply the bitwise OR operation to the content of two blocks. - pub inline fn orBlocks(block1: Block, block2: Block) Block { + pub fn orBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr | block2.repr }; } @@ -114,7 +114,7 @@ pub const Block = struct { }; /// Encrypt multiple blocks in parallel, each their own round key. - pub inline fn encryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block { + pub fn encryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -124,7 +124,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel, each their own round key. - pub inline fn decryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block { + pub fn decryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -134,7 +134,7 @@ pub const Block = struct { } /// Encrypt multiple blocks in parallel with the same round key. - pub inline fn encryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn encryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -144,7 +144,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel with the same round key. - pub inline fn decryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn decryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -154,7 +154,7 @@ pub const Block = struct { } /// Encrypt multiple blocks in parallel with the same last round key. - pub inline fn encryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn encryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -164,7 +164,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel with the same last round key. - pub inline fn decryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn decryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { diff --git a/lib/std/crypto/aes/armcrypto.zig b/lib/std/crypto/aes/armcrypto.zig index 79eb9dda75..d331783284 100644 --- a/lib/std/crypto/aes/armcrypto.zig +++ b/lib/std/crypto/aes/armcrypto.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -19,18 +19,18 @@ pub const Block = struct { repr: BlockVec, /// Convert a byte sequence into an internal representation. - pub inline fn fromBytes(bytes: *const [16]u8) Block { + pub fn fromBytes(bytes: *const [16]u8) callconv(.Inline) Block { const repr = mem.bytesToValue(BlockVec, bytes); return Block{ .repr = repr }; } /// Convert the internal representation of a block into a byte sequence. - pub inline fn toBytes(block: Block) [16]u8 { + pub fn toBytes(block: Block) callconv(.Inline) [16]u8 { return mem.toBytes(block.repr); } /// XOR the block with a byte sequence. - pub inline fn xorBytes(block: Block, bytes: *const [16]u8) [16]u8 { + pub fn xorBytes(block: Block, bytes: *const [16]u8) callconv(.Inline) [16]u8 { const x = block.repr ^ fromBytes(bytes).repr; return mem.toBytes(x); } @@ -38,7 +38,7 @@ pub const Block = struct { const zero = Vector(2, u64){ 0, 0 }; /// Encrypt a block with a round key. - pub inline fn encrypt(block: Block, round_key: Block) Block { + pub fn encrypt(block: Block, round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ mov %[out].16b, %[in].16b @@ -54,7 +54,7 @@ pub const Block = struct { } /// Encrypt a block with the last round key. - pub inline fn encryptLast(block: Block, round_key: Block) Block { + pub fn encryptLast(block: Block, round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ mov %[out].16b, %[in].16b @@ -69,7 +69,7 @@ pub const Block = struct { } /// Decrypt a block with a round key. - pub inline fn decrypt(block: Block, inv_round_key: Block) Block { + pub fn decrypt(block: Block, inv_round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ mov %[out].16b, %[in].16b @@ -85,7 +85,7 @@ pub const Block = struct { } /// Decrypt a block with the last round key. - pub inline fn decryptLast(block: Block, inv_round_key: Block) Block { + pub fn decryptLast(block: Block, inv_round_key: Block) callconv(.Inline) Block { return Block{ .repr = asm ( \\ mov %[out].16b, %[in].16b @@ -100,17 +100,17 @@ pub const Block = struct { } /// Apply the bitwise XOR operation to the content of two blocks. - pub inline fn xorBlocks(block1: Block, block2: Block) Block { + pub fn xorBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr ^ block2.repr }; } /// Apply the bitwise AND operation to the content of two blocks. - pub inline fn andBlocks(block1: Block, block2: Block) Block { + pub fn andBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr & block2.repr }; } /// Apply the bitwise OR operation to the content of two blocks. - pub inline fn orBlocks(block1: Block, block2: Block) Block { + pub fn orBlocks(block1: Block, block2: Block) callconv(.Inline) Block { return Block{ .repr = block1.repr | block2.repr }; } @@ -120,7 +120,7 @@ pub const Block = struct { pub const optimal_parallel_blocks = 8; /// Encrypt multiple blocks in parallel, each their own round key. - pub inline fn encryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block { + pub fn encryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -130,7 +130,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel, each their own round key. - pub inline fn decryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block { + pub fn decryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -140,7 +140,7 @@ pub const Block = struct { } /// Encrypt multiple blocks in parallel with the same round key. - pub inline fn encryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn encryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -150,7 +150,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel with the same round key. - pub inline fn decryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn decryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -160,7 +160,7 @@ pub const Block = struct { } /// Encrypt multiple blocks in parallel with the same last round key. - pub inline fn encryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn encryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { @@ -170,7 +170,7 @@ pub const Block = struct { } /// Decrypt multiple blocks in parallel with the same last round key. - pub inline fn decryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block { + pub fn decryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) callconv(.Inline) [count]Block { comptime var i = 0; var out: [count]Block = undefined; inline while (i < count) : (i += 1) { diff --git a/lib/std/crypto/aes/soft.zig b/lib/std/crypto/aes/soft.zig index e9108820b1..6f305b4050 100644 --- a/lib/std/crypto/aes/soft.zig +++ b/lib/std/crypto/aes/soft.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -18,7 +18,7 @@ pub const Block = struct { repr: BlockVec align(16), /// Convert a byte sequence into an internal representation. - pub inline fn fromBytes(bytes: *const [16]u8) Block { + pub fn fromBytes(bytes: *const [16]u8) callconv(.Inline) Block { const s0 = mem.readIntBig(u32, bytes[0..4]); const s1 = mem.readIntBig(u32, bytes[4..8]); const s2 = mem.readIntBig(u32, bytes[8..12]); @@ -27,7 +27,7 @@ pub const Block = struct { } /// Convert the internal representation of a block into a byte sequence. - pub inline fn toBytes(block: Block) [16]u8 { + pub fn toBytes(block: Block) callconv(.Inline) [16]u8 { var bytes: [16]u8 = undefined; mem.writeIntBig(u32, bytes[0..4], block.repr[0]); mem.writeIntBig(u32, bytes[4..8], block.repr[1]); @@ -37,7 +37,7 @@ pub const Block = struct { } /// XOR the block with a byte sequence. - pub inline fn xorBytes(block: Block, bytes: *const [16]u8) [16]u8 { + pub fn xorBytes(block: Block, bytes: *const [16]u8) callconv(.Inline) [16]u8 { const block_bytes = block.toBytes(); var x: [16]u8 = undefined; comptime var i: usize = 0; @@ -48,7 +48,7 @@ pub const Block = struct { } /// Encrypt a block with a round key. - pub inline fn encrypt(block: Block, round_key: Block) Block { + pub fn encrypt(block: Block, round_key: Block) callconv(.Inline) Block { const src = &block.repr; const s0 = block.repr[0]; @@ -65,7 +65,7 @@ pub const Block = struct { } /// Encrypt a block with the last round key. - pub inline fn encryptLast(block: Block, round_key: Block) Block { + pub fn encryptLast(block: Block, round_key: Block) callconv(.Inline) Block { const src = &block.repr; const t0 = block.repr[0]; @@ -87,7 +87,7 @@ pub const Block = struct { } /// Decrypt a block with a round key. - pub inline fn decrypt(block: Block, round_key: Block) Block { + pub fn decrypt(block: Block, round_key: Block) callconv(.Inline) Block { const src = &block.repr; const s0 = block.repr[0]; @@ -104,7 +104,7 @@ pub const Block = struct { } /// Decrypt a block with the last round key. - pub inline fn decryptLast(block: Block, round_key: Block) Block { + pub fn decryptLast(block: Block, round_key: Block) callconv(.Inline) Block { const src = &block.repr; const t0 = block.repr[0]; @@ -126,7 +126,7 @@ pub const Block = struct { } /// Apply the bitwise XOR operation to the content of two blocks. - pub inline fn xorBlocks(block1: Block, block2: Block) Block { + pub fn xorBlocks(block1: Block, block2: Block) callconv(.Inline) Block { var x: BlockVec = undefined; comptime var i = 0; inline while (i < 4) : (i += 1) { @@ -136,7 +136,7 @@ pub const Block = struct { } /// Apply the bitwise AND operation to the content of two blocks. - pub inline fn andBlocks(block1: Block, block2: Block) Block { + pub fn andBlocks(block1: Block, block2: Block) callconv(.Inline) Block { var x: BlockVec = undefined; comptime var i = 0; inline while (i < 4) : (i += 1) { @@ -146,7 +146,7 @@ pub const Block = struct { } /// Apply the bitwise OR operation to the content of two blocks. - pub inline fn orBlocks(block1: Block, block2: Block) Block { + pub fn orBlocks(block1: Block, block2: Block) callconv(.Inline) Block { var x: BlockVec = undefined; comptime var i = 0; inline while (i < 4) : (i += 1) { diff --git a/lib/std/crypto/aes_gcm.zig b/lib/std/crypto/aes_gcm.zig index e57decb2b2..5ef3f93963 100644 --- a/lib/std/crypto/aes_gcm.zig +++ b/lib/std/crypto/aes_gcm.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 4cec59961b..caceb6d7b9 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -109,9 +109,7 @@ const State = struct { } } - const Halves = struct { - l: u32, r: u32 - }; + const Halves = struct { l: u32, r: u32 }; fn feistelF(state: State, x: u32) u32 { var r = state.sboxes[0][@truncate(u8, x >> 24)]; @@ -247,7 +245,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) Codec.encode(ct_str[0..], ct[0 .. ct.len - 1]); var s_buf: [hash_length]u8 = undefined; - const s = fmt.bufPrint(s_buf[0..], "$2b${}{}${}{}", .{ rounds_log / 10, rounds_log % 10, salt_str, ct_str }) catch unreachable; + const s = fmt.bufPrint(s_buf[0..], "$2b${d}{d}${s}{s}", .{ rounds_log / 10, rounds_log % 10, salt_str, ct_str }) catch unreachable; debug.assert(s.len == s_buf.len); return s_buf; } @@ -262,7 +260,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) /// and then use the resulting hash as the password parameter for bcrypt. pub fn strHash(password: []const u8, rounds_log: u6) ![hash_length]u8 { var salt: [salt_length]u8 = undefined; - try crypto.randomBytes(&salt); + crypto.random.bytes(&salt); return strHashInternal(password, rounds_log, salt); } @@ -283,7 +281,7 @@ pub fn strVerify(h: [hash_length]u8, password: []const u8) BcryptError!void { test "bcrypt codec" { var salt: [salt_length]u8 = undefined; - try crypto.randomBytes(&salt); + crypto.random.bytes(&salt); var salt_str: [salt_str_length]u8 = undefined; Codec.encode(salt_str[0..], salt[0..]); var salt2: [salt_length]u8 = undefined; diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 7a0253861b..00336aef87 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -314,7 +314,7 @@ fn mode(comptime x: comptime_int) comptime_int { } pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); var buffer: [1024]u8 = undefined; var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); diff --git a/lib/std/crypto/blake2.zig b/lib/std/crypto/blake2.zig index b90661aa19..4203a99459 100644 --- a/lib/std/crypto/blake2.zig +++ b/lib/std/crypto/blake2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -33,6 +33,7 @@ fn roundParam(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundP // Blake2s pub const Blake2s128 = Blake2s(128); +pub const Blake2s160 = Blake2s(160); pub const Blake2s224 = Blake2s(224); pub const Blake2s256 = Blake2s(256); @@ -137,12 +138,8 @@ pub fn Blake2s(comptime out_bits: usize) type { mem.set(u8, d.buf[d.buf_len..], 0); d.t += d.buf_len; d.round(d.buf[0..], true); - - const rr = d.h[0 .. digest_length / 4]; - - for (rr) |s, j| { - mem.writeIntSliceLittle(u32, out[4 * j ..], s); - } + for (d.h) |*x| x.* = mem.nativeToLittle(u32, x.*); + mem.copy(u8, out[0..], @ptrCast(*[digest_length]u8, &d.h)); } fn round(d: *Self, b: *const [64]u8, last: bool) void { @@ -195,6 +192,89 @@ pub fn Blake2s(comptime out_bits: usize) type { }; } +test "blake2s160 single" { + const h1 = "354c9c33f735962418bdacb9479873429c34916f"; + htest.assertEqualHash(Blake2s160, h1, ""); + + const h2 = "5ae3b99be29b01834c3b508521ede60438f8de17"; + htest.assertEqualHash(Blake2s160, h2, "abc"); + + const h3 = "5a604fec9713c369e84b0ed68daed7d7504ef240"; + htest.assertEqualHash(Blake2s160, h3, "The quick brown fox jumps over the lazy dog"); + + const h4 = "b60c4dc60e2681e58fbc24e77f07e02c69e72ed0"; + htest.assertEqualHash(Blake2s160, h4, "a" ** 32 ++ "b" ** 32); +} + +test "blake2s160 streaming" { + var h = Blake2s160.init(.{}); + var out: [20]u8 = undefined; + + const h1 = "354c9c33f735962418bdacb9479873429c34916f"; + + h.final(out[0..]); + htest.assertEqual(h1, out[0..]); + + const h2 = "5ae3b99be29b01834c3b508521ede60438f8de17"; + + h = Blake2s160.init(.{}); + h.update("abc"); + h.final(out[0..]); + htest.assertEqual(h2, out[0..]); + + h = Blake2s160.init(.{}); + h.update("a"); + h.update("b"); + h.update("c"); + h.final(out[0..]); + htest.assertEqual(h2, out[0..]); + + const h3 = "b60c4dc60e2681e58fbc24e77f07e02c69e72ed0"; + + h = Blake2s160.init(.{}); + h.update("a" ** 32); + h.update("b" ** 32); + h.final(out[0..]); + htest.assertEqual(h3, out[0..]); + + h = Blake2s160.init(.{}); + h.update("a" ** 32 ++ "b" ** 32); + h.final(out[0..]); + htest.assertEqual(h3, out[0..]); + + const h4 = "4667fd60791a7fe41f939bca646b4529e296bd68"; + + h = Blake2s160.init(.{ .context = [_]u8{0x69} ** 8, .salt = [_]u8{0x42} ** 8 }); + h.update("a" ** 32); + h.update("b" ** 32); + h.final(out[0..]); + htest.assertEqual(h4, out[0..]); + + h = Blake2s160.init(.{ .context = [_]u8{0x69} ** 8, .salt = [_]u8{0x42} ** 8 }); + h.update("a" ** 32 ++ "b" ** 32); + h.final(out[0..]); + htest.assertEqual(h4, out[0..]); +} + +test "comptime blake2s160" { + //comptime + { + @setEvalBranchQuota(10000); + var block = [_]u8{0} ** Blake2s160.block_length; + var out: [Blake2s160.digest_length]u8 = undefined; + + const h1 = "2c56ad9d0b2c8b474aafa93ab307db2f0940105f"; + + htest.assertEqualHash(Blake2s160, h1, block[0..]); + + var h = Blake2s160.init(.{}); + h.update(&block); + h.final(out[0..]); + + htest.assertEqual(h1, out[0..]); + } +} + test "blake2s224 single" { const h1 = "1fa1291e65248b37b3433475b2a0dd63d54a11ecc4e3e034e7bc1ef4"; htest.assertEqualHash(Blake2s224, h1, ""); @@ -373,6 +453,7 @@ test "comptime blake2s256" { // Blake2b pub const Blake2b128 = Blake2b(128); +pub const Blake2b160 = Blake2b(160); pub const Blake2b256 = Blake2b(256); pub const Blake2b384 = Blake2b(384); pub const Blake2b512 = Blake2b(512); @@ -480,12 +561,8 @@ pub fn Blake2b(comptime out_bits: usize) type { mem.set(u8, d.buf[d.buf_len..], 0); d.t += d.buf_len; d.round(d.buf[0..], true); - - const rr = d.h[0 .. digest_length / 8]; - - for (rr) |s, j| { - mem.writeIntSliceLittle(u64, out[8 * j ..], s); - } + for (d.h) |*x| x.* = mem.nativeToLittle(u64, x.*); + mem.copy(u8, out[0..], @ptrCast(*[digest_length]u8, &d.h)); } fn round(d: *Self, b: *const [128]u8, last: bool) void { @@ -538,6 +615,95 @@ pub fn Blake2b(comptime out_bits: usize) type { }; } +test "blake2b160 single" { + const h1 = "3345524abf6bbe1809449224b5972c41790b6cf2"; + htest.assertEqualHash(Blake2b160, h1, ""); + + const h2 = "384264f676f39536840523f284921cdc68b6846b"; + htest.assertEqualHash(Blake2b160, h2, "abc"); + + const h3 = "3c523ed102ab45a37d54f5610d5a983162fde84f"; + htest.assertEqualHash(Blake2b160, h3, "The quick brown fox jumps over the lazy dog"); + + const h4 = "43758f5de1740f651f1ae39de92260fe8bd5a11f"; + htest.assertEqualHash(Blake2b160, h4, "a" ** 64 ++ "b" ** 64); +} + +test "blake2b160 streaming" { + var h = Blake2b160.init(.{}); + var out: [20]u8 = undefined; + + const h1 = "3345524abf6bbe1809449224b5972c41790b6cf2"; + + h.final(out[0..]); + htest.assertEqual(h1, out[0..]); + + const h2 = "384264f676f39536840523f284921cdc68b6846b"; + + h = Blake2b160.init(.{}); + h.update("abc"); + h.final(out[0..]); + htest.assertEqual(h2, out[0..]); + + h = Blake2b160.init(.{}); + h.update("a"); + h.update("b"); + h.update("c"); + h.final(out[0..]); + htest.assertEqual(h2, out[0..]); + + const h3 = "43758f5de1740f651f1ae39de92260fe8bd5a11f"; + + h = Blake2b160.init(.{}); + h.update("a" ** 64 ++ "b" ** 64); + h.final(out[0..]); + htest.assertEqual(h3, out[0..]); + + h = Blake2b160.init(.{}); + h.update("a" ** 64); + h.update("b" ** 64); + h.final(out[0..]); + htest.assertEqual(h3, out[0..]); + + h = Blake2b160.init(.{}); + h.update("a" ** 64); + h.update("b" ** 64); + h.final(out[0..]); + htest.assertEqual(h3, out[0..]); + + const h4 = "72328f8a8200663752fc302d372b5dd9b49dd8dc"; + + h = Blake2b160.init(.{ .context = [_]u8{0x69} ** 16, .salt = [_]u8{0x42} ** 16 }); + h.update("a" ** 64); + h.update("b" ** 64); + h.final(out[0..]); + htest.assertEqual(h4, out[0..]); + + h = Blake2b160.init(.{ .context = [_]u8{0x69} ** 16, .salt = [_]u8{0x42} ** 16 }); + h.update("a" ** 64); + h.update("b" ** 64); + h.final(out[0..]); + htest.assertEqual(h4, out[0..]); +} + +test "comptime blake2b160" { + comptime { + @setEvalBranchQuota(10000); + var block = [_]u8{0} ** Blake2b160.block_length; + var out: [Blake2b160.digest_length]u8 = undefined; + + const h1 = "8d26f158f564e3293b42f5e3d34263cb173aa9c9"; + + htest.assertEqualHash(Blake2b160, h1, block[0..]); + + var h = Blake2b160.init(.{}); + h.update(&block); + h.final(out[0..]); + + htest.assertEqual(h1, out[0..]); + } +} + test "blake2b384 single" { const h1 = "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100"; htest.assertEqualHash(Blake2b384, h1, ""); diff --git a/lib/std/crypto/blake3.zig b/lib/std/crypto/blake3.zig index b22429b8e2..a10c50b074 100644 --- a/lib/std/crypto/blake3.zig +++ b/lib/std/crypto/blake3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -66,7 +66,7 @@ const CompressVectorized = struct { const Lane = Vector(4, u32); const Rows = [4]Lane; - inline fn g(comptime even: bool, rows: *Rows, m: Lane) void { + fn g(comptime even: bool, rows: *Rows, m: Lane) callconv(.Inline) void { rows[0] +%= rows[1] +% m; rows[3] ^= rows[0]; rows[3] = math.rotr(Lane, rows[3], if (even) 8 else 16); @@ -75,13 +75,13 @@ const CompressVectorized = struct { rows[1] = math.rotr(Lane, rows[1], if (even) 7 else 12); } - inline fn diagonalize(rows: *Rows) void { + fn diagonalize(rows: *Rows) callconv(.Inline) void { rows[0] = @shuffle(u32, rows[0], undefined, [_]i32{ 3, 0, 1, 2 }); rows[3] = @shuffle(u32, rows[3], undefined, [_]i32{ 2, 3, 0, 1 }); rows[2] = @shuffle(u32, rows[2], undefined, [_]i32{ 1, 2, 3, 0 }); } - inline fn undiagonalize(rows: *Rows) void { + fn undiagonalize(rows: *Rows) callconv(.Inline) void { rows[0] = @shuffle(u32, rows[0], undefined, [_]i32{ 1, 2, 3, 0 }); rows[3] = @shuffle(u32, rows[3], undefined, [_]i32{ 2, 3, 0, 1 }); rows[2] = @shuffle(u32, rows[2], undefined, [_]i32{ 3, 0, 1, 2 }); @@ -663,7 +663,7 @@ fn testBlake3(hasher: *Blake3, input_len: usize, expected_hex: [262]u8) void { // Compare to expected value var expected_bytes: [expected_hex.len / 2]u8 = undefined; - fmt.hexToBytes(expected_bytes[0..], expected_hex[0..]) catch unreachable; + _ = fmt.hexToBytes(expected_bytes[0..], expected_hex[0..]) catch unreachable; testing.expectEqual(actual_bytes, expected_bytes); // Restore initial state diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig index 5acd2bd4f5..0f79707279 100644 --- a/lib/std/crypto/chacha20.zig +++ b/lib/std/crypto/chacha20.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -35,7 +35,7 @@ const ChaCha20VecImpl = struct { }; } - inline fn chacha20Core(x: *BlockVec, input: BlockVec) void { + fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { x.* = input; var r: usize = 0; @@ -80,7 +80,7 @@ const ChaCha20VecImpl = struct { } } - inline fn hashToBytes(out: *[64]u8, x: BlockVec) void { + fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { var i: usize = 0; while (i < 4) : (i += 1) { mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]); @@ -90,7 +90,7 @@ const ChaCha20VecImpl = struct { } } - inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void { + fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { x[0] +%= ctx[0]; x[1] +%= ctx[1]; x[2] +%= ctx[2]; @@ -190,7 +190,7 @@ const ChaCha20NonVecImpl = struct { }; } - inline fn chacha20Core(x: *BlockVec, input: BlockVec) void { + fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { x.* = input; const rounds = comptime [_]QuarterRound{ @@ -219,7 +219,7 @@ const ChaCha20NonVecImpl = struct { } } - inline fn hashToBytes(out: *[64]u8, x: BlockVec) void { + fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { var i: usize = 0; while (i < 4) : (i += 1) { mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); @@ -229,7 +229,7 @@ const ChaCha20NonVecImpl = struct { } } - inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void { + fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { var i: usize = 0; while (i < 16) : (i += 1) { x[i] +%= ctx[i]; diff --git a/lib/std/crypto/ghash.zig b/lib/std/crypto/ghash.zig index d5d4ae98ea..ffc9ef41ae 100644 --- a/lib/std/crypto/ghash.zig +++ b/lib/std/crypto/ghash.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -95,7 +95,7 @@ pub const Ghash = struct { } } - inline fn clmul_pclmul(x: u64, y: u64) u64 { + fn clmul_pclmul(x: u64, y: u64) callconv(.Inline) u64 { const Vector = std.meta.Vector; const product = asm ( \\ vpclmulqdq $0x00, %[x], %[y], %[out] @@ -106,7 +106,7 @@ pub const Ghash = struct { return product[0]; } - inline fn clmul_pmull(x: u64, y: u64) u64 { + fn clmul_pmull(x: u64, y: u64) callconv(.Inline) u64 { const Vector = std.meta.Vector; const product = asm ( \\ pmull %[out].1q, %[x].1d, %[y].1d diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig index 78ab88b9cf..1c1d6c79db 100644 --- a/lib/std/crypto/gimli.zig +++ b/lib/std/crypto/gimli.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -48,7 +48,7 @@ pub const State = struct { return mem.asBytes(&self.data); } - inline fn endianSwap(self: *Self) void { + fn endianSwap(self: *Self) callconv(.Inline) void { for (self.data) |*w| { w.* = mem.littleToNative(u32, w.*); } @@ -116,7 +116,7 @@ pub const State = struct { const Lane = Vector(4, u32); - inline fn shift(x: Lane, comptime n: comptime_int) Lane { + fn shift(x: Lane, comptime n: comptime_int) callconv(.Inline) Lane { return x << @splat(4, @as(u5, n)); } @@ -229,18 +229,17 @@ pub const Hash = struct { const buf = self.state.toSlice(); var in = data; while (in.len > 0) { - var left = State.RATE - self.buf_off; - if (left == 0) { - self.state.permute(); - self.buf_off = 0; - left = State.RATE; - } + const left = State.RATE - self.buf_off; const ps = math.min(in.len, left); for (buf[self.buf_off .. self.buf_off + ps]) |*p, i| { p.* ^= in[i]; } self.buf_off += ps; in = in[ps..]; + if (self.buf_off == State.RATE) { + self.state.permute(); + self.buf_off = 0; + } } } @@ -271,12 +270,28 @@ pub fn hash(out: []u8, in: []const u8, options: Hash.Options) void { test "hash" { // a test vector (30) from NIST KAT submission. var msg: [58 / 2]u8 = undefined; - try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C"); + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C"); var md: [32]u8 = undefined; hash(&md, &msg, .{}); htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md); } +test "hash test vector 17" { + var msg: [32 / 2]u8 = undefined; + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F"); + var md: [32]u8 = undefined; + hash(&md, &msg, .{}); + htest.assertEqual("404C130AF1B9023A7908200919F690FFBB756D5176E056FFDE320016A37C7282", &md); +} + +test "hash test vector 33" { + var msg: [32]u8 = undefined; + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + var md: [32]u8 = undefined; + hash(&md, &msg, .{}); + htest.assertEqual("A8F4FA28708BDA7EFB4C1914CA4AFA9E475B82D588D36504F87DBB0ED9AB3C4B", &md); +} + pub const Aead = struct { pub const tag_length = State.RATE; pub const nonce_length = 16; @@ -421,9 +436,9 @@ pub const Aead = struct { test "cipher" { var key: [32]u8 = undefined; - try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + _ = try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); var nonce: [16]u8 = undefined; - try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F"); + _ = try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F"); { // test vector (1) from NIST KAT submission. const ad: [0]u8 = undefined; const pt: [0]u8 = undefined; @@ -441,7 +456,7 @@ test "cipher" { { // test vector (34) from NIST KAT submission. const ad: [0]u8 = undefined; var pt: [2 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "00"); + _ = try std.fmt.hexToBytes(&pt, "00"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -455,9 +470,9 @@ test "cipher" { } { // test vector (106) from NIST KAT submission. var ad: [12 / 2]u8 = undefined; - try std.fmt.hexToBytes(&ad, "000102030405"); + _ = try std.fmt.hexToBytes(&ad, "000102030405"); var pt: [6 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102"); + _ = try std.fmt.hexToBytes(&pt, "000102"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -471,9 +486,9 @@ test "cipher" { } { // test vector (790) from NIST KAT submission. var ad: [60 / 2]u8 = undefined; - try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D"); + _ = try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D"); var pt: [46 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516"); + _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -488,7 +503,7 @@ test "cipher" { { // test vector (1057) from NIST KAT submission. const ad: [0]u8 = undefined; var pt: [64 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; diff --git a/lib/std/crypto/hkdf.zig b/lib/std/crypto/hkdf.zig index b1c6f58acd..c0f919ef82 100644 --- a/lib/std/crypto/hkdf.zig +++ b/lib/std/crypto/hkdf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/hmac.zig b/lib/std/crypto/hmac.zig index 3978ff6b81..7f29c62941 100644 --- a/lib/std/crypto/hmac.zig +++ b/lib/std/crypto/hmac.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/md5.zig b/lib/std/crypto/md5.zig index 8b454c52a7..78454ce3c1 100644 --- a/lib/std/crypto/md5.zig +++ b/lib/std/crypto/md5.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/modes.zig b/lib/std/crypto/modes.zig index a81d30e50f..a74704d1ae 100644 --- a/lib/std/crypto/modes.zig +++ b/lib/std/crypto/modes.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 85c8e01105..25df1ba440 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/poly1305.zig b/lib/std/crypto/poly1305.zig index 0b7b4cd64a..739c057178 100644 --- a/lib/std/crypto/poly1305.zig +++ b/lib/std/crypto/poly1305.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index dd3e4fe99b..e22668f998 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -37,7 +37,7 @@ const Salsa20VecImpl = struct { }; } - inline fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) void { + fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) callconv(.Inline) void { const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] }; const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] }; const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] }; @@ -146,9 +146,9 @@ const Salsa20VecImpl = struct { while (j < 64) : (j += 1) { xout[j] ^= buf[j]; } - ctx[2][0] +%= 1; - if (ctx[2][0] == 0) { - ctx[2][1] += 1; + ctx[3][2] +%= 1; + if (ctx[3][2] == 0) { + ctx[3][3] += 1; } } if (i < in.len) { @@ -211,7 +211,7 @@ const Salsa20NonVecImpl = struct { d: u6, }; - inline fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound { + fn Rp(a: usize, b: usize, c: usize, d: u6) callconv(.Inline) QuarterRound { return QuarterRound{ .a = a, .b = b, @@ -220,7 +220,7 @@ const Salsa20NonVecImpl = struct { }; } - inline fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) void { + fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) callconv(.Inline) void { const arx_steps = comptime [_]QuarterRound{ Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18), Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18), @@ -571,9 +571,9 @@ test "xsalsa20poly1305" { var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&key); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&key); + crypto.random.bytes(&nonce); XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); @@ -585,9 +585,9 @@ test "xsalsa20poly1305 secretbox" { var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&key); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&key); + crypto.random.bytes(&nonce); SecretBox.seal(boxed[0..], msg[0..], nonce, key); try SecretBox.open(msg2[0..], boxed[0..], nonce, key); @@ -598,8 +598,8 @@ test "xsalsa20poly1305 box" { var msg2: [msg.len]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&nonce); var kp1 = try Box.KeyPair.create(null); var kp2 = try Box.KeyPair.create(null); @@ -611,9 +611,18 @@ test "xsalsa20poly1305 sealedbox" { var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; - try crypto.randomBytes(&msg); + crypto.random.bytes(&msg); var kp = try Box.KeyPair.create(null); try SealedBox.seal(boxed[0..], msg[0..], kp.public_key); try SealedBox.open(msg2[0..], boxed[0..], kp); } + +test "secretbox twoblocks" { + const key = [_]u8{ 0xc9, 0xc9, 0x4d, 0xcf, 0x68, 0xbe, 0x00, 0xe4, 0x7f, 0xe6, 0x13, 0x26, 0xfc, 0xc4, 0x2f, 0xd0, 0xdb, 0x93, 0x91, 0x1c, 0x09, 0x94, 0x89, 0xe1, 0x1b, 0x88, 0x63, 0x18, 0x86, 0x64, 0x8b, 0x7b }; + const nonce = [_]u8{ 0xa4, 0x33, 0xe9, 0x0a, 0x07, 0x68, 0x6e, 0x9a, 0x2b, 0x6d, 0xd4, 0x59, 0x04, 0x72, 0x3e, 0xd3, 0x8a, 0x67, 0x55, 0xc7, 0x9e, 0x3e, 0x77, 0xdc }; + const msg = [_]u8{'a'} ** 97; + var ciphertext: [msg.len + SecretBox.tag_length]u8 = undefined; + SecretBox.seal(&ciphertext, &msg, nonce, key); + htest.assertEqual("b05760e217288ba079caa2fd57fd3701784974ffcfda20fe523b89211ad8af065a6eb37cdb29d51aca5bd75dafdd21d18b044c54bb7c526cf576c94ee8900f911ceab0147e82b667a28c52d58ceb29554ff45471224d37b03256b01c119b89ff6d36855de8138d103386dbc9d971f52261", &ciphertext); +} diff --git a/lib/std/crypto/sha1.zig b/lib/std/crypto/sha1.zig index c2ae0a6544..ac699f0ef1 100644 --- a/lib/std/crypto/sha1.zig +++ b/lib/std/crypto/sha1.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/sha2.zig b/lib/std/crypto/sha2.zig index 4e06a214dd..5a37004e8c 100644 --- a/lib/std/crypto/sha2.zig +++ b/lib/std/crypto/sha2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig index 3aecf25e5b..d68821133e 100644 --- a/lib/std/crypto/sha3.zig +++ b/lib/std/crypto/sha3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -13,6 +13,8 @@ pub const Sha3_224 = Keccak(224, 0x06); pub const Sha3_256 = Keccak(256, 0x06); pub const Sha3_384 = Keccak(384, 0x06); pub const Sha3_512 = Keccak(512, 0x06); +pub const Keccak_256 = Keccak(256, 0x01); +pub const Keccak_512 = Keccak(512, 0x01); fn Keccak(comptime bits: usize, comptime delim: u8) type { return struct { @@ -297,3 +299,15 @@ test "sha3-512 aligned final" { h.update(&block); h.final(out[0..]); } + +test "keccak-256 single" { + htest.assertEqualHash(Keccak_256, "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""); + htest.assertEqualHash(Keccak_256, "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"); + htest.assertEqualHash(Keccak_256, "f519747ed599024f3882238e5ab43960132572b7345fbeb9a90769dafd21ad67", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); +} + +test "keccak-512 single" { + htest.assertEqualHash(Keccak_512, "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""); + htest.assertEqualHash(Keccak_512, "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"); + htest.assertEqualHash(Keccak_512, "ac2fb35251825d3aa48468a9948c0a91b8256f6d97d8fa4160faff2dd9dfcc24f3f1db7a983dad13d53439ccac0b37e24037e7b95f80f59f37a2f683c4ba4682", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); +} diff --git a/lib/std/crypto/siphash.zig b/lib/std/crypto/siphash.zig index 5dfd2bf326..67bb2a329a 100644 --- a/lib/std/crypto/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -7,10 +7,10 @@ // SipHash is a moderately fast pseudorandom function, returning a 64-bit or 128-bit tag for an arbitrary long input. // // Typical use cases include: -// - protection against against DoS attacks for hash tables and bloom filters +// - protection against DoS attacks for hash tables and bloom filters // - authentication of short-lived messages in online protocols // -// https://131002.net/siphash/ +// https://www.aumasson.jp/siphash/siphash.pdf const std = @import("../std.zig"); const assert = std.debug.assert; const testing = std.testing; diff --git a/lib/std/crypto/test.zig b/lib/std/crypto/test.zig index 692e331e39..cab07c50ec 100644 --- a/lib/std/crypto/test.zig +++ b/lib/std/crypto/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig new file mode 100644 index 0000000000..115a7ab882 --- /dev/null +++ b/lib/std/crypto/tlcsprng.zig @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Thread-local cryptographically secure pseudo-random number generator. +//! This file has public declarations that are intended to be used internally +//! by the standard library; this namespace is not intended to be exposed +//! directly to standard library users. + +const std = @import("std"); +const root = @import("root"); +const mem = std.mem; + +/// We use this as a layer of indirection because global const pointers cannot +/// point to thread-local variables. +pub var interface = std.rand.Random{ .fillFn = tlsCsprngFill }; + +const os_has_fork = switch (std.Target.current.os.tag) { + .dragonfly, + .freebsd, + .ios, + .kfreebsd, + .linux, + .macos, + .netbsd, + .openbsd, + .solaris, + .tvos, + .watchos, + .haiku, + => true, + + else => false, +}; +const os_has_arc4random = std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf"); +const want_fork_safety = os_has_fork and !os_has_arc4random and + (std.meta.globalOption("crypto_fork_safety", bool) orelse true); +const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{ + .major = 4, + .minor = 14, +}) orelse true; + +const WipeMe = struct { + init_state: enum { uninitialized, initialized, failed }, + gimli: std.crypto.core.Gimli, +}; +const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe); + +threadlocal var wipe_me: WipeMe align(wipe_align) = .{ + .gimli = undefined, + .init_state = .uninitialized, +}; + +fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void { + if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) { + // arc4random is already a thread-local CSPRNG. + return std.c.arc4random_buf(buffer.ptr, buffer.len); + } + // Allow applications to decide they would prefer to have every call to + // std.crypto.random always make an OS syscall, rather than rely on an + // application implementation of a CSPRNG. + if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) { + return fillWithOsEntropy(buffer); + } + switch (wipe_me.init_state) { + .uninitialized => { + if (want_fork_safety) { + if (maybe_have_wipe_on_fork) { + if (std.os.madvise( + @ptrCast([*]align(mem.page_size) u8, &wipe_me), + @sizeOf(@TypeOf(wipe_me)), + std.os.MADV_WIPEONFORK, + )) |_| { + return initAndFill(buffer); + } else |_| if (std.Thread.use_pthreads) { + return setupPthreadAtforkAndFill(buffer); + } else { + // Since we failed to set up fork safety, we fall back to always + // calling getrandom every time. + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } + } else if (std.Thread.use_pthreads) { + return setupPthreadAtforkAndFill(buffer); + } else { + // We have no mechanism to provide fork safety, but we want fork safety, + // so we fall back to calling getrandom every time. + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } + } else { + return initAndFill(buffer); + } + }, + .initialized => { + return fillWithCsprng(buffer); + }, + .failed => { + if (want_fork_safety) { + return fillWithOsEntropy(buffer); + } else { + unreachable; + } + }, + } +} + +fn setupPthreadAtforkAndFill(buffer: []u8) void { + const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0; + if (failed) { + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } else { + return initAndFill(buffer); + } +} + +fn childAtForkHandler() callconv(.C) void { + const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))]; + std.crypto.utils.secureZero(u8, wipe_slice); +} + +fn fillWithCsprng(buffer: []u8) void { + if (buffer.len != 0) { + wipe_me.gimli.squeeze(buffer); + } else { + wipe_me.gimli.permute(); + } + mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0); +} + +fn fillWithOsEntropy(buffer: []u8) void { + std.os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); +} + +fn initAndFill(buffer: []u8) void { + var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; + // Because we panic on getrandom() failing, we provide the opportunity + // to override the default seed function. This also makes + // `std.crypto.random` available on freestanding targets, provided that + // the `cryptoRandomSeed` function is provided. + if (@hasDecl(root, "cryptoRandomSeed")) { + root.cryptoRandomSeed(&seed); + } else { + fillWithOsEntropy(&seed); + } + + wipe_me.gimli = std.crypto.core.Gimli.init(seed); + + // This is at the end so that accidental recursive dependencies result + // in stack overflows instead of invalid random data. + wipe_me.init_state = .initialized; + + return fillWithCsprng(buffer); +} diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig index 33ad6360f6..08271ac9f4 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/utils.zig @@ -51,8 +51,8 @@ pub fn secureZero(comptime T: type, s: []T) void { test "crypto.utils.timingSafeEql" { var a: [100]u8 = undefined; var b: [100]u8 = undefined; - try std.crypto.randomBytes(a[0..]); - try std.crypto.randomBytes(b[0..]); + std.crypto.random.bytes(a[0..]); + std.crypto.random.bytes(b[0..]); testing.expect(!timingSafeEql([100]u8, a, b)); mem.copy(u8, a[0..], b[0..]); testing.expect(timingSafeEql([100]u8, a, b)); @@ -61,8 +61,8 @@ test "crypto.utils.timingSafeEql" { test "crypto.utils.timingSafeEql (vectors)" { var a: [100]u8 = undefined; var b: [100]u8 = undefined; - try std.crypto.randomBytes(a[0..]); - try std.crypto.randomBytes(b[0..]); + std.crypto.random.bytes(a[0..]); + std.crypto.random.bytes(b[0..]); const v1: std.meta.Vector(100, u8) = a; const v2: std.meta.Vector(100, u8) = b; testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2)); diff --git a/lib/std/cstr.zig b/lib/std/cstr.zig index 4e11ad9201..33f32ae892 100644 --- a/lib/std/cstr.zig +++ b/lib/std/cstr.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/debug.zig b/lib/std/debug.zig index be5635ee8f..f32c1a6156 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -50,7 +50,7 @@ pub const LineInfo = struct { } }; -var stderr_mutex = std.Mutex{}; +var stderr_mutex = std.Thread.Mutex{}; /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for /// "printf debugging". @@ -65,7 +65,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { nosuspend stderr.print(fmt, args) catch return; } -pub fn getStderrMutex() *std.Mutex { +pub fn getStderrMutex() *std.Thread.Mutex { return &stderr_mutex; } @@ -108,11 +108,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { return; } const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { - stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; + stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; } @@ -129,7 +129,7 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { return; } const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; const tty_config = detectTTYConfig(); @@ -199,11 +199,11 @@ pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { return; } const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| { - stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; + stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; } @@ -235,7 +235,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { var panicking: u8 = 0; // Locked to avoid interleaving panic messages from multiple threads. -var panic_mutex = std.Mutex{}; +var panic_mutex = std.Thread.Mutex{}; /// Counts how many times the panic handler is invoked by this thread. /// This is used to catch and handle panics triggered by the panic handler. @@ -250,6 +250,24 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c resetSegfaultHandler(); } + if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) + nosuspend { + // As a workaround for not having threadlocal variable support in LLD for this target, + // we have a simpler panic implementation that does not use threadlocal variables. + // TODO https://github.com/ziglang/zig/issues/7527 + const stderr = io.getStdErr().writer(); + if (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst) == 0) { + stderr.print("panic: " ++ format ++ "\n", args) catch os.abort(); + if (trace) |t| { + dumpStackTrace(t.*); + } + dumpCurrentStackTrace(first_trace_addr); + } else { + stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); + } + os.abort(); + }; + nosuspend switch (panic_stage) { 0 => { panic_stage = 1; @@ -262,6 +280,12 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c defer held.release(); const stderr = io.getStdErr().writer(); + if (builtin.single_threaded) { + stderr.print("panic: ", .{}) catch os.abort(); + } else { + const current_thread_id = std.Thread.getCurrentThreadId(); + stderr.print("thread {d} panic: ", .{current_thread_id}) catch os.abort(); + } stderr.print(format ++ "\n", args) catch os.abort(); if (trace) |t| { dumpStackTrace(t.*); @@ -274,9 +298,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c // and call abort() // Sleep forever without hammering the CPU - var event = std.ResetEvent.init(); + var event: std.Thread.StaticResetEvent = .{}; event.wait(); - unreachable; } }, @@ -512,15 +535,15 @@ fn populateModule(di: *ModuleDebugInfo, mod: *Module) !void { const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo; - const signature = try modi.inStream().readIntLittle(u32); + const signature = try modi.reader().readIntLittle(u32); if (signature != 4) return error.InvalidDebugInfo; mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4); - try modi.inStream().readNoEof(mod.symbols); + try modi.reader().readNoEof(mod.symbols); mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize); - try modi.inStream().readNoEof(mod.subsect_info); + try modi.reader().readNoEof(mod.subsect_info); var sect_offset: usize = 0; var skip_len: usize = undefined; @@ -606,7 +629,7 @@ fn printLineInfo( tty_config.setColor(out_stream, .White); if (line_info) |*li| { - try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column }); + try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); } else { try out_stream.writeAll("???:?:?"); } @@ -614,7 +637,7 @@ fn printLineInfo( tty_config.setColor(out_stream, .Reset); try out_stream.writeAll(": "); tty_config.setColor(out_stream, .Dim); - try out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name }); + try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); tty_config.setColor(out_stream, .Reset); try out_stream.writeAll("\n"); @@ -634,6 +657,7 @@ fn printLineInfo( } else |err| switch (err) { error.EndOfFile, error.FileNotFound => {}, error.BadPathName => {}, + error.AccessDenied => {}, else => return err, } } @@ -699,11 +723,11 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf try di.pdb.openFile(di.coff, path); var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; - const version = try pdb_stream.inStream().readIntLittle(u32); - const signature = try pdb_stream.inStream().readIntLittle(u32); - const age = try pdb_stream.inStream().readIntLittle(u32); + const version = try pdb_stream.reader().readIntLittle(u32); + const signature = try pdb_stream.reader().readIntLittle(u32); + const age = try pdb_stream.reader().readIntLittle(u32); var guid: [16]u8 = undefined; - try pdb_stream.inStream().readNoEof(&guid); + try pdb_stream.reader().readNoEof(&guid); if (version != 20000404) // VC70, only value observed by LLVM team return error.UnknownPDBVersion; if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age) @@ -711,9 +735,9 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf // We validated the executable and pdb match. const string_table_index = str_tab_index: { - const name_bytes_len = try pdb_stream.inStream().readIntLittle(u32); + const name_bytes_len = try pdb_stream.reader().readIntLittle(u32); const name_bytes = try allocator.alloc(u8, name_bytes_len); - try pdb_stream.inStream().readNoEof(name_bytes); + try pdb_stream.reader().readNoEof(name_bytes); const HashTableHeader = packed struct { Size: u32, @@ -723,17 +747,17 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf return cap * 2 / 3 + 1; } }; - const hash_tbl_hdr = try pdb_stream.inStream().readStruct(HashTableHeader); + const hash_tbl_hdr = try pdb_stream.reader().readStruct(HashTableHeader); if (hash_tbl_hdr.Capacity == 0) return error.InvalidDebugInfo; if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) return error.InvalidDebugInfo; - const present = try readSparseBitVector(&pdb_stream.inStream(), allocator); + const present = try readSparseBitVector(&pdb_stream.reader(), allocator); if (present.len != hash_tbl_hdr.Size) return error.InvalidDebugInfo; - const deleted = try readSparseBitVector(&pdb_stream.inStream(), allocator); + const deleted = try readSparseBitVector(&pdb_stream.reader(), allocator); const Bucket = struct { first: u32, @@ -741,8 +765,8 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf }; const bucket_list = try allocator.alloc(Bucket, present.len); for (present) |_| { - const name_offset = try pdb_stream.inStream().readIntLittle(u32); - const name_index = try pdb_stream.inStream().readIntLittle(u32); + const name_offset = try pdb_stream.reader().readIntLittle(u32); + const name_index = try pdb_stream.reader().readIntLittle(u32); const name = mem.spanZ(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0)); if (mem.eql(u8, name, "/names")) { break :str_tab_index name_index; @@ -757,7 +781,7 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf const dbi = di.pdb.dbi; // Dbi Header - const dbi_stream_header = try dbi.inStream().readStruct(pdb.DbiStreamHeader); + const dbi_stream_header = try dbi.reader().readStruct(pdb.DbiStreamHeader); if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team return error.UnknownPDBVersion; if (dbi_stream_header.Age != age) @@ -771,7 +795,7 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf // Module Info Substream var mod_info_offset: usize = 0; while (mod_info_offset != mod_info_size) { - const mod_info = try dbi.inStream().readStruct(pdb.ModInfo); + const mod_info = try dbi.reader().readStruct(pdb.ModInfo); var this_record_len: usize = @sizeOf(pdb.ModInfo); const module_name = try dbi.readNullTermString(allocator); @@ -809,14 +833,14 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); var sect_cont_offset: usize = 0; if (section_contrib_size != 0) { - const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.inStream().readIntLittle(u32)); + const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.reader().readIntLittle(u32)); if (ver != pdb.SectionContrSubstreamVersion.Ver60) return error.InvalidDebugInfo; sect_cont_offset += @sizeOf(u32); } while (sect_cont_offset != section_contrib_size) { const entry = try sect_contribs.addOne(); - entry.* = try dbi.inStream().readStruct(pdb.SectionContribEntry); + entry.* = try dbi.reader().readStruct(pdb.SectionContribEntry); sect_cont_offset += @sizeOf(pdb.SectionContribEntry); if (sect_cont_offset > section_contrib_size) @@ -1101,12 +1125,15 @@ pub const DebugInfo = struct { } pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo { - if (comptime std.Target.current.isDarwin()) - return self.lookupModuleDyld(address) - else if (builtin.os.tag == .windows) - return self.lookupModuleWin32(address) - else + if (comptime std.Target.current.isDarwin()) { + return self.lookupModuleDyld(address); + } else if (builtin.os.tag == .windows) { + return self.lookupModuleWin32(address); + } else if (builtin.os.tag == .haiku) { + return self.lookupModuleHaiku(address); + } else { return self.lookupModuleDl(address); + } } fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo { @@ -1312,6 +1339,10 @@ pub const DebugInfo = struct { return obj_di; } + + fn lookupModuleHaiku(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + @panic("TODO implement lookup module for Haiku"); + } }; const SymbolInfo = struct { @@ -1696,6 +1727,7 @@ fn getDebugInfoAllocator() *mem.Allocator { pub const have_segfault_handling_support = switch (builtin.os.tag) { .linux, .netbsd => true, .windows => true, + .freebsd, .openbsd => @hasDecl(os, "ucontext_t"), else => false, }; pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) @@ -1757,7 +1789,9 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v const addr = switch (builtin.os.tag) { .linux => @ptrToInt(info.fields.sigfault.addr), + .freebsd => @ptrToInt(info.addr), .netbsd => @ptrToInt(info.info.reason.fault.addr), + .openbsd => @ptrToInt(info.data.fault.addr), else => unreachable, }; @@ -1781,8 +1815,18 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v }, .x86_64 => { const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); - const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]); - const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]); + const ip = switch (builtin.os.tag) { + .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]), + .freebsd => @intCast(usize, ctx.mcontext.rip), + .openbsd => @intCast(usize, ctx.sc_rip), + else => unreachable, + }; + const bp = switch (builtin.os.tag) { + .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]), + .openbsd => @intCast(usize, ctx.sc_rbp), + .freebsd => @intCast(usize, ctx.mcontext.rbp), + else => unreachable, + }; dumpStackTraceFromBase(bp, ip); }, .arm => { diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 3ef0540bdd..7df3a1bff6 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -213,13 +213,11 @@ const LineNumberProgram = struct { return error.MissingDebugInfo; } else if (self.prev_file - 1 >= self.file_entries.items.len) { return error.InvalidDebugInfo; - } else - &self.file_entries.items[self.prev_file - 1]; + } else &self.file_entries.items[self.prev_file - 1]; const dir_name = if (file_entry.dir_index >= self.include_dirs.len) { return error.InvalidDebugInfo; - } else - self.include_dirs[file_entry.dir_index]; + } else self.include_dirs[file_entry.dir_index]; const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name }); errdefer self.file_entries.allocator.free(file_name); return debug.LineInfo{ @@ -408,15 +406,12 @@ pub const DwarfInfo = struct { fn scanAllFunctions(di: *DwarfInfo) !void { var stream = io.fixedBufferStream(di.debug_info); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); var this_unit_offset: u64 = 0; while (this_unit_offset < try seekable.getEndPos()) { - seekable.seekTo(this_unit_offset) catch |err| switch (err) { - error.EndOfStream => unreachable, - else => return err, - }; + try seekable.seekTo(this_unit_offset); var is_64: bool = undefined; const unit_length = try readUnitLength(in, di.endian, &is_64); @@ -515,15 +510,12 @@ pub const DwarfInfo = struct { fn scanAllCompileUnits(di: *DwarfInfo) !void { var stream = io.fixedBufferStream(di.debug_info); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); var this_unit_offset: u64 = 0; while (this_unit_offset < try seekable.getEndPos()) { - seekable.seekTo(this_unit_offset) catch |err| switch (err) { - error.EndOfStream => unreachable, - else => return err, - }; + try seekable.seekTo(this_unit_offset); var is_64: bool = undefined; const unit_length = try readUnitLength(in, di.endian, &is_64); @@ -591,7 +583,7 @@ pub const DwarfInfo = struct { if (di.debug_ranges) |debug_ranges| { if (compile_unit.die.getAttrSecOffset(AT_ranges)) |ranges_offset| { var stream = io.fixedBufferStream(debug_ranges); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); // All the addresses in the list are relative to the value @@ -646,7 +638,7 @@ pub const DwarfInfo = struct { fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable { var stream = io.fixedBufferStream(di.debug_abbrev); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); try seekable.seekTo(offset); @@ -697,7 +689,7 @@ pub const DwarfInfo = struct { pub fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !debug.LineInfo { var stream = io.fixedBufferStream(di.debug_line); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT_comp_dir); diff --git a/lib/std/dwarf_bits.zig b/lib/std/dwarf_bits.zig index bf9c97154c..99e268beb7 100644 --- a/lib/std/dwarf_bits.zig +++ b/lib/std/dwarf_bits.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index f2e138c3f3..98c59d4105 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/elf.zig b/lib/std/elf.zig index a28780fd03..cfb6b448c0 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -720,10 +720,10 @@ pub const Elf32_Rel = extern struct { r_offset: Elf32_Addr, r_info: Elf32_Word, - pub inline fn r_sym(self: @This()) u24 { + pub fn r_sym(self: @This()) callconv(.Inline) u24 { return @truncate(u24, self.r_info >> 8); } - pub inline fn r_type(self: @This()) u8 { + pub fn r_type(self: @This()) callconv(.Inline) u8 { return @truncate(u8, self.r_info & 0xff); } }; @@ -731,10 +731,10 @@ pub const Elf64_Rel = extern struct { r_offset: Elf64_Addr, r_info: Elf64_Xword, - pub inline fn r_sym(self: @This()) u32 { + pub fn r_sym(self: @This()) callconv(.Inline) u32 { return @truncate(u32, self.r_info >> 32); } - pub inline fn r_type(self: @This()) u32 { + pub fn r_type(self: @This()) callconv(.Inline) u32 { return @truncate(u32, self.r_info & 0xffffffff); } }; @@ -743,10 +743,10 @@ pub const Elf32_Rela = extern struct { r_info: Elf32_Word, r_addend: Elf32_Sword, - pub inline fn r_sym(self: @This()) u24 { + pub fn r_sym(self: @This()) callconv(.Inline) u24 { return @truncate(u24, self.r_info >> 8); } - pub inline fn r_type(self: @This()) u8 { + pub fn r_type(self: @This()) callconv(.Inline) u8 { return @truncate(u8, self.r_info & 0xff); } }; @@ -755,10 +755,10 @@ pub const Elf64_Rela = extern struct { r_info: Elf64_Xword, r_addend: Elf64_Sxword, - pub inline fn r_sym(self: @This()) u32 { + pub fn r_sym(self: @This()) callconv(.Inline) u32 { return @truncate(u32, self.r_info >> 32); } - pub inline fn r_type(self: @This()) u32 { + pub fn r_type(self: @This()) callconv(.Inline) u32 { return @truncate(u32, self.r_info & 0xffffffff); } }; diff --git a/lib/std/event.zig b/lib/std/event.zig index bba17a3388..cd4af07d64 100644 --- a/lib/std/event.zig +++ b/lib/std/event.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/batch.zig b/lib/std/event/batch.zig index 2ace2f7914..5368c5336d 100644 --- a/lib/std/event/batch.zig +++ b/lib/std/event/batch.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -98,15 +98,16 @@ pub fn Batch( /// This function is *not* thread-safe. It must be called from one thread at /// a time, however, it need not be the same thread. pub fn wait(self: *Self) CollectedResult { - for (self.jobs) |*job| if (job.frame) |f| { - job.result = if (async_ok) await f else nosuspend await f; - if (CollectedResult != void) { - job.result catch |err| { - self.collected_result = err; - }; - } - job.frame = null; - }; + for (self.jobs) |*job| + if (job.frame) |f| { + job.result = if (async_ok) await f else nosuspend await f; + if (CollectedResult != void) { + job.result catch |err| { + self.collected_result = err; + }; + } + job.frame = null; + }; return self.collected_result; } }; diff --git a/lib/std/event/channel.zig b/lib/std/event/channel.zig index b7c55360d7..2711488705 100644 --- a/lib/std/event/channel.zig +++ b/lib/std/event/channel.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/future.zig b/lib/std/event/future.zig index 40c7845d53..30a2e46ec5 100644 --- a/lib/std/event/future.zig +++ b/lib/std/event/future.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/group.zig b/lib/std/event/group.zig index e91adbbe8c..b052c15704 100644 --- a/lib/std/event/group.zig +++ b/lib/std/event/group.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/lock.zig b/lib/std/event/lock.zig index 6819e413d2..d48c6c1520 100644 --- a/lib/std/event/lock.zig +++ b/lib/std/event/lock.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -16,7 +16,7 @@ const Loop = std.event.Loop; /// Allows only one actor to hold the lock. /// TODO: make this API also work in blocking I/O mode. pub const Lock = struct { - mutex: std.Mutex = std.Mutex{}, + mutex: std.Thread.Mutex = std.Thread.Mutex{}, head: usize = UNLOCKED, const UNLOCKED = 0; diff --git a/lib/std/event/locked.zig b/lib/std/event/locked.zig index 9a53116fd6..c9303274a9 100644 --- a/lib/std/event/locked.zig +++ b/lib/std/event/locked.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index a000820348..912f99e961 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -29,7 +29,7 @@ pub const Loop = struct { fs_thread: *Thread, fs_queue: std.atomic.Queue(Request), fs_end_request: Request.Node, - fs_thread_wakeup: std.ResetEvent, + fs_thread_wakeup: std.Thread.ResetEvent, /// For resources that have the same lifetime as the `Loop`. /// This is only used by `Loop` for the thread pool and associated resources. @@ -164,9 +164,10 @@ pub const Loop = struct { .fs_end_request = .{ .data = .{ .msg = .end, .finish = .NoAction } }, .fs_queue = std.atomic.Queue(Request).init(), .fs_thread = undefined, - .fs_thread_wakeup = std.ResetEvent.init(), + .fs_thread_wakeup = undefined, .delay_queue = undefined, }; + try self.fs_thread_wakeup.init(); errdefer self.fs_thread_wakeup.deinit(); errdefer self.arena.deinit(); @@ -439,13 +440,11 @@ pub const Loop = struct { .overlapped = ResumeNode.overlapped_init, }, }; - var need_to_delete = false; + var need_to_delete = true; defer if (need_to_delete) self.linuxRemoveFd(fd); suspend { - if (self.linuxAddFd(fd, &resume_node.base, flags)) |_| { - need_to_delete = true; - } else |err| switch (err) { + self.linuxAddFd(fd, &resume_node.base, flags) catch |err| switch (err) { error.FileDescriptorNotRegistered => unreachable, error.OperationCausesCircularLoop => unreachable, error.FileDescriptorIncompatibleWithEpoll => unreachable, @@ -455,6 +454,7 @@ pub const Loop = struct { error.UserResourceLimitReached, error.Unexpected, => { + need_to_delete = false; // Fall back to a blocking poll(). Ideally this codepath is never hit, since // epoll should be just fine. But this is better than incorrect behavior. var poll_flags: i16 = 0; @@ -478,7 +478,7 @@ pub const Loop = struct { }; resume @frame(); }, - } + }; } } @@ -784,7 +784,7 @@ pub const Loop = struct { timer: std.time.Timer, waiters: Waiters, thread: *std.Thread, - event: std.AutoResetEvent, + event: std.Thread.AutoResetEvent, is_running: bool, /// Initialize the delay queue by spawning the timer thread @@ -795,9 +795,10 @@ pub const Loop = struct { .waiters = DelayQueue.Waiters{ .entries = std.atomic.Queue(anyframe).init(), }, - .thread = try std.Thread.spawn(self, DelayQueue.run), - .event = std.AutoResetEvent{}, + .event = std.Thread.AutoResetEvent{}, .is_running = true, + // Must be last so that it can read the other state, such as `is_running`. + .thread = try std.Thread.spawn(self, DelayQueue.run), }; } @@ -1261,7 +1262,7 @@ pub const Loop = struct { flags: u32, dest_addr: ?*const os.sockaddr, addrlen: os.socklen_t, - ) os.SendError!usize { + ) os.SendToError!usize { while (true) { return os.sendto(sockfd, buf, flags, dest_addr, addrlen) catch |err| switch (err) { error.WouldBlock => { diff --git a/lib/std/event/rwlock.zig b/lib/std/event/rwlock.zig index 3e3928d379..750131beda 100644 --- a/lib/std/event/rwlock.zig +++ b/lib/std/event/rwlock.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/rwlocked.zig b/lib/std/event/rwlocked.zig index 4fb25b59a1..0272ca39c7 100644 --- a/lib/std/event/rwlocked.zig +++ b/lib/std/event/rwlocked.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/event/wait_group.zig b/lib/std/event/wait_group.zig index c59e3b233d..0b83c18c74 100644 --- a/lib/std/event/wait_group.zig +++ b/lib/std/event/wait_group.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -30,7 +30,7 @@ pub fn WaitGroupGeneric(comptime counter_size: u16) type { return struct { counter: CounterType = 0, max_counter: CounterType = std.math.maxInt(CounterType), - mutex: std.Mutex = .{}, + mutex: std.Thread.Mutex = .{}, waiters: ?*Waiter = null, const Waiter = struct { next: ?*Waiter, diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 9a7d2209f5..b0771bba16 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -44,6 +44,8 @@ pub fn LinearFifo( count: usize, const Self = @This(); + pub const Reader = std.io.Reader(*Self, error{}, readFn); + pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite); // Type of Self argument for slice operations. // If buffer is inline (Static) then we need to ensure we haven't @@ -153,7 +155,7 @@ pub fn LinearFifo( var start = self.head + offset; if (start >= self.buf.len) { start -= self.buf.len; - return self.buf[start .. self.count - offset]; + return self.buf[start .. start + (self.count - offset)]; } else { const end = math.min(self.head + self.count, self.buf.len); return self.buf[start..end]; @@ -228,12 +230,7 @@ pub fn LinearFifo( return self.read(dest); } - pub fn reader(self: *Self) std.io.Reader(*Self, error{}, readFn) { - return .{ .context = self }; - } - - /// Deprecated: `use reader` - pub fn inStream(self: *Self) std.io.InStream(*Self, error{}, readFn) { + pub fn reader(self: *Self) Reader { return .{ .context = self }; } @@ -317,18 +314,13 @@ pub fn LinearFifo( } /// Same as `write` except it returns the number of bytes written, which is always the same - /// as `bytes.len`. The purpose of this function existing is to match `std.io.OutStream` API. + /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API. fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize { try self.write(bytes); return bytes.len; } - pub fn writer(self: *Self) std.io.Writer(*Self, error{OutOfMemory}, appendWrite) { - return .{ .context = self }; - } - - /// Deprecated: `use writer` - pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { + pub fn writer(self: *Self) Writer { return .{ .context = self }; } @@ -437,6 +429,8 @@ test "LinearFifo(u8, .Dynamic)" { fifo.writeAssumeCapacity("6<chars<11"); testing.expectEqualSlices(u8, "HELLO6<char", fifo.readableSlice(0)); testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(11)); + testing.expectEqualSlices(u8, "11", fifo.readableSlice(13)); + testing.expectEqualSlices(u8, "", fifo.readableSlice(15)); fifo.discard(11); testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(0)); fifo.discard(4); @@ -466,7 +460,7 @@ test "LinearFifo(u8, .Dynamic)" { fifo.shrink(0); { - try fifo.writer().print("{}, {}!", .{ "Hello", "World" }); + try fifo.writer().print("{s}, {s}!", .{ "Hello", "World" }); var result: [30]u8 = undefined; testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]); testing.expectEqual(@as(usize, 0), fifo.readableLength()); diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 485303518a..fca21000cf 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -12,6 +12,7 @@ const meta = std.meta; const builtin = @import("builtin"); const errol = @import("fmt/errol.zig"); const lossyCast = std.math.lossyCast; +const expectFmt = std.testing.expectFmt; pub const default_max_depth = 3; @@ -68,6 +69,7 @@ pub const FormatOptions = struct { /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. /// - `*`: output the address of the value instead of the value itself. +/// - `any`: output a value of any type using its default format /// /// If a formatted user type contains a function of the type /// ``` @@ -367,6 +369,51 @@ pub fn format( } } +pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + const T = @TypeOf(value); + + switch (@typeInfo(T)) { + .Pointer => |info| { + try writer.writeAll(@typeName(info.child) ++ "@"); + if (info.size == .Slice) + try formatInt(@ptrToInt(value.ptr), 16, false, FormatOptions{}, writer) + else + try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); + return; + }, + .Optional => |info| { + if (@typeInfo(info.child) == .Pointer) { + try writer.writeAll(@typeName(info.child) ++ "@"); + try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); + return; + } + }, + else => {}, + } + + @compileError("Cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); +} + +// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 +const ANY = "any"; + +fn defaultSpec(comptime T: type) [:0]const u8 { + switch (@typeInfo(T)) { + .Array => |_| return ANY, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => switch (@typeInfo(ptr_info.child)) { + .Array => |_| return "*", + else => {}, + }, + .Many, .C => return "*", + .Slice => return ANY, + }, + .Optional => |info| return defaultSpec(info.child), + else => {}, + } + return ""; +} + pub fn formatType( value: anytype, comptime fmt: []const u8, @@ -374,21 +421,19 @@ pub fn formatType( writer: anytype, max_depth: usize, ) @TypeOf(writer).Error!void { - if (comptime std.mem.eql(u8, fmt, "*")) { - try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value)))); - try writer.writeAll("@"); - try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); - return; + const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY)) defaultSpec(@TypeOf(value)) else fmt; + if (comptime std.mem.eql(u8, actual_fmt, "*")) { + return formatAddress(value, options, writer); } const T = @TypeOf(value); if (comptime std.meta.trait.hasFn("format")(T)) { - return try value.format(fmt, options, writer); + return try value.format(actual_fmt, options, writer); } switch (@typeInfo(T)) { .ComptimeInt, .Int, .ComptimeFloat, .Float => { - return formatValue(value, fmt, options, writer); + return formatValue(value, actual_fmt, options, writer); }, .Void => { return formatBuf("void", options, writer); @@ -398,16 +443,16 @@ pub fn formatType( }, .Optional => { if (value) |payload| { - return formatType(payload, fmt, options, writer, max_depth); + return formatType(payload, actual_fmt, options, writer, max_depth); } else { return formatBuf("null", options, writer); } }, .ErrorUnion => { if (value) |payload| { - return formatType(payload, fmt, options, writer, max_depth); + return formatType(payload, actual_fmt, options, writer, max_depth); } else |err| { - return formatType(err, fmt, options, writer, max_depth); + return formatType(err, actual_fmt, options, writer, max_depth); } }, .ErrorSet => { @@ -433,22 +478,21 @@ pub fn formatType( } try writer.writeAll("("); - try formatType(@enumToInt(value), fmt, options, writer, max_depth); + try formatType(@enumToInt(value), actual_fmt, options, writer, max_depth); try writer.writeAll(")"); }, - .Union => { + .Union => |info| { try writer.writeAll(@typeName(T)); if (max_depth == 0) { return writer.writeAll("{ ... }"); } - const info = @typeInfo(T).Union; if (info.tag_type) |UnionTagType| { try writer.writeAll("{ ."); try writer.writeAll(@tagName(@as(UnionTagType, value))); try writer.writeAll(" = "); inline for (info.fields) |u_field| { if (value == @field(UnionTagType, u_field.name)) { - try formatType(@field(value, u_field.name), fmt, options, writer, max_depth - 1); + try formatType(@field(value, u_field.name), ANY, options, writer, max_depth - 1); } } try writer.writeAll(" }"); @@ -456,13 +500,13 @@ pub fn formatType( try format(writer, "@{x}", .{@ptrToInt(&value)}); } }, - .Struct => |StructT| { + .Struct => |info| { try writer.writeAll(@typeName(T)); if (max_depth == 0) { return writer.writeAll("{ ... }"); } try writer.writeAll("{"); - inline for (StructT.fields) |f, i| { + inline for (info.fields) |f, i| { if (i == 0) { try writer.writeAll(" ."); } else { @@ -470,77 +514,99 @@ pub fn formatType( } try writer.writeAll(f.name); try writer.writeAll(" = "); - try formatType(@field(value, f.name), fmt, options, writer, max_depth - 1); + try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); } try writer.writeAll(" }"); }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { .Array => |info| { + if (actual_fmt.len == 0) + @compileError("cannot format array ref without a specifier (i.e. {s} or {*})"); if (info.child == u8) { - return formatText(value, fmt, options, writer); + if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) { + return formatText(value, actual_fmt, options, writer); + } } - return format(writer, "{}@{x}", .{ @typeName(@typeInfo(T).Pointer.child), @ptrToInt(value) }); + @compileError("Unknown format string: '" ++ actual_fmt ++ "'"); }, .Enum, .Union, .Struct => { - return formatType(value.*, fmt, options, writer, max_depth); + return formatType(value.*, actual_fmt, options, writer, max_depth); }, - else => return format(writer, "{}@{x}", .{ @typeName(@typeInfo(T).Pointer.child), @ptrToInt(value) }), + else => return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value) }), }, .Many, .C => { + if (actual_fmt.len == 0) + @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); if (ptr_info.sentinel) |sentinel| { - return formatType(mem.span(value), fmt, options, writer, max_depth); + return formatType(mem.span(value), actual_fmt, options, writer, max_depth); } if (ptr_info.child == u8) { - if (fmt.len > 0 and fmt[0] == 's') { - return formatText(mem.span(value), fmt, options, writer); + if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) { + return formatText(mem.span(value), actual_fmt, options, writer); } } - return format(writer, "{}@{x}", .{ @typeName(@typeInfo(T).Pointer.child), @ptrToInt(value) }); + @compileError("Unknown format string: '" ++ actual_fmt ++ "'"); }, .Slice => { - if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - return formatText(value, fmt, options, writer); + if (actual_fmt.len == 0) + @compileError("cannot format slice without a specifier (i.e. {s} or {any})"); + if (max_depth == 0) { + return writer.writeAll("{ ... }"); } if (ptr_info.child == u8) { - return formatText(value, fmt, options, writer); + if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) { + return formatText(value, actual_fmt, options, writer); + } + } + try writer.writeAll("{ "); + for (value) |elem, i| { + try formatType(elem, actual_fmt, options, writer, max_depth - 1); + if (i != value.len - 1) { + try writer.writeAll(", "); + } } - return format(writer, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); + try writer.writeAll(" }"); }, }, .Array => |info| { - const Slice = @Type(builtin.TypeInfo{ - .Pointer = .{ - .size = .Slice, - .is_const = true, - .is_volatile = false, - .is_allowzero = false, - .alignment = @alignOf(info.child), - .child = info.child, - .sentinel = null, - }, - }); - return formatType(@as(Slice, &value), fmt, options, writer, max_depth); + if (actual_fmt.len == 0) + @compileError("cannot format array without a specifier (i.e. {s} or {any})"); + if (max_depth == 0) { + return writer.writeAll("{ ... }"); + } + if (info.child == u8) { + if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) { + return formatText(&value, actual_fmt, options, writer); + } + } + try writer.writeAll("{ "); + for (value) |elem, i| { + try formatType(elem, actual_fmt, options, writer, max_depth - 1); + if (i < value.len - 1) { + try writer.writeAll(", "); + } + } + try writer.writeAll(" }"); }, - .Vector => { - const len = @typeInfo(T).Vector.len; + .Vector => |info| { try writer.writeAll("{ "); var i: usize = 0; - while (i < len) : (i += 1) { - try formatValue(value[i], fmt, options, writer); - if (i < len - 1) { + while (i < info.len) : (i += 1) { + try formatValue(value[i], actual_fmt, options, writer); + if (i < info.len - 1) { try writer.writeAll(", "); } } try writer.writeAll(" }"); }, .Fn => { - return format(writer, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); + return format(writer, "{s}@{x}", .{ @typeName(T), @ptrToInt(value) }); }, .Type => return formatBuf(@typeName(value), options, writer), .EnumLiteral => { const buffer = [_]u8{'.'} ++ @tagName(value); - return formatType(buffer, fmt, options, writer, max_depth); + return formatBuf(buffer, options, writer); }, .Null => return formatBuf("null", options, writer), else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), @@ -580,8 +646,7 @@ pub fn formatIntValue( const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); - } else - value; + } else value; if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { radix = 10; @@ -592,13 +657,6 @@ pub fn formatIntValue( } else { @compileError("Cannot print integer that is larger than 8 bits as a ascii"); } - } else if (comptime std.mem.eql(u8, fmt, "Z")) { - if (@typeInfo(@TypeOf(int_value)).Int.bits <= 8) { - const c: u8 = int_value; - return formatZigEscapes(@as(*const [1]u8, &c), options, writer); - } else { - @compileError("Cannot escape character with more than 8 bits"); - } } else if (comptime std.mem.eql(u8, fmt, "u")) { if (@typeInfo(@TypeOf(int_value)).Int.bits <= 21) { return formatUnicodeCodepoint(@as(u21, int_value), options, writer); @@ -618,7 +676,7 @@ pub fn formatIntValue( radix = 8; uppercase = false; } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); + @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); } return formatInt(int_value, radix, uppercase, options, writer); @@ -645,7 +703,7 @@ fn formatFloatValue( else => |e| return e, }; } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); + @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); } return formatBuf(buf_stream.getWritten(), options, writer); @@ -657,7 +715,7 @@ pub fn formatText( options: FormatOptions, writer: anytype, ) !void { - if (comptime std.mem.eql(u8, fmt, "s") or (fmt.len == 0)) { + if (comptime std.mem.eql(u8, fmt, "s")) { return formatBuf(bytes, options, writer); } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { for (bytes) |c| { @@ -674,12 +732,8 @@ pub fn formatText( } } return; - } else if (comptime std.mem.eql(u8, fmt, "z")) { - return formatZigIdentifier(bytes, options, writer); - } else if (comptime std.mem.eql(u8, fmt, "Z")) { - return formatZigEscapes(bytes, options, writer); } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); + @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); } } @@ -742,52 +796,6 @@ pub fn formatBuf( } } -/// Print the string as a Zig identifier escaping it with @"" syntax if needed. -pub fn formatZigIdentifier( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - if (isValidZigIdentifier(bytes)) { - return writer.writeAll(bytes); - } - try writer.writeAll("@\""); - try formatZigEscapes(bytes, options, writer); - try writer.writeByte('"'); -} - -fn isValidZigIdentifier(bytes: []const u8) bool { - for (bytes) |c, i| { - switch (c) { - '_', 'a'...'z', 'A'...'Z' => {}, - '0'...'9' => if (i == 0) return false, - else => return false, - } - } - return std.zig.Token.getKeyword(bytes) == null; -} - -pub fn formatZigEscapes( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - for (bytes) |byte| switch (byte) { - '\n' => try writer.writeAll("\\n"), - '\r' => try writer.writeAll("\\r"), - '\t' => try writer.writeAll("\\t"), - '\\' => try writer.writeAll("\\\\"), - '"' => try writer.writeAll("\\\""), - '\'' => try writer.writeAll("\\'"), - ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), - // Use hex escapes for rest any unprintable characters. - else => { - try writer.writeAll("\\x"); - try formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); - }, - }; -} - /// Print a float in scientific notation to the specified precision. Null uses full precision. /// It should be the case that every full precision, printed value can be re-parsed back to the /// same type unambiguously. @@ -1078,8 +1086,7 @@ pub fn formatInt( const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); - } else - value; + } else value; const value_info = @typeInfo(@TypeOf(int_value)).Int; @@ -1125,6 +1132,103 @@ pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, op return fbs.pos; } +/// Formats a number of nanoseconds according to its magnitude: +/// +/// - #ns +/// - [#y][#w][#d][#h][#m]#[.###][u|m]s +pub fn formatDuration(ns: u64, writer: anytype) !void { + var ns_remaining = ns; + inline for (.{ + .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, + .{ .ns = std.time.ns_per_week, .sep = 'w' }, + .{ .ns = std.time.ns_per_day, .sep = 'd' }, + .{ .ns = std.time.ns_per_hour, .sep = 'h' }, + .{ .ns = std.time.ns_per_min, .sep = 'm' }, + }) |unit| { + if (ns_remaining >= unit.ns) { + const units = ns_remaining / unit.ns; + try formatInt(units, 10, false, .{}, writer); + try writer.writeByte(unit.sep); + ns_remaining -= units * unit.ns; + if (ns_remaining == 0) return; + } + } + + inline for (.{ + .{ .ns = std.time.ns_per_s, .sep = "s" }, + .{ .ns = std.time.ns_per_ms, .sep = "ms" }, + .{ .ns = std.time.ns_per_us, .sep = "us" }, + }) |unit| { + const kunits = ns_remaining * 1000 / unit.ns; + if (kunits >= 1000) { + try formatInt(kunits / 1000, 10, false, .{}, writer); + if (kunits > 1000) { + // Write up to 3 decimal places + const frac = kunits % 1000; + var buf = [_]u8{ '.', 0, 0, 0 }; + _ = formatIntBuf(buf[1..], frac, 10, false, .{ .fill = '0', .width = 3 }); + var end: usize = 4; + while (end > 1) : (end -= 1) { + if (buf[end - 1] != '0') break; + } + try writer.writeAll(buf[0..end]); + } + try writer.writeAll(unit.sep); + return; + } + } + + try formatInt(ns, 10, false, .{}, writer); + try writer.writeAll("ns"); + return; +} + +test "formatDuration" { + var buf: [24]u8 = undefined; + inline for (.{ + .{ .s = "0ns", .d = 0 }, + .{ .s = "1ns", .d = 1 }, + .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, + .{ .s = "1us", .d = std.time.ns_per_us }, + .{ .s = "1.45us", .d = 1450 }, + .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, + .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, + .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, + .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, + .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, + .{ .s = "1s", .d = std.time.ns_per_s }, + .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, + .{ .s = "1m", .d = std.time.ns_per_min }, + .{ .s = "1h", .d = std.time.ns_per_hour }, + .{ .s = "1d", .d = std.time.ns_per_day }, + .{ .s = "1w", .d = std.time.ns_per_week }, + .{ .s = "1y", .d = 365 * std.time.ns_per_day }, + .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d + .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, + .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, + .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, + .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, + .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, + }) |tc| { + const slice = try bufPrint(&buf, "{}", .{duration(tc.d)}); + std.testing.expectEqualStrings(tc.s, slice); + } +} + +/// Wraps a `u64` to format with `formatDuration`. +const Duration = struct { + ns: u64, + + pub fn format(self: Duration, comptime fmt: []const u8, options: FormatOptions, writer: anytype) !void { + return formatDuration(self.ns, writer); + } +}; + +/// Formats a number of nanoseconds according to its magnitude. See `formatDuration`. +pub fn duration(ns: u64) Duration { + return Duration{ .ns = ns }; +} + pub const ParseIntError = error{ /// The result cannot fit in the type specified Overflow, @@ -1133,6 +1237,32 @@ pub const ParseIntError = error{ InvalidCharacter, }; +/// Creates a Formatter type from a format function. Wrapping data in Formatter(func) causes +/// the data to be formatted using the given function `func`. `func` must be of the following +/// form: +/// +/// fn formatExample( +/// data: T, +/// comptime fmt: []const u8, +/// options: std.fmt.FormatOptions, +/// writer: anytype, +/// ) !void; +/// +pub fn Formatter(comptime format_fn: anytype) type { + const Data = @typeInfo(@TypeOf(format_fn)).Fn.args[0].arg_type.?; + return struct { + data: Data, + pub fn format( + self: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + try format_fn(self.data, fmt, options, writer); + } + }; +} + /// Parses the string `buf` as signed or unsigned representation in the /// specified radix of an integral value of type `T`. /// @@ -1414,91 +1544,91 @@ test "parse unsigned comptime" { } test "escaped braces" { - try testFmt("escaped: {{foo}}\n", "escaped: {{{{foo}}}}\n", .{}); - try testFmt("escaped: {foo}\n", "escaped: {{foo}}\n", .{}); + try expectFmt("escaped: {{foo}}\n", "escaped: {{{{foo}}}}\n", .{}); + try expectFmt("escaped: {foo}\n", "escaped: {{foo}}\n", .{}); } test "optional" { { const value: ?i32 = 1234; - try testFmt("optional: 1234\n", "optional: {}\n", .{value}); + try expectFmt("optional: 1234\n", "optional: {}\n", .{value}); } { const value: ?i32 = null; - try testFmt("optional: null\n", "optional: {}\n", .{value}); + try expectFmt("optional: null\n", "optional: {}\n", .{value}); } { const value = @intToPtr(?*i32, 0xf000d000); - try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); + try expectFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); } } test "error" { { const value: anyerror!i32 = 1234; - try testFmt("error union: 1234\n", "error union: {}\n", .{value}); + try expectFmt("error union: 1234\n", "error union: {}\n", .{value}); } { const value: anyerror!i32 = error.InvalidChar; - try testFmt("error union: error.InvalidChar\n", "error union: {}\n", .{value}); + try expectFmt("error union: error.InvalidChar\n", "error union: {}\n", .{value}); } } test "int.small" { { const value: u3 = 0b101; - try testFmt("u3: 5\n", "u3: {}\n", .{value}); + try expectFmt("u3: 5\n", "u3: {}\n", .{value}); } } test "int.specifier" { { const value: u8 = 'a'; - try testFmt("u8: a\n", "u8: {c}\n", .{value}); + try expectFmt("u8: a\n", "u8: {c}\n", .{value}); } { const value: u8 = 0b1100; - try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value}); + try expectFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value}); } { const value: u16 = 0o1234; - try testFmt("u16: 0o1234\n", "u16: 0o{o}\n", .{value}); + try expectFmt("u16: 0o1234\n", "u16: 0o{o}\n", .{value}); } { const value: u8 = 'a'; - try testFmt("UTF-8: a\n", "UTF-8: {u}\n", .{value}); + try expectFmt("UTF-8: a\n", "UTF-8: {u}\n", .{value}); } { const value: u21 = 0x1F310; - try testFmt("UTF-8: 🌐\n", "UTF-8: {u}\n", .{value}); + try expectFmt("UTF-8: 🌐\n", "UTF-8: {u}\n", .{value}); } { const value: u21 = 0xD800; - try testFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); + try expectFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); } { const value: u21 = 0x110001; - try testFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); + try expectFmt("UTF-8: �\n", "UTF-8: {u}\n", .{value}); } } test "int.padded" { - try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); - try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); - try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); - try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); - try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); - try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); - try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); - try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); - try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); - try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); - try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); - try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); - - try testFmt("UTF-8: 'ü '", "UTF-8: '{u:<4}'", .{'ü'}); - try testFmt("UTF-8: ' ü'", "UTF-8: '{u:>4}'", .{'ü'}); - try testFmt("UTF-8: ' ü '", "UTF-8: '{u:^4}'", .{'ü'}); + try expectFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); + try expectFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); + try expectFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); + try expectFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); + try expectFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); + try expectFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); + try expectFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); + try expectFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); + try expectFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); + try expectFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); + try expectFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); + try expectFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); + + try expectFmt("UTF-8: 'ü '", "UTF-8: '{u:<4}'", .{'ü'}); + try expectFmt("UTF-8: ' ü'", "UTF-8: '{u:>4}'", .{'ü'}); + try expectFmt("UTF-8: ' ü '", "UTF-8: '{u:^4}'", .{'ü'}); } test "buffer" { @@ -1521,11 +1651,12 @@ test "buffer" { test "array" { { const value: [3]u8 = "abc".*; - try testFmt("array: abc\n", "array: {}\n", .{value}); - try testFmt("array: abc\n", "array: {}\n", .{&value}); + try expectFmt("array: abc\n", "array: {s}\n", .{value}); + try expectFmt("array: abc\n", "array: {s}\n", .{&value}); + try expectFmt("array: { 97, 98, 99 }\n", "array: {d}\n", .{value}); var buf: [100]u8 = undefined; - try testFmt( + try expectFmt( try bufPrint(buf[0..], "array: [3]u8@{x}\n", .{@ptrToInt(&value)}), "array: {*}\n", .{&value}, @@ -1536,62 +1667,60 @@ test "array" { test "slice" { { const value: []const u8 = "abc"; - try testFmt("slice: abc\n", "slice: {}\n", .{value}); + try expectFmt("slice: abc\n", "slice: {s}\n", .{value}); } { var runtime_zero: usize = 0; const value = @intToPtr([*]align(1) const []const u8, 0xdeadbeef)[runtime_zero..runtime_zero]; - try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); + try expectFmt("slice: []const u8@deadbeef\n", "slice: {*}\n", .{value}); } { const null_term_slice: [:0]const u8 = "\x00hello\x00"; - try testFmt("buf: \x00hello\x00\n", "buf: {s}\n", .{null_term_slice}); + try expectFmt("buf: \x00hello\x00\n", "buf: {s}\n", .{null_term_slice}); } - try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); - try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); -} + try expectFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); + try expectFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); -test "escape non-printable" { - try testFmt("abc", "{e}", .{"abc"}); - try testFmt("ab\\xffc", "{e}", .{"ab\xffc"}); - try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); + { + var int_slice = [_]u32{ 1, 4096, 391891, 1111111111 }; + var runtime_zero: usize = 0; + try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {any}", .{int_slice[runtime_zero..]}); + try expectFmt("int: { 1, 4096, 391891, 1111111111 }", "int: {d}", .{int_slice[runtime_zero..]}); + try expectFmt("int: { 1, 1000, 5fad3, 423a35c7 }", "int: {x}", .{int_slice[runtime_zero..]}); + try expectFmt("int: { 00001, 01000, 5fad3, 423a35c7 }", "int: {x:0>5}", .{int_slice[runtime_zero..]}); + } } -test "escape invalid identifiers" { - try testFmt("@\"while\"", "{z}", .{"while"}); - try testFmt("hello", "{z}", .{"hello"}); - try testFmt("@\"11\\\"23\"", "{z}", .{"11\"23"}); - try testFmt("@\"11\\x0f23\"", "{z}", .{"11\x0F23"}); - try testFmt("\\x0f", "{Z}", .{0x0f}); - try testFmt( - \\" \\ hi \x07 \x11 \" derp \'" - , "\"{Z}\"", .{" \\ hi \x07 \x11 \" derp '"}); +test "escape non-printable" { + try expectFmt("abc", "{e}", .{"abc"}); + try expectFmt("ab\\xffc", "{e}", .{"ab\xffc"}); + try expectFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); } test "pointer" { { const value = @intToPtr(*align(1) i32, 0xdeadbeef); - try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", .{value}); - try testFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", .{value}); + try expectFmt("pointer: i32@deadbeef\n", "pointer: {}\n", .{value}); + try expectFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", .{value}); } { const value = @intToPtr(fn () void, 0xdeadbeef); - try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); + try expectFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); } { const value = @intToPtr(fn () void, 0xdeadbeef); - try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); + try expectFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); } } test "cstr" { - try testFmt( + try expectFmt( "cstr: Test C\n", "cstr: {s}\n", .{@ptrCast([*c]const u8, "Test C")}, ); - try testFmt( + try expectFmt( "cstr: Test C\n", "cstr: {s:10}\n", .{@ptrCast([*c]const u8, "Test C")}, @@ -1599,8 +1728,8 @@ test "cstr" { } test "filesize" { - try testFmt("file size: 63MiB\n", "file size: {Bi}\n", .{@as(usize, 63 * 1024 * 1024)}); - try testFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{@as(usize, 63 * 1024 * 1024)}); + try expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{@as(usize, 63 * 1024 * 1024)}); + try expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{@as(usize, 63 * 1024 * 1024)}); } test "struct" { @@ -1609,8 +1738,8 @@ test "struct" { field: u8, }; const value = Struct{ .field = 42 }; - try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{value}); - try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); + try expectFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{value}); + try expectFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); } { const Struct = struct { @@ -1618,7 +1747,7 @@ test "struct" { b: u1, }; const value = Struct{ .a = 0, .b = 1 }; - try testFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); + try expectFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); } } @@ -1628,13 +1757,13 @@ test "enum" { Two, }; const value = Enum.Two; - try testFmt("enum: Enum.Two\n", "enum: {}\n", .{value}); - try testFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); - try testFmt("enum: Enum.One\n", "enum: {x}\n", .{Enum.One}); - try testFmt("enum: Enum.Two\n", "enum: {X}\n", .{Enum.Two}); + try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{value}); + try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); + try expectFmt("enum: Enum.One\n", "enum: {x}\n", .{Enum.One}); + try expectFmt("enum: Enum.Two\n", "enum: {X}\n", .{Enum.Two}); // test very large enum to verify ct branch quota is large enough - try testFmt("enum: Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); + try expectFmt("enum: Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); } test "non-exhaustive enum" { @@ -1643,78 +1772,78 @@ test "non-exhaustive enum" { Two = 0xbeef, _, }; - try testFmt("enum: Enum.One\n", "enum: {}\n", .{Enum.One}); - try testFmt("enum: Enum.Two\n", "enum: {}\n", .{Enum.Two}); - try testFmt("enum: Enum(4660)\n", "enum: {}\n", .{@intToEnum(Enum, 0x1234)}); - try testFmt("enum: Enum.One\n", "enum: {x}\n", .{Enum.One}); - try testFmt("enum: Enum.Two\n", "enum: {x}\n", .{Enum.Two}); - try testFmt("enum: Enum.Two\n", "enum: {X}\n", .{Enum.Two}); - try testFmt("enum: Enum(1234)\n", "enum: {x}\n", .{@intToEnum(Enum, 0x1234)}); + try expectFmt("enum: Enum.One\n", "enum: {}\n", .{Enum.One}); + try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{Enum.Two}); + try expectFmt("enum: Enum(4660)\n", "enum: {}\n", .{@intToEnum(Enum, 0x1234)}); + try expectFmt("enum: Enum.One\n", "enum: {x}\n", .{Enum.One}); + try expectFmt("enum: Enum.Two\n", "enum: {x}\n", .{Enum.Two}); + try expectFmt("enum: Enum.Two\n", "enum: {X}\n", .{Enum.Two}); + try expectFmt("enum: Enum(1234)\n", "enum: {x}\n", .{@intToEnum(Enum, 0x1234)}); } test "float.scientific" { - try testFmt("f32: 1.34000003e+00", "f32: {e}", .{@as(f32, 1.34)}); - try testFmt("f32: 1.23400001e+01", "f32: {e}", .{@as(f32, 12.34)}); - try testFmt("f64: -1.234e+11", "f64: {e}", .{@as(f64, -12.34e10)}); - try testFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)}); + try expectFmt("f32: 1.34000003e+00", "f32: {e}", .{@as(f32, 1.34)}); + try expectFmt("f32: 1.23400001e+01", "f32: {e}", .{@as(f32, 12.34)}); + try expectFmt("f64: -1.234e+11", "f64: {e}", .{@as(f64, -12.34e10)}); + try expectFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)}); } test "float.scientific.precision" { - try testFmt("f64: 1.40971e-42", "f64: {e:.5}", .{@as(f64, 1.409706e-42)}); - try testFmt("f64: 1.00000e-09", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 814313563)))}); - try testFmt("f64: 7.81250e-03", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1006632960)))}); + try expectFmt("f64: 1.40971e-42", "f64: {e:.5}", .{@as(f64, 1.409706e-42)}); + try expectFmt("f64: 1.00000e-09", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 814313563)))}); + try expectFmt("f64: 7.81250e-03", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1006632960)))}); // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. - try testFmt("f64: 1.00001e+05", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1203982400)))}); + try expectFmt("f64: 1.00001e+05", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1203982400)))}); } test "float.special" { - try testFmt("f64: nan", "f64: {}", .{math.nan_f64}); + try expectFmt("f64: nan", "f64: {}", .{math.nan_f64}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan if (builtin.arch != builtin.Arch.arm) { - try testFmt("f64: -nan", "f64: {}", .{-math.nan_f64}); + try expectFmt("f64: -nan", "f64: {}", .{-math.nan_f64}); } - try testFmt("f64: inf", "f64: {}", .{math.inf_f64}); - try testFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); + try expectFmt("f64: inf", "f64: {}", .{math.inf_f64}); + try expectFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); } test "float.decimal" { - try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); - try testFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); - try testFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)}); - try testFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)}); + try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); + try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); + try expectFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)}); + try expectFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)}); // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). // -11.12339... is rounded back up to -11.1234 - try testFmt("f32: -11.1234", "f32: {d:.4}", .{@as(f32, -11.1234)}); - try testFmt("f32: 91.12345", "f32: {d:.5}", .{@as(f32, 91.12345)}); - try testFmt("f64: 91.1234567890", "f64: {d:.10}", .{@as(f64, 91.12345678901235)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 0.0)}); - try testFmt("f64: 6", "f64: {d:.0}", .{@as(f64, 5.700)}); - try testFmt("f64: 10.0", "f64: {d:.1}", .{@as(f64, 9.999)}); - try testFmt("f64: 1.000", "f64: {d:.3}", .{@as(f64, 1.0)}); - try testFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)}); + try expectFmt("f32: -11.1234", "f32: {d:.4}", .{@as(f32, -11.1234)}); + try expectFmt("f32: 91.12345", "f32: {d:.5}", .{@as(f32, 91.12345)}); + try expectFmt("f64: 91.1234567890", "f64: {d:.10}", .{@as(f64, 91.12345678901235)}); + try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 0.0)}); + try expectFmt("f64: 6", "f64: {d:.0}", .{@as(f64, 5.700)}); + try expectFmt("f64: 10.0", "f64: {d:.1}", .{@as(f64, 9.999)}); + try expectFmt("f64: 1.000", "f64: {d:.3}", .{@as(f64, 1.0)}); + try expectFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)}); + try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)}); + try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)}); } test "float.libc.sanity" { - try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 916964781)))}); - try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 925353389)))}); - try testFmt("f64: 0.10000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1036831278)))}); - try testFmt("f64: 1.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1065353133)))}); - try testFmt("f64: 10.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1092616192)))}); + try expectFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 916964781)))}); + try expectFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 925353389)))}); + try expectFmt("f64: 0.10000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1036831278)))}); + try expectFmt("f64: 1.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1065353133)))}); + try expectFmt("f64: 10.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1092616192)))}); // libc differences // // This is 0.015625 exactly according to gdb. We thus round down, // however glibc rounds up for some reason. This occurs for all // floats of the form x.yyyy25 on a precision point. - try testFmt("f64: 0.01563", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1015021568)))}); + try expectFmt("f64: 0.01563", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1015021568)))}); // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 // also rounds to 630 so I'm inclined to believe libc is not // optimal here. - try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1518338049)))}); + try expectFmt("f64: 18014400656965630.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1518338049)))}); } test "custom" { @@ -1744,12 +1873,12 @@ test "custom" { .x = 10.2, .y = 2.22, }; - try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{&value}); - try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{&value}); + try expectFmt("point: (10.200,2.220)\n", "point: {}\n", .{&value}); + try expectFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{&value}); // same thing but not passing a pointer - try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{value}); - try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value}); + try expectFmt("point: (10.200,2.220)\n", "point: {}\n", .{value}); + try expectFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value}); } test "struct" { @@ -1763,7 +1892,7 @@ test "struct" { .b = error.Unused, }; - try testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); + try expectFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); } test "union" { @@ -1786,7 +1915,7 @@ test "union" { const uu_inst = UU{ .int = 456 }; const eu_inst = EU{ .float = 321.123 }; - try testFmt("TU{ .int = 123 }", "{}", .{tu_inst}); + try expectFmt("TU{ .int = 123 }", "{}", .{tu_inst}); var buf: [100]u8 = undefined; const uu_result = try bufPrint(buf[0..], "{}", .{uu_inst}); @@ -1805,7 +1934,7 @@ test "enum" { const inst = E.Two; - try testFmt("E.Two", "{}", .{inst}); + try expectFmt("E.Two", "{}", .{inst}); } test "struct.self-referential" { @@ -1819,7 +1948,7 @@ test "struct.self-referential" { }; inst.a = &inst; - try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", .{inst}); + try expectFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", .{inst}); } test "struct.zero-size" { @@ -1834,53 +1963,51 @@ test "struct.zero-size" { const a = A{}; const b = B{ .a = a, .c = 0 }; - try testFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b}); + try expectFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b}); } test "bytes.hex" { const some_bytes = "\xCA\xFE\xBA\xBE"; - try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); - try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); + try expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); + try expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); //Test Slices - try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); - try testFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); + try expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); + try expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); const bytes_with_zeros = "\x00\x0E\xBA\xBE"; - try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); -} - -fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { - var buf: [100]u8 = undefined; - const result = try bufPrint(buf[0..], template, args); - if (mem.eql(u8, result, expected)) return; - - std.debug.warn("\n====== expected this output: =========\n", .{}); - std.debug.warn("{}", .{expected}); - std.debug.warn("\n======== instead found this: =========\n", .{}); - std.debug.warn("{}", .{result}); - std.debug.warn("\n======================================\n", .{}); - return error.TestFailed; + try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); } pub const trim = @compileError("deprecated; use std.mem.trim with std.ascii.spaces instead"); pub const isWhiteSpace = @compileError("deprecated; use std.ascii.isSpace instead"); -pub fn hexToBytes(out: []u8, input: []const u8) !void { - if (out.len * 2 < input.len) +/// Decodes the sequence of bytes represented by the specified string of +/// hexadecimal characters. +/// Returns a slice of the output buffer containing the decoded bytes. +pub fn hexToBytes(out: []u8, input: []const u8) ![]u8 { + // Expect 0 or n pairs of hexadecimal digits. + if (input.len & 1 != 0) return error.InvalidLength; + if (out.len * 2 < input.len) + return error.NoSpaceLeft; var in_i: usize = 0; - while (in_i != input.len) : (in_i += 2) { + while (in_i < input.len) : (in_i += 2) { const hi = try charToDigit(input[in_i], 16); const lo = try charToDigit(input[in_i + 1], 16); out[in_i / 2] = (hi << 4) | lo; } + + return out[0 .. in_i / 2]; } test "hexToBytes" { - const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; - var pb: [32]u8 = undefined; - try hexToBytes(pb[0..], test_hex_str); - try testFmt(test_hex_str, "{X}", .{pb}); + var buf: [32]u8 = undefined; + try expectFmt("90" ** 32, "{X}", .{try hexToBytes(&buf, "90" ** 32)}); + try expectFmt("ABCD", "{X}", .{try hexToBytes(&buf, "ABCD")}); + try expectFmt("", "{X}", .{try hexToBytes(&buf, "")}); + std.testing.expectError(error.InvalidCharacter, hexToBytes(&buf, "012Z")); + std.testing.expectError(error.InvalidLength, hexToBytes(&buf, "AAA")); + std.testing.expectError(error.NoSpaceLeft, hexToBytes(buf[0..1], "ABAB")); } test "formatIntValue with comptime_int" { @@ -1900,8 +2027,8 @@ test "formatFloatValue with comptime_float" { try formatFloatValue(value, "", FormatOptions{}, fbs.writer()); std.testing.expect(mem.eql(u8, fbs.getWritten(), "1.0e+00")); - try testFmt("1.0e+00", "{}", .{value}); - try testFmt("1.0e+00", "{}", .{1.0}); + try expectFmt("1.0e+00", "{}", .{value}); + try expectFmt("1.0e+00", "{}", .{1.0}); } test "formatType max_depth" { @@ -1970,19 +2097,19 @@ test "formatType max_depth" { } test "positional" { - try testFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); - try testFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); - try testFmt("0 0", "{0} {0}", .{@as(usize, 0)}); - try testFmt("0 1", "{} {1}", .{ @as(usize, 0), @as(usize, 1) }); - try testFmt("1 0 0 1", "{1} {} {0} {}", .{ @as(usize, 0), @as(usize, 1) }); + try expectFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); + try expectFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); + try expectFmt("0 0", "{0} {0}", .{@as(usize, 0)}); + try expectFmt("0 1", "{} {1}", .{ @as(usize, 0), @as(usize, 1) }); + try expectFmt("1 0 0 1", "{1} {} {0} {}", .{ @as(usize, 0), @as(usize, 1) }); } test "positional with specifier" { - try testFmt("10.0", "{0d:.1}", .{@as(f64, 9.999)}); + try expectFmt("10.0", "{0d:.1}", .{@as(f64, 9.999)}); } test "positional/alignment/width/precision" { - try testFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)}); + try expectFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)}); } test "vector" { @@ -2003,76 +2130,76 @@ test "vector" { const vi64: std.meta.Vector(4, i64) = [_]i64{ -2, -1, 0, 1 }; const vu64: std.meta.Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 }; - try testFmt("{ true, false, true, false }", "{}", .{vbool}); - try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); - try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); - try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); - try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); - try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); - try testFmt("{ 1000B, 1.953125KiB, 2.9296875KiB, 3.90625KiB }", "{Bi}", .{vu64}); + try expectFmt("{ true, false, true, false }", "{}", .{vbool}); + try expectFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); + try expectFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); + try expectFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); + try expectFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); + try expectFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); + try expectFmt("{ 1000B, 1.953125KiB, 2.9296875KiB, 3.90625KiB }", "{Bi}", .{vu64}); } test "enum-literal" { - try testFmt(".hello_world", "{}", .{.hello_world}); + try expectFmt(".hello_world", "{s}", .{.hello_world}); } test "padding" { - try testFmt("Simple", "{}", .{"Simple"}); - try testFmt(" true", "{:10}", .{true}); - try testFmt(" true", "{:>10}", .{true}); - try testFmt("======true", "{:=>10}", .{true}); - try testFmt("true======", "{:=<10}", .{true}); - try testFmt(" true ", "{:^10}", .{true}); - try testFmt("===true===", "{:=^10}", .{true}); - try testFmt(" Minimum width", "{:18} width", .{"Minimum"}); - try testFmt("==================Filled", "{:=>24}", .{"Filled"}); - try testFmt(" Centered ", "{:^24}", .{"Centered"}); - try testFmt("-", "{:-^1}", .{""}); - try testFmt("==crêpe===", "{:=^10}", .{"crêpe"}); - try testFmt("=====crêpe", "{:=>10}", .{"crêpe"}); - try testFmt("crêpe=====", "{:=<10}", .{"crêpe"}); + try expectFmt("Simple", "{s}", .{"Simple"}); + try expectFmt(" true", "{:10}", .{true}); + try expectFmt(" true", "{:>10}", .{true}); + try expectFmt("======true", "{:=>10}", .{true}); + try expectFmt("true======", "{:=<10}", .{true}); + try expectFmt(" true ", "{:^10}", .{true}); + try expectFmt("===true===", "{:=^10}", .{true}); + try expectFmt(" Minimum width", "{s:18} width", .{"Minimum"}); + try expectFmt("==================Filled", "{s:=>24}", .{"Filled"}); + try expectFmt(" Centered ", "{s:^24}", .{"Centered"}); + try expectFmt("-", "{s:-^1}", .{""}); + try expectFmt("==crêpe===", "{s:=^10}", .{"crêpe"}); + try expectFmt("=====crêpe", "{s:=>10}", .{"crêpe"}); + try expectFmt("crêpe=====", "{s:=<10}", .{"crêpe"}); } test "decimal float padding" { var number: f32 = 3.1415; - try testFmt("left-pad: **3.141\n", "left-pad: {d:*>7.3}\n", .{number}); - try testFmt("center-pad: *3.141*\n", "center-pad: {d:*^7.3}\n", .{number}); - try testFmt("right-pad: 3.141**\n", "right-pad: {d:*<7.3}\n", .{number}); + try expectFmt("left-pad: **3.141\n", "left-pad: {d:*>7.3}\n", .{number}); + try expectFmt("center-pad: *3.141*\n", "center-pad: {d:*^7.3}\n", .{number}); + try expectFmt("right-pad: 3.141**\n", "right-pad: {d:*<7.3}\n", .{number}); } test "sci float padding" { var number: f32 = 3.1415; - try testFmt("left-pad: **3.141e+00\n", "left-pad: {e:*>11.3}\n", .{number}); - try testFmt("center-pad: *3.141e+00*\n", "center-pad: {e:*^11.3}\n", .{number}); - try testFmt("right-pad: 3.141e+00**\n", "right-pad: {e:*<11.3}\n", .{number}); + try expectFmt("left-pad: **3.141e+00\n", "left-pad: {e:*>11.3}\n", .{number}); + try expectFmt("center-pad: *3.141e+00*\n", "center-pad: {e:*^11.3}\n", .{number}); + try expectFmt("right-pad: 3.141e+00**\n", "right-pad: {e:*<11.3}\n", .{number}); } test "null" { const inst = null; - try testFmt("null", "{}", .{inst}); + try expectFmt("null", "{}", .{inst}); } test "type" { - try testFmt("u8", "{}", .{u8}); - try testFmt("?f32", "{}", .{?f32}); - try testFmt("[]const u8", "{}", .{[]const u8}); + try expectFmt("u8", "{}", .{u8}); + try expectFmt("?f32", "{}", .{?f32}); + try expectFmt("[]const u8", "{}", .{[]const u8}); } test "named arguments" { - try testFmt("hello world!", "{} world{c}", .{ "hello", '!' }); - try testFmt("hello world!", "{[greeting]} world{[punctuation]c}", .{ .punctuation = '!', .greeting = "hello" }); - try testFmt("hello world!", "{[1]} world{[0]c}", .{ '!', "hello" }); + try expectFmt("hello world!", "{s} world{c}", .{ "hello", '!' }); + try expectFmt("hello world!", "{[greeting]s} world{[punctuation]c}", .{ .punctuation = '!', .greeting = "hello" }); + try expectFmt("hello world!", "{[1]s} world{[0]c}", .{ '!', "hello" }); } test "runtime width specifier" { var width: usize = 9; - try testFmt("~~hello~~", "{:~^[1]}", .{ "hello", width }); - try testFmt("~~hello~~", "{:~^[width]}", .{ .string = "hello", .width = width }); + try expectFmt("~~hello~~", "{s:~^[1]}", .{ "hello", width }); + try expectFmt("~~hello~~", "{s:~^[width]}", .{ .string = "hello", .width = width }); } test "runtime precision specifier" { var number: f32 = 3.1415; var precision: usize = 2; - try testFmt("3.14e+00", "{:1.[1]}", .{ number, precision }); - try testFmt("3.14e+00", "{:1.[precision]}", .{ .number = number, .precision = precision }); + try expectFmt("3.14e+00", "{:1.[1]}", .{ number, precision }); + try expectFmt("3.14e+00", "{:1.[precision]}", .{ .number = number, .precision = precision }); } diff --git a/lib/std/fmt/errol.zig b/lib/std/fmt/errol.zig index 6a0a2256d8..a4f46e7f13 100644 --- a/lib/std/fmt/errol.zig +++ b/lib/std/fmt/errol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/fmt/errol/enum3.zig b/lib/std/fmt/errol/enum3.zig index 9dbe27c072..db671b8d25 100644 --- a/lib/std/fmt/errol/enum3.zig +++ b/lib/std/fmt/errol/enum3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/fmt/errol/lookup.zig b/lib/std/fmt/errol/lookup.zig index 85a4234bca..4499d3fdef 100644 --- a/lib/std/fmt/errol/lookup.zig +++ b/lib/std/fmt/errol/lookup.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/fmt/parse_float.zig b/lib/std/fmt/parse_float.zig index 4396676d9e..324b06898e 100644 --- a/lib/std/fmt/parse_float.zig +++ b/lib/std/fmt/parse_float.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -52,21 +52,21 @@ const Z96 = struct { d2: u32, // d = s >> 1 - inline fn shiftRight1(d: *Z96, s: Z96) void { + fn shiftRight1(d: *Z96, s: Z96) callconv(.Inline) void { d.d0 = (s.d0 >> 1) | ((s.d1 & 1) << 31); d.d1 = (s.d1 >> 1) | ((s.d2 & 1) << 31); d.d2 = s.d2 >> 1; } // d = s << 1 - inline fn shiftLeft1(d: *Z96, s: Z96) void { + fn shiftLeft1(d: *Z96, s: Z96) callconv(.Inline) void { d.d2 = (s.d2 << 1) | ((s.d1 & (1 << 31)) >> 31); d.d1 = (s.d1 << 1) | ((s.d0 & (1 << 31)) >> 31); d.d0 = s.d0 << 1; } // d += s - inline fn add(d: *Z96, s: Z96) void { + fn add(d: *Z96, s: Z96) callconv(.Inline) void { var w = @as(u64, d.d0) + @as(u64, s.d0); d.d0 = @truncate(u32, w); @@ -80,7 +80,7 @@ const Z96 = struct { } // d -= s - inline fn sub(d: *Z96, s: Z96) void { + fn sub(d: *Z96, s: Z96) callconv(.Inline) void { var w = @as(u64, d.d0) -% @as(u64, s.d0); d.d0 = @truncate(u32, w); diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 8b949a57f1..79385708af 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -39,7 +39,7 @@ pub const Watch = @import("fs/watch.zig").Watch; /// fit into a UTF-8 encoded array of this length. /// The byte count includes room for a null sentinel byte. pub const MAX_PATH_BYTES = switch (builtin.os.tag) { - .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => os.PATH_MAX, + .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .haiku => os.PATH_MAX, // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. // If it would require 4 UTF-8 bytes, then there would be a surrogate // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. @@ -82,8 +82,8 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: mem.copy(u8, tmp_path[0..], dirname); tmp_path[dirname.len] = path.sep; while (true) { - try crypto.randomBytes(rand_buf[0..]); - base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); + crypto.random.bytes(rand_buf[0..]); + _ = base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); if (cwd().symLink(existing_path, tmp_path, .{})) { return cwd().rename(tmp_path, new_path); @@ -153,15 +153,14 @@ pub const AtomicFile = struct { ) InitError!AtomicFile { var rand_buf: [RANDOM_BYTES]u8 = undefined; var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined; - // TODO: should be able to use TMP_PATH_LEN here. - tmp_path_buf[base64.Base64Encoder.calcSize(RANDOM_BYTES)] = 0; while (true) { - try crypto.randomBytes(rand_buf[0..]); - base64_encoder.encode(&tmp_path_buf, &rand_buf); + crypto.random.bytes(rand_buf[0..]); + const tmp_path = base64_encoder.encode(&tmp_path_buf, &rand_buf); + tmp_path_buf[tmp_path.len] = 0; const file = dir.createFile( - &tmp_path_buf, + tmp_path, .{ .mode = mode, .exclusive = true }, ) catch |err| switch (err) { error.PathAlreadyExists => continue, @@ -428,6 +427,78 @@ pub const Dir = struct { } } }, + .haiku => struct { + dir: Dir, + buf: [8192]u8, // TODO align(@alignOf(os.dirent64)), + index: usize, + end_index: usize, + + const Self = @This(); + + pub const Error = IteratorError; + + /// Memory such as file names referenced in this returned entry becomes invalid + /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. + pub fn next(self: *Self) Error!?Entry { + start_over: while (true) { + // TODO: find a better max + const HAIKU_MAX_COUNT = 10000; + if (self.index >= self.end_index) { + const rc = os.system._kern_read_dir( + self.dir.fd, + &self.buf, + self.buf.len, + HAIKU_MAX_COUNT, + ); + if (rc == 0) return null; + if (rc < 0) { + switch (os.errno(rc)) { + os.EBADF => unreachable, // Dir is invalid or was opened without iteration ability + os.EFAULT => unreachable, + os.ENOTDIR => unreachable, + os.EINVAL => unreachable, + else => |err| return os.unexpectedErrno(err), + } + } + self.index = 0; + self.end_index = @intCast(usize, rc); + } + const haiku_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]); + const next_index = self.index + haiku_entry.reclen(); + self.index = next_index; + const name = mem.spanZ(@ptrCast([*:0]u8, &haiku_entry.d_name)); + + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) { + continue :start_over; + } + + var stat_info: os.libc_stat = undefined; + const rc2 = os.system._kern_read_stat( + self.dir.fd, + &haiku_entry.d_name, + false, + &stat_info, + 0, + ); + const statmode = stat_info.mode & os.S_IFMT; + + const entry_kind = switch (statmode) { + os.S_IFDIR => Entry.Kind.Directory, + os.S_IFBLK => Entry.Kind.BlockDevice, + os.S_IFCHR => Entry.Kind.CharacterDevice, + os.S_IFLNK => Entry.Kind.SymLink, + os.S_IFREG => Entry.Kind.File, + os.S_IFIFO => Entry.Kind.NamedPipe, + else => Entry.Kind.Unknown, + }; + + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + }, .linux => struct { dir: Dir, buf: [8192]u8, // TODO align(@alignOf(os.dirent64)), @@ -622,14 +693,20 @@ pub const Dir = struct { pub fn iterate(self: Dir) Iterator { switch (builtin.os.tag) { - .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => return Iterator{ + .macos, + .ios, + .freebsd, + .netbsd, + .dragonfly, + .openbsd, + => return Iterator{ .dir = self, .seek = 0, .index = 0, .end_index = 0, .buf = undefined, }, - .linux => return Iterator{ + .linux, .haiku => return Iterator{ .dir = self, .index = 0, .end_index = 0, @@ -2184,10 +2261,10 @@ pub const Walker = struct { while (true) { if (self.stack.items.len == 0) return null; // `top` becomes invalid after appending to `self.stack`. - const top = &self.stack.items[self.stack.items.len - 1]; + var top = &self.stack.items[self.stack.items.len - 1]; const dirname_len = top.dirname_len; if (try top.dir_it.next()) |base| { - self.name_buffer.shrink(dirname_len); + self.name_buffer.shrinkRetainingCapacity(dirname_len); try self.name_buffer.append(path.sep); try self.name_buffer.appendSlice(base.name); if (base.kind == .Directory) { @@ -2201,6 +2278,7 @@ pub const Walker = struct { .dir_it = new_dir.iterate(), .dirname_len = self.name_buffer.items.len, }); + top = &self.stack.items[self.stack.items.len - 1]; } } return Entry{ @@ -2339,7 +2417,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { // TODO could this slice from 0 to out_len instead? return mem.spanZ(std.meta.assumeSentinel(out_buffer.ptr, 0)); }, - .openbsd => { + .openbsd, .haiku => { // OpenBSD doesn't support getting the path of a running process, so try to guess it if (os.argv.len == 0) return error.FileNotFound; @@ -2475,7 +2553,7 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { } } -test "" { +test { if (builtin.os.tag != .wasi) { _ = makeDirAbsolute; _ = makeDirAbsoluteZ; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index dc7b39eef1..baea3b4e7f 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -117,7 +117,7 @@ pub const File = struct { truncate: bool = true, /// Ensures that this open call creates the file, otherwise causes - /// `error.FileAlreadyExists` to be returned. + /// `error.PathAlreadyExists` to be returned. exclusive: bool = false, /// Open the file with a lock to prevent other processes from accessing it at the @@ -459,6 +459,7 @@ pub const File = struct { return index; } + /// See https://github.com/ziglang/zig/issues/7699 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter @@ -479,6 +480,7 @@ pub const File = struct { /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return; @@ -500,6 +502,7 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter @@ -520,6 +523,7 @@ pub const File = struct { /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { if (iovecs.len == 0) return; @@ -582,6 +586,8 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 + /// See equivalent function: `std.net.Stream.writev`. pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter @@ -599,6 +605,8 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 + /// See equivalent function: `std.net.Stream.writevAll`. pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { if (iovecs.len == 0) return; @@ -615,6 +623,7 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter @@ -632,6 +641,7 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { if (iovecs.len == 0) return; @@ -690,7 +700,7 @@ pub const File = struct { header_count: usize = 0, }; - pub const WriteFileError = ReadError || WriteError; + pub const WriteFileError = ReadError || error{EndOfStream} || WriteError; pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) { @@ -698,6 +708,8 @@ pub const File = struct { error.FastOpenAlreadyInProgress, error.MessageTooBig, error.FileDescriptorNotASocket, + error.NetworkUnreachable, + error.NetworkSubsystemFailed, => return self.writeFileAllUnseekable(in_file, args), else => |e| return e, @@ -712,23 +724,14 @@ pub const File = struct { try self.writevAll(headers); - var buffer: [4096]u8 = undefined; - { - var index: usize = 0; - // Skip in_offset bytes. - while (index < args.in_offset) { - const ask = math.min(buffer.len, args.in_offset - index); - const amt = try in_file.read(buffer[0..ask]); - index += amt; - } - } - const in_len = args.in_len orelse math.maxInt(u64); - var index: usize = 0; - while (index < in_len) { - const ask = math.min(buffer.len, in_len - index); - const amt = try in_file.read(buffer[0..ask]); - if (amt == 0) break; - index += try self.write(buffer[0..amt]); + try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 }); + + var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); + if (args.in_len) |len| { + var stream = std.io.limitedReader(in_file.reader(), len); + try fifo.pump(stream.reader(), self.writer()); + } else { + try fifo.pump(in_file.reader(), self.writer()); } try self.writevAll(trailers); @@ -803,32 +806,16 @@ pub const File = struct { pub const Reader = io.Reader(File, ReadError, read); - /// Deprecated: use `Reader` - pub const InStream = Reader; - pub fn reader(file: File) Reader { return .{ .context = file }; } - /// Deprecated: use `reader` - pub fn inStream(file: File) Reader { - return .{ .context = file }; - } - pub const Writer = io.Writer(File, WriteError, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; - pub fn writer(file: File) Writer { return .{ .context = file }; } - /// Deprecated: use `writer` - pub fn outStream(file: File) Writer { - return .{ .context = file }; - } - pub const SeekableStream = io.SeekableStream( File, SeekError, diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 9e7e54e1b5..18f8458eb2 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -56,6 +56,18 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD }; return fs.path.join(allocator, &[_][]const u8{ home_dir, ".local", "share", appname }); }, + .haiku => { + var dir_path_ptr: [*:0]u8 = undefined; + // TODO look into directory_which + const be_user_settings = 0xbbe; + const rc = os.system.find_directory(be_user_settings, -1, true, dir_path_ptr, 1) ; + const settings_dir = try allocator.dupeZ(u8, mem.spanZ(dir_path_ptr)); + defer allocator.free(settings_dir); + switch (rc) { + 0 => return fs.path.join(allocator, &[_][]const u8{ settings_dir, appname }), + else => return error.AppDataDirUnavailable, + } + }, else => @compileError("Unsupported OS"), } } diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 9043889aa9..c04b9dff66 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -39,7 +39,7 @@ pub fn isSep(byte: u8) bool { /// This is different from mem.join in that the separator will not be repeated if /// it is found at the end or beginning of a pair of consecutive paths. -fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u8 { +fn joinSep(allocator: *Allocator, separator: u8, sepPredicate: fn (u8) bool, paths: []const []const u8) ![]u8 { if (paths.len == 0) return &[0]u8{}; const total_len = blk: { @@ -48,8 +48,8 @@ fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u while (i < paths.len) : (i += 1) { const prev_path = paths[i - 1]; const this_path = paths[i]; - const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator); - const this_sep = (this_path.len != 0 and this_path[0] == separator); + const prev_sep = (prev_path.len != 0 and sepPredicate(prev_path[prev_path.len - 1])); + const this_sep = (this_path.len != 0 and sepPredicate(this_path[0])); sum += @boolToInt(!prev_sep and !this_sep); sum += if (prev_sep and this_sep) this_path.len - 1 else this_path.len; } @@ -65,8 +65,8 @@ fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u while (i < paths.len) : (i += 1) { const prev_path = paths[i - 1]; const this_path = paths[i]; - const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator); - const this_sep = (this_path.len != 0 and this_path[0] == separator); + const prev_sep = (prev_path.len != 0 and sepPredicate(prev_path[prev_path.len - 1])); + const this_sep = (this_path.len != 0 and sepPredicate(this_path[0])); if (!prev_sep and !this_sep) { buf[buf_index] = separator; buf_index += 1; @@ -80,28 +80,30 @@ fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u return buf; } -pub const join = if (builtin.os.tag == .windows) joinWindows else joinPosix; - -/// Naively combines a series of paths with the native path seperator. -/// Allocates memory for the result, which must be freed by the caller. -pub fn joinWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { - return joinSep(allocator, sep_windows, paths); -} - /// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. -pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { - return joinSep(allocator, sep_posix, paths); +pub fn join(allocator: *Allocator, paths: []const []const u8) ![]u8 { + return joinSep(allocator, sep, isSep, paths); } fn testJoinWindows(paths: []const []const u8, expected: []const u8) void { - const actual = joinWindows(testing.allocator, paths) catch @panic("fail"); + const windowsIsSep = struct { + fn isSep(byte: u8) bool { + return byte == '/' or byte == '\\'; + } + }.isSep; + const actual = joinSep(testing.allocator, sep_windows, windowsIsSep, paths) catch @panic("fail"); defer testing.allocator.free(actual); testing.expectEqualSlices(u8, expected, actual); } fn testJoinPosix(paths: []const []const u8, expected: []const u8) void { - const actual = joinPosix(testing.allocator, paths) catch @panic("fail"); + const posixIsSep = struct { + fn isSep(byte: u8) bool { + return byte == '/'; + } + }.isSep; + const actual = joinSep(testing.allocator, sep_posix, posixIsSep, paths) catch @panic("fail"); defer testing.allocator.free(actual); testing.expectEqualSlices(u8, expected, actual); } @@ -119,6 +121,9 @@ test "join" { "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig", ); + testJoinWindows(&[_][]const u8{ "c:\\", "a", "b/", "c" }, "c:\\a\\b/c"); + testJoinWindows(&[_][]const u8{ "c:\\a/", "b\\", "/c" }, "c:\\a/b\\c"); + testJoinPosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c"); testJoinPosix(&[_][]const u8{ "/a/b/", "c" }, "/a/b/c"); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 61df39be0c..e9f28a0b60 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -750,7 +750,7 @@ test "open file with exclusive lock twice, make sure it waits" { errdefer file.close(); const S = struct { - const C = struct { dir: *fs.Dir, evt: *std.ResetEvent }; + const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent }; fn checkFn(ctx: C) !void { const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive }); defer file1.close(); @@ -758,7 +758,8 @@ test "open file with exclusive lock twice, make sure it waits" { } }; - var evt = std.ResetEvent.init(); + var evt: std.Thread.ResetEvent = undefined; + try evt.init(); defer evt.deinit(); const t = try std.Thread.spawn(S.C{ .dir = &tmp.dir, .evt = &evt }, S.checkFn); @@ -771,8 +772,6 @@ test "open file with exclusive lock twice, make sure it waits" { std.time.sleep(SLEEP_TIMEOUT_NS); if (timer.read() >= SLEEP_TIMEOUT_NS) break; } - // Check that createFile is still waiting for the lock to be released. - testing.expect(!evt.isSet()); file.close(); // No timeout to avoid failures on heavily loaded systems. evt.wait(); @@ -795,3 +794,42 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { try fs.deleteFileAbsolute(filename); } + +test "walker" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + var allocator = &arena.allocator; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const nb_dirs = 8; + + var i: usize = 0; + var sub_dir = tmp.dir; + while (i < nb_dirs) : (i += 1) { + const dir_name = try std.fmt.allocPrint(allocator, "{}", .{i}); + try sub_dir.makeDir(dir_name); + sub_dir = try sub_dir.openDir(dir_name, .{}); + } + + const tmp_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); + + var walker = try fs.walkPath(testing.allocator, tmp_path); + defer walker.deinit(); + + i = 0; + var expected_dir_name: []const u8 = ""; + while (i < nb_dirs) : (i += 1) { + const name = try std.fmt.allocPrint(allocator, "{}", .{i}); + expected_dir_name = if (expected_dir_name.len == 0) + name + else + try fs.path.join(allocator, &[_][]const u8{ expected_dir_name, name }); + + var entry = (try walker.next()).?; + testing.expectEqualStrings(expected_dir_name, try fs.path.relative(allocator, tmp_path, entry.path)); + } +} diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index cad86e2314..cf5431f994 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -38,7 +38,7 @@ pub const PreopenType = union(PreopenTypeTag) { pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void { try out_stream.print("PreopenType{{ ", .{}); switch (self) { - PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}), + PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{std.zig.fmtId(path)}), } return out_stream.print(" }}", .{}); } diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index 2e75b865cf..1c7cb68b32 100644 --- a/lib/std/fs/watch.zig +++ b/lib/std/fs/watch.zig @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -const std = @import("../std.zig"); +const std = @import("std"); const builtin = @import("builtin"); const event = std.event; const assert = std.debug.assert; @@ -24,16 +24,6 @@ const WatchEventId = enum { Delete, }; -fn eqlString(a: []const u16, b: []const u16) bool { - if (a.len != b.len) return false; - if (a.ptr == b.ptr) return true; - return mem.compare(u16, a, b) == .Equal; -} - -fn hashString(s: []const u16) u32 { - return @truncate(u32, std.hash.Wyhash.hash(0, mem.sliceAsBytes(s))); -} - const WatchEventError = error{ UserResourceLimitReached, SystemResources, @@ -43,7 +33,7 @@ const WatchEventError = error{ pub fn Watch(comptime V: type) type { return struct { - channel: *event.Channel(Event.Error!Event), + channel: event.Channel(Event.Error!Event), os_data: OsData, allocator: *Allocator, @@ -57,10 +47,10 @@ pub fn Watch(comptime V: type) type { }; const KqOsData = struct { - file_table: FileTable, table_lock: event.Lock, + file_table: FileTable, - const FileTable = std.StringHashMap(*Put); + const FileTable = std.StringHashMapUnmanaged(*Put); const Put = struct { putter_frame: @Frame(kqPutEvents), cancelled: bool = false, @@ -71,21 +61,15 @@ pub fn Watch(comptime V: type) type { const WindowsOsData = struct { table_lock: event.Lock, dir_table: DirTable, - all_putters: std.atomic.Queue(Put), - ref_count: std.atomic.Int(usize), - - const Put = struct { - putter: anyframe, - cancelled: bool = false, - }; + cancelled: bool = false, - const DirTable = std.StringHashMap(*Dir); - const FileTable = std.HashMap([]const u16, V, hashString, eqlString); + const DirTable = std.StringHashMapUnmanaged(*Dir); + const FileTable = std.StringHashMapUnmanaged(V); const Dir = struct { putter_frame: @Frame(windowsDirReader), file_table: FileTable, - table_lock: event.Lock, + dir_handle: os.windows.HANDLE, }; }; @@ -96,8 +80,8 @@ pub fn Watch(comptime V: type) type { table_lock: event.Lock, cancelled: bool = false, - const WdTable = std.AutoHashMap(i32, Dir); - const FileTable = std.StringHashMap(V); + const WdTable = std.AutoHashMapUnmanaged(i32, Dir); + const FileTable = std.StringHashMapUnmanaged(V); const Dir = struct { dirname: []const u8, @@ -110,19 +94,14 @@ pub fn Watch(comptime V: type) type { pub const Event = struct { id: Id, data: V, + dirname: []const u8, + basename: []const u8, pub const Id = WatchEventId; pub const Error = WatchEventError; }; pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self { - const channel = try allocator.create(event.Channel(Event.Error!Event)); - errdefer allocator.destroy(channel); - var buf = try allocator.alloc(Event.Error!Event, event_buf_count); - errdefer allocator.free(buf); - channel.init(buf); - errdefer channel.deinit(); - const self = try allocator.create(Self); errdefer allocator.destroy(self); @@ -133,15 +112,17 @@ pub fn Watch(comptime V: type) type { self.* = Self{ .allocator = allocator, - .channel = channel, + .channel = undefined, .os_data = OsData{ .putter_frame = undefined, .inotify_fd = inotify_fd, .wd_table = OsData.WdTable.init(allocator), - .table_lock = event.Lock.init(), + .table_lock = event.Lock{}, }, }; + var buf = try allocator.alloc(Event.Error!Event, event_buf_count); + self.channel.init(buf); self.os_data.putter_frame = async self.linuxEventPutter(); return self; }, @@ -149,82 +130,93 @@ pub fn Watch(comptime V: type) type { .windows => { self.* = Self{ .allocator = allocator, - .channel = channel, + .channel = undefined, .os_data = OsData{ - .table_lock = event.Lock.init(), + .table_lock = event.Lock{}, .dir_table = OsData.DirTable.init(allocator), - .ref_count = std.atomic.Int(usize).init(1), - .all_putters = std.atomic.Queue(anyframe).init(), }, }; + + var buf = try allocator.alloc(Event.Error!Event, event_buf_count); + self.channel.init(buf); return self; }, .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { self.* = Self{ .allocator = allocator, - .channel = channel, + .channel = undefined, .os_data = OsData{ - .table_lock = event.Lock.init(), + .table_lock = event.Lock{}, .file_table = OsData.FileTable.init(allocator), }, }; + + var buf = try allocator.alloc(Event.Error!Event, event_buf_count); + self.channel.init(buf); return self; }, else => @compileError("Unsupported OS"), } } - /// All addFile calls and removeFile calls must have completed. pub fn deinit(self: *Self) void { switch (builtin.os.tag) { .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { - // TODO we need to cancel the frames before destroying the lock - self.os_data.table_lock.deinit(); var it = self.os_data.file_table.iterator(); while (it.next()) |entry| { - entry.cancelled = true; - await entry.value.putter; + entry.value.cancelled = true; + // @TODO Close the fd here? + await entry.value.putter_frame; self.allocator.free(entry.key); - self.allocator.free(entry.value); + self.allocator.destroy(entry.value); } - self.channel.deinit(); - self.allocator.destroy(self.channel.buffer_nodes); - self.allocator.destroy(self); }, .linux => { self.os_data.cancelled = true; + { + // Remove all directory watches linuxEventPutter will take care of + // cleaning up the memory and closing the inotify fd. + var dir_it = self.os_data.wd_table.iterator(); + while (dir_it.next()) |wd_entry| { + const rc = os.linux.inotify_rm_watch(self.os_data.inotify_fd, wd_entry.key); + // Errno can only be EBADF, EINVAL if either the inotify fs or the wd are invalid + std.debug.assert(rc == 0); + } + } await self.os_data.putter_frame; - self.allocator.destroy(self); }, .windows => { - while (self.os_data.all_putters.get()) |putter_node| { - putter_node.cancelled = true; - await putter_node.frame; + self.os_data.cancelled = true; + var dir_it = self.os_data.dir_table.iterator(); + while (dir_it.next()) |dir_entry| { + if (windows.kernel32.CancelIoEx(dir_entry.value.dir_handle, null) != 0) { + // We canceled the pending ReadDirectoryChangesW operation, but our + // frame is still suspending, now waiting indefinitely. + // Thus, it is safe to resume it ourslves + resume dir_entry.value.putter_frame; + } else { + std.debug.assert(windows.kernel32.GetLastError() == .NOT_FOUND); + // We are at another suspend point, we can await safely for the + // function to exit the loop + await dir_entry.value.putter_frame; + } + + self.allocator.free(dir_entry.key); + var file_it = dir_entry.value.file_table.iterator(); + while (file_it.next()) |file_entry| { + self.allocator.free(file_entry.key); + } + dir_entry.value.file_table.deinit(self.allocator); + self.allocator.destroy(dir_entry.value); } - self.deref(); + self.os_data.dir_table.deinit(self.allocator); }, else => @compileError("Unsupported OS"), } - } - - fn ref(self: *Self) void { - _ = self.os_data.ref_count.incr(); - } - - fn deref(self: *Self) void { - if (self.os_data.ref_count.decr() == 1) { - self.os_data.table_lock.deinit(); - var it = self.os_data.dir_table.iterator(); - while (it.next()) |entry| { - self.allocator.free(entry.key); - self.allocator.destroy(entry.value); - } - self.os_data.dir_table.deinit(); - self.channel.deinit(); - self.allocator.destroy(self.channel.buffer_nodes); - self.allocator.destroy(self); - } + self.allocator.free(self.channel.buffer_nodes); + self.channel.deinit(); + self.allocator.destroy(self); } pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { @@ -237,217 +229,208 @@ pub fn Watch(comptime V: type) type { } fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { - const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path}); - var resolved_path_consumed = false; - defer if (!resolved_path_consumed) self.allocator.free(resolved_path); + var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const realpath = try os.realpath(file_path, &realpath_buf); - var close_op = try CloseOperation.start(self.allocator); - var close_op_consumed = false; - defer if (!close_op_consumed) close_op.finish(); - - const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; - const mode = 0; - const fd = try openPosix(self.allocator, resolved_path, flags, mode); - close_op.setHandle(fd); + const held = self.os_data.table_lock.acquire(); + defer held.release(); - var put = try self.allocator.create(OsData.Put); - errdefer self.allocator.destroy(put); - put.* = OsData.Put{ - .value = value, - .putter_frame = undefined, - }; - put.putter_frame = async self.kqPutEvents(close_op, put); - close_op_consumed = true; - errdefer { - put.cancelled = true; - await put.putter_frame; + const gop = try self.os_data.file_table.getOrPut(self.allocator, realpath); + errdefer self.os_data.file_table.removeAssertDiscard(realpath); + if (gop.found_existing) { + const prev_value = gop.entry.value.value; + gop.entry.value.value = value; + return prev_value; } - const result = blk: { - const held = self.os_data.table_lock.acquire(); - defer held.release(); - - const gop = try self.os_data.file_table.getOrPut(resolved_path); - if (gop.found_existing) { - const prev_value = gop.kv.value.value; - await gop.kv.value.putter_frame; - gop.kv.value = put; - break :blk prev_value; - } else { - resolved_path_consumed = true; - gop.kv.value = put; - break :blk null; - } + gop.entry.key = try self.allocator.dupe(u8, realpath); + errdefer self.allocator.free(gop.entry.key); + gop.entry.value = try self.allocator.create(OsData.Put); + errdefer self.allocator.destroy(gop.entry.value); + gop.entry.value.* = .{ + .putter_frame = undefined, + .value = value, }; - return result; + // @TODO Can I close this fd and get an error from bsdWaitKev? + const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; + const fd = try os.open(realpath, flags, 0); + gop.entry.value.putter_frame = async self.kqPutEvents(fd, gop.entry.key, gop.entry.value); + return null; } - fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void { + fn kqPutEvents(self: *Self, fd: os.fd_t, file_path: []const u8, put: *OsData.Put) void { global_event_loop.beginOneEvent(); - defer { - close_op.finish(); global_event_loop.finishOneEvent(); + // @TODO: Remove this if we force close otherwise + os.close(fd); } + // We need to manually do a bsdWaitKev to access the fflags. + var resume_node = event.Loop.ResumeNode.Basic{ + .base = .{ + .id = .Basic, + .handle = @frame(), + .overlapped = event.Loop.ResumeNode.overlapped_init, + }, + .kev = undefined, + }; + + var kevs = [1]os.Kevent{undefined}; + const kev = &kevs[0]; + while (!put.cancelled) { - if (global_event_loop.bsdWaitKev( - @intCast(usize, close_op.getHandle()), - os.EVFILT_VNODE, - os.NOTE_WRITE | os.NOTE_DELETE, - )) |kev| { - // TODO handle EV_ERROR - if (kev.fflags & os.NOTE_DELETE != 0) { - self.channel.put(Self.Event{ - .id = Event.Id.Delete, - .data = put.value, - }); - } else if (kev.fflags & os.NOTE_WRITE != 0) { - self.channel.put(Self.Event{ - .id = Event.Id.CloseWrite, - .data = put.value, - }); - } - } else |err| switch (err) { - error.EventNotFound => unreachable, - error.ProcessNotFound => unreachable, - error.Overflow => unreachable, - error.AccessDenied, error.SystemResources => |casted_err| { - self.channel.put(casted_err); - }, + kev.* = os.Kevent{ + .ident = @intCast(usize, fd), + .filter = os.EVFILT_VNODE, + .flags = os.EV_ADD | os.EV_ENABLE | os.EV_CLEAR | os.EV_ONESHOT | + os.NOTE_WRITE | os.NOTE_DELETE | os.NOTE_REVOKE, + .fflags = 0, + .data = 0, + .udata = @ptrToInt(&resume_node.base), + }; + suspend { + global_event_loop.beginOneEvent(); + errdefer global_event_loop.finishOneEvent(); + + const empty_kevs = &[0]os.Kevent{}; + _ = os.kevent(global_event_loop.os_data.kqfd, &kevs, empty_kevs, null) catch |err| switch (err) { + error.EventNotFound, + error.ProcessNotFound, + error.Overflow, + => unreachable, + error.AccessDenied, error.SystemResources => |e| { + self.channel.put(e); + continue; + }, + }; + } + + if (kev.flags & os.EV_ERROR != 0) { + self.channel.put(os.unexpectedErrno(os.errno(kev.data))); + continue; + } + + if (kev.fflags & os.NOTE_DELETE != 0 or kev.fflags & os.NOTE_REVOKE != 0) { + self.channel.put(Self.Event{ + .id = .Delete, + .data = put.value, + .dirname = std.fs.path.dirname(file_path) orelse "/", + .basename = std.fs.path.basename(file_path), + }); + } else if (kev.fflags & os.NOTE_WRITE != 0) { + self.channel.put(Self.Event{ + .id = .CloseWrite, + .data = put.value, + .dirname = std.fs.path.dirname(file_path) orelse "/", + .basename = std.fs.path.basename(file_path), + }); } } } fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { - const dirname = std.fs.path.dirname(file_path) orelse "."; - const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname); - var dirname_with_null_consumed = false; - defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null); - + const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else "."; const basename = std.fs.path.basename(file_path); - const basename_with_null = try std.cstr.addNullByte(self.allocator, basename); - var basename_with_null_consumed = false; - defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null); - const wd = try os.inotify_add_watchZ( + const wd = try os.inotify_add_watch( self.os_data.inotify_fd, - dirname_with_null.ptr, - os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, + dirname, + os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_DELETE | os.linux.IN_EXCL_UNLINK, ); // wd is either a newly created watch or an existing one. const held = self.os_data.table_lock.acquire(); defer held.release(); - const gop = try self.os_data.wd_table.getOrPut(wd); + const gop = try self.os_data.wd_table.getOrPut(self.allocator, wd); + errdefer self.os_data.wd_table.removeAssertDiscard(wd); if (!gop.found_existing) { - gop.kv.value = OsData.Dir{ - .dirname = dirname_with_null, + gop.entry.value = OsData.Dir{ + .dirname = try self.allocator.dupe(u8, dirname), .file_table = OsData.FileTable.init(self.allocator), }; - dirname_with_null_consumed = true; } - const dir = &gop.kv.value; - const file_table_gop = try dir.file_table.getOrPut(basename_with_null); + const dir = &gop.entry.value; + const file_table_gop = try dir.file_table.getOrPut(self.allocator, basename); + errdefer dir.file_table.removeAssertDiscard(basename); if (file_table_gop.found_existing) { - const prev_value = file_table_gop.kv.value; - file_table_gop.kv.value = value; + const prev_value = file_table_gop.entry.value; + file_table_gop.entry.value = value; return prev_value; } else { - file_table_gop.kv.value = value; - basename_with_null_consumed = true; + file_table_gop.entry.key = try self.allocator.dupe(u8, basename); + file_table_gop.entry.value = value; return null; } } fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { // TODO we might need to convert dirname and basename to canonical file paths ("short"?) - const dirname = try self.allocator.dupe(u8, std.fs.path.dirname(file_path) orelse "."); - var dirname_consumed = false; - defer if (!dirname_consumed) self.allocator.free(dirname); - - const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname); - defer self.allocator.free(dirname_utf16le); + const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else "."; + var dirname_path_space: windows.PathSpace = undefined; + dirname_path_space.len = try std.unicode.utf8ToUtf16Le(&dirname_path_space.data, dirname); + dirname_path_space.data[dirname_path_space.len] = 0; - // TODO https://github.com/ziglang/zig/issues/265 const basename = std.fs.path.basename(file_path); - const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename); - var basename_utf16le_null_consumed = false; - defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); - const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; - - const dir_handle = try windows.OpenFile(dirname_utf16le, .{ - .dir = std.fs.cwd().fd, - .access_mask = windows.FILE_LIST_DIRECTORY, - .creation = windows.FILE_OPEN, - .io_mode = .blocking, - .open_dir = true, - }); - var dir_handle_consumed = false; - defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); + var basename_path_space: windows.PathSpace = undefined; + basename_path_space.len = try std.unicode.utf8ToUtf16Le(&basename_path_space.data, basename); + basename_path_space.data[basename_path_space.len] = 0; const held = self.os_data.table_lock.acquire(); defer held.release(); - const gop = try self.os_data.dir_table.getOrPut(dirname); + const gop = try self.os_data.dir_table.getOrPut(self.allocator, dirname); + errdefer self.os_data.dir_table.removeAssertDiscard(dirname); if (gop.found_existing) { - const dir = gop.kv.value; - const held_dir_lock = dir.table_lock.acquire(); - defer held_dir_lock.release(); + const dir = gop.entry.value; - const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); + const file_gop = try dir.file_table.getOrPut(self.allocator, basename); + errdefer dir.file_table.removeAssertDiscard(basename); if (file_gop.found_existing) { - const prev_value = file_gop.kv.value; - file_gop.kv.value = value; + const prev_value = file_gop.entry.value; + file_gop.entry.value = value; return prev_value; } else { - file_gop.kv.value = value; - basename_utf16le_null_consumed = true; + file_gop.entry.value = value; + file_gop.entry.key = try self.allocator.dupe(u8, basename); return null; } } else { - errdefer _ = self.os_data.dir_table.remove(dirname); + const dir_handle = try windows.OpenFile(dirname_path_space.span(), .{ + .dir = std.fs.cwd().fd, + .access_mask = windows.FILE_LIST_DIRECTORY, + .creation = windows.FILE_OPEN, + .io_mode = .evented, + .open_dir = true, + }); + errdefer windows.CloseHandle(dir_handle); + const dir = try self.allocator.create(OsData.Dir); errdefer self.allocator.destroy(dir); + gop.entry.key = try self.allocator.dupe(u8, dirname); + errdefer self.allocator.free(gop.entry.key); + dir.* = OsData.Dir{ .file_table = OsData.FileTable.init(self.allocator), - .table_lock = event.Lock.init(), .putter_frame = undefined, + .dir_handle = dir_handle, }; - gop.kv.value = dir; - assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null); - basename_utf16le_null_consumed = true; - - dir.putter_frame = async self.windowsDirReader(dir_handle, dir); - dir_handle_consumed = true; - - dirname_consumed = true; - + gop.entry.value = dir; + try dir.file_table.put(self.allocator, try self.allocator.dupe(u8, basename), value); + dir.putter_frame = async self.windowsDirReader(dir, gop.entry.key); return null; } } - fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { - self.ref(); - defer self.deref(); - - defer os.close(dir_handle); - - var putter_node = std.atomic.Queue(anyframe).Node{ - .data = .{ .putter = @frame() }, - .prev = null, - .next = null, - }; - self.os_data.all_putters.put(&putter_node); - defer _ = self.os_data.all_putters.remove(&putter_node); - + fn windowsDirReader(self: *Self, dir: *OsData.Dir, dirname: []const u8) void { + defer os.close(dir.dir_handle); var resume_node = Loop.ResumeNode.Basic{ .base = Loop.ResumeNode{ - .id = Loop.ResumeNode.Id.Basic, + .id = .Basic, .handle = @frame(), .overlapped = windows.OVERLAPPED{ .Internal = 0, @@ -458,157 +441,193 @@ pub fn Watch(comptime V: type) type { }, }, }; - var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; - // TODO handle this error not in the channel but in the setup - _ = windows.CreateIoCompletionPort( - dir_handle, - global_event_loop.os_data.io_port, - undefined, - undefined, - ) catch |err| { - self.channel.put(err); - return; - }; + var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; - while (!putter_node.data.cancelled) { - { - // TODO only 1 beginOneEvent for the whole function - global_event_loop.beginOneEvent(); - errdefer global_event_loop.finishOneEvent(); - errdefer { - _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); - } - suspend { - _ = windows.kernel32.ReadDirectoryChangesW( - dir_handle, - &event_buf, - @intCast(windows.DWORD, event_buf.len), - windows.FALSE, // watch subtree - windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | - windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | - windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | - windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, - null, // number of bytes transferred (unused for async) - &resume_node.base.overlapped, - null, // completion routine - unused because we use IOCP - ); - } + global_event_loop.beginOneEvent(); + defer global_event_loop.finishOneEvent(); + + while (!self.os_data.cancelled) main_loop: { + suspend { + _ = windows.kernel32.ReadDirectoryChangesW( + dir.dir_handle, + &event_buf, + event_buf.len, + windows.FALSE, // watch subtree + windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | + windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | + windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | + windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, + null, // number of bytes transferred (unused for async) + &resume_node.base.overlapped, + null, // completion routine - unused because we use IOCP + ); } + var bytes_transferred: windows.DWORD = undefined; - if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { - const err = switch (windows.kernel32.GetLastError()) { + if (windows.kernel32.GetOverlappedResult( + dir.dir_handle, + &resume_node.base.overlapped, + &bytes_transferred, + windows.FALSE, + ) == 0) { + const potential_error = windows.kernel32.GetLastError(); + const err = switch (potential_error) { + .OPERATION_ABORTED, .IO_INCOMPLETE => err_blk: { + if (self.os_data.cancelled) + break :main_loop + else + break :err_blk windows.unexpectedError(potential_error); + }, else => |err| windows.unexpectedError(err), }; self.channel.put(err); } else { - // can't use @bytesToSlice because of the special variable length name field - var ptr = event_buf[0..].ptr; + var ptr: [*]u8 = &event_buf; const end_ptr = ptr + bytes_transferred; - var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; - while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { - ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); + while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { + const ev = @ptrCast(*const windows.FILE_NOTIFY_INFORMATION, ptr); const emit = switch (ev.Action) { windows.FILE_ACTION_REMOVED => WatchEventId.Delete, - windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, + windows.FILE_ACTION_MODIFIED => .CloseWrite, else => null, }; if (emit) |id| { - const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; - const user_value = blk: { - const held = dir.table_lock.acquire(); - defer held.release(); - - if (dir.file_table.get(basename_utf16le)) |entry| { - break :blk entry.value; - } else { - break :blk null; - } - }; - if (user_value) |v| { + const basename_ptr = @ptrCast([*]u16, ptr + @sizeOf(windows.FILE_NOTIFY_INFORMATION)); + const basename_utf16le = basename_ptr[0 .. ev.FileNameLength / 2]; + var basename_data: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const basename = basename_data[0 .. std.unicode.utf16leToUtf8(&basename_data, basename_utf16le) catch unreachable]; + + if (dir.file_table.getEntry(basename)) |entry| { self.channel.put(Event{ .id = id, - .data = v, + .data = entry.value, + .dirname = dirname, + .basename = entry.key, }); } } + if (ev.NextEntryOffset == 0) break; + ptr = @alignCast(@alignOf(windows.FILE_NOTIFY_INFORMATION), ptr + ev.NextEntryOffset); } } } } - pub fn removeFile(self: *Self, file_path: []const u8) ?V { - @panic("TODO"); + pub fn removeFile(self: *Self, file_path: []const u8) !?V { + switch (builtin.os.tag) { + .linux => { + const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else "."; + const basename = std.fs.path.basename(file_path); + + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const dir = self.os_data.wd_table.get(dirname) orelse return null; + if (dir.file_table.remove(basename)) |file_entry| { + self.allocator.free(file_entry.key); + return file_entry.value; + } + return null; + }, + .windows => { + const dirname = std.fs.path.dirname(file_path) orelse if (file_path[0] == '/') "/" else "."; + const basename = std.fs.path.basename(file_path); + + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const dir = self.os_data.dir_table.get(dirname) orelse return null; + if (dir.file_table.remove(basename)) |file_entry| { + self.allocator.free(file_entry.key); + return file_entry.value; + } + return null; + }, + .macos, .freebsd, .netbsd, .dragonfly, .openbsd => { + var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const realpath = try os.realpath(file_path, &realpath_buf); + + const held = self.os_data.table_lock.acquire(); + defer held.release(); + + const entry = self.os_data.file_table.get(realpath) orelse return null; + entry.value.cancelled = true; + // @TODO Close the fd here? + await entry.value.putter_frame; + self.allocator.free(entry.key); + self.allocator.destroy(entry.value); + + self.os_data.file_table.removeAssertDiscard(realpath); + }, + else => @compileError("Unsupported OS"), + } } fn linuxEventPutter(self: *Self) void { global_event_loop.beginOneEvent(); defer { - self.os_data.table_lock.deinit(); - var wd_it = self.os_data.wd_table.iterator(); - while (wd_it.next()) |wd_entry| { - var file_it = wd_entry.value.file_table.iterator(); - while (file_it.next()) |file_entry| { - self.allocator.free(file_entry.key); - } - self.allocator.free(wd_entry.value.dirname); - wd_entry.value.file_table.deinit(); - } - self.os_data.wd_table.deinit(); - global_event_loop.finishOneEvent(); + std.debug.assert(self.os_data.wd_table.count() == 0); + self.os_data.wd_table.deinit(self.allocator); os.close(self.os_data.inotify_fd); - self.channel.deinit(); self.allocator.free(self.channel.buffer_nodes); + self.channel.deinit(); + global_event_loop.finishOneEvent(); } var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; while (!self.os_data.cancelled) { - const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len); - const errno = os.linux.getErrno(rc); - switch (errno) { - 0 => { - // can't use @bytesToSlice because of the special variable length name field - var ptr = event_buf[0..].ptr; - const end_ptr = ptr + event_buf.len; - var ev: *os.linux.inotify_event = undefined; - while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { - ev = @ptrCast(*os.linux.inotify_event, ptr); - if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { - const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); - // `ev.len` counts all bytes in `ev.name` including terminating null byte. - const basename_with_null = basename_ptr[0..ev.len]; - const user_value = blk: { - const held = self.os_data.table_lock.acquire(); - defer held.release(); - - const dir = &self.os_data.wd_table.get(ev.wd).?.value; - if (dir.file_table.get(basename_with_null)) |entry| { - break :blk entry.value; - } else { - break :blk null; - } - }; - if (user_value) |v| { - self.channel.put(Event{ - .id = WatchEventId.CloseWrite, - .data = v, - }); - } + const bytes_read = global_event_loop.read(self.os_data.inotify_fd, &event_buf, false) catch unreachable; + + var ptr: [*]u8 = &event_buf; + const end_ptr = ptr + bytes_read; + while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { + const ev = @ptrCast(*const os.linux.inotify_event, ptr); + if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { + const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); + const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); + + const dir = &self.os_data.wd_table.get(ev.wd).?; + if (dir.file_table.getEntry(basename)) |file_value| { + self.channel.put(Event{ + .id = .CloseWrite, + .data = file_value.value, + .dirname = dir.dirname, + .basename = file_value.key, + }); + } + } else if (ev.mask & os.linux.IN_IGNORED == os.linux.IN_IGNORED) { + // Directory watch was removed + const held = self.os_data.table_lock.acquire(); + defer held.release(); + if (self.os_data.wd_table.remove(ev.wd)) |*wd_entry| { + var file_it = wd_entry.value.file_table.iterator(); + while (file_it.next()) |file_entry| { + self.allocator.free(file_entry.key); } - - ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len); + self.allocator.free(wd_entry.value.dirname); + wd_entry.value.file_table.deinit(self.allocator); } - }, - os.linux.EINTR => continue, - os.linux.EINVAL => unreachable, - os.linux.EFAULT => unreachable, - os.linux.EAGAIN => { - global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT); - }, - else => unreachable, + } else if (ev.mask & os.linux.IN_DELETE == os.linux.IN_DELETE) { + // File or directory was removed or deleted + const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); + const basename = std.mem.span(@ptrCast([*:0]u8, basename_ptr)); + + const dir = &self.os_data.wd_table.get(ev.wd).?; + if (dir.file_table.getEntry(basename)) |file_value| { + self.channel.put(Event{ + .id = .Delete, + .data = file_value.value, + .dirname = dir.dirname, + .basename = file_value.key, + }); + } + } + + ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len); } } } @@ -617,19 +636,19 @@ pub fn Watch(comptime V: type) type { const test_tmp_dir = "std_event_fs_test"; -test "write a file, watch it, write it again" { - // TODO re-enable this test - if (true) return error.SkipZigTest; +test "write a file, watch it, write it again, delete it" { + if (!std.io.is_async) return error.SkipZigTest; + // TODO https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; - try fs.cwd().makePath(test_tmp_dir); - defer fs.cwd().deleteTree(test_tmp_dir) catch {}; + try std.fs.cwd().makePath(test_tmp_dir); + defer std.fs.cwd().deleteTree(test_tmp_dir) catch {}; - const allocator = std.heap.page_allocator; - return testFsWatch(&allocator); + return testWriteWatchWriteDelete(std.testing.allocator); } -fn testFsWatch(allocator: *Allocator) !void { - const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" }); +fn testWriteWatchWriteDelete(allocator: *Allocator) !void { + const file_path = try std.fs.path.join(allocator, &[_][]const u8{ test_tmp_dir, "file.txt" }); defer allocator.free(file_path); const contents = @@ -639,9 +658,10 @@ fn testFsWatch(allocator: *Allocator) !void { const line2_offset = 7; // first just write then read the file - try writeFile(allocator, file_path, contents); + try std.fs.cwd().writeFile(file_path, contents); - const read_contents = try readFile(allocator, file_path, 1024 * 1024); + const read_contents = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024); + defer allocator.free(read_contents); testing.expectEqualSlices(u8, contents, read_contents); // now watch the file @@ -650,28 +670,49 @@ fn testFsWatch(allocator: *Allocator) !void { testing.expect((try watch.addFile(file_path, {})) == null); - const ev = watch.channel.get(); + var ev = async watch.channel.get(); var ev_consumed = false; - defer if (!ev_consumed) await ev; + defer if (!ev_consumed) { + _ = await ev; + }; // overwrite line 2 - const fd = try await openReadWrite(file_path, File.default_mode); + const file = try std.fs.cwd().openFile(file_path, .{ .read = true, .write = true }); { - defer os.close(fd); - - try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset); + defer file.close(); + const write_contents = "lorem ipsum"; + var iovec = [_]os.iovec_const{.{ + .iov_base = write_contents, + .iov_len = write_contents.len, + }}; + _ = try file.pwritevAll(&iovec, line2_offset); } - ev_consumed = true; switch ((try await ev).id) { - WatchEventId.CloseWrite => {}, - WatchEventId.Delete => @panic("wrong event"), + .CloseWrite => { + ev_consumed = true; + }, + .Delete => @panic("wrong event"), } - const contents_updated = try readFile(allocator, file_path, 1024 * 1024); + + const contents_updated = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024); + defer allocator.free(contents_updated); + testing.expectEqualSlices(u8, \\line 1 \\lorem ipsum , contents_updated); - // TODO test deleting the file and then re-adding it. we should get events for both + ev = async watch.channel.get(); + ev_consumed = false; + + try std.fs.cwd().deleteFile(file_path); + switch ((try await ev).id) { + .Delete => { + ev_consumed = true; + }, + .CloseWrite => @panic("wrong event"), + } } + +// TODO Test: Add another file watch, remove the old file watch, get an event in the new diff --git a/lib/std/hash.zig b/lib/std/hash.zig index 7bac378316..f5b94725e2 100644 --- a/lib/std/hash.zig +++ b/lib/std/hash.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash/adler.zig b/lib/std/hash/adler.zig index a3fc915f76..9cd85ba7cf 100644 --- a/lib/std/hash/adler.zig +++ b/lib/std/hash/adler.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 2e707d5450..4afc2b425b 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -99,7 +99,16 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { // Help the optimizer see that hashing an int is easy by inlining! // TODO Check if the situation is better after #561 is resolved. - .Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}), + .Int => { + if (comptime meta.trait.hasUniqueRepresentation(Key)) { + @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}); + } else { + // Take only the part containing the key value, the remaining + // bytes are undefined and must not be hashed! + const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable; + @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)[0..byte_size]}); + } + }, .Bool => hash(hasher, @boolToInt(key), strat), .Enum => hash(hasher, @enumToInt(key), strat), @@ -160,21 +169,38 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { } } +fn typeContainsSlice(comptime K: type) bool { + comptime { + if (meta.trait.isSlice(K)) { + return true; + } + if (meta.trait.is(.Struct)(K)) { + inline for (@typeInfo(K).Struct.fields) |field| { + if (typeContainsSlice(field.field_type)) { + return true; + } + } + } + if (meta.trait.is(.Union)(K)) { + inline for (@typeInfo(K).Union.fields) |field| { + if (typeContainsSlice(field.field_type)) { + return true; + } + } + } + return false; + } +} + /// Provides generic hashing for any eligible type. /// Only hashes `key` itself, pointers are not followed. -/// Slices are rejected to avoid ambiguity on the user's intention. +/// Slices as well as unions and structs containing slices are rejected to avoid +/// ambiguity on the user's intention. pub fn autoHash(hasher: anytype, key: anytype) void { const Key = @TypeOf(key); - if (comptime meta.trait.isSlice(Key)) { - comptime assert(@hasDecl(std, "StringHashMap")); // detect when the following message needs updated - const extra_help = if (Key == []const u8) - " Consider std.StringHashMap for hashing the contents of []const u8." - else - ""; - - @compileError("std.auto_hash.autoHash does not allow slices (here " ++ @typeName(Key) ++ - ") because the intent is unclear. Consider using std.auto_hash.hash or providing your own hash function instead." ++ - extra_help); + if (comptime typeContainsSlice(Key)) { + @compileError("std.auto_hash.autoHash does not allow slices as well as unions and structs containing slices here (" ++ @typeName(Key) ++ + ") because the intent is unclear. Consider using std.auto_hash.hash or providing your own hash function instead."); } hash(hasher, key, .Shallow); @@ -211,6 +237,23 @@ fn testHashDeepRecursive(key: anytype) u64 { return hasher.final(); } +test "typeContainsSlice" { + comptime { + testing.expect(!typeContainsSlice(meta.Tag(std.builtin.TypeInfo))); + + testing.expect(typeContainsSlice([]const u8)); + testing.expect(!typeContainsSlice(u8)); + const A = struct { x: []const u8 }; + const B = struct { a: A }; + const C = struct { b: B }; + const D = struct { x: u8 }; + testing.expect(typeContainsSlice(A)); + testing.expect(typeContainsSlice(B)); + testing.expect(typeContainsSlice(C)); + testing.expect(!typeContainsSlice(D)); + } +} + test "hash pointer" { const array = [_]u32{ 123, 123, 123 }; const a = &array[0]; diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index f0cafa9971..19bb9aa8bf 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash/cityhash.zig b/lib/std/hash/cityhash.zig index 38e62d88ef..d7f2f1a9eb 100644 --- a/lib/std/hash/cityhash.zig +++ b/lib/std/hash/cityhash.zig @@ -1,11 +1,24 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("std"); const builtin = @import("builtin"); +fn offsetPtr(ptr: [*]const u8, offset: usize) callconv(.Inline) [*]const u8 { + // ptr + offset doesn't work at comptime so we need this instead. + return @ptrCast([*]const u8, &ptr[offset]); +} + +fn fetch32(ptr: [*]const u8, offset: usize) u32 { + return std.mem.readIntLittle(u32, offsetPtr(ptr, offset)[0..4]); +} + +fn fetch64(ptr: [*]const u8, offset: usize) u64 { + return std.mem.readIntLittle(u64, offsetPtr(ptr, offset)[0..8]); +} + pub const CityHash32 = struct { const Self = @This(); @@ -13,14 +26,6 @@ pub const CityHash32 = struct { const c1: u32 = 0xcc9e2d51; const c2: u32 = 0x1b873593; - fn fetch32(ptr: [*]const u8) u32 { - var v: u32 = undefined; - @memcpy(@ptrCast([*]u8, &v), ptr, 4); - if (builtin.endian == .Big) - return @byteSwap(u32, v); - return v; - } - // A 32-bit to 32-bit integer hash copied from Murmur3. fn fmix(h: u32) u32 { var h1: u32 = h; @@ -66,21 +71,21 @@ pub const CityHash32 = struct { var c: u32 = 9; const d: u32 = b; - a +%= fetch32(str.ptr); - b +%= fetch32(str.ptr + str.len - 4); - c +%= fetch32(str.ptr + ((str.len >> 1) & 4)); + a +%= fetch32(str.ptr, 0); + b +%= fetch32(str.ptr, str.len - 4); + c +%= fetch32(str.ptr, (str.len >> 1) & 4); return fmix(mur(c, mur(b, mur(a, d)))); } fn hash32Len13To24(str: []const u8) u32 { const len: u32 = @truncate(u32, str.len); - const a: u32 = fetch32(str.ptr + (str.len >> 1) - 4); - const b: u32 = fetch32(str.ptr + 4); - const c: u32 = fetch32(str.ptr + str.len - 8); - const d: u32 = fetch32(str.ptr + (str.len >> 1)); - const e: u32 = fetch32(str.ptr); - const f: u32 = fetch32(str.ptr + str.len - 4); + const a: u32 = fetch32(str.ptr, (str.len >> 1) - 4); + const b: u32 = fetch32(str.ptr, 4); + const c: u32 = fetch32(str.ptr, str.len - 8); + const d: u32 = fetch32(str.ptr, str.len >> 1); + const e: u32 = fetch32(str.ptr, 0); + const f: u32 = fetch32(str.ptr, str.len - 4); return fmix(mur(f, mur(e, mur(d, mur(c, mur(b, mur(a, len))))))); } @@ -101,11 +106,11 @@ pub const CityHash32 = struct { var g: u32 = c1 *% len; var f: u32 = g; - const a0: u32 = rotr32(fetch32(str.ptr + str.len - 4) *% c1, 17) *% c2; - const a1: u32 = rotr32(fetch32(str.ptr + str.len - 8) *% c1, 17) *% c2; - const a2: u32 = rotr32(fetch32(str.ptr + str.len - 16) *% c1, 17) *% c2; - const a3: u32 = rotr32(fetch32(str.ptr + str.len - 12) *% c1, 17) *% c2; - const a4: u32 = rotr32(fetch32(str.ptr + str.len - 20) *% c1, 17) *% c2; + const a0: u32 = rotr32(fetch32(str.ptr, str.len - 4) *% c1, 17) *% c2; + const a1: u32 = rotr32(fetch32(str.ptr, str.len - 8) *% c1, 17) *% c2; + const a2: u32 = rotr32(fetch32(str.ptr, str.len - 16) *% c1, 17) *% c2; + const a3: u32 = rotr32(fetch32(str.ptr, str.len - 12) *% c1, 17) *% c2; + const a4: u32 = rotr32(fetch32(str.ptr, str.len - 20) *% c1, 17) *% c2; h ^= a0; h = rotr32(h, 19); @@ -125,11 +130,11 @@ pub const CityHash32 = struct { var iters = (str.len - 1) / 20; var ptr = str.ptr; while (iters != 0) : (iters -= 1) { - const b0: u32 = rotr32(fetch32(ptr) *% c1, 17) *% c2; - const b1: u32 = fetch32(ptr + 4); - const b2: u32 = rotr32(fetch32(ptr + 8) *% c1, 17) *% c2; - const b3: u32 = rotr32(fetch32(ptr + 12) *% c1, 17) *% c2; - const b4: u32 = fetch32(ptr + 16); + const b0: u32 = rotr32(fetch32(ptr, 0) *% c1, 17) *% c2; + const b1: u32 = fetch32(ptr, 4); + const b2: u32 = rotr32(fetch32(ptr, 8) *% c1, 17) *% c2; + const b3: u32 = rotr32(fetch32(ptr, 12) *% c1, 17) *% c2; + const b4: u32 = fetch32(ptr, 16); h ^= b0; h = rotr32(h, 18); @@ -152,7 +157,7 @@ pub const CityHash32 = struct { h = f; f = g; g = t; - ptr += 20; + ptr = offsetPtr(ptr, 20); } g = rotr32(g, 11) *% c1; g = rotr32(g, 17) *% c1; @@ -176,22 +181,6 @@ pub const CityHash64 = struct { const k1: u64 = 0xb492b66fbe98f273; const k2: u64 = 0x9ae16a3b2f90404f; - fn fetch32(ptr: [*]const u8) u32 { - var v: u32 = undefined; - @memcpy(@ptrCast([*]u8, &v), ptr, 4); - if (builtin.endian == .Big) - return @byteSwap(u32, v); - return v; - } - - fn fetch64(ptr: [*]const u8) u64 { - var v: u64 = undefined; - @memcpy(@ptrCast([*]u8, &v), ptr, 8); - if (builtin.endian == .Big) - return @byteSwap(u64, v); - return v; - } - // Rotate right helper fn rotr64(x: u64, comptime r: u64) u64 { return (x >> r) | (x << (64 - r)); @@ -222,16 +211,16 @@ pub const CityHash64 = struct { const len: u64 = @as(u64, str.len); if (len >= 8) { const mul: u64 = k2 +% len *% 2; - const a: u64 = fetch64(str.ptr) +% k2; - const b: u64 = fetch64(str.ptr + str.len - 8); + const a: u64 = fetch64(str.ptr, 0) +% k2; + const b: u64 = fetch64(str.ptr, str.len - 8); const c: u64 = rotr64(b, 37) *% mul +% a; const d: u64 = (rotr64(a, 25) +% b) *% mul; return hashLen16Mul(c, d, mul); } if (len >= 4) { const mul: u64 = k2 +% len *% 2; - const a: u64 = fetch32(str.ptr); - return hashLen16Mul(len +% (a << 3), fetch32(str.ptr + str.len - 4), mul); + const a: u64 = fetch32(str.ptr, 0); + return hashLen16Mul(len +% (a << 3), fetch32(str.ptr, str.len - 4), mul); } if (len > 0) { const a: u8 = str[0]; @@ -247,10 +236,10 @@ pub const CityHash64 = struct { fn hashLen17To32(str: []const u8) u64 { const len: u64 = @as(u64, str.len); const mul: u64 = k2 +% len *% 2; - const a: u64 = fetch64(str.ptr) *% k1; - const b: u64 = fetch64(str.ptr + 8); - const c: u64 = fetch64(str.ptr + str.len - 8) *% mul; - const d: u64 = fetch64(str.ptr + str.len - 16) *% k2; + const a: u64 = fetch64(str.ptr, 0) *% k1; + const b: u64 = fetch64(str.ptr, 8); + const c: u64 = fetch64(str.ptr, str.len - 8) *% mul; + const d: u64 = fetch64(str.ptr, str.len - 16) *% k2; return hashLen16Mul(rotr64(a +% b, 43) +% rotr64(c, 30) +% d, a +% rotr64(b +% k2, 18) +% c, mul); } @@ -258,14 +247,14 @@ pub const CityHash64 = struct { fn hashLen33To64(str: []const u8) u64 { const len: u64 = @as(u64, str.len); const mul: u64 = k2 +% len *% 2; - const a: u64 = fetch64(str.ptr) *% k2; - const b: u64 = fetch64(str.ptr + 8); - const c: u64 = fetch64(str.ptr + str.len - 24); - const d: u64 = fetch64(str.ptr + str.len - 32); - const e: u64 = fetch64(str.ptr + 16) *% k2; - const f: u64 = fetch64(str.ptr + 24) *% 9; - const g: u64 = fetch64(str.ptr + str.len - 8); - const h: u64 = fetch64(str.ptr + str.len - 16) *% mul; + const a: u64 = fetch64(str.ptr, 0) *% k2; + const b: u64 = fetch64(str.ptr, 8); + const c: u64 = fetch64(str.ptr, str.len - 24); + const d: u64 = fetch64(str.ptr, str.len - 32); + const e: u64 = fetch64(str.ptr, 16) *% k2; + const f: u64 = fetch64(str.ptr, 24) *% 9; + const g: u64 = fetch64(str.ptr, str.len - 8); + const h: u64 = fetch64(str.ptr, str.len - 16) *% mul; const u: u64 = rotr64(a +% g, 43) +% (rotr64(b, 30) +% c) *% 9; const v: u64 = ((a +% g) ^ d) +% f +% 1; @@ -297,10 +286,10 @@ pub const CityHash64 = struct { fn weakHashLen32WithSeeds(ptr: [*]const u8, a: u64, b: u64) WeakPair { return @call(.{ .modifier = .always_inline }, weakHashLen32WithSeedsHelper, .{ - fetch64(ptr), - fetch64(ptr + 8), - fetch64(ptr + 16), - fetch64(ptr + 24), + fetch64(ptr, 0), + fetch64(ptr, 8), + fetch64(ptr, 16), + fetch64(ptr, 24), a, b, }); @@ -319,29 +308,29 @@ pub const CityHash64 = struct { var len: u64 = @as(u64, str.len); - var x: u64 = fetch64(str.ptr + str.len - 40); - var y: u64 = fetch64(str.ptr + str.len - 16) +% fetch64(str.ptr + str.len - 56); - var z: u64 = hashLen16(fetch64(str.ptr + str.len - 48) +% len, fetch64(str.ptr + str.len - 24)); - var v: WeakPair = weakHashLen32WithSeeds(str.ptr + str.len - 64, len, z); - var w: WeakPair = weakHashLen32WithSeeds(str.ptr + str.len - 32, y +% k1, x); + var x: u64 = fetch64(str.ptr, str.len - 40); + var y: u64 = fetch64(str.ptr, str.len - 16) +% fetch64(str.ptr, str.len - 56); + var z: u64 = hashLen16(fetch64(str.ptr, str.len - 48) +% len, fetch64(str.ptr, str.len - 24)); + var v: WeakPair = weakHashLen32WithSeeds(offsetPtr(str.ptr, str.len - 64), len, z); + var w: WeakPair = weakHashLen32WithSeeds(offsetPtr(str.ptr, str.len - 32), y +% k1, x); - x = x *% k1 +% fetch64(str.ptr); + x = x *% k1 +% fetch64(str.ptr, 0); len = (len - 1) & ~@intCast(u64, 63); var ptr: [*]const u8 = str.ptr; while (true) { - x = rotr64(x +% y +% v.first +% fetch64(ptr + 8), 37) *% k1; - y = rotr64(y +% v.second +% fetch64(ptr + 48), 42) *% k1; + x = rotr64(x +% y +% v.first +% fetch64(ptr, 8), 37) *% k1; + y = rotr64(y +% v.second +% fetch64(ptr, 48), 42) *% k1; x ^= w.second; - y +%= v.first +% fetch64(ptr + 40); + y +%= v.first +% fetch64(ptr, 40); z = rotr64(z +% w.first, 33) *% k1; v = weakHashLen32WithSeeds(ptr, v.second *% k1, x +% w.first); - w = weakHashLen32WithSeeds(ptr + 32, z +% w.second, y +% fetch64(ptr + 16)); + w = weakHashLen32WithSeeds(offsetPtr(ptr, 32), z +% w.second, y +% fetch64(ptr, 16)); const t: u64 = z; z = x; x = t; - ptr += 64; + ptr = offsetPtr(ptr, 64); len -= 64; if (len == 0) break; @@ -359,27 +348,31 @@ pub const CityHash64 = struct { } }; -fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 { - const hashbytes = hashbits / 8; +fn SMHasherTest(comptime hash_fn: anytype) u32 { + const HashResult = @typeInfo(@TypeOf(hash_fn)).Fn.return_type.?; + var key: [256]u8 = undefined; - var hashes: [hashbytes * 256]u8 = undefined; - var final: [hashbytes]u8 = undefined; + var hashes_bytes: [256 * @sizeOf(HashResult)]u8 = undefined; + var final: HashResult = 0; - @memset(@ptrCast([*]u8, &key[0]), 0, @sizeOf(@TypeOf(key))); - @memset(@ptrCast([*]u8, &hashes[0]), 0, @sizeOf(@TypeOf(hashes))); - @memset(@ptrCast([*]u8, &final[0]), 0, @sizeOf(@TypeOf(final))); + std.mem.set(u8, &key, 0); + std.mem.set(u8, &hashes_bytes, 0); var i: u32 = 0; while (i < 256) : (i += 1) { key[i] = @intCast(u8, i); - var h = hash_fn(key[0..i], 256 - i); - if (builtin.endian == .Big) - h = @byteSwap(@TypeOf(h), h); - @memcpy(@ptrCast([*]u8, &hashes[i * hashbytes]), @ptrCast([*]u8, &h), hashbytes); + var h: HashResult = hash_fn(key[0..i], 256 - i); + + // comptime can't really do reinterpret casting yet, + // so we need to write the bytes manually. + for (hashes_bytes[i * @sizeOf(HashResult) ..][0..@sizeOf(HashResult)]) |*byte| { + byte.* = @truncate(u8, h); + h = h >> 8; + } } - return @truncate(u32, hash_fn(&hashes, 0)); + return @truncate(u32, hash_fn(&hashes_bytes, 0)); } fn CityHash32hashIgnoreSeed(str: []const u8, seed: u32) u32 { @@ -387,13 +380,32 @@ fn CityHash32hashIgnoreSeed(str: []const u8, seed: u32) u32 { } test "cityhash32" { - // Note: SMHasher doesn't provide a 32bit version of the algorithm. - // Note: The implementation was verified against the Google Abseil version. - std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed, 32), 0x68254F81); + const Test = struct { + fn doTest() void { + // Note: SMHasher doesn't provide a 32bit version of the algorithm. + // Note: The implementation was verified against the Google Abseil version. + std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed), 0x68254F81); + std.testing.expectEqual(SMHasherTest(CityHash32hashIgnoreSeed), 0x68254F81); + } + }; + Test.doTest(); + // TODO This is uncommented to prevent OOM on the CI server. Re-enable this test + // case once we ship stage2. + //@setEvalBranchQuota(50000); + //comptime Test.doTest(); } test "cityhash64" { - // Note: This is not compliant with the SMHasher implementation of CityHash64! - // Note: The implementation was verified against the Google Abseil version. - std.testing.expectEqual(SMHasherTest(CityHash64.hashWithSeed, 64), 0x5FABC5C5); + const Test = struct { + fn doTest() void { + // Note: This is not compliant with the SMHasher implementation of CityHash64! + // Note: The implementation was verified against the Google Abseil version. + std.testing.expectEqual(SMHasherTest(CityHash64.hashWithSeed), 0x5FABC5C5); + } + }; + Test.doTest(); + // TODO This is uncommented to prevent OOM on the CI server. Re-enable this test + // case once we ship stage2. + //@setEvalBranchQuota(50000); + //comptime Test.doTest(); } diff --git a/lib/std/hash/crc.zig b/lib/std/hash/crc.zig index 6290369fca..a2d6ed429c 100644 --- a/lib/std/hash/crc.zig +++ b/lib/std/hash/crc.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -102,7 +102,11 @@ pub fn Crc32WithPoly(comptime poly: Polynomial) type { }; } +const please_windows_dont_oom = std.Target.current.os.tag == .windows; + test "crc32 ieee" { + if (please_windows_dont_oom) return error.SkipZigTest; + const Crc32Ieee = Crc32WithPoly(.IEEE); testing.expect(Crc32Ieee.hash("") == 0x00000000); @@ -111,6 +115,8 @@ test "crc32 ieee" { } test "crc32 castagnoli" { + if (please_windows_dont_oom) return error.SkipZigTest; + const Crc32Castagnoli = Crc32WithPoly(.Castagnoli); testing.expect(Crc32Castagnoli.hash("") == 0x00000000); @@ -167,6 +173,8 @@ pub fn Crc32SmallWithPoly(comptime poly: Polynomial) type { } test "small crc32 ieee" { + if (please_windows_dont_oom) return error.SkipZigTest; + const Crc32Ieee = Crc32SmallWithPoly(.IEEE); testing.expect(Crc32Ieee.hash("") == 0x00000000); @@ -175,6 +183,8 @@ test "small crc32 ieee" { } test "small crc32 castagnoli" { + if (please_windows_dont_oom) return error.SkipZigTest; + const Crc32Castagnoli = Crc32SmallWithPoly(.Castagnoli); testing.expect(Crc32Castagnoli.hash("") == 0x00000000); diff --git a/lib/std/hash/fnv.zig b/lib/std/hash/fnv.zig index 81285be9a8..99e3bd482d 100644 --- a/lib/std/hash/fnv.zig +++ b/lib/std/hash/fnv.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash/murmur.zig b/lib/std/hash/murmur.zig index 1e9156be4b..65dd523396 100644 --- a/lib/std/hash/murmur.zig +++ b/lib/std/hash/murmur.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash/wyhash.zig b/lib/std/hash/wyhash.zig index 8799a36b39..45530eccff 100644 --- a/lib/std/hash/wyhash.zig +++ b/lib/std/hash/wyhash.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index d330863277..679575875d 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -17,6 +17,17 @@ const Allocator = mem.Allocator; const Wyhash = std.hash.Wyhash; pub fn getAutoHashFn(comptime K: type) (fn (K) u64) { + comptime { + assert(@hasDecl(std, "StringHashMap")); // detect when the following message needs updated + if (K == []const u8) { + @compileError("std.auto_hash.autoHash does not allow slices here (" ++ + @typeName(K) ++ + ") because the intent is unclear. " ++ + "Consider using std.StringHashMap for hashing the contents of []const u8. " ++ + "Alternatively, consider using std.auto_hash.hash or providing your own hash function instead."); + } + } + return struct { fn hash(key: K) u64 { if (comptime trait.hasUniqueRepresentation(K)) { diff --git a/lib/std/heap.zig b/lib/std/heap.zig index a0484d4fdc..3e1a24beea 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -160,8 +160,8 @@ var c_allocator_state = Allocator{ /// Asserts allocations are within `@alignOf(std.c.max_align_t)` and directly calls /// `malloc`/`free`. Does not attempt to utilize `malloc_usable_size`. /// This allocator is safe to use as the backing allocator with -/// `ArenaAllocator` and `GeneralPurposeAllocator`, and is more optimal in these cases -/// than to using `c_allocator`. +/// `ArenaAllocator` for example and is more optimal in such a case +/// than `c_allocator`. pub const raw_c_allocator = &raw_c_allocator_state; var raw_c_allocator_state = Allocator{ .allocFn = rawCAlloc, @@ -789,7 +789,7 @@ pub fn stackFallback(comptime size: usize, fallback_allocator: *Allocator) Stack .fallback_allocator = fallback_allocator, .fixed_buffer_allocator = undefined, .allocator = Allocator{ - .allocFn = StackFallbackAllocator(size).realloc, + .allocFn = StackFallbackAllocator(size).alloc, .resizeFn = StackFallbackAllocator(size).resize, }, }; @@ -815,25 +815,25 @@ pub fn StackFallbackAllocator(comptime size: usize) type { ptr_align: u29, len_align: u29, return_address: usize, - ) error{OutOfMemory}![*]u8 { + ) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(Self, "allocator", allocator); - return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align) catch - return fallback_allocator.alloc(len, ptr_align); + return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator.allocator, len, ptr_align, len_align, return_address) catch + return self.fallback_allocator.allocFn(self.fallback_allocator, len, ptr_align, len_align, return_address); } fn resize( - self: *Allocator, + allocator: *Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, return_address: usize, - ) error{OutOfMemory}!void { + ) error{OutOfMemory}!usize { const self = @fieldParentPtr(Self, "allocator", allocator); if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { - try self.fixed_buffer_allocator.resize(buf, new_len); + return FixedBufferAllocator.resize(&self.fixed_buffer_allocator.allocator, buf, buf_align, new_len, len_align, return_address); } else { - try self.fallback_allocator.resize(buf, new_len); + return self.fallback_allocator.resizeFn(self.fallback_allocator, buf, buf_align, new_len, len_align, return_address); } } }; @@ -970,6 +970,16 @@ test "FixedBufferAllocator.reset" { testing.expect(y.* == Y); } +test "StackFallbackAllocator" { + const fallback_allocator = page_allocator; + var stack_allocator = stackFallback(4096, fallback_allocator); + + try testAllocator(stack_allocator.get()); + try testAllocatorAligned(stack_allocator.get()); + try testAllocatorLargeAlignment(stack_allocator.get()); + try testAllocatorAlignedShrink(stack_allocator.get()); +} + test "FixedBufferAllocator Reuse memory on realloc" { var small_fixed_buffer: [10]u8 = undefined; // check if we re-use the memory diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index b7ee1d54c1..1b301bbb50 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index acda1e116e..c731f22d66 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -98,7 +98,7 @@ //! in a `std.HashMap` using the backing allocator. const std = @import("std"); -const log = std.log.scoped(.std); +const log = std.log.scoped(.gpa); const math = std.math; const assert = std.debug.assert; const mem = std.mem; @@ -149,19 +149,22 @@ pub const Config = struct { thread_safe: bool = !std.builtin.single_threaded, /// What type of mutex you'd like to use, for thread safety. - /// when specfied, the mutex type must have the same shape as `std.Mutex` and - /// `std.mutex.Dummy`, and have no required fields. Specifying this field causes + /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and + /// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes /// the `thread_safe` field to be ignored. /// /// when null (default): - /// * the mutex type defaults to `std.Mutex` when thread_safe is enabled. - /// * the mutex type defaults to `std.mutex.Dummy` otherwise. + /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. + /// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise. MutexType: ?type = null, /// This is a temporary debugging trick you can use to turn segfaults into more helpful /// logged error messages with stack trace details. The downside is that every allocation /// will be leaked! never_unmap: bool = false, + + /// Enables emitting info messages with the size and address of every allocation. + verbose_log: bool = false, }; pub fn GeneralPurposeAllocator(comptime config: Config) type { @@ -187,9 +190,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const mutex_init = if (config.MutexType) |T| T{} else if (config.thread_safe) - std.Mutex{} + std.Thread.Mutex{} else - std.mutex.Dummy{}; + std.Thread.Mutex.Dummy{}; const stack_n = config.stack_trace_frames; const one_trace_size = @sizeOf(usize) * stack_n; @@ -314,7 +317,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { if (is_used) { const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index); const stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc); - log.err("Memory leak detected: {}", .{stack_trace}); + log.err("Memory leak detected: {s}", .{stack_trace}); leaks = true; } if (bit_index == math.maxInt(u3)) @@ -342,7 +345,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { } var it = self.large_allocations.iterator(); while (it.next()) |large_alloc| { - log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); + log.err("Memory leak detected: {s}", .{large_alloc.value.getStackTrace()}); leaks = true; } return leaks; @@ -443,7 +446,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { .index = 0, }; std.debug.captureStackTrace(ret_addr, &free_stack_trace); - log.err("Allocation size {} bytes does not match free size {}. Allocation: {} Free: {}", .{ + log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {s} Free: {s}", .{ entry.value.bytes.len, old_mem.len, entry.value.getStackTrace(), @@ -454,10 +457,19 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const result_len = try self.backing_allocator.resizeFn(self.backing_allocator, old_mem, old_align, new_size, len_align, ret_addr); if (result_len == 0) { + if (config.verbose_log) { + log.info("large free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); + } + self.large_allocations.removeAssertDiscard(@ptrToInt(old_mem.ptr)); return 0; } + if (config.verbose_log) { + log.info("large resize {d} bytes at {*} to {d}", .{ + old_mem.len, old_mem.ptr, new_size, + }); + } entry.value.bytes = old_mem.ptr[0..result_len]; collectStackTrace(ret_addr, &entry.value.stack_addresses); return result_len; @@ -526,7 +538,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { .index = 0, }; std.debug.captureStackTrace(ret_addr, &second_free_stack_trace); - log.err("Double free detected. Allocation: {} First free: {} Second free: {}", .{ + log.err("Double free detected. Allocation: {s} First free: {s} Second free: {s}", .{ alloc_stack_trace, free_stack_trace, second_free_stack_trace, @@ -568,6 +580,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { } else { @memset(old_mem.ptr, undefined, old_mem.len); } + if (config.verbose_log) { + log.info("small free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr }); + } return @as(usize, 0); } const new_aligned_size = math.max(new_size, old_align); @@ -576,6 +591,11 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { if (old_mem.len > new_size) { @memset(old_mem.ptr + new_size, undefined, old_mem.len - new_size); } + if (config.verbose_log) { + log.info("small resize {d} bytes at {*} to {d}", .{ + old_mem.len, old_mem.ptr, new_size, + }); + } return new_size; } return error.OutOfMemory; @@ -623,6 +643,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { gop.entry.value.bytes = slice; collectStackTrace(ret_addr, &gop.entry.value.stack_addresses); + if (config.verbose_log) { + log.info("large alloc {d} bytes at {*}", .{ slice.len, slice.ptr }); + } return slice; } @@ -632,6 +655,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); const ptr = try self.allocSlot(new_size_class, ret_addr); + if (config.verbose_log) { + log.info("small alloc {d} bytes at {*}", .{ len, ptr }); + } return ptr[0..len]; } @@ -869,9 +895,9 @@ test "realloc large object to small object" { } test "overrideable mutexes" { - var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Mutex }){ + var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Thread.Mutex }){ .backing_allocator = std.testing.allocator, - .mutex = std.Mutex{}, + .mutex = std.Thread.Mutex{}, }; defer std.testing.expect(!gpa.deinit()); const allocator = &gpa.allocator; diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index f4e0ada764..7885571ab3 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/lib/std/heap/logging_allocator.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,22 +9,22 @@ const Allocator = std.mem.Allocator; /// This allocator is used in front of another allocator and logs to the provided stream /// on every call to the allocator. Stream errors are ignored. /// If https://github.com/ziglang/zig/issues/2586 is implemented, this API can be improved. -pub fn LoggingAllocator(comptime OutStreamType: type) type { +pub fn LoggingAllocator(comptime Writer: type) type { return struct { allocator: Allocator, parent_allocator: *Allocator, - out_stream: OutStreamType, + writer: Writer, const Self = @This(); - pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self { + pub fn init(parent_allocator: *Allocator, writer: Writer) Self { return Self{ .allocator = Allocator{ .allocFn = alloc, .resizeFn = resize, }, .parent_allocator = parent_allocator, - .out_stream = out_stream, + .writer = writer, }; } @@ -36,12 +36,12 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { ra: usize, ) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(Self, "allocator", allocator); - self.out_stream.print("alloc : {}", .{len}) catch {}; + self.writer.print("alloc : {}", .{len}) catch {}; const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra); if (result) |buff| { - self.out_stream.print(" success!\n", .{}) catch {}; + self.writer.print(" success!\n", .{}) catch {}; } else |err| { - self.out_stream.print(" failure!\n", .{}) catch {}; + self.writer.print(" failure!\n", .{}) catch {}; } return result; } @@ -56,20 +56,20 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { ) error{OutOfMemory}!usize { const self = @fieldParentPtr(Self, "allocator", allocator); if (new_len == 0) { - self.out_stream.print("free : {}\n", .{buf.len}) catch {}; + self.writer.print("free : {}\n", .{buf.len}) catch {}; } else if (new_len <= buf.len) { - self.out_stream.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {}; + self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {}; } else { - self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; + self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; } if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| { if (new_len > buf.len) { - self.out_stream.print(" success!\n", .{}) catch {}; + self.writer.print(" success!\n", .{}) catch {}; } return resized_len; } else |e| { std.debug.assert(new_len > buf.len); - self.out_stream.print(" failure!\n", .{}) catch {}; + self.writer.print(" failure!\n", .{}) catch {}; return e; } } @@ -78,9 +78,9 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { pub fn loggingAllocator( parent_allocator: *Allocator, - out_stream: anytype, -) LoggingAllocator(@TypeOf(out_stream)) { - return LoggingAllocator(@TypeOf(out_stream)).init(parent_allocator, out_stream); + writer: anytype, +) LoggingAllocator(@TypeOf(writer)) { + return LoggingAllocator(@TypeOf(writer)).init(parent_allocator, writer); } test "LoggingAllocator" { @@ -89,7 +89,7 @@ test "LoggingAllocator" { var allocator_buf: [10]u8 = undefined; var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); - const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator; + const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator; var a = try allocator.alloc(u8, 10); a = allocator.shrink(a, 5); diff --git a/lib/std/io.zig b/lib/std/io.zig index 3f02128a6c..b529c57866 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -107,26 +107,14 @@ pub fn getStdIn() File { } pub const Reader = @import("io/reader.zig").Reader; -/// Deprecated: use `Reader` -pub const InStream = Reader; pub const Writer = @import("io/writer.zig").Writer; -/// Deprecated: use `Writer` -pub const OutStream = Writer; pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter; pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter; -/// Deprecated: use `BufferedWriter` -pub const BufferedOutStream = BufferedWriter; -/// Deprecated: use `bufferedWriter` -pub const bufferedOutStream = bufferedWriter; pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader; pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader; -/// Deprecated: use `BufferedReader` -pub const BufferedInStream = BufferedReader; -/// Deprecated: use `bufferedReader` -pub const bufferedInStream = bufferedReader; pub const PeekStream = @import("io/peek_stream.zig").PeekStream; pub const peekStream = @import("io/peek_stream.zig").peekStream; @@ -136,55 +124,33 @@ pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferS pub const CWriter = @import("io/c_writer.zig").CWriter; pub const cWriter = @import("io/c_writer.zig").cWriter; -/// Deprecated: use `CWriter` -pub const COutStream = CWriter; -/// Deprecated: use `cWriter` -pub const cOutStream = cWriter; + +pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader; +pub const limitedReader = @import("io/limited_reader.zig").limitedReader; pub const CountingWriter = @import("io/counting_writer.zig").CountingWriter; pub const countingWriter = @import("io/counting_writer.zig").countingWriter; -/// Deprecated: use `CountingWriter` -pub const CountingOutStream = CountingWriter; -/// Deprecated: use `countingWriter` -pub const countingOutStream = countingWriter; +pub const CountingReader = @import("io/counting_reader.zig").CountingReader; +pub const countingReader = @import("io/counting_reader.zig").countingReader; pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter; pub const multiWriter = @import("io/multi_writer.zig").multiWriter; -/// Deprecated: use `MultiWriter` -pub const MultiOutStream = MultiWriter; -/// Deprecated: use `multiWriter` -pub const multiOutStream = multiWriter; pub const BitReader = @import("io/bit_reader.zig").BitReader; pub const bitReader = @import("io/bit_reader.zig").bitReader; -/// Deprecated: use `BitReader` -pub const BitInStream = BitReader; -/// Deprecated: use `bitReader` -pub const bitInStream = bitReader; pub const BitWriter = @import("io/bit_writer.zig").BitWriter; pub const bitWriter = @import("io/bit_writer.zig").bitWriter; -/// Deprecated: use `BitWriter` -pub const BitOutStream = BitWriter; -/// Deprecated: use `bitWriter` -pub const bitOutStream = bitWriter; - -pub const AutoIndentingStream = @import("io/auto_indenting_stream.zig").AutoIndentingStream; -pub const autoIndentingStream = @import("io/auto_indenting_stream.zig").autoIndentingStream; pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream; pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream; -pub const FindByteOutStream = @import("io/find_byte_out_stream.zig").FindByteOutStream; -pub const findByteOutStream = @import("io/find_byte_out_stream.zig").findByteOutStream; - -pub const Packing = @import("io/serialization.zig").Packing; - -pub const Serializer = @import("io/serialization.zig").Serializer; -pub const serializer = @import("io/serialization.zig").serializer; - -pub const Deserializer = @import("io/serialization.zig").Deserializer; -pub const deserializer = @import("io/serialization.zig").deserializer; +pub const FindByteWriter = @import("io/find_byte_writer.zig").FindByteWriter; +pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter; +/// Deprecated: use `FindByteWriter`. +pub const FindByteOutStream = FindByteWriter; +/// Deprecated: use `findByteWriter`. +pub const findByteOutStream = findByteWriter; pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; @@ -193,12 +159,7 @@ pub const StreamSource = @import("io/stream_source.zig").StreamSource; /// A Writer that doesn't write to anything. pub const null_writer = @as(NullWriter, .{ .context = {} }); -/// Deprecated: use `null_writer` -pub const null_out_stream = null_writer; - const NullWriter = Writer(void, error{}, dummyWrite); -/// Deprecated: use NullWriter -const NullOutStream = NullWriter; fn dummyWrite(context: void, data: []const u8) error{}!usize { return data.len; } @@ -207,7 +168,7 @@ test "null_writer" { null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; } -test "" { +test { _ = @import("io/bit_reader.zig"); _ = @import("io/bit_writer.zig"); _ = @import("io/buffered_atomic_file.zig"); @@ -215,12 +176,12 @@ test "" { _ = @import("io/buffered_writer.zig"); _ = @import("io/c_writer.zig"); _ = @import("io/counting_writer.zig"); + _ = @import("io/counting_reader.zig"); _ = @import("io/fixed_buffer_stream.zig"); _ = @import("io/reader.zig"); _ = @import("io/writer.zig"); _ = @import("io/peek_stream.zig"); _ = @import("io/seekable_stream.zig"); - _ = @import("io/serialization.zig"); _ = @import("io/stream_source.zig"); _ = @import("io/test.zig"); } diff --git a/lib/std/io/auto_indenting_stream.zig b/lib/std/io/auto_indenting_stream.zig deleted file mode 100644 index bea4af7519..0000000000 --- a/lib/std/io/auto_indenting_stream.zig +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. - -const std = @import("../std.zig"); -const io = std.io; -const mem = std.mem; -const assert = std.debug.assert; - -/// Automatically inserts indentation of written data by keeping -/// track of the current indentation level -pub fn AutoIndentingStream(comptime UnderlyingWriter: type) type { - return struct { - const Self = @This(); - pub const Error = UnderlyingWriter.Error; - pub const Writer = io.Writer(*Self, Error, write); - - underlying_writer: UnderlyingWriter, - - indent_count: usize = 0, - indent_delta: usize, - current_line_empty: bool = true, - indent_one_shot_count: usize = 0, // automatically popped when applied - applied_indent: usize = 0, // the most recently applied indent - indent_next_line: usize = 0, // not used until the next line - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - - pub fn write(self: *Self, bytes: []const u8) Error!usize { - if (bytes.len == 0) - return @as(usize, 0); - - try self.applyIndent(); - return self.writeNoIndent(bytes); - } - - // Change the indent delta without changing the final indentation level - pub fn setIndentDelta(self: *Self, indent_delta: usize) void { - if (self.indent_delta == indent_delta) { - return; - } else if (self.indent_delta > indent_delta) { - assert(self.indent_delta % indent_delta == 0); - self.indent_count = self.indent_count * (self.indent_delta / indent_delta); - } else { - // assert that the current indentation (in spaces) in a multiple of the new delta - assert((self.indent_count * self.indent_delta) % indent_delta == 0); - self.indent_count = self.indent_count / (indent_delta / self.indent_delta); - } - self.indent_delta = indent_delta; - } - - fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { - if (bytes.len == 0) - return @as(usize, 0); - - try self.underlying_writer.writeAll(bytes); - if (bytes[bytes.len - 1] == '\n') - self.resetLine(); - return bytes.len; - } - - pub fn insertNewline(self: *Self) Error!void { - _ = try self.writeNoIndent("\n"); - } - - fn resetLine(self: *Self) void { - self.current_line_empty = true; - self.indent_next_line = 0; - } - - /// Insert a newline unless the current line is blank - pub fn maybeInsertNewline(self: *Self) Error!void { - if (!self.current_line_empty) - try self.insertNewline(); - } - - /// Push default indentation - pub fn pushIndent(self: *Self) void { - // Doesn't actually write any indentation. - // Just primes the stream to be able to write the correct indentation if it needs to. - self.indent_count += 1; - } - - /// Push an indent that is automatically popped after being applied - pub fn pushIndentOneShot(self: *Self) void { - self.indent_one_shot_count += 1; - self.pushIndent(); - } - - /// Turns all one-shot indents into regular indents - /// Returns number of indents that must now be manually popped - pub fn lockOneShotIndent(self: *Self) usize { - var locked_count = self.indent_one_shot_count; - self.indent_one_shot_count = 0; - return locked_count; - } - - /// Push an indent that should not take effect until the next line - pub fn pushIndentNextLine(self: *Self) void { - self.indent_next_line += 1; - self.pushIndent(); - } - - pub fn popIndent(self: *Self) void { - assert(self.indent_count != 0); - self.indent_count -= 1; - - if (self.indent_next_line > 0) - self.indent_next_line -= 1; - } - - /// Writes ' ' bytes if the current line is empty - fn applyIndent(self: *Self) Error!void { - const current_indent = self.currentIndent(); - if (self.current_line_empty and current_indent > 0) { - try self.underlying_writer.writeByteNTimes(' ', current_indent); - self.applied_indent = current_indent; - } - - self.indent_count -= self.indent_one_shot_count; - self.indent_one_shot_count = 0; - self.current_line_empty = false; - } - - /// Checks to see if the most recent indentation exceeds the currently pushed indents - pub fn isLineOverIndented(self: *Self) bool { - if (self.current_line_empty) return false; - return self.applied_indent > self.currentIndent(); - } - - fn currentIndent(self: *Self) usize { - var indent_current: usize = 0; - if (self.indent_count > 0) { - const indent_count = self.indent_count - self.indent_next_line; - indent_current = indent_count * self.indent_delta; - } - return indent_current; - } - }; -} - -pub fn autoIndentingStream( - indent_delta: usize, - underlying_writer: anytype, -) AutoIndentingStream(@TypeOf(underlying_writer)) { - return AutoIndentingStream(@TypeOf(underlying_writer)){ - .underlying_writer = underlying_writer, - .indent_delta = indent_delta, - }; -} diff --git a/lib/std/io/bit_in_stream.zig b/lib/std/io/bit_in_stream.zig deleted file mode 100644 index a027deb802..0000000000 --- a/lib/std/io/bit_in_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.bit_reader.BitReader` -pub const BitInStream = @import("./bit_reader.zig").BitReader; - -/// Deprecated: use `std.io.bit_reader.bitReader` -pub const bitInStream = @import("./bit_reader.zig").bitReader; diff --git a/lib/std/io/bit_out_stream.zig b/lib/std/io/bit_out_stream.zig deleted file mode 100644 index 171fb542da..0000000000 --- a/lib/std/io/bit_out_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.bit_writer.BitWriter` -pub const BitOutStream = @import("./bit_writer.zig").BitWriter; - -/// Deprecated: use `std.io.bit_writer.bitWriter` -pub const bitOutStream = @import("./bit_writer.zig").bitWriter; diff --git a/lib/std/io/bit_reader.zig b/lib/std/io/bit_reader.zig index 75d217068e..213cd2b503 100644 --- a/lib/std/io/bit_reader.zig +++ b/lib/std/io/bit_reader.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -21,8 +21,6 @@ pub fn BitReader(endian: builtin.Endian, comptime ReaderType: type) type { pub const Error = ReaderType.Error; pub const Reader = io.Reader(*Self, Error, read); - /// Deprecated: use `Reader` - pub const InStream = io.InStream(*Self, Error, read); const Self = @This(); const u8_bit_count = comptime meta.bitCount(u8); @@ -165,11 +163,6 @@ pub fn BitReader(endian: builtin.Endian, comptime ReaderType: type) type { pub fn reader(self: *Self) Reader { return .{ .context = self }; } - - /// Deprecated: use `reader` - pub fn inStream(self: *Self) InStream { - return .{ .context = self }; - } }; } diff --git a/lib/std/io/bit_writer.zig b/lib/std/io/bit_writer.zig index d2ea9b525e..3ad2b75efb 100644 --- a/lib/std/io/bit_writer.zig +++ b/lib/std/io/bit_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -21,8 +21,6 @@ pub fn BitWriter(endian: builtin.Endian, comptime WriterType: type) type { pub const Error = WriterType.Error; pub const Writer = io.Writer(*Self, Error, write); - /// Deprecated: use `Writer` - pub const OutStream = io.OutStream(*Self, Error, write); const Self = @This(); const u8_bit_count = comptime meta.bitCount(u8); @@ -141,10 +139,6 @@ pub fn BitWriter(endian: builtin.Endian, comptime WriterType: type) type { pub fn writer(self: *Self) Writer { return .{ .context = self }; } - /// Deprecated: use `writer` - pub fn outStream(self: *Self) OutStream { - return .{ .context = self }; - } }; } diff --git a/lib/std/io/buffered_atomic_file.zig b/lib/std/io/buffered_atomic_file.zig index 6284d4e44f..1aed190a47 100644 --- a/lib/std/io/buffered_atomic_file.zig +++ b/lib/std/io/buffered_atomic_file.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -10,13 +10,13 @@ const File = std.fs.File; pub const BufferedAtomicFile = struct { atomic_file: fs.AtomicFile, - file_stream: File.OutStream, - buffered_stream: BufferedOutStream, + file_writer: File.Writer, + buffered_writer: BufferedWriter, allocator: *mem.Allocator, pub const buffer_size = 4096; - pub const BufferedOutStream = std.io.BufferedOutStream(buffer_size, File.OutStream); - pub const OutStream = std.io.OutStream(*BufferedOutStream, BufferedOutStream.Error, BufferedOutStream.write); + pub const BufferedWriter = std.io.BufferedWriter(buffer_size, File.Writer); + pub const Writer = std.io.Writer(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write); /// TODO when https://github.com/ziglang/zig/issues/2761 is solved /// this API will not need an allocator @@ -29,8 +29,8 @@ pub const BufferedAtomicFile = struct { var self = try allocator.create(BufferedAtomicFile); self.* = BufferedAtomicFile{ .atomic_file = undefined, - .file_stream = undefined, - .buffered_stream = undefined, + .file_writer = undefined, + .buffered_writer = undefined, .allocator = allocator, }; errdefer allocator.destroy(self); @@ -38,8 +38,8 @@ pub const BufferedAtomicFile = struct { self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options); errdefer self.atomic_file.deinit(); - self.file_stream = self.atomic_file.file.outStream(); - self.buffered_stream = .{ .unbuffered_writer = self.file_stream }; + self.file_writer = self.atomic_file.file.writer(); + self.buffered_writer = .{ .unbuffered_writer = self.file_writer }; return self; } @@ -50,11 +50,11 @@ pub const BufferedAtomicFile = struct { } pub fn finish(self: *BufferedAtomicFile) !void { - try self.buffered_stream.flush(); + try self.buffered_writer.flush(); try self.atomic_file.finish(); } - pub fn stream(self: *BufferedAtomicFile) OutStream { - return .{ .context = &self.buffered_stream }; + pub fn writer(self: *BufferedAtomicFile) Writer { + return .{ .context = &self.buffered_writer }; } }; diff --git a/lib/std/io/buffered_in_stream.zig b/lib/std/io/buffered_in_stream.zig deleted file mode 100644 index f055978152..0000000000 --- a/lib/std/io/buffered_in_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.buffered_reader.BufferedReader` -pub const BufferedInStream = @import("./buffered_reader.zig").BufferedReader; - -/// Deprecated: use `std.io.buffered_reader.bufferedReader` -pub const bufferedInStream = @import("./buffered_reader.zig").bufferedReader; diff --git a/lib/std/io/buffered_out_stream.zig b/lib/std/io/buffered_out_stream.zig deleted file mode 100644 index 5f1eaa6faf..0000000000 --- a/lib/std/io/buffered_out_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.buffered_writer.BufferedWriter` -pub const BufferedOutStream = @import("./buffered_writer.zig").BufferedWriter; - -/// Deprecated: use `std.io.buffered_writer.bufferedWriter` -pub const bufferedOutStream = @import("./buffered_writer.zig").bufferedWriter; diff --git a/lib/std/io/buffered_reader.zig b/lib/std/io/buffered_reader.zig index 58c4f3b4fc..5fda7f2741 100644 --- a/lib/std/io/buffered_reader.zig +++ b/lib/std/io/buffered_reader.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,8 +15,6 @@ pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) ty pub const Error = ReaderType.Error; pub const Reader = io.Reader(*Self, Error, read); - /// Deprecated: use `Reader` - pub const InStream = Reader; const Self = @This(); const FifoType = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = buffer_size }); @@ -45,11 +43,6 @@ pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) ty pub fn reader(self: *Self) Reader { return .{ .context = self }; } - - /// Deprecated: use `reader` - pub fn inStream(self: *Self) InStream { - return .{ .context = self }; - } }; } diff --git a/lib/std/io/buffered_writer.zig b/lib/std/io/buffered_writer.zig index bee3ff48af..056ff08987 100644 --- a/lib/std/io/buffered_writer.zig +++ b/lib/std/io/buffered_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -13,8 +13,6 @@ pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) ty pub const Error = WriterType.Error; pub const Writer = io.Writer(*Self, Error, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; const Self = @This(); const FifoType = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = buffer_size }); @@ -32,11 +30,6 @@ pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) ty return .{ .context = self }; } - /// Deprecated: use writer - pub fn outStream(self: *Self) Writer { - return .{ .context = self }; - } - pub fn write(self: *Self, bytes: []const u8) Error!usize { if (bytes.len >= self.fifo.writableLength()) { try self.flush(); diff --git a/lib/std/io/c_out_stream.zig b/lib/std/io/c_out_stream.zig deleted file mode 100644 index 69f4d9f5af..0000000000 --- a/lib/std/io/c_out_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.c_writer.CWriter` -pub const COutStream = @import("./c_writer.zig").CWriter; - -/// Deprecated: use `std.io.c_writer.cWriter` -pub const cOutStream = @import("./c_writer.zig").cWriter; diff --git a/lib/std/io/c_writer.zig b/lib/std/io/c_writer.zig index 9fd10d827e..fa7d7eb13a 100644 --- a/lib/std/io/c_writer.zig +++ b/lib/std/io/c_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -34,7 +34,7 @@ fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!u } } -test "" { +test { if (!builtin.link_libc) return error.SkipZigTest; const filename = "tmp_io_test_file.txt"; diff --git a/lib/std/io/change_detection_stream.zig b/lib/std/io/change_detection_stream.zig index 52c3372094..57ef8a82bd 100644 --- a/lib/std/io/change_detection_stream.zig +++ b/lib/std/io/change_detection_stream.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/io/counting_out_stream.zig b/lib/std/io/counting_out_stream.zig deleted file mode 100644 index fecdf8adb0..0000000000 --- a/lib/std/io/counting_out_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.counting_writer.CountingWriter` -pub const CountingOutStream = @import("./counting_writer.zig").CountingWriter; - -/// Deprecated: use `std.io.counting_writer.countingWriter` -pub const countingOutStream = @import("./counting_writer.zig").countingWriter; diff --git a/lib/std/io/counting_reader.zig b/lib/std/io/counting_reader.zig new file mode 100644 index 0000000000..1369155a73 --- /dev/null +++ b/lib/std/io/counting_reader.zig @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("../std.zig"); +const io = std.io; +const testing = std.testing; + +/// A Reader that counts how many bytes has been read from it. +pub fn CountingReader(comptime ReaderType: anytype) type { + return struct { + child_reader: ReaderType, + bytes_read: u64 = 0, + + pub const Error = ReaderType.Error; + pub const Reader = io.Reader(*@This(), Error, read); + + pub fn read(self: *@This(), buf: []u8) Error!usize { + const amt = try self.child_reader.read(buf); + self.bytes_read += amt; + return amt; + } + + pub fn reader(self: *@This()) Reader { + return .{ .context = self }; + } + }; +} + +pub fn countingReader(reader: anytype) CountingReader(@TypeOf(reader)) { + return .{ .child_reader = reader }; +} + +test "io.CountingReader" { + const bytes = "yay" ** 100; + var fbs = io.fixedBufferStream(bytes); + + var counting_stream = countingReader(fbs.reader()); + const stream = counting_stream.reader(); + + //read and discard all bytes + while (stream.readByte()) |_| {} else |err| { + testing.expect(err == error.EndOfStream); + } + + testing.expect(counting_stream.bytes_read == bytes.len); +} diff --git a/lib/std/io/counting_writer.zig b/lib/std/io/counting_writer.zig index aefd459b90..f68c257486 100644 --- a/lib/std/io/counting_writer.zig +++ b/lib/std/io/counting_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,8 +15,6 @@ pub fn CountingWriter(comptime WriterType: type) type { pub const Error = WriterType.Error; pub const Writer = io.Writer(*Self, Error, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; const Self = @This(); @@ -29,11 +27,6 @@ pub fn CountingWriter(comptime WriterType: type) type { pub fn writer(self: *Self) Writer { return .{ .context = self }; } - - /// Deprecated: use `writer` - pub fn outStream(self: *Self) OutStream { - return .{ .context = self }; - } }; } diff --git a/lib/std/io/find_byte_out_stream.zig b/lib/std/io/find_byte_writer.zig index 70e1e190b1..db45114d3e 100644 --- a/lib/std/io/find_byte_out_stream.zig +++ b/lib/std/io/find_byte_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -8,9 +8,9 @@ const std = @import("../std.zig"); const io = std.io; const assert = std.debug.assert; -/// An OutStream that returns whether the given character has been written to it. +/// A Writer that returns whether the given character has been written to it. /// The contents are not written to anything. -pub fn FindByteOutStream(comptime UnderlyingWriter: type) type { +pub fn FindByteWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); pub const Error = UnderlyingWriter.Error; @@ -37,8 +37,8 @@ pub fn FindByteOutStream(comptime UnderlyingWriter: type) type { }; } -pub fn findByteOutStream(byte: u8, underlying_writer: anytype) FindByteOutStream(@TypeOf(underlying_writer)) { - return FindByteOutStream(@TypeOf(underlying_writer)){ +pub fn findByteWriter(byte: u8, underlying_writer: anytype) FindByteWriter(@TypeOf(underlying_writer)) { + return FindByteWriter(@TypeOf(underlying_writer)){ .underlying_writer = underlying_writer, .byte = byte, .byte_found = false, diff --git a/lib/std/io/fixed_buffer_stream.zig b/lib/std/io/fixed_buffer_stream.zig index b1d2aaf89a..f86fd5a8d8 100644 --- a/lib/std/io/fixed_buffer_stream.zig +++ b/lib/std/io/fixed_buffer_stream.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -23,11 +23,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { pub const GetSeekPosError = error{}; pub const Reader = io.Reader(*Self, ReadError, read); - /// Deprecated: use `Reader` - pub const InStream = io.InStream(*Self, ReadError, read); pub const Writer = io.Writer(*Self, WriteError, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; pub const SeekableStream = io.SeekableStream( *Self, @@ -45,20 +41,10 @@ pub fn FixedBufferStream(comptime Buffer: type) type { return .{ .context = self }; } - /// Deprecated: use `inStream` - pub fn inStream(self: *Self) InStream { - return .{ .context = self }; - } - pub fn writer(self: *Self) Writer { return .{ .context = self }; } - /// Deprecated: use `writer` - pub fn outStream(self: *Self) OutStream { - return .{ .context = self }; - } - pub fn seekableStream(self: *Self) SeekableStream { return .{ .context = self }; } @@ -147,7 +133,7 @@ test "FixedBufferStream output" { var fbs = fixedBufferStream(&buf); const stream = fbs.writer(); - try stream.print("{}{}!", .{ "Hello", "World" }); + try stream.print("{s}{s}!", .{ "Hello", "World" }); testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); } diff --git a/lib/std/io/in_stream.zig b/lib/std/io/in_stream.zig deleted file mode 100644 index 4583591d42..0000000000 --- a/lib/std/io/in_stream.zig +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.reader.Reader` -pub const InStream = @import("./reader.zig").Reader; diff --git a/lib/std/io/limited_reader.zig b/lib/std/io/limited_reader.zig new file mode 100644 index 0000000000..734558b1e6 --- /dev/null +++ b/lib/std/io/limited_reader.zig @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("../std.zig"); +const io = std.io; +const assert = std.debug.assert; +const testing = std.testing; + +pub fn LimitedReader(comptime ReaderType: type) type { + return struct { + inner_reader: ReaderType, + bytes_left: u64, + + pub const Error = ReaderType.Error; + pub const Reader = io.Reader(*Self, Error, read); + + const Self = @This(); + + pub fn read(self: *Self, dest: []u8) Error!usize { + const max_read = std.math.min(self.bytes_left, dest.len); + const n = try self.inner_reader.read(dest[0..max_read]); + self.bytes_left -= n; + return n; + } + + pub fn reader(self: *Self) Reader { + return .{ .context = self }; + } + }; +} + +/// Returns an initialised `LimitedReader` +/// `bytes_left` is a `u64` to be able to take 64 bit file offsets +pub fn limitedReader(inner_reader: anytype, bytes_left: u64) LimitedReader(@TypeOf(inner_reader)) { + return .{ .inner_reader = inner_reader, .bytes_left = bytes_left }; +} + +test "basic usage" { + const data = "hello world"; + var fbs = std.io.fixedBufferStream(data); + var early_stream = limitedReader(fbs.reader(), 3); + + var buf: [5]u8 = undefined; + testing.expectEqual(@as(usize, 3), try early_stream.reader().read(&buf)); + testing.expectEqualSlices(u8, data[0..3], buf[0..3]); + testing.expectEqual(@as(usize, 0), try early_stream.reader().read(&buf)); + testing.expectError(error.EndOfStream, early_stream.reader().skipBytes(10, .{})); +} diff --git a/lib/std/io/multi_out_stream.zig b/lib/std/io/multi_out_stream.zig deleted file mode 100644 index 7b96cc3d15..0000000000 --- a/lib/std/io/multi_out_stream.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -/// Deprecated: use `std.io.multi_writer.MultiWriter` -pub const MultiOutStream = @import("./multi_writer.zig").MultiWriter; - -/// Deprecated: use `std.io.multi_writer.multiWriter` -pub const multiOutStream = @import("./multi_writer.zig").multiWriter; diff --git a/lib/std/io/multi_writer.zig b/lib/std/io/multi_writer.zig index 7ee43eddeb..639dd3cd18 100644 --- a/lib/std/io/multi_writer.zig +++ b/lib/std/io/multi_writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -22,18 +22,11 @@ pub fn MultiWriter(comptime Writers: type) type { pub const Error = ErrSet; pub const Writer = io.Writer(*Self, Error, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; pub fn writer(self: *Self) Writer { return .{ .context = self }; } - /// Deprecated: use `writer` - pub fn outStream(self: *Self) OutStream { - return .{ .context = self }; - } - pub fn write(self: *Self, bytes: []const u8) Error!usize { var batch = std.event.Batch(Error!void, self.streams.len, .auto_async).init(); comptime var i = 0; diff --git a/lib/std/io/peek_stream.zig b/lib/std/io/peek_stream.zig index 82554d05ca..b431b0184d 100644 --- a/lib/std/io/peek_stream.zig +++ b/lib/std/io/peek_stream.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -16,13 +16,11 @@ pub fn PeekStream( comptime ReaderType: type, ) type { return struct { - unbuffered_in_stream: ReaderType, + unbuffered_reader: ReaderType, fifo: FifoType, pub const Error = ReaderType.Error; pub const Reader = io.Reader(*Self, Error, read); - /// Deprecated: use `Reader` - pub const InStream = Reader; const Self = @This(); const FifoType = std.fifo.LinearFifo(u8, buffer_type); @@ -31,7 +29,7 @@ pub fn PeekStream( .Static => struct { pub fn init(base: ReaderType) Self { return .{ - .unbuffered_in_stream = base, + .unbuffered_reader = base, .fifo = FifoType.init(), }; } @@ -39,7 +37,7 @@ pub fn PeekStream( .Slice => struct { pub fn init(base: ReaderType, buf: []u8) Self { return .{ - .unbuffered_in_stream = base, + .unbuffered_reader = base, .fifo = FifoType.init(buf), }; } @@ -47,7 +45,7 @@ pub fn PeekStream( .Dynamic => struct { pub fn init(base: ReaderType, allocator: *mem.Allocator) Self { return .{ - .unbuffered_in_stream = base, + .unbuffered_reader = base, .fifo = FifoType.init(allocator), }; } @@ -68,18 +66,13 @@ pub fn PeekStream( if (dest_index == dest.len) return dest_index; // ask the backing stream for more - dest_index += try self.unbuffered_in_stream.read(dest[dest_index..]); + dest_index += try self.unbuffered_reader.read(dest[dest_index..]); return dest_index; } pub fn reader(self: *Self) Reader { return .{ .context = self }; } - - /// Deprecated: use `reader` - pub fn inStream(self: *Self) InStream { - return .{ .context = self }; - } }; } diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig index b9a9eb3eeb..916e2155fa 100644 --- a/lib/std/io/reader.zig +++ b/lib/std/io/reader.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -76,12 +76,12 @@ pub fn Reader( start_index += bytes_read; if (start_index - original_len > max_append_size) { - array_list.shrink(original_len + max_append_size); + array_list.shrinkAndFree(original_len + max_append_size); return error.StreamTooLong; } if (bytes_read != dest_slice.len) { - array_list.shrink(start_index); + array_list.shrinkAndFree(start_index); return; } @@ -101,35 +101,6 @@ pub fn Reader( return array_list.toOwnedSlice(); } - /// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` or end-of-stream is found. - /// Does not include the delimiter in the result. - /// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the - /// `std.ArrayList` is populated with `max_size` bytes from the stream. - pub fn readUntilDelimiterOrEofArrayList( - self: Self, - array_list: *std.ArrayList(u8), - delimiter: u8, - max_size: usize, - ) !void { - array_list.shrink(0); - while (true) { - var byte: u8 = self.readByte() catch |err| switch (err) { - error.EndOfStream => return, - else => |e| return e, - }; - - if (byte == delimiter) { - return; - } - - if (array_list.items.len == max_size) { - return error.StreamTooLong; - } - - try array_list.append(byte); - } - } - /// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found. /// Does not include the delimiter in the result. /// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the @@ -140,7 +111,7 @@ pub fn Reader( delimiter: u8, max_size: usize, ) !void { - array_list.shrink(0); + array_list.shrinkRetainingCapacity(0); while (true) { var byte: u8 = try self.readByte(); @@ -173,7 +144,10 @@ pub fn Reader( } /// Allocates enough memory to read until `delimiter` or end-of-stream. - /// If the allocated memory would be greater than `max_size`, returns `error.StreamTooLong`. + /// If the allocated memory would be greater than `max_size`, returns + /// `error.StreamTooLong`. If end-of-stream is found, returns the rest + /// of the stream. If this function is called again after that, returns + /// null. /// Caller owns returned memory. /// If this function returns an error, the contents from the stream read so far are lost. pub fn readUntilDelimiterOrEofAlloc( @@ -181,10 +155,17 @@ pub fn Reader( allocator: *mem.Allocator, delimiter: u8, max_size: usize, - ) ![]u8 { + ) !?[]u8 { var array_list = std.ArrayList(u8).init(allocator); defer array_list.deinit(); - try self.readUntilDelimiterOrEofArrayList(&array_list, delimiter, max_size); + self.readUntilDelimiterArrayList(&array_list, delimiter, max_size) catch |err| switch (err) { + error.EndOfStream => if (array_list.items.len == 0) { + return null; + } else { + return array_list.toOwnedSlice(); + }, + else => |e| return e, + }; return array_list.toOwnedSlice(); } @@ -290,8 +271,9 @@ pub fn Reader( buf_size: usize = 512, }; + // `num_bytes` is a `u64` to match `off_t` /// Reads `num_bytes` bytes from the stream and discards them - pub fn skipBytes(self: Self, num_bytes: usize, comptime options: SkipBytesOptions) !void { + pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) !void { var buf: [options.buf_size]u8 = undefined; var remaining = num_bytes; diff --git a/lib/std/io/seekable_stream.zig b/lib/std/io/seekable_stream.zig index 15e537baa2..4ba39fff42 100644 --- a/lib/std/io/seekable_stream.zig +++ b/lib/std/io/seekable_stream.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/io/serialization.zig b/lib/std/io/serialization.zig deleted file mode 100644 index 3fbc203242..0000000000 --- a/lib/std/io/serialization.zig +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("../std.zig"); -const builtin = std.builtin; -const io = std.io; -const assert = std.debug.assert; -const math = std.math; -const meta = std.meta; -const trait = meta.trait; -const testing = std.testing; - -pub const Packing = enum { - /// Pack data to byte alignment - Byte, - - /// Pack data to bit alignment - Bit, -}; - -/// Creates a deserializer that deserializes types from any stream. -/// If `is_packed` is true, the data stream is treated as bit-packed, -/// otherwise data is expected to be packed to the smallest byte. -/// Types may implement a custom deserialization routine with a -/// function named `deserialize` in the form of: -/// ``` -/// pub fn deserialize(self: *Self, deserializer: anytype) !void -/// ``` -/// which will be called when the deserializer is used to deserialize -/// that type. It will pass a pointer to the type instance to deserialize -/// into and a pointer to the deserializer struct. -pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime ReaderType: type) type { - return struct { - in_stream: if (packing == .Bit) io.BitReader(endian, ReaderType) else ReaderType, - - const Self = @This(); - - pub fn init(in_stream: ReaderType) Self { - return Self{ - .in_stream = switch (packing) { - .Bit => io.bitReader(endian, in_stream), - .Byte => in_stream, - }, - }; - } - - pub fn alignToByte(self: *Self) void { - if (packing == .Byte) return; - self.in_stream.alignToByte(); - } - - //@BUG: inferred error issue. See: #1386 - fn deserializeInt(self: *Self, comptime T: type) (ReaderType.Error || error{EndOfStream})!T { - comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); - - const u8_bit_count = 8; - const t_bit_count = comptime meta.bitCount(T); - - const U = std.meta.Int(.unsigned, t_bit_count); - const Log2U = math.Log2Int(U); - const int_size = (t_bit_count + 7) / 8; - - if (packing == .Bit) { - const result = try self.in_stream.readBitsNoEof(U, t_bit_count); - return @bitCast(T, result); - } - - var buffer: [int_size]u8 = undefined; - const read_size = try self.in_stream.read(buffer[0..]); - if (read_size < int_size) return error.EndOfStream; - - if (int_size == 1) { - if (t_bit_count == 8) return @bitCast(T, buffer[0]); - const PossiblySignedByte = std.meta.Int(@typeInfo(T).Int.signedness, 8); - return @truncate(T, @bitCast(PossiblySignedByte, buffer[0])); - } - - var result = @as(U, 0); - for (buffer) |byte, i| { - switch (endian) { - .Big => { - result = (result << u8_bit_count) | byte; - }, - .Little => { - result |= @as(U, byte) << @intCast(Log2U, u8_bit_count * i); - }, - } - } - - return @bitCast(T, result); - } - - /// Deserializes and returns data of the specified type from the stream - pub fn deserialize(self: *Self, comptime T: type) !T { - var value: T = undefined; - try self.deserializeInto(&value); - return value; - } - - /// Deserializes data into the type pointed to by `ptr` - pub fn deserializeInto(self: *Self, ptr: anytype) !void { - const T = @TypeOf(ptr); - comptime assert(trait.is(.Pointer)(T)); - - if (comptime trait.isSlice(T) or comptime trait.isPtrTo(.Array)(T)) { - for (ptr) |*v| - try self.deserializeInto(v); - return; - } - - comptime assert(trait.isSingleItemPtr(T)); - - const C = comptime meta.Child(T); - const child_type_id = @typeInfo(C); - - //custom deserializer: fn(self: *Self, deserializer: anytype) !void - if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self); - - if (comptime trait.isPacked(C) and packing != .Bit) { - var packed_deserializer = deserializer(endian, .Bit, self.in_stream); - return packed_deserializer.deserializeInto(ptr); - } - - switch (child_type_id) { - .Void => return, - .Bool => ptr.* = (try self.deserializeInt(u1)) > 0, - .Float, .Int => ptr.* = try self.deserializeInt(C), - .Struct => { - const info = @typeInfo(C).Struct; - - inline for (info.fields) |*field_info| { - const name = field_info.name; - const FieldType = field_info.field_type; - - if (FieldType == void or FieldType == u0) continue; - - //it doesn't make any sense to read pointers - if (comptime trait.is(.Pointer)(FieldType)) { - @compileError("Will not " ++ "read field " ++ name ++ " of struct " ++ - @typeName(C) ++ " because it " ++ "is of pointer-type " ++ - @typeName(FieldType) ++ "."); - } - - try self.deserializeInto(&@field(ptr, name)); - } - }, - .Union => { - const info = @typeInfo(C).Union; - if (info.tag_type) |TagType| { - //we avoid duplicate iteration over the enum tags - // by getting the int directly and casting it without - // safety. If it is bad, it will be caught anyway. - const TagInt = @TagType(TagType); - const tag = try self.deserializeInt(TagInt); - - inline for (info.fields) |field_info| { - if (@enumToInt(@field(TagType, field_info.name)) == tag) { - const name = field_info.name; - const FieldType = field_info.field_type; - ptr.* = @unionInit(C, name, undefined); - try self.deserializeInto(&@field(ptr, name)); - return; - } - } - //This is reachable if the enum data is bad - return error.InvalidEnumTag; - } - @compileError("Cannot meaningfully deserialize " ++ @typeName(C) ++ - " because it is an untagged union. Use a custom deserialize()."); - }, - .Optional => { - const OC = comptime meta.Child(C); - const exists = (try self.deserializeInt(u1)) > 0; - if (!exists) { - ptr.* = null; - return; - } - - ptr.* = @as(OC, undefined); //make it non-null so the following .? is guaranteed safe - const val_ptr = &ptr.*.?; - try self.deserializeInto(val_ptr); - }, - .Enum => { - var value = try self.deserializeInt(@TagType(C)); - ptr.* = try meta.intToEnum(C, value); - }, - else => { - @compileError("Cannot deserialize " ++ @tagName(child_type_id) ++ " types (unimplemented)."); - }, - } - } - }; -} - -pub fn deserializer( - comptime endian: builtin.Endian, - comptime packing: Packing, - in_stream: anytype, -) Deserializer(endian, packing, @TypeOf(in_stream)) { - return Deserializer(endian, packing, @TypeOf(in_stream)).init(in_stream); -} - -/// Creates a serializer that serializes types to any stream. -/// If `is_packed` is true, the data will be bit-packed into the stream. -/// Note that the you must call `serializer.flush()` when you are done -/// writing bit-packed data in order ensure any unwritten bits are committed. -/// If `is_packed` is false, data is packed to the smallest byte. In the case -/// of packed structs, the struct will written bit-packed and with the specified -/// endianess, after which data will resume being written at the next byte boundary. -/// Types may implement a custom serialization routine with a -/// function named `serialize` in the form of: -/// ``` -/// pub fn serialize(self: Self, serializer: anytype) !void -/// ``` -/// which will be called when the serializer is used to serialize that type. It will -/// pass a const pointer to the type instance to be serialized and a pointer -/// to the serializer struct. -pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime OutStreamType: type) type { - return struct { - out_stream: if (packing == .Bit) io.BitOutStream(endian, OutStreamType) else OutStreamType, - - const Self = @This(); - pub const Error = OutStreamType.Error; - - pub fn init(out_stream: OutStreamType) Self { - return Self{ - .out_stream = switch (packing) { - .Bit => io.bitOutStream(endian, out_stream), - .Byte => out_stream, - }, - }; - } - - /// Flushes any unwritten bits to the stream - pub fn flush(self: *Self) Error!void { - if (packing == .Bit) return self.out_stream.flushBits(); - } - - fn serializeInt(self: *Self, value: anytype) Error!void { - const T = @TypeOf(value); - comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); - - const t_bit_count = comptime meta.bitCount(T); - const u8_bit_count = comptime meta.bitCount(u8); - - const U = std.meta.Int(.unsigned, t_bit_count); - const Log2U = math.Log2Int(U); - const int_size = (t_bit_count + 7) / 8; - - const u_value = @bitCast(U, value); - - if (packing == .Bit) return self.out_stream.writeBits(u_value, t_bit_count); - - var buffer: [int_size]u8 = undefined; - if (int_size == 1) buffer[0] = u_value; - - for (buffer) |*byte, i| { - const idx = switch (endian) { - .Big => int_size - i - 1, - .Little => i, - }; - const shift = @intCast(Log2U, idx * u8_bit_count); - const v = u_value >> shift; - byte.* = if (t_bit_count < u8_bit_count) v else @truncate(u8, v); - } - - try self.out_stream.writeAll(&buffer); - } - - /// Serializes the passed value into the stream - pub fn serialize(self: *Self, value: anytype) Error!void { - const T = comptime @TypeOf(value); - - if (comptime trait.isIndexable(T)) { - for (value) |v| - try self.serialize(v); - return; - } - - //custom serializer: fn(self: Self, serializer: anytype) !void - if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self); - - if (comptime trait.isPacked(T) and packing != .Bit) { - var packed_serializer = Serializer(endian, .Bit, OutStreamType).init(self.out_stream); - try packed_serializer.serialize(value); - try packed_serializer.flush(); - return; - } - - switch (@typeInfo(T)) { - .Void => return, - .Bool => try self.serializeInt(@as(u1, @boolToInt(value))), - .Float, .Int => try self.serializeInt(value), - .Struct => { - const info = @typeInfo(T); - - inline for (info.Struct.fields) |*field_info| { - const name = field_info.name; - const FieldType = field_info.field_type; - - if (FieldType == void or FieldType == u0) continue; - - //It doesn't make sense to write pointers - if (comptime trait.is(.Pointer)(FieldType)) { - @compileError("Will not " ++ "serialize field " ++ name ++ - " of struct " ++ @typeName(T) ++ " because it " ++ - "is of pointer-type " ++ @typeName(FieldType) ++ "."); - } - try self.serialize(@field(value, name)); - } - }, - .Union => { - const info = @typeInfo(T).Union; - if (info.tag_type) |TagType| { - const active_tag = meta.activeTag(value); - try self.serialize(active_tag); - //This inline loop is necessary because active_tag is a runtime - // value, but @field requires a comptime value. Our alternative - // is to check each field for a match - inline for (info.fields) |field_info| { - if (@field(TagType, field_info.name) == active_tag) { - const name = field_info.name; - const FieldType = field_info.field_type; - try self.serialize(@field(value, name)); - return; - } - } - unreachable; - } - @compileError("Cannot meaningfully serialize " ++ @typeName(T) ++ - " because it is an untagged union. Use a custom serialize()."); - }, - .Optional => { - if (value == null) { - try self.serializeInt(@as(u1, @boolToInt(false))); - return; - } - try self.serializeInt(@as(u1, @boolToInt(true))); - - const OC = comptime meta.Child(T); - const val_ptr = &value.?; - try self.serialize(val_ptr.*); - }, - .Enum => { - try self.serializeInt(@enumToInt(value)); - }, - else => @compileError("Cannot serialize " ++ @tagName(@typeInfo(T)) ++ " types (unimplemented)."), - } - } - }; -} - -pub fn serializer( - comptime endian: builtin.Endian, - comptime packing: Packing, - out_stream: anytype, -) Serializer(endian, packing, @TypeOf(out_stream)) { - return Serializer(endian, packing, @TypeOf(out_stream)).init(out_stream); -} - -fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { - @setEvalBranchQuota(1500); - //@NOTE: if this test is taking too long, reduce the maximum tested bitsize - const max_test_bitsize = 128; - - const total_bytes = comptime blk: { - var bytes = 0; - comptime var i = 0; - while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0); - break :blk bytes * 2; - }; - - var data_mem: [total_bytes]u8 = undefined; - var out = io.fixedBufferStream(&data_mem); - var _serializer = serializer(endian, packing, out.outStream()); - - var in = io.fixedBufferStream(&data_mem); - var _deserializer = deserializer(endian, packing, in.reader()); - - comptime var i = 0; - inline while (i <= max_test_bitsize) : (i += 1) { - const U = std.meta.Int(.unsigned, i); - const S = std.meta.Int(.signed, i); - try _serializer.serializeInt(@as(U, i)); - if (i != 0) try _serializer.serializeInt(@as(S, -1)) else try _serializer.serialize(@as(S, 0)); - } - try _serializer.flush(); - - i = 0; - inline while (i <= max_test_bitsize) : (i += 1) { - const U = std.meta.Int(.unsigned, i); - const S = std.meta.Int(.signed, i); - const x = try _deserializer.deserializeInt(U); - const y = try _deserializer.deserializeInt(S); - testing.expect(x == @as(U, i)); - if (i != 0) testing.expect(y == @as(S, -1)) else testing.expect(y == 0); - } - - const u8_bit_count = comptime meta.bitCount(u8); - //0 + 1 + 2 + ... n = (n * (n + 1)) / 2 - //and we have each for unsigned and signed, so * 2 - const total_bits = (max_test_bitsize * (max_test_bitsize + 1)); - const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0); - const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte; - - testing.expect(in.pos == if (packing == .Bit) total_packed_bytes else total_bytes); - - //Verify that empty error set works with serializer. - //deserializer is covered by FixedBufferStream - var null_serializer = io.serializer(endian, packing, std.io.null_out_stream); - try null_serializer.serialize(data_mem[0..]); - try null_serializer.flush(); -} - -test "Serializer/Deserializer Int" { - try testIntSerializerDeserializer(.Big, .Byte); - try testIntSerializerDeserializer(.Little, .Byte); - // TODO these tests are disabled due to tripping an LLVM assertion - // https://github.com/ziglang/zig/issues/2019 - //try testIntSerializerDeserializer(builtin.Endian.Big, true); - //try testIntSerializerDeserializer(builtin.Endian.Little, true); -} - -fn testIntSerializerDeserializerInfNaN( - comptime endian: builtin.Endian, - comptime packing: io.Packing, -) !void { - const mem_size = (16 * 2 + 32 * 2 + 64 * 2 + 128 * 2) / comptime meta.bitCount(u8); - var data_mem: [mem_size]u8 = undefined; - - var out = io.fixedBufferStream(&data_mem); - var _serializer = serializer(endian, packing, out.outStream()); - - var in = io.fixedBufferStream(&data_mem); - var _deserializer = deserializer(endian, packing, in.reader()); - - //@TODO: isInf/isNan not currently implemented for f128. - try _serializer.serialize(std.math.nan(f16)); - try _serializer.serialize(std.math.inf(f16)); - try _serializer.serialize(std.math.nan(f32)); - try _serializer.serialize(std.math.inf(f32)); - try _serializer.serialize(std.math.nan(f64)); - try _serializer.serialize(std.math.inf(f64)); - //try serializer.serialize(std.math.nan(f128)); - //try serializer.serialize(std.math.inf(f128)); - const nan_check_f16 = try _deserializer.deserialize(f16); - const inf_check_f16 = try _deserializer.deserialize(f16); - const nan_check_f32 = try _deserializer.deserialize(f32); - _deserializer.alignToByte(); - const inf_check_f32 = try _deserializer.deserialize(f32); - const nan_check_f64 = try _deserializer.deserialize(f64); - const inf_check_f64 = try _deserializer.deserialize(f64); - //const nan_check_f128 = try deserializer.deserialize(f128); - //const inf_check_f128 = try deserializer.deserialize(f128); - testing.expect(std.math.isNan(nan_check_f16)); - testing.expect(std.math.isInf(inf_check_f16)); - testing.expect(std.math.isNan(nan_check_f32)); - testing.expect(std.math.isInf(inf_check_f32)); - testing.expect(std.math.isNan(nan_check_f64)); - testing.expect(std.math.isInf(inf_check_f64)); - //expect(std.math.isNan(nan_check_f128)); - //expect(std.math.isInf(inf_check_f128)); -} - -test "Serializer/Deserializer Int: Inf/NaN" { - try testIntSerializerDeserializerInfNaN(.Big, .Byte); - try testIntSerializerDeserializerInfNaN(.Little, .Byte); - try testIntSerializerDeserializerInfNaN(.Big, .Bit); - try testIntSerializerDeserializerInfNaN(.Little, .Bit); -} - -fn testAlternateSerializer(self: anytype, _serializer: anytype) !void { - try _serializer.serialize(self.f_f16); -} - -fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { - const ColorType = enum(u4) { - RGB8 = 1, - RA16 = 2, - R32 = 3, - }; - - const TagAlign = union(enum(u32)) { - A: u8, - B: u8, - C: u8, - }; - - const Color = union(ColorType) { - RGB8: struct { - r: u8, - g: u8, - b: u8, - a: u8, - }, - RA16: struct { - r: u16, - a: u16, - }, - R32: u32, - }; - - const PackedStruct = packed struct { - f_i3: i3, - f_u2: u2, - }; - - //to test custom serialization - const Custom = struct { - f_f16: f16, - f_unused_u32: u32, - - pub fn deserialize(self: *@This(), _deserializer: anytype) !void { - try _deserializer.deserializeInto(&self.f_f16); - self.f_unused_u32 = 47; - } - - pub const serialize = testAlternateSerializer; - }; - - const MyStruct = struct { - f_i3: i3, - f_u8: u8, - f_tag_align: TagAlign, - f_u24: u24, - f_i19: i19, - f_void: void, - f_f32: f32, - f_f128: f128, - f_packed_0: PackedStruct, - f_i7arr: [10]i7, - f_of64n: ?f64, - f_of64v: ?f64, - f_color_type: ColorType, - f_packed_1: PackedStruct, - f_custom: Custom, - f_color: Color, - }; - - const my_inst = MyStruct{ - .f_i3 = -1, - .f_u8 = 8, - .f_tag_align = TagAlign{ .B = 148 }, - .f_u24 = 24, - .f_i19 = 19, - .f_void = {}, - .f_f32 = 32.32, - .f_f128 = 128.128, - .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 }, - .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, - .f_of64n = null, - .f_of64v = 64.64, - .f_color_type = ColorType.R32, - .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 }, - .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 }, - .f_color = Color{ .R32 = 123822 }, - }; - - var data_mem: [@sizeOf(MyStruct)]u8 = undefined; - var out = io.fixedBufferStream(&data_mem); - var _serializer = serializer(endian, packing, out.outStream()); - - var in = io.fixedBufferStream(&data_mem); - var _deserializer = deserializer(endian, packing, in.reader()); - - try _serializer.serialize(my_inst); - - const my_copy = try _deserializer.deserialize(MyStruct); - testing.expect(meta.eql(my_copy, my_inst)); -} - -test "Serializer/Deserializer generic" { - try testSerializerDeserializer(builtin.Endian.Big, .Byte); - try testSerializerDeserializer(builtin.Endian.Little, .Byte); - try testSerializerDeserializer(builtin.Endian.Big, .Bit); - try testSerializerDeserializer(builtin.Endian.Little, .Bit); -} - -fn testBadData(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { - const E = enum(u14) { - One = 1, - Two = 2, - }; - - const A = struct { - e: E, - }; - - const C = union(E) { - One: u14, - Two: f16, - }; - - var data_mem: [4]u8 = undefined; - var out = io.fixedBufferStream(&data_mem); - var _serializer = serializer(endian, packing, out.outStream()); - - var in = io.fixedBufferStream(&data_mem); - var _deserializer = deserializer(endian, packing, in.reader()); - - try _serializer.serialize(@as(u14, 3)); - testing.expectError(error.InvalidEnumTag, _deserializer.deserialize(A)); - out.pos = 0; - try _serializer.serialize(@as(u14, 3)); - try _serializer.serialize(@as(u14, 88)); - testing.expectError(error.InvalidEnumTag, _deserializer.deserialize(C)); -} - -test "Deserializer bad data" { - try testBadData(.Big, .Byte); - try testBadData(.Little, .Byte); - try testBadData(.Big, .Bit); - try testBadData(.Little, .Bit); -} diff --git a/lib/std/io/stream_source.zig b/lib/std/io/stream_source.zig index 3bfe0d2e64..df0d6cd352 100644 --- a/lib/std/io/stream_source.zig +++ b/lib/std/io/stream_source.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,7 +9,7 @@ const testing = std.testing; /// Provides `io.Reader`, `io.Writer`, and `io.SeekableStream` for in-memory buffers as /// well as files. -/// For memory sources, if the supplied byte buffer is const, then `io.OutStream` is not available. +/// For memory sources, if the supplied byte buffer is const, then `io.Writer` is not available. /// The error set of the stream functions is the error set of the corresponding file functions. pub const StreamSource = union(enum) { buffer: io.FixedBufferStream([]u8), @@ -22,11 +22,7 @@ pub const StreamSource = union(enum) { pub const GetSeekPosError = std.fs.File.GetPosError; pub const Reader = io.Reader(*StreamSource, ReadError, read); - /// Deprecated: use `Reader` - pub const InStream = Reader; pub const Writer = io.Writer(*StreamSource, WriteError, write); - /// Deprecated: use `Writer` - pub const OutStream = Writer; pub const SeekableStream = io.SeekableStream( *StreamSource, SeekError, @@ -89,20 +85,10 @@ pub const StreamSource = union(enum) { return .{ .context = self }; } - /// Deprecated: use `reader` - pub fn inStream(self: *StreamSource) InStream { - return .{ .context = self }; - } - pub fn writer(self: *StreamSource) Writer { return .{ .context = self }; } - /// Deprecated: use `writer` - pub fn outStream(self: *StreamSource) OutStream { - return .{ .context = self }; - } - pub fn seekableStream(self: *StreamSource) SeekableStream { return .{ .context = self }; } diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 584369966b..9fdef0de1d 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -30,8 +30,8 @@ test "write a file, read it, then delete it" { var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); - var buf_stream = io.bufferedOutStream(file.outStream()); - const st = buf_stream.outStream(); + var buf_stream = io.bufferedWriter(file.writer()); + const st = buf_stream.writer(); try st.print("begin", .{}); try st.writeAll(data[0..]); try st.print("end", .{}); @@ -72,7 +72,7 @@ test "BitStreams with File Stream" { var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); - var bit_stream = io.bitOutStream(builtin.endian, file.outStream()); + var bit_stream = io.bitWriter(builtin.endian, file.writer()); try bit_stream.writeBits(@as(u2, 1), 1); try bit_stream.writeBits(@as(u5, 2), 2); diff --git a/lib/std/io/writer.zig b/lib/std/io/writer.zig index 770cd5f0fa..0a9edb425a 100644 --- a/lib/std/io/writer.zig +++ b/lib/std/io/writer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/json.zig b/lib/std/json.zig index ac1047d1a9..f9fc371049 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -246,7 +246,7 @@ pub const StreamingParser = struct { // Only call this function to generate array/object final state. pub fn fromInt(x: anytype) State { debug.assert(x == 0 or x == 1); - const T = @TagType(State); + const T = std.meta.Tag(State); return @intToEnum(State, @intCast(T, x)); } }; @@ -1138,7 +1138,7 @@ pub const TokenStream = struct { } }; -fn checkNext(p: *TokenStream, id: std.meta.TagType(Token)) void { +fn checkNext(p: *TokenStream, id: std.meta.Tag(Token)) void { const token = (p.next() catch unreachable).?; debug.assert(std.meta.activeTag(token) == id); } @@ -1255,6 +1255,7 @@ pub const Value = union(enum) { Bool: bool, Integer: i64, Float: f64, + NumberString: []const u8, String: []const u8, Array: Array, Object: ObjectMap, @@ -1269,6 +1270,7 @@ pub const Value = union(enum) { .Bool => |inner| try stringify(inner, options, out_stream), .Integer => |inner| try stringify(inner, options, out_stream), .Float => |inner| try stringify(inner, options, out_stream), + .NumberString => |inner| try out_stream.writeAll(inner), .String => |inner| try stringify(inner, options, out_stream), .Array => |inner| try stringify(inner.items, options, out_stream), .Object => |inner| { @@ -1323,31 +1325,37 @@ test "Value.jsonStringify" { { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try @as(Value, .Null).jsonStringify(.{}, fbs.outStream()); + try @as(Value, .Null).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "null"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Bool = true }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "true"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "42"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .NumberString = "43" }).jsonStringify(.{}, fbs.writer()); + testing.expectEqualSlices(u8, fbs.getWritten(), "43"); + } + { + var buffer: [10]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); } { @@ -1356,11 +1364,11 @@ test "Value.jsonStringify" { var vals = [_]Value{ .{ .Integer = 1 }, .{ .Integer = 2 }, - .{ .Integer = 3 }, + .{ .NumberString = "3" }, }; try (Value{ .Array = Array.fromOwnedSlice(undefined, &vals), - }).jsonStringify(.{}, fbs.outStream()); + }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); } { @@ -1369,11 +1377,70 @@ test "Value.jsonStringify" { var obj = ObjectMap.init(testing.allocator); defer obj.deinit(); try obj.putNoClobber("a", .{ .String = "b" }); - try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Object = obj }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); } } +/// parse tokens from a stream, returning `false` if they do not decode to `value` +fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { + // TODO: should be able to write this function to not require an allocator + const tmp = try parse(T, tokens, options); + defer parseFree(T, tmp, options); + + return parsedEqual(tmp, value); +} + +/// Returns if a value returned by `parse` is deep-equal to another value +fn parsedEqual(a: anytype, b: @TypeOf(a)) bool { + switch (@typeInfo(@TypeOf(a))) { + .Optional => { + if (a == null and b == null) return true; + if (a == null or b == null) return false; + return parsedEqual(a.?, b.?); + }, + .Union => |unionInfo| { + if (info.tag_type) |UnionTag| { + const tag_a = std.meta.activeTag(a); + const tag_b = std.meta.activeTag(b); + if (tag_a != tag_b) return false; + + inline for (info.fields) |field_info| { + if (@field(UnionTag, field_info.name) == tag_a) { + return parsedEqual(@field(a, field_info.name), @field(b, field_info.name)); + } + } + return false; + } else { + unreachable; + } + }, + .Array => { + for (a) |e, i| + if (!parsedEqual(e, b[i])) return false; + return true; + }, + .Struct => |info| { + inline for (info.fields) |field_info| { + if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false; + } + return true; + }, + .Pointer => |ptrInfo| switch (ptrInfo.size) { + .One => return parsedEqual(a.*, b.*), + .Slice => { + if (a.len != b.len) return false; + for (a) |e, i| + if (!parsedEqual(e, b[i])) return false; + return true; + }, + .Many, .C => unreachable, + }, + else => return a == b, + } + unreachable; +} + pub const ParseOptions = struct { allocator: ?*Allocator = null, @@ -1454,6 +1521,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: // Parsing some types won't have OutOfMemory in their // error-sets, for the condition to be valid, merge it in. if (@as(@TypeOf(err) || error{OutOfMemory}, err) == error.OutOfMemory) return err; + // Bubble up AllocatorRequired, as it indicates missing option + if (@as(@TypeOf(err) || error{AllocatorRequired}, err) == error.AllocatorRequired) return err; // otherwise continue through the `inline for` } } @@ -1471,7 +1540,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: var fields_seen = [_]bool{false} ** structInfo.fields.len; errdefer { inline for (structInfo.fields) |field, i| { - if (fields_seen[i]) { + if (fields_seen[i] and !field.is_comptime) { parseFree(field.field_type, @field(r, field.name), options); } } @@ -1504,7 +1573,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: parseFree(field.field_type, @field(r, field.name), options); } } - @field(r, field.name) = try parse(field.field_type, tokens, options); + if (field.is_comptime) { + if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) { + return error.UnexpectedValue; + } + } else { + @field(r, field.name) = try parse(field.field_type, tokens, options); + } fields_seen[i] = true; found = true; break; @@ -1518,7 +1593,9 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: inline for (structInfo.fields) |field, i| { if (!fields_seen[i]) { if (field.default_value) |default| { - @field(r, field.name) = default; + if (!field.is_comptime) { + @field(r, field.name) = default; + } } else { return error.MissingField; } @@ -1553,7 +1630,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); switch (stringToken.escapes) { .None => mem.copy(u8, &r, source_slice), - .Some => try unescapeString(&r, source_slice), + .Some => try unescapeValidString(&r, source_slice), } return r; }, @@ -1600,7 +1677,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: .Some => |some_escapes| { const output = try allocator.alloc(u8, stringToken.decodedLength()); errdefer allocator.free(output); - try unescapeString(output, source_slice); + try unescapeValidString(output, source_slice); return output; }, } @@ -1731,18 +1808,6 @@ test "parse into tagged union" { testing.expectEqual(T{ .float = 1.5 }, try parse(T, &TokenStream.init("1.5"), ParseOptions{})); } - { // if union matches string member, fails with NoUnionMembersMatched rather than AllocatorRequired - // Note that this behaviour wasn't necessarily by design, but was - // what fell out of the implementation and may result in interesting - // API breakage if changed - const T = union(enum) { - int: i32, - float: f64, - string: []const u8, - }; - testing.expectError(error.NoUnionMembersMatched, parse(T, &TokenStream.init("\"foo\""), ParseOptions{})); - } - { // failing allocations should be bubbled up instantly without trying next member var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0); const options = ParseOptions{ .allocator = &fail_alloc.allocator }; @@ -1772,6 +1837,25 @@ test "parse into tagged union" { } } +test "parse union bubbles up AllocatorRequired" { + { // string member first in union (and not matching) + const T = union(enum) { + string: []const u8, + int: i32, + }; + testing.expectError(error.AllocatorRequired, parse(T, &TokenStream.init("42"), ParseOptions{})); + } + + { // string member not first in union (and matching) + const T = union(enum) { + int: i32, + float: f64, + string: []const u8, + }; + testing.expectError(error.AllocatorRequired, parse(T, &TokenStream.init("\"foo\""), ParseOptions{})); + } +} + test "parseFree descends into tagged union" { var fail_alloc = testing.FailingAllocator.init(testing.allocator, 1); const options = ParseOptions{ .allocator = &fail_alloc.allocator }; @@ -1782,13 +1866,50 @@ test "parseFree descends into tagged union" { }; // use a string with unicode escape so we know result can't be a reference to global constant const r = try parse(T, &TokenStream.init("\"with\\u0105unicode\""), options); - testing.expectEqual(@TagType(T).string, @as(@TagType(T), r)); + testing.expectEqual(std.meta.Tag(T).string, @as(std.meta.Tag(T), r)); testing.expectEqualSlices(u8, "withąunicode", r.string); testing.expectEqual(@as(usize, 0), fail_alloc.deallocations); parseFree(T, r, options); testing.expectEqual(@as(usize, 1), fail_alloc.deallocations); } +test "parse with comptime field" { + { + const T = struct { + comptime a: i32 = 0, + b: bool, + }; + testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init( + \\{ + \\ "a": 0, + \\ "b": true + \\} + ), ParseOptions{})); + } + + { // string comptime values currently require an allocator + const T = union(enum) { + foo: struct { + comptime kind: []const u8 = "boolean", + b: bool, + }, + bar: struct { + comptime kind: []const u8 = "float", + b: f64, + }, + }; + + const r = try std.json.parse(T, &std.json.TokenStream.init( + \\{ + \\ "kind": "float", + \\ "b": 1.0 + \\} + ), .{ + .allocator = std.testing.allocator, + }); + } +} + test "parse into struct with no fields" { const T = struct {}; testing.expectEqual(T{}, try parse(T, &TokenStream.init("{}"), ParseOptions{})); @@ -1897,7 +2018,7 @@ pub const Parser = struct { pub fn reset(p: *Parser) void { p.state = .Simple; - p.stack.shrink(0); + p.stack.shrinkRetainingCapacity(0); } pub fn parse(p: *Parser, input: []const u8) !ValueTree { @@ -2077,31 +2198,36 @@ pub const Parser = struct { } } - fn parseString(p: *Parser, allocator: *Allocator, s: std.meta.TagPayloadType(Token, Token.String), input: []const u8, i: usize) !Value { + fn parseString(p: *Parser, allocator: *Allocator, s: std.meta.TagPayload(Token, Token.String), input: []const u8, i: usize) !Value { const slice = s.slice(input, i); switch (s.escapes) { .None => return Value{ .String = if (p.copy_strings) try allocator.dupe(u8, slice) else slice }, .Some => |some_escapes| { const output = try allocator.alloc(u8, s.decodedLength()); errdefer allocator.free(output); - try unescapeString(output, slice); + try unescapeValidString(output, slice); return Value{ .String = output }; }, } } - fn parseNumber(p: *Parser, n: std.meta.TagPayloadType(Token, Token.Number), input: []const u8, i: usize) !Value { + fn parseNumber(p: *Parser, n: std.meta.TagPayload(Token, Token.Number), input: []const u8, i: usize) !Value { return if (n.is_integer) - Value{ .Integer = try std.fmt.parseInt(i64, n.slice(input, i), 10) } + Value{ + .Integer = std.fmt.parseInt(i64, n.slice(input, i), 10) catch |e| switch (e) { + error.Overflow => return Value{ .NumberString = n.slice(input, i) }, + error.InvalidCharacter => |err| return err, + }, + } else Value{ .Float = try std.fmt.parseFloat(f64, n.slice(input, i)) }; } }; -// Unescape a JSON string -// Only to be used on strings already validated by the parser -// (note the unreachable statements and lack of bounds checking) -pub fn unescapeString(output: []u8, input: []const u8) !void { +/// Unescape a JSON string +/// Only to be used on strings already validated by the parser +/// (note the unreachable statements and lack of bounds checking) +pub fn unescapeValidString(output: []u8, input: []const u8) !void { var inIndex: usize = 0; var outIndex: usize = 0; @@ -2180,7 +2306,8 @@ test "json.parser.dynamic" { \\ "Animated" : false, \\ "IDs": [116, 943, 234, 38793], \\ "ArrayOfObject": [{"n": "m"}], - \\ "double": 1.3412 + \\ "double": 1.3412, + \\ "LargeInt": 18446744073709551615 \\ } \\} ; @@ -2212,6 +2339,9 @@ test "json.parser.dynamic" { const double = image.Object.get("double").?; testing.expect(double.Float == 1.3412); + + const large_int = image.Object.get("LargeInt").?; + testing.expect(mem.eql(u8, large_int.NumberString, "18446744073709551615")); } test "import more json tests" { @@ -2223,7 +2353,7 @@ test "write json then parse it" { var out_buffer: [1000]u8 = undefined; var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); - const out_stream = fixed_buffer_stream.outStream(); + const out_stream = fixed_buffer_stream.writer(); var jw = writeStream(out_stream, 4); try jw.beginObject(); @@ -2620,9 +2750,9 @@ pub fn stringify( } fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { - const ValidationOutStream = struct { + const ValidationWriter = struct { const Self = @This(); - pub const OutStream = std.io.OutStream(*Self, Error, write); + pub const Writer = std.io.Writer(*Self, Error, write); pub const Error = error{ TooMuchData, DifferentData, @@ -2634,7 +2764,7 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions return .{ .expected_remaining = exp }; } - pub fn outStream(self: *Self) OutStream { + pub fn writer(self: *Self) Writer { return .{ .context = self }; } @@ -2642,9 +2772,9 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions if (self.expected_remaining.len < bytes.len) { std.debug.warn( \\====== expected this output: ========= - \\{} + \\{s} \\======== instead found this: ========= - \\{} + \\{s} \\====================================== , .{ self.expected_remaining, @@ -2655,9 +2785,9 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) { std.debug.warn( \\====== expected this output: ========= - \\{} + \\{s} \\======== instead found this: ========= - \\{} + \\{s} \\====================================== , .{ self.expected_remaining[0..bytes.len], @@ -2670,8 +2800,8 @@ fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions } }; - var vos = ValidationOutStream.init(expected); - try stringify(value, options, vos.outStream()); + var vos = ValidationWriter.init(expected); + try stringify(value, options, vos.writer()); if (vos.expected_remaining.len > 0) return error.NotEnoughData; } diff --git a/lib/std/json/test.zig b/lib/std/json/test.zig index f1f351e84f..897e2e3364 100644 --- a/lib/std/json/test.zig +++ b/lib/std/json/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/json/write_stream.zig b/lib/std/json/write_stream.zig index ee2c12154f..b4a8aed84c 100644 --- a/lib/std/json/write_stream.zig +++ b/lib/std/json/write_stream.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -238,7 +238,7 @@ pub fn writeStream( test "json write stream" { var out_buf: [1024]u8 = undefined; var slice_stream = std.io.fixedBufferStream(&out_buf); - const out = slice_stream.outStream(); + const out = slice_stream.writer(); var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_allocator.deinit(); diff --git a/lib/std/leb128.zig b/lib/std/leb128.zig index 2a8a6fc2ff..90a329545f 100644 --- a/lib/std/leb128.zig +++ b/lib/std/leb128.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/linked_list.zig b/lib/std/linked_list.zig index 870b823aac..2a6b58c8c9 100644 --- a/lib/std/linked_list.zig +++ b/lib/std/linked_list.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -62,7 +62,7 @@ pub fn SinglyLinkedList(comptime T: type) type { /// This operation is O(N). pub fn countChildren(node: *const Node) usize { var count: usize = 0; - var it: ?*const Node = node; + var it: ?*const Node = node.next; while (it) |n| : (it = n.next) { count += 1; } @@ -123,6 +123,8 @@ test "basic SinglyLinkedList test" { const L = SinglyLinkedList(u32); var list = L{}; + testing.expect(list.len() == 0); + var one = L.Node{ .data = 1 }; var two = L.Node{ .data = 2 }; var three = L.Node{ .data = 3 }; @@ -135,6 +137,8 @@ test "basic SinglyLinkedList test" { two.insertAfter(&three); // {1, 2, 3, 5} three.insertAfter(&four); // {1, 2, 3, 4, 5} + testing.expect(list.len() == 5); + // Traverse forwards. { var it = list.first; diff --git a/lib/std/log.zig b/lib/std/log.zig index 0cc2b54452..215e611bc1 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -1,11 +1,8 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -const std = @import("std.zig"); -const builtin = std.builtin; -const root = @import("root"); //! std.log is a standardized interface for logging which allows for the logging //! of programs and libraries using this interface to be formatted and filtered @@ -77,6 +74,10 @@ const root = @import("root"); //! [err] (nice_library): Something went very wrong, sorry //! ``` +const std = @import("std.zig"); +const builtin = std.builtin; +const root = @import("root"); + pub const Level = enum { /// Emergency: a condition that cannot be handled, usually followed by a /// panic. diff --git a/lib/std/macho.zig b/lib/std/macho.zig index ec0d23cd92..6785abffca 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -1257,6 +1257,51 @@ pub const VM_PROT_WRITE: vm_prot_t = 0x2; /// VM execute permission pub const VM_PROT_EXECUTE: vm_prot_t = 0x4; +// The following are used to encode rebasing information +pub const REBASE_TYPE_POINTER: u8 = 1; +pub const REBASE_TYPE_TEXT_ABSOLUTE32: u8 = 2; +pub const REBASE_TYPE_TEXT_PCREL32: u8 = 3; + +pub const REBASE_OPCODE_MASK: u8 = 0xF0; +pub const REBASE_IMMEDIATE_MASK: u8 = 0x0F; +pub const REBASE_OPCODE_DONE: u8 = 0x00; +pub const REBASE_OPCODE_SET_TYPE_IMM: u8 = 0x10; +pub const REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x20; +pub const REBASE_OPCODE_ADD_ADDR_ULEB: u8 = 0x30; +pub const REBASE_OPCODE_ADD_ADDR_IMM_SCALED: u8 = 0x40; +pub const REBASE_OPCODE_DO_REBASE_IMM_TIMES: u8 = 0x50; +pub const REBASE_OPCODE_DO_REBASE_ULEB_TIMES: u8 = 0x60; +pub const REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: u8 = 0x70; +pub const REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: u8 = 0x80; + +// The following are used to encode binding information +pub const BIND_TYPE_POINTER: u8 = 1; +pub const BIND_TYPE_TEXT_ABSOLUTE32: u8 = 2; +pub const BIND_TYPE_TEXT_PCREL32: u8 = 3; + +pub const BIND_SPECIAL_DYLIB_SELF: i8 = 0; +pub const BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE: i8 = -1; +pub const BIND_SPECIAL_DYLIB_FLAT_LOOKUP: i8 = -2; + +pub const BIND_SYMBOL_FLAGS_WEAK_IMPORT: u8 = 0x1; +pub const BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION: u8 = 0x8; + +pub const BIND_OPCODE_MASK: u8 = 0xf0; +pub const BIND_IMMEDIATE_MASK: u8 = 0x0f; +pub const BIND_OPCODE_DONE: u8 = 0x00; +pub const BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: u8 = 0x10; +pub const BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: u8 = 0x20; +pub const BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: u8 = 0x30; +pub const BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: u8 = 0x40; +pub const BIND_OPCODE_SET_TYPE_IMM: u8 = 0x50; +pub const BIND_OPCODE_SET_ADDEND_SLEB: u8 = 0x60; +pub const BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x70; +pub const BIND_OPCODE_ADD_ADDR_ULEB: 0x80; +pub const BIND_OPCODE_DO_BIND: u8 = 0x90; +pub const BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: u8 = 0xa0; +pub const BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: u8 = 0xb0; +pub const BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: u8 = xc0; + pub const reloc_type_x86_64 = packed enum(u4) { /// for absolute addresses X86_64_RELOC_UNSIGNED = 0, @@ -1289,6 +1334,41 @@ pub const reloc_type_x86_64 = packed enum(u4) { X86_64_RELOC_TLV, }; +pub const reloc_type_arm64 = packed enum(u4) { + /// For pointers. + ARM64_RELOC_UNSIGNED = 0, + + /// Must be followed by a ARM64_RELOC_UNSIGNED. + ARM64_RELOC_SUBTRACTOR, + + /// A B/BL instruction with 26-bit displacement. + ARM64_RELOC_BRANCH26, + + /// Pc-rel distance to page of target. + ARM64_RELOC_PAGE21, + + /// Offset within page, scaled by r_length. + ARM64_RELOC_PAGEOFF12, + + /// Pc-rel distance to page of GOT slot. + ARM64_RELOC_GOT_LOAD_PAGE21, + + /// Offset within page of GOT slot, scaled by r_length. + ARM64_RELOC_GOT_LOAD_PAGEOFF12, + + /// For pointers to GOT slots. + ARM64_RELOC_POINTER_TO_GOT, + + /// Pc-rel distance to page of TLVP slot. + ARM64_RELOC_TLVP_LOAD_PAGE21, + + /// Offset within page of TLVP slot, scaled by r_length. + ARM64_RELOC_TLVP_LOAD_PAGEOFF12, + + /// Must be followed by PAGE21 or PAGEOFF12. + ARM64_RELOC_ADDEND, +}; + /// This symbol is a reference to an external non-lazy (data) symbol. pub const REFERENCE_FLAG_UNDEFINED_NON_LAZY: u16 = 0x0; @@ -1333,7 +1413,7 @@ pub const N_WEAK_DEF: u16 = 0x80; /// This bit is only available in .o files (MH_OBJECT filetype) pub const N_SYMBOL_RESOLVER: u16 = 0x100; -// The following are used on the flags byte of a terminal node // in the export information. +// The following are used on the flags byte of a terminal node in the export information. pub const EXPORT_SYMBOL_FLAGS_KIND_MASK: u8 = 0x03; pub const EXPORT_SYMBOL_FLAGS_KIND_REGULAR: u8 = 0x00; pub const EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: u8 = 0x01; diff --git a/lib/std/math.zig b/lib/std/math.zig index a51cac6e7d..6e7c5c0915 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -278,7 +278,7 @@ pub const Complex = complex.Complex; pub const big = @import("math/big.zig"); -test "" { +test { std.testing.refAllDecls(@This()); } @@ -415,6 +415,7 @@ pub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T) { } pub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T) { + if (T == comptime_int) return a + b; var answer: T = undefined; return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer; } @@ -1070,16 +1071,52 @@ test "std.math.log2_int_ceil" { testing.expect(log2_int_ceil(u32, 10) == 4); } +///Cast a value to a different type. If the value doesn't fit in, or can't be perfectly represented by, +///the new type, it will be converted to the closest possible representation. pub fn lossyCast(comptime T: type, value: anytype) T { - switch (@typeInfo(@TypeOf(value))) { - .Int => return @intToFloat(T, value), - .Float => return @floatCast(T, value), - .ComptimeInt => return @as(T, value), - .ComptimeFloat => return @as(T, value), - else => @compileError("bad type"), + switch (@typeInfo(T)) { + .Float => { + switch (@typeInfo(@TypeOf(value))) { + .Int => return @intToFloat(T, value), + .Float => return @floatCast(T, value), + .ComptimeInt => return @as(T, value), + .ComptimeFloat => return @as(T, value), + else => @compileError("bad type"), + } + }, + .Int => { + switch (@typeInfo(@TypeOf(value))) { + .Int, .ComptimeInt => { + if (value > maxInt(T)) { + return @as(T, maxInt(T)); + } else if (value < minInt(T)) { + return @as(T, minInt(T)); + } else { + return @intCast(T, value); + } + }, + .Float, .ComptimeFloat => { + if (value > maxInt(T)) { + return @as(T, maxInt(T)); + } else if (value < minInt(T)) { + return @as(T, minInt(T)); + } else { + return @floatToInt(T, value); + } + }, + else => @compileError("bad type"), + } + }, + else => @compileError("bad result type"), } } +test "math.lossyCast" { + testing.expect(lossyCast(i16, 70000.0) == @as(i16, 32767)); + testing.expect(lossyCast(u32, @as(i16, -255)) == @as(u32, 0)); + testing.expect(lossyCast(i9, @as(u32, 200)) == @as(i9, 200)); +} + test "math.f64_min" { const f64_min_u64 = 0x0010000000000000; const fmin: f64 = f64_min; diff --git a/lib/std/math/acos.zig b/lib/std/math/acos.zig index 0153fd6835..7f3d4bfe9b 100644 --- a/lib/std/math/acos.zig +++ b/lib/std/math/acos.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/acosh.zig b/lib/std/math/acosh.zig index 773b125bea..0993989d47 100644 --- a/lib/std/math/acosh.zig +++ b/lib/std/math/acosh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/asin.zig b/lib/std/math/asin.zig index 38602a76d2..c4fca95c10 100644 --- a/lib/std/math/asin.zig +++ b/lib/std/math/asin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/asinh.zig b/lib/std/math/asinh.zig index 4dc0702a0a..a2c8ee3583 100644 --- a/lib/std/math/asinh.zig +++ b/lib/std/math/asinh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/atan.zig b/lib/std/math/atan.zig index 1608328579..59dda307cc 100644 --- a/lib/std/math/atan.zig +++ b/lib/std/math/atan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/atan2.zig b/lib/std/math/atan2.zig index cb4c28e713..3ecabe9e31 100644 --- a/lib/std/math/atan2.zig +++ b/lib/std/math/atan2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/atanh.zig b/lib/std/math/atanh.zig index ffebc58ed4..87d92a9fa5 100644 --- a/lib/std/math/atanh.zig +++ b/lib/std/math/atanh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/big.zig b/lib/std/math/big.zig index 5e2073954c..8ae214c666 100644 --- a/lib/std/math/big.zig +++ b/lib/std/math/big.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -20,7 +20,7 @@ comptime { assert(limb_info.signedness == .unsigned); } -test "" { +test { _ = int; _ = Rational; _ = Limb; diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index dd0b925692..d1d7b33508 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -549,8 +549,8 @@ pub const Mutable = struct { return; } - const r_len = llshr(r.limbs[0..], a.limbs[0..a.limbs.len], shift); - r.len = a.limbs.len - (shift / limb_bits); + llshr(r.limbs[0..], a.limbs[0..a.limbs.len], shift); + r.normalize(a.limbs.len - (shift / limb_bits)); r.positive = a.positive; } @@ -607,7 +607,7 @@ pub const Mutable = struct { /// it will have the same length as it had when the function was called. pub fn gcd(rma: *Mutable, x: Const, y: Const, limbs_buffer: *std.ArrayList(Limb)) !void { const prev_len = limbs_buffer.items.len; - defer limbs_buffer.shrink(prev_len); + defer limbs_buffer.shrinkRetainingCapacity(prev_len); const x_copy = if (rma.limbs.ptr == x.limbs.ptr) blk: { const start = limbs_buffer.items.len; try limbs_buffer.appendSlice(x.limbs); @@ -1348,7 +1348,9 @@ pub const Const = struct { /// Returns true if `a == 0`. pub fn eqZero(a: Const) bool { - return a.limbs.len == 1 and a.limbs[0] == 0; + var d: Limb = 0; + for (a.limbs) |limb| d |= limb; + return d == 0; } /// Returns true if `|a| == |b|`. @@ -2344,6 +2346,6 @@ fn fixedIntFromSignedDoubleLimb(A: SignedDoubleLimb, storage: []Limb) Mutable { }; } -test "" { +test { _ = @import("int_test.zig"); } diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 1f4bd65974..179e55ff69 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -1287,6 +1287,12 @@ test "big.int shift-right multi" { try a.shiftRight(a, 67); testing.expect((try a.to(u64)) == 0x1fffe0001dddc222); + + try a.set(0xffff0000eeee1111dddd2222cccc3333); + try a.shiftRight(a, 63); + try a.shiftRight(a, 63); + try a.shiftRight(a, 2); + testing.expect(a.eqZero()); } test "big.int shift-left single" { diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig index 8eb1d9f2b3..2299205e7b 100644 --- a/lib/std/math/big/rational.zig +++ b/lib/std/math/big/rational.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/cbrt.zig b/lib/std/math/cbrt.zig index c516cae73b..a876e0a9d1 100644 --- a/lib/std/math/cbrt.zig +++ b/lib/std/math/cbrt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/ceil.zig b/lib/std/math/ceil.zig index 2f043300b1..d313475717 100644 --- a/lib/std/math/ceil.zig +++ b/lib/std/math/ceil.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex.zig b/lib/std/math/complex.zig index f9f13a1161..e046ed9fa9 100644 --- a/lib/std/math/complex.zig +++ b/lib/std/math/complex.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/abs.zig b/lib/std/math/complex/abs.zig index 228b56c286..609cdba5a7 100644 --- a/lib/std/math/complex/abs.zig +++ b/lib/std/math/complex/abs.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/acos.zig b/lib/std/math/complex/acos.zig index 47130c8a98..b7c43e9381 100644 --- a/lib/std/math/complex/acos.zig +++ b/lib/std/math/complex/acos.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/acosh.zig b/lib/std/math/complex/acosh.zig index 51626b10a4..d7d596e084 100644 --- a/lib/std/math/complex/acosh.zig +++ b/lib/std/math/complex/acosh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/arg.zig b/lib/std/math/complex/arg.zig index 43c1d93874..7c3b00bd5d 100644 --- a/lib/std/math/complex/arg.zig +++ b/lib/std/math/complex/arg.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/asin.zig b/lib/std/math/complex/asin.zig index 4911ccc2b2..0ed352b3b7 100644 --- a/lib/std/math/complex/asin.zig +++ b/lib/std/math/complex/asin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/asinh.zig b/lib/std/math/complex/asinh.zig index e93a2dabd7..762a601fbf 100644 --- a/lib/std/math/complex/asinh.zig +++ b/lib/std/math/complex/asinh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/atan.zig b/lib/std/math/complex/atan.zig index e838751d73..af40c05a81 100644 --- a/lib/std/math/complex/atan.zig +++ b/lib/std/math/complex/atan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/atanh.zig b/lib/std/math/complex/atanh.zig index f3d378315f..2c3708f57f 100644 --- a/lib/std/math/complex/atanh.zig +++ b/lib/std/math/complex/atanh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/conj.zig b/lib/std/math/complex/conj.zig index 159469da86..b79c7de6ca 100644 --- a/lib/std/math/complex/conj.zig +++ b/lib/std/math/complex/conj.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/cos.zig b/lib/std/math/complex/cos.zig index 2abfce58c6..66fd5b9b7b 100644 --- a/lib/std/math/complex/cos.zig +++ b/lib/std/math/complex/cos.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig index 0a6be49e3e..e43cd1d665 100644 --- a/lib/std/math/complex/cosh.zig +++ b/lib/std/math/complex/cosh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/exp.zig b/lib/std/math/complex/exp.zig index 156a947a32..eb738a6d88 100644 --- a/lib/std/math/complex/exp.zig +++ b/lib/std/math/complex/exp.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/ldexp.zig b/lib/std/math/complex/ldexp.zig index b1cf8a0e42..3ae0382fe3 100644 --- a/lib/std/math/complex/ldexp.zig +++ b/lib/std/math/complex/ldexp.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/log.zig b/lib/std/math/complex/log.zig index 88175d00cc..90124af2eb 100644 --- a/lib/std/math/complex/log.zig +++ b/lib/std/math/complex/log.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/pow.zig b/lib/std/math/complex/pow.zig index 30636dd10d..a6589262cd 100644 --- a/lib/std/math/complex/pow.zig +++ b/lib/std/math/complex/pow.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/proj.zig b/lib/std/math/complex/proj.zig index 67f087f8ba..42886d8263 100644 --- a/lib/std/math/complex/proj.zig +++ b/lib/std/math/complex/proj.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/sin.zig b/lib/std/math/complex/sin.zig index d5e2713b13..4288dbb1a1 100644 --- a/lib/std/math/complex/sin.zig +++ b/lib/std/math/complex/sin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/sinh.zig b/lib/std/math/complex/sinh.zig index 8c8930c0ba..2861d99f9a 100644 --- a/lib/std/math/complex/sinh.zig +++ b/lib/std/math/complex/sinh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/sqrt.zig b/lib/std/math/complex/sqrt.zig index a01473a5ea..e03ed221eb 100644 --- a/lib/std/math/complex/sqrt.zig +++ b/lib/std/math/complex/sqrt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/tan.zig b/lib/std/math/complex/tan.zig index 8d6e5da313..04d900bd99 100644 --- a/lib/std/math/complex/tan.zig +++ b/lib/std/math/complex/tan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/complex/tanh.zig b/lib/std/math/complex/tanh.zig index cf82f04f41..19fda8d82f 100644 --- a/lib/std/math/complex/tanh.zig +++ b/lib/std/math/complex/tanh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/copysign.zig b/lib/std/math/copysign.zig index 1547382cbd..2804d10495 100644 --- a/lib/std/math/copysign.zig +++ b/lib/std/math/copysign.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/cos.zig b/lib/std/math/cos.zig index f8135e5d4f..21804a8e5e 100644 --- a/lib/std/math/cos.zig +++ b/lib/std/math/cos.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/cosh.zig b/lib/std/math/cosh.zig index c3736415d3..25d22057ef 100644 --- a/lib/std/math/cosh.zig +++ b/lib/std/math/cosh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/epsilon.zig b/lib/std/math/epsilon.zig index 3243b085ad..61758f1ee0 100644 --- a/lib/std/math/epsilon.zig +++ b/lib/std/math/epsilon.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/exp.zig b/lib/std/math/exp.zig index 87e1031a24..1156cc6c5a 100644 --- a/lib/std/math/exp.zig +++ b/lib/std/math/exp.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/exp2.zig b/lib/std/math/exp2.zig index 1c25504de9..155d10c7f1 100644 --- a/lib/std/math/exp2.zig +++ b/lib/std/math/exp2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/expm1.zig b/lib/std/math/expm1.zig index 1c22db342a..8389b01eb9 100644 --- a/lib/std/math/expm1.zig +++ b/lib/std/math/expm1.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/expo2.zig b/lib/std/math/expo2.zig index a81c9920e0..b88d4c2236 100644 --- a/lib/std/math/expo2.zig +++ b/lib/std/math/expo2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/fabs.zig b/lib/std/math/fabs.zig index f263bfbc58..d59d185b99 100644 --- a/lib/std/math/fabs.zig +++ b/lib/std/math/fabs.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/floor.zig b/lib/std/math/floor.zig index d28b5e102c..6e0b99f47c 100644 --- a/lib/std/math/floor.zig +++ b/lib/std/math/floor.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/fma.zig b/lib/std/math/fma.zig index 852bbe9d75..1b04e1aa18 100644 --- a/lib/std/math/fma.zig +++ b/lib/std/math/fma.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/frexp.zig b/lib/std/math/frexp.zig index 3f73c9eec3..5f7bafb494 100644 --- a/lib/std/math/frexp.zig +++ b/lib/std/math/frexp.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/hypot.zig b/lib/std/math/hypot.zig index f04a42d1d5..78aef476f9 100644 --- a/lib/std/math/hypot.zig +++ b/lib/std/math/hypot.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/ilogb.zig b/lib/std/math/ilogb.zig index a6fb031973..e43012b831 100644 --- a/lib/std/math/ilogb.zig +++ b/lib/std/math/ilogb.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/inf.zig b/lib/std/math/inf.zig index f2e0283e03..5011193e95 100644 --- a/lib/std/math/inf.zig +++ b/lib/std/math/inf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/isfinite.zig b/lib/std/math/isfinite.zig index 938c495d65..5266b918df 100644 --- a/lib/std/math/isfinite.zig +++ b/lib/std/math/isfinite.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/isinf.zig b/lib/std/math/isinf.zig index 2ecd9c2b9c..b7c3199f15 100644 --- a/lib/std/math/isinf.zig +++ b/lib/std/math/isinf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/isnan.zig b/lib/std/math/isnan.zig index fc58e7334c..498d181118 100644 --- a/lib/std/math/isnan.zig +++ b/lib/std/math/isnan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/isnormal.zig b/lib/std/math/isnormal.zig index b9ff515bdc..6317535203 100644 --- a/lib/std/math/isnormal.zig +++ b/lib/std/math/isnormal.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/ln.zig b/lib/std/math/ln.zig index cb5c966ab8..e0ce32a7e1 100644 --- a/lib/std/math/ln.zig +++ b/lib/std/math/ln.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/log.zig b/lib/std/math/log.zig index 240ef759d1..ef4d4bbb97 100644 --- a/lib/std/math/log.zig +++ b/lib/std/math/log.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/log10.zig b/lib/std/math/log10.zig index 269bc1d228..719e0cf51d 100644 --- a/lib/std/math/log10.zig +++ b/lib/std/math/log10.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/log1p.zig b/lib/std/math/log1p.zig index c0ca027ffb..4eaee2c43f 100644 --- a/lib/std/math/log1p.zig +++ b/lib/std/math/log1p.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/log2.zig b/lib/std/math/log2.zig index 201364a8cf..c44672751e 100644 --- a/lib/std/math/log2.zig +++ b/lib/std/math/log2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/modf.zig b/lib/std/math/modf.zig index fe73f0ce75..390b3e4f49 100644 --- a/lib/std/math/modf.zig +++ b/lib/std/math/modf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/nan.zig b/lib/std/math/nan.zig index b8e3b517e2..98051b155a 100644 --- a/lib/std/math/nan.zig +++ b/lib/std/math/nan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/pow.zig b/lib/std/math/pow.zig index d4ea8876cc..5c49c95865 100644 --- a/lib/std/math/pow.zig +++ b/lib/std/math/pow.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/powi.zig b/lib/std/math/powi.zig index 8846ee8833..e415b74d87 100644 --- a/lib/std/math/powi.zig +++ b/lib/std/math/powi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/round.zig b/lib/std/math/round.zig index 0855b11cbb..9167bcfc82 100644 --- a/lib/std/math/round.zig +++ b/lib/std/math/round.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/scalbn.zig b/lib/std/math/scalbn.zig index 7243084dd4..cf8ff9003d 100644 --- a/lib/std/math/scalbn.zig +++ b/lib/std/math/scalbn.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/signbit.zig b/lib/std/math/signbit.zig index defd02aa3a..9fb245c3c6 100644 --- a/lib/std/math/signbit.zig +++ b/lib/std/math/signbit.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/sin.zig b/lib/std/math/sin.zig index 0f30e6749f..d051e3f88a 100644 --- a/lib/std/math/sin.zig +++ b/lib/std/math/sin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/sinh.zig b/lib/std/math/sinh.zig index d39c7ee002..16329a9108 100644 --- a/lib/std/math/sinh.zig +++ b/lib/std/math/sinh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/sqrt.zig b/lib/std/math/sqrt.zig index 54c2fff13a..38609115d8 100644 --- a/lib/std/math/sqrt.zig +++ b/lib/std/math/sqrt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/tan.zig b/lib/std/math/tan.zig index b80e2fbb27..d0e8a0d4f8 100644 --- a/lib/std/math/tan.zig +++ b/lib/std/math/tan.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/tanh.zig b/lib/std/math/tanh.zig index 81df2aed35..c53f03122b 100644 --- a/lib/std/math/tanh.zig +++ b/lib/std/math/tanh.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/math/trunc.zig b/lib/std/math/trunc.zig index 935da85013..69c300efee 100644 --- a/lib/std/math/trunc.zig +++ b/lib/std/math/trunc.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 22e340810e..5f23a10401 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -991,6 +991,40 @@ test "mem.count" { testing.expect(count(u8, "owowowu", "owowu") == 1); } +/// Returns true if the haystack contains expected_count or more needles +/// needle.len must be > 0 +/// does not count overlapping needles +pub fn containsAtLeast(comptime T: type, haystack: []const T, expected_count: usize, needle: []const T) bool { + assert(needle.len > 0); + if (expected_count == 0) return true; + + var i: usize = 0; + var found: usize = 0; + + while (indexOfPos(T, haystack, i, needle)) |idx| { + i = idx + needle.len; + found += 1; + if (found == expected_count) return true; + } + return false; +} + +test "mem.containsAtLeast" { + testing.expect(containsAtLeast(u8, "aa", 0, "a")); + testing.expect(containsAtLeast(u8, "aa", 1, "a")); + testing.expect(containsAtLeast(u8, "aa", 2, "a")); + testing.expect(!containsAtLeast(u8, "aa", 3, "a")); + + testing.expect(containsAtLeast(u8, "radaradar", 1, "radar")); + testing.expect(!containsAtLeast(u8, "radaradar", 2, "radar")); + + testing.expect(containsAtLeast(u8, "radarradaradarradar", 3, "radar")); + testing.expect(!containsAtLeast(u8, "radarradaradarradar", 4, "radar")); + + testing.expect(containsAtLeast(u8, " radar radar ", 2, "radar")); + testing.expect(!containsAtLeast(u8, " radar radar ", 3, "radar")); +} + /// Reads an integer from memory with size equal to bytes.len. /// T specifies the return type, which must be large enough to store /// the result. @@ -1473,7 +1507,7 @@ pub fn joinZ(allocator: *Allocator, separator: []const u8, slices: []const []con } fn joinMaybeZ(allocator: *Allocator, separator: []const u8, slices: []const []const u8, zero: bool) ![]u8 { - if (slices.len == 0) return &[0]u8{}; + if (slices.len == 0) return if (zero) try allocator.dupe(u8, &[1]u8{0}) else &[0]u8{}; const total_len = blk: { var sum: usize = separator.len * (slices.len - 1); @@ -1502,6 +1536,11 @@ fn joinMaybeZ(allocator: *Allocator, separator: []const u8, slices: []const []co test "mem.join" { { + const str = try join(testing.allocator, ",", &[_][]const u8{}); + defer testing.allocator.free(str); + testing.expect(eql(u8, str, "")); + } + { const str = try join(testing.allocator, ",", &[_][]const u8{ "a", "b", "c" }); defer testing.allocator.free(str); testing.expect(eql(u8, str, "a,b,c")); @@ -1520,6 +1559,12 @@ test "mem.join" { test "mem.joinZ" { { + const str = try joinZ(testing.allocator, ",", &[_][]const u8{}); + defer testing.allocator.free(str); + testing.expect(eql(u8, str, "")); + testing.expectEqual(str[str.len], 0); + } + { const str = try joinZ(testing.allocator, ",", &[_][]const u8{ "a", "b", "c" }); defer testing.allocator.free(str); testing.expect(eql(u8, str, "a,b,c")); @@ -2402,3 +2447,46 @@ test "freeing empty string with null-terminated sentinel" { const empty_string = try dupeZ(testing.allocator, u8, ""); testing.allocator.free(empty_string); } + +/// Returns a slice with the given new alignment, +/// all other pointer attributes copied from `AttributeSource`. +fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) type { + const info = @typeInfo(AttributeSource).Pointer; + return @Type(.{ + .Pointer = .{ + .size = .Slice, + .is_const = info.is_const, + .is_volatile = info.is_volatile, + .is_allowzero = info.is_allowzero, + .alignment = new_alignment, + .child = info.child, + .sentinel = null, + }, + }); +} + +/// Returns the largest slice in the given bytes that conforms to the new alignment, +/// or `null` if the given bytes contain no conforming address. +pub fn alignInBytes(bytes: []u8, comptime new_alignment: usize) ?[]align(new_alignment) u8 { + const begin_address = @ptrToInt(bytes.ptr); + const end_address = begin_address + bytes.len; + + const begin_address_aligned = mem.alignForward(begin_address, new_alignment); + const new_length = std.math.sub(usize, end_address, begin_address_aligned) catch |e| switch (e) { + error.Overflow => return null, + }; + const alignment_offset = begin_address_aligned - begin_address; + return @alignCast(new_alignment, bytes[alignment_offset .. alignment_offset + new_length]); +} + +/// Returns the largest sub-slice within the given slice that conforms to the new alignment, +/// or `null` if the given slice contains no conforming address. +pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice(@TypeOf(slice), new_alignment) { + const bytes = sliceAsBytes(slice); + const aligned_bytes = alignInBytes(bytes, new_alignment) orelse return null; + + const Element = @TypeOf(slice[0]); + const slice_length_bytes = aligned_bytes.len - (aligned_bytes.len % @sizeOf(Element)); + const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]); + return @alignCast(new_alignment, aligned_slice); +} diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 4511acb275..6cc888fa10 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -146,12 +146,8 @@ fn moveBytes( assert(new_len > 0); const new_mem = try self.allocFn(self, new_len, new_alignment, len_align, return_address); @memcpy(new_mem.ptr, old_mem.ptr, math.min(new_len, old_mem.len)); - // TODO DISABLED TO AVOID BUGS IN TRANSLATE C - // TODO see also https://github.com/ziglang/zig/issues/4298 - // use './zig build test-translate-c' to reproduce, some of the symbols in the - // generated C code will be a sequence of 0xaa (the undefined value), meaning - // it is printing data that has been freed - //@memset(old_mem.ptr, undefined, old_mem.len); + // TODO https://github.com/ziglang/zig/issues/4298 + @memset(old_mem.ptr, undefined, old_mem.len); _ = self.shrinkBytes(old_mem, old_align, 0, 0, return_address); return new_mem; } diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 2cf7f6de81..7ec29dcd0e 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,6 +9,7 @@ const debug = std.debug; const mem = std.mem; const math = std.math; const testing = std.testing; +const root = @import("root"); pub const trait = @import("meta/trait.zig"); pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags; @@ -486,19 +487,14 @@ test "std.meta.fields" { testing.expect(comptime uf[0].field_type == u8); } -pub fn fieldInfo(comptime T: type, comptime field_name: []const u8) switch (@typeInfo(T)) { +pub fn fieldInfo(comptime T: type, comptime field: FieldEnum(T)) switch (@typeInfo(T)) { .Struct => TypeInfo.StructField, .Union => TypeInfo.UnionField, .ErrorSet => TypeInfo.Error, .Enum => TypeInfo.EnumField, else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"), } { - inline for (comptime fields(T)) |field| { - if (comptime mem.eql(u8, field.name, field_name)) - return field; - } - - @compileError("'" ++ @typeName(T) ++ "' has no field '" ++ field_name ++ "'"); + return fields(T)[@enumToInt(field)]; } test "std.meta.fieldInfo" { @@ -513,10 +509,10 @@ test "std.meta.fieldInfo" { a: u8, }; - const e1f = comptime fieldInfo(E1, "A"); - const e2f = comptime fieldInfo(E2, "A"); - const sf = comptime fieldInfo(S1, "a"); - const uf = comptime fieldInfo(U1, "a"); + const e1f = fieldInfo(E1, .A); + const e2f = fieldInfo(E2, .A); + const sf = fieldInfo(S1, .a); + const uf = fieldInfo(U1, .a); testing.expect(mem.eql(u8, e1f.name, "A")); testing.expect(mem.eql(u8, e2f.name, "A")); @@ -538,9 +534,7 @@ pub fn fieldNames(comptime T: type) *const [fields(T).len][]const u8 { } test "std.meta.fieldNames" { - const E1 = enum { - A, B - }; + const E1 = enum { A, B }; const E2 = error{A}; const S1 = struct { a: u8, @@ -567,15 +561,55 @@ test "std.meta.fieldNames" { testing.expectEqualSlices(u8, u1names[1], "b"); } -pub fn TagType(comptime T: type) type { +pub fn FieldEnum(comptime T: type) type { + const fieldInfos = fields(T); + var enumFields: [fieldInfos.len]std.builtin.TypeInfo.EnumField = undefined; + var decls = [_]std.builtin.TypeInfo.Declaration{}; + inline for (fieldInfos) |field, i| { + enumFields[i] = .{ + .name = field.name, + .value = i, + }; + } + return @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = std.math.IntFittingRange(0, fieldInfos.len - 1), + .fields = &enumFields, + .decls = &decls, + .is_exhaustive = true, + }, + }); +} + +fn expectEqualEnum(expected: anytype, actual: @TypeOf(expected)) void { + // TODO: https://github.com/ziglang/zig/issues/7419 + // testing.expectEqual(@typeInfo(expected).Enum, @typeInfo(actual).Enum); + testing.expectEqual(@typeInfo(expected).Enum.layout, @typeInfo(actual).Enum.layout); + testing.expectEqual(@typeInfo(expected).Enum.tag_type, @typeInfo(actual).Enum.tag_type); + comptime testing.expectEqualSlices(std.builtin.TypeInfo.EnumField, @typeInfo(expected).Enum.fields, @typeInfo(actual).Enum.fields); + comptime testing.expectEqualSlices(std.builtin.TypeInfo.Declaration, @typeInfo(expected).Enum.decls, @typeInfo(actual).Enum.decls); + testing.expectEqual(@typeInfo(expected).Enum.is_exhaustive, @typeInfo(actual).Enum.is_exhaustive); +} + +test "std.meta.FieldEnum" { + expectEqualEnum(enum { a }, FieldEnum(struct { a: u8 })); + expectEqualEnum(enum { a, b, c }, FieldEnum(struct { a: u8, b: void, c: f32 })); + expectEqualEnum(enum { a, b, c }, FieldEnum(union { a: u8, b: void, c: f32 })); +} + +// Deprecated: use Tag +pub const TagType = Tag; + +pub fn Tag(comptime T: type) type { return switch (@typeInfo(T)) { .Enum => |info| info.tag_type, - .Union => |info| if (info.tag_type) |Tag| Tag else null, + .Union => |info| info.tag_type orelse @compileError(@typeName(T) ++ " has no tag type"), else => @compileError("expected enum or union type, found '" ++ @typeName(T) ++ "'"), }; } -test "std.meta.TagType" { +test "std.meta.Tag" { const E = enum(u8) { C = 33, D, @@ -585,14 +619,14 @@ test "std.meta.TagType" { D: u16, }; - testing.expect(TagType(E) == u8); - testing.expect(TagType(U) == E); + testing.expect(Tag(E) == u8); + testing.expect(Tag(U) == E); } ///Returns the active tag of a tagged union -pub fn activeTag(u: anytype) @TagType(@TypeOf(u)) { +pub fn activeTag(u: anytype) Tag(@TypeOf(u)) { const T = @TypeOf(u); - return @as(@TagType(T), u); + return @as(Tag(T), u); } test "std.meta.activeTag" { @@ -613,13 +647,15 @@ test "std.meta.activeTag" { testing.expect(activeTag(u) == UE.Float); } +const TagPayloadType = TagPayload; + ///Given a tagged union type, and an enum, return the type of the union /// field corresponding to the enum tag. -pub fn TagPayloadType(comptime U: type, tag: @TagType(U)) type { +pub fn TagPayload(comptime U: type, tag: Tag(U)) type { testing.expect(trait.is(.Union)(U)); const info = @typeInfo(U).Union; - const tag_info = @typeInfo(@TagType(U)).Enum; + const tag_info = @typeInfo(Tag(U)).Enum; inline for (info.fields) |field_info| { if (comptime mem.eql(u8, field_info.name, @tagName(tag))) @@ -629,14 +665,14 @@ pub fn TagPayloadType(comptime U: type, tag: @TagType(U)) type { unreachable; } -test "std.meta.TagPayloadType" { +test "std.meta.TagPayload" { const Event = union(enum) { Moved: struct { from: i32, to: i32, }, }; - const MovedEvent = TagPayloadType(Event, Event.Moved); + const MovedEvent = TagPayload(Event, Event.Moved); var e: Event = undefined; testing.expect(MovedEvent == @TypeOf(e.Moved)); } @@ -661,13 +697,13 @@ pub fn eql(a: anytype, b: @TypeOf(a)) bool { } }, .Union => |info| { - if (info.tag_type) |Tag| { + if (info.tag_type) |UnionTag| { const tag_a = activeTag(a); const tag_b = activeTag(b); if (tag_a != tag_b) return false; inline for (info.fields) |field_info| { - if (@field(Tag, field_info.name) == tag_a) { + if (@field(UnionTag, field_info.name) == tag_a) { return eql(@field(a, field_info.name), @field(b, field_info.name)); } } @@ -789,9 +825,9 @@ test "intToEnum with error return" { pub const IntToEnumError = error{InvalidEnumTag}; -pub fn intToEnum(comptime Tag: type, tag_int: anytype) IntToEnumError!Tag { - inline for (@typeInfo(Tag).Enum.fields) |f| { - const this_tag_value = @field(Tag, f.name); +pub fn intToEnum(comptime EnumTag: type, tag_int: anytype) IntToEnumError!EnumTag { + inline for (@typeInfo(EnumTag).Enum.fields) |f| { + const this_tag_value = @field(EnumTag, f.name); if (tag_int == @enumToInt(this_tag_value)) { return this_tag_value; } @@ -946,9 +982,60 @@ test "std.meta.cast" { /// Given a value returns its size as C's sizeof operator would. /// This is for translate-c and is not intended for general use. pub fn sizeof(target: anytype) usize { - switch (@typeInfo(@TypeOf(target))) { - .Type => return @sizeOf(target), - .Float, .Int, .Struct, .Union, .Enum => return @sizeOf(@TypeOf(target)), + const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); + switch (@typeInfo(T)) { + .Float, .Int, .Struct, .Union, .Enum, .Array, .Bool, .Vector => return @sizeOf(T), + .Fn => { + // sizeof(main) returns 1, sizeof(&main) returns pointer size. + // We cannot distinguish those types in Zig, so use pointer size. + return @sizeOf(T); + }, + .Null => return @sizeOf(*c_void), + .Void => { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + }, + .Opaque => { + if (T == c_void) { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + } else { + @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); + } + }, + .Optional => |opt| { + if (@typeInfo(opt.child) == .Pointer) { + return sizeof(opt.child); + } else { + @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); + } + }, + .Pointer => |ptr| { + if (ptr.size == .Slice) { + @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); + } + // for strings, sizeof("a") returns 2. + // normal pointer decay scenarios from C are handled + // in the .Array case above, but strings remain literals + // and are therefore always pointers, so they need to be + // specially handled here. + if (ptr.size == .One and ptr.is_const and @typeInfo(ptr.child) == .Array) { + const array_info = @typeInfo(ptr.child).Array; + if ((array_info.child == u8 or array_info.child == u16) and + array_info.sentinel != null and + array_info.sentinel.? == 0) + { + // length of the string plus one for the null terminator. + return (array_info.len + 1) * @sizeOf(array_info.child); + } + } + // When zero sized pointers are removed, this case will no + // longer be reachable and can be deleted. + if (@sizeOf(T) == 0) { + return @sizeOf(*c_void); + } + return @sizeOf(T); + }, .ComptimeFloat => return @sizeOf(f64), // TODO c_double #3999 .ComptimeInt => { // TODO to get the correct result we have to translate @@ -958,7 +1045,7 @@ pub fn sizeof(target: anytype) usize { // TODO test if target fits in int, long or long long return @sizeOf(c_int); }, - else => @compileError("TODO implement std.meta.sizeof for type " ++ @typeName(@TypeOf(target))), + else => @compileError("std.meta.sizeof does not support type " ++ @typeName(T)), } } @@ -966,12 +1053,45 @@ test "sizeof" { const E = extern enum(c_int) { One, _ }; const S = extern struct { a: u32 }; + const ptr_size = @sizeOf(*c_void); + testing.expect(sizeof(u32) == 4); testing.expect(sizeof(@as(u32, 2)) == 4); testing.expect(sizeof(2) == @sizeOf(c_int)); + + testing.expect(sizeof(2.0) == @sizeOf(f64)); + testing.expect(sizeof(E) == @sizeOf(c_int)); testing.expect(sizeof(E.One) == @sizeOf(c_int)); + testing.expect(sizeof(S) == 4); + + testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); + testing.expect(sizeof([3]u32) == 12); + testing.expect(sizeof([3:0]u32) == 16); + testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); + + testing.expect(sizeof(*u32) == ptr_size); + testing.expect(sizeof([*]u32) == ptr_size); + testing.expect(sizeof([*c]u32) == ptr_size); + testing.expect(sizeof(?*u32) == ptr_size); + testing.expect(sizeof(?[*]u32) == ptr_size); + testing.expect(sizeof(*c_void) == ptr_size); + testing.expect(sizeof(*void) == ptr_size); + testing.expect(sizeof(null) == ptr_size); + + testing.expect(sizeof("foobar") == 7); + testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); + testing.expect(sizeof(*const [4:0]u8) == 5); + testing.expect(sizeof(*[4:0]u8) == ptr_size); + testing.expect(sizeof([*]const [4:0]u8) == ptr_size); + testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); + testing.expect(sizeof(*const [4]u8) == ptr_size); + + testing.expect(sizeof(sizeof) == @sizeOf(@TypeOf(sizeof))); + + testing.expect(sizeof(void) == 1); + testing.expect(sizeof(c_void) == 1); } /// For a given function type, returns a tuple type which fields will @@ -1085,3 +1205,10 @@ test "Tuple" { TupleTester.assertTuple(.{ u32, f16 }, Tuple(&[_]type{ u32, f16 })); TupleTester.assertTuple(.{ u32, f16, []const u8, void }, Tuple(&[_]type{ u32, f16, []const u8, void })); } + +/// TODO: https://github.com/ziglang/zig/issues/425 +pub fn globalOption(comptime name: []const u8, comptime T: type) ?T { + if (!@hasDecl(root, name)) + return null; + return @as(T, @field(root, name)); +} diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig index 505afdb3c8..1697e9fe43 100644 --- a/lib/std/meta/trailer_flags.zig +++ b/lib/std/meta/trailer_flags.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -21,20 +21,7 @@ pub fn TrailerFlags(comptime Fields: type) type { pub const Int = meta.Int(.unsigned, bit_count); pub const bit_count = @typeInfo(Fields).Struct.fields.len; - pub const FieldEnum = blk: { - comptime var fields: [bit_count]TypeInfo.EnumField = undefined; - inline for (@typeInfo(Fields).Struct.fields) |struct_field, i| - fields[i] = .{ .name = struct_field.name, .value = i }; - break :blk @Type(.{ - .Enum = .{ - .layout = .Auto, - .tag_type = std.math.IntFittingRange(0, bit_count - 1), - .fields = &fields, - .decls = &[_]TypeInfo.Declaration{}, - .is_exhaustive = true, - }, - }); - }; + pub const FieldEnum = std.meta.FieldEnum(Fields); pub const InitStruct = blk: { comptime var fields: [bit_count]TypeInfo.StructField = undefined; @@ -135,10 +122,7 @@ pub fn TrailerFlags(comptime Fields: type) type { } pub fn Field(comptime field: FieldEnum) type { - inline for (@typeInfo(Fields).Struct.fields) |field_info, i| { - if (i == @enumToInt(field)) - return field_info.field_type; - } + return @typeInfo(Fields).Struct.fields[@enumToInt(field)].field_type; } pub fn sizeInBytes(self: Self) usize { @@ -162,7 +146,7 @@ test "TrailerFlags" { b: bool, c: u64, }); - testing.expectEqual(u2, @TagType(Flags.FieldEnum)); + testing.expectEqual(u2, meta.Tag(Flags.FieldEnum)); var flags = Flags.init(.{ .b = true, diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index eb294a857c..e67f9b9bc4 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -298,6 +298,20 @@ pub fn isNumber(comptime T: type) bool { }; } +pub fn isIntegerNumber(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Int, .ComptimeInt => true, + else => false, + }; +} + +pub fn isFloatingNumber(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Float, .ComptimeFloat => true, + else => false, + }; +} + test "std.meta.trait.isNumber" { const NotANumber = struct { number: u8, @@ -476,15 +490,20 @@ pub fn hasUniqueRepresentation(comptime T: type) bool { else => return false, // TODO can we know if it's true for some of these types ? .AnyFrame, - .Bool, .BoundFn, .Enum, .ErrorSet, .Fn, - .Int, // TODO check that it is still true - .Pointer, => return true, + .Bool => return false, + + // The padding bits are undefined. + .Int => |info| return (info.bits % 8) == 0 and + (info.bits == 0 or std.math.isPowerOfTwo(info.bits)), + + .Pointer => |info| return info.size != .Slice, + .Array => |info| return comptime hasUniqueRepresentation(info.child), .Struct => |info| { @@ -525,14 +544,49 @@ test "std.meta.trait.hasUniqueRepresentation" { testing.expect(hasUniqueRepresentation(TestStruct3)); - testing.expect(hasUniqueRepresentation(i1)); - testing.expect(hasUniqueRepresentation(u2)); - testing.expect(hasUniqueRepresentation(i3)); - testing.expect(hasUniqueRepresentation(u4)); - testing.expect(hasUniqueRepresentation(i5)); - testing.expect(hasUniqueRepresentation(u6)); - testing.expect(hasUniqueRepresentation(i7)); - testing.expect(hasUniqueRepresentation(u8)); - testing.expect(hasUniqueRepresentation(i9)); - testing.expect(hasUniqueRepresentation(u10)); + const TestStruct4 = struct { a: []const u8 }; + + testing.expect(!hasUniqueRepresentation(TestStruct4)); + + const TestStruct5 = struct { a: TestStruct4 }; + + testing.expect(!hasUniqueRepresentation(TestStruct5)); + + const TestUnion1 = packed union { + a: u32, + b: u16, + }; + + testing.expect(!hasUniqueRepresentation(TestUnion1)); + + const TestUnion2 = extern union { + a: u32, + b: u16, + }; + + testing.expect(!hasUniqueRepresentation(TestUnion2)); + + const TestUnion3 = union { + a: u32, + b: u16, + }; + + testing.expect(!hasUniqueRepresentation(TestUnion3)); + + const TestUnion4 = union(enum) { + a: u32, + b: u16, + }; + + testing.expect(!hasUniqueRepresentation(TestUnion4)); + + inline for ([_]type{ i0, u8, i16, u32, i64 }) |T| { + testing.expect(hasUniqueRepresentation(T)); + } + inline for ([_]type{ i1, u9, i17, u33, i24 }) |T| { + testing.expect(!hasUniqueRepresentation(T)); + } + + testing.expect(!hasUniqueRepresentation([]u8)); + testing.expect(!hasUniqueRepresentation([]const u8)); } diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig new file mode 100644 index 0000000000..3306fd3ef0 --- /dev/null +++ b/lib/std/multi_array_list.zig @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("std.zig"); +const assert = std.debug.assert; +const meta = std.meta; +const mem = std.mem; +const Allocator = mem.Allocator; + +pub fn MultiArrayList(comptime S: type) type { + return struct { + bytes: [*]align(@alignOf(S)) u8 = undefined, + len: usize = 0, + capacity: usize = 0, + + pub const Elem = S; + + pub const Field = meta.FieldEnum(S); + + pub const Slice = struct { + /// This array is indexed by the field index which can be obtained + /// by using @enumToInt() on the Field enum + ptrs: [fields.len][*]u8, + len: usize, + capacity: usize, + + pub fn items(self: Slice, comptime field: Field) []FieldType(field) { + const byte_ptr = self.ptrs[@enumToInt(field)]; + const F = FieldType(field); + const casted_ptr = @ptrCast([*]F, @alignCast(@alignOf(F), byte_ptr)); + return casted_ptr[0..self.len]; + } + + pub fn toMultiArrayList(self: Slice) Self { + if (self.ptrs.len == 0) { + return .{}; + } + const unaligned_ptr = self.ptrs[sizes.fields[0]]; + const aligned_ptr = @alignCast(@alignOf(S), unaligned_ptr); + const casted_ptr = @ptrCast([*]align(@alignOf(S)) u8, aligned_ptr); + return .{ + .bytes = casted_ptr, + .len = self.len, + .capacity = self.capacity, + }; + } + + pub fn deinit(self: *Slice, gpa: *Allocator) void { + var other = self.toMultiArrayList(); + other.deinit(gpa); + self.* = undefined; + } + }; + + const Self = @This(); + + const fields = meta.fields(S); + /// `sizes.bytes` is an array of @sizeOf each S field. Sorted by alignment, descending. + /// `sizes.fields` is an array mapping from `sizes.bytes` array index to field index. + const sizes = blk: { + const Data = struct { + size: usize, + size_index: usize, + alignment: usize, + }; + var data: [fields.len]Data = undefined; + for (fields) |field_info, i| { + data[i] = .{ + .size = @sizeOf(field_info.field_type), + .size_index = i, + .alignment = field_info.alignment, + }; + } + const Sort = struct { + fn lessThan(trash: *i32, lhs: Data, rhs: Data) bool { + return lhs.alignment >= rhs.alignment; + } + }; + var trash: i32 = undefined; // workaround for stage1 compiler bug + std.sort.sort(Data, &data, &trash, Sort.lessThan); + var sizes_bytes: [fields.len]usize = undefined; + var field_indexes: [fields.len]usize = undefined; + for (data) |elem, i| { + sizes_bytes[i] = elem.size; + field_indexes[i] = elem.size_index; + } + break :blk .{ + .bytes = sizes_bytes, + .fields = field_indexes, + }; + }; + + /// Release all allocated memory. + pub fn deinit(self: *Self, gpa: *Allocator) void { + gpa.free(self.allocatedBytes()); + self.* = undefined; + } + + /// The caller owns the returned memory. Empties this MultiArrayList. + pub fn toOwnedSlice(self: *Self) Slice { + const result = self.slice(); + self.* = .{}; + return result; + } + + pub fn slice(self: Self) Slice { + var result: Slice = .{ + .ptrs = undefined, + .len = self.len, + .capacity = self.capacity, + }; + var ptr: [*]u8 = self.bytes; + for (sizes.bytes) |field_size, i| { + result.ptrs[sizes.fields[i]] = ptr; + ptr += field_size * self.capacity; + } + return result; + } + + pub fn items(self: Self, comptime field: Field) []FieldType(field) { + return self.slice().items(field); + } + + /// Overwrite one array element with new data. + pub fn set(self: *Self, index: usize, elem: S) void { + const slices = self.slice(); + inline for (fields) |field_info, i| { + slices.items(@intToEnum(Field, i))[index] = @field(elem, field_info.name); + } + } + + /// Obtain all the data for one array element. + pub fn get(self: *Self, index: usize) S { + const slices = self.slice(); + var result: S = undefined; + inline for (fields) |field_info, i| { + @field(elem, field_info.name) = slices.items(@intToEnum(Field, i))[index]; + } + return result; + } + + /// Extend the list by 1 element. Allocates more memory as necessary. + pub fn append(self: *Self, gpa: *Allocator, elem: S) !void { + try self.ensureCapacity(gpa, self.len + 1); + self.appendAssumeCapacity(elem); + } + + /// Extend the list by 1 element, but asserting `self.capacity` + /// is sufficient to hold an additional item. + pub fn appendAssumeCapacity(self: *Self, elem: S) void { + assert(self.len < self.capacity); + self.len += 1; + self.set(self.len - 1, elem); + } + + /// Adjust the list's length to `new_len`. + /// Does not initialize added items, if any. + pub fn resize(self: *Self, gpa: *Allocator, new_len: usize) !void { + try self.ensureCapacity(gpa, new_len); + self.len = new_len; + } + + /// Attempt to reduce allocated capacity to `new_len`. + /// If `new_len` is greater than zero, this may fail to reduce the capacity, + /// but the data remains intact and the length is updated to new_len. + pub fn shrinkAndFree(self: *Self, gpa: *Allocator, new_len: usize) void { + if (new_len == 0) { + gpa.free(self.allocatedBytes()); + self.* = .{}; + return; + } + assert(new_len <= self.capacity); + assert(new_len <= self.len); + + const other_bytes = gpa.allocAdvanced( + u8, + @alignOf(S), + capacityInBytes(new_len), + .exact, + ) catch { + const self_slice = self.slice(); + inline for (fields) |field_info, i| { + const field = @intToEnum(Field, i); + const dest_slice = self_slice.items(field)[new_len..]; + const byte_count = dest_slice.len * @sizeOf(field_info.field_type); + // We use memset here for more efficient codegen in safety-checked, + // valgrind-enabled builds. Otherwise the valgrind client request + // will be repeated for every element. + @memset(@ptrCast([*]u8, dest_slice.ptr), undefined, byte_count); + } + self.len = new_len; + return; + }; + var other = Self{ + .bytes = other_bytes.ptr, + .capacity = new_len, + .len = new_len, + }; + self.len = new_len; + const self_slice = self.slice(); + const other_slice = other.slice(); + inline for (fields) |field_info, i| { + const field = @intToEnum(Field, i); + // TODO we should be able to use std.mem.copy here but it causes a + // test failure on aarch64 with -OReleaseFast + const src_slice = mem.sliceAsBytes(self_slice.items(field)); + const dst_slice = mem.sliceAsBytes(other_slice.items(field)); + @memcpy(dst_slice.ptr, src_slice.ptr, src_slice.len); + } + gpa.free(self.allocatedBytes()); + self.* = other; + } + + /// Reduce length to `new_len`. + /// Invalidates pointers to elements `items[new_len..]`. + /// Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + self.len = new_len; + } + + /// Modify the array so that it can hold at least `new_capacity` items. + /// Implements super-linear growth to achieve amortized O(1) append operations. + /// Invalidates pointers if additional memory is needed. + pub fn ensureCapacity(self: *Self, gpa: *Allocator, new_capacity: usize) !void { + var better_capacity = self.capacity; + if (better_capacity >= new_capacity) return; + + while (true) { + better_capacity += better_capacity / 2 + 8; + if (better_capacity >= new_capacity) break; + } + + return self.setCapacity(gpa, better_capacity); + } + + /// Modify the array so that it can hold exactly `new_capacity` items. + /// Invalidates pointers if additional memory is needed. + /// `new_capacity` must be greater or equal to `len`. + pub fn setCapacity(self: *Self, gpa: *Allocator, new_capacity: usize) !void { + assert(new_capacity >= self.len); + const new_bytes = try gpa.allocAdvanced( + u8, + @alignOf(S), + capacityInBytes(new_capacity), + .exact, + ); + if (self.len == 0) { + self.bytes = new_bytes.ptr; + self.capacity = new_capacity; + return; + } + var other = Self{ + .bytes = new_bytes.ptr, + .capacity = new_capacity, + .len = self.len, + }; + const self_slice = self.slice(); + const other_slice = other.slice(); + inline for (fields) |field_info, i| { + const field = @intToEnum(Field, i); + // TODO we should be able to use std.mem.copy here but it causes a + // test failure on aarch64 with -OReleaseFast + const src_slice = mem.sliceAsBytes(self_slice.items(field)); + const dst_slice = mem.sliceAsBytes(other_slice.items(field)); + @memcpy(dst_slice.ptr, src_slice.ptr, src_slice.len); + } + gpa.free(self.allocatedBytes()); + self.* = other; + } + + fn capacityInBytes(capacity: usize) usize { + const sizes_vector: std.meta.Vector(sizes.bytes.len, usize) = sizes.bytes; + const capacity_vector = @splat(sizes.bytes.len, capacity); + return @reduce(.Add, capacity_vector * sizes_vector); + } + + fn allocatedBytes(self: Self) []align(@alignOf(S)) u8 { + return self.bytes[0..capacityInBytes(self.capacity)]; + } + + fn FieldType(field: Field) type { + return meta.fieldInfo(S, field).field_type; + } + }; +} + +test "basic usage" { + const testing = std.testing; + const ally = testing.allocator; + + const Foo = struct { + a: u32, + b: []const u8, + c: u8, + }; + + var list = MultiArrayList(Foo){}; + defer list.deinit(ally); + + try list.ensureCapacity(ally, 2); + + list.appendAssumeCapacity(.{ + .a = 1, + .b = "foobar", + .c = 'a', + }); + + list.appendAssumeCapacity(.{ + .a = 2, + .b = "zigzag", + .c = 'b', + }); + + testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2 }); + testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b' }); + + testing.expectEqual(@as(usize, 2), list.items(.b).len); + testing.expectEqualStrings("foobar", list.items(.b)[0]); + testing.expectEqualStrings("zigzag", list.items(.b)[1]); + + try list.append(ally, .{ + .a = 3, + .b = "fizzbuzz", + .c = 'c', + }); + + testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 }); + testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' }); + + testing.expectEqual(@as(usize, 3), list.items(.b).len); + testing.expectEqualStrings("foobar", list.items(.b)[0]); + testing.expectEqualStrings("zigzag", list.items(.b)[1]); + testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]); + + // Add 6 more things to force a capacity increase. + var i: usize = 0; + while (i < 6) : (i += 1) { + try list.append(ally, .{ + .a = @intCast(u32, 4 + i), + .b = "whatever", + .c = @intCast(u8, 'd' + i), + }); + } + + testing.expectEqualSlices( + u32, + &[_]u32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + list.items(.a), + ); + testing.expectEqualSlices( + u8, + &[_]u8{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' }, + list.items(.c), + ); + + list.shrinkAndFree(ally, 3); + + testing.expectEqualSlices(u32, list.items(.a), &[_]u32{ 1, 2, 3 }); + testing.expectEqualSlices(u8, list.items(.c), &[_]u8{ 'a', 'b', 'c' }); + + testing.expectEqual(@as(usize, 3), list.items(.b).len); + testing.expectEqualStrings("foobar", list.items(.b)[0]); + testing.expectEqualStrings("zigzag", list.items(.b)[1]); + testing.expectEqualStrings("fizzbuzz", list.items(.b)[2]); +} + +// This was observed to fail on aarch64 with LLVM 11, when the capacityInBytes +// function used the @reduce code path. +test "regression test for @reduce bug" { + const ally = std.testing.allocator; + var list = MultiArrayList(struct { + tag: std.zig.Token.Tag, + start: u32, + }){}; + defer list.deinit(ally); + + try list.ensureCapacity(ally, 20); + + try list.append(ally, .{ .tag = .keyword_const, .start = 0 }); + try list.append(ally, .{ .tag = .identifier, .start = 6 }); + try list.append(ally, .{ .tag = .equal, .start = 10 }); + try list.append(ally, .{ .tag = .builtin, .start = 12 }); + try list.append(ally, .{ .tag = .l_paren, .start = 19 }); + try list.append(ally, .{ .tag = .string_literal, .start = 20 }); + try list.append(ally, .{ .tag = .r_paren, .start = 25 }); + try list.append(ally, .{ .tag = .semicolon, .start = 26 }); + try list.append(ally, .{ .tag = .keyword_pub, .start = 29 }); + try list.append(ally, .{ .tag = .keyword_fn, .start = 33 }); + try list.append(ally, .{ .tag = .identifier, .start = 36 }); + try list.append(ally, .{ .tag = .l_paren, .start = 40 }); + try list.append(ally, .{ .tag = .r_paren, .start = 41 }); + try list.append(ally, .{ .tag = .identifier, .start = 43 }); + try list.append(ally, .{ .tag = .bang, .start = 51 }); + try list.append(ally, .{ .tag = .identifier, .start = 52 }); + try list.append(ally, .{ .tag = .l_brace, .start = 57 }); + try list.append(ally, .{ .tag = .identifier, .start = 63 }); + try list.append(ally, .{ .tag = .period, .start = 66 }); + try list.append(ally, .{ .tag = .identifier, .start = 67 }); + try list.append(ally, .{ .tag = .period, .start = 70 }); + try list.append(ally, .{ .tag = .identifier, .start = 71 }); + try list.append(ally, .{ .tag = .l_paren, .start = 75 }); + try list.append(ally, .{ .tag = .string_literal, .start = 76 }); + try list.append(ally, .{ .tag = .comma, .start = 113 }); + try list.append(ally, .{ .tag = .period, .start = 115 }); + try list.append(ally, .{ .tag = .l_brace, .start = 116 }); + try list.append(ally, .{ .tag = .r_brace, .start = 117 }); + try list.append(ally, .{ .tag = .r_paren, .start = 118 }); + try list.append(ally, .{ .tag = .semicolon, .start = 119 }); + try list.append(ally, .{ .tag = .r_brace, .start = 121 }); + try list.append(ally, .{ .tag = .eof, .start = 123 }); + + const tags = list.items(.tag); + std.testing.expectEqual(tags[1], .identifier); + std.testing.expectEqual(tags[2], .equal); + std.testing.expectEqual(tags[3], .builtin); + std.testing.expectEqual(tags[4], .l_paren); + std.testing.expectEqual(tags[5], .string_literal); + std.testing.expectEqual(tags[6], .r_paren); + std.testing.expectEqual(tags[7], .semicolon); + std.testing.expectEqual(tags[8], .keyword_pub); + std.testing.expectEqual(tags[9], .keyword_fn); + std.testing.expectEqual(tags[10], .identifier); + std.testing.expectEqual(tags[11], .l_paren); + std.testing.expectEqual(tags[12], .r_paren); + std.testing.expectEqual(tags[13], .identifier); + std.testing.expectEqual(tags[14], .bang); + std.testing.expectEqual(tags[15], .identifier); + std.testing.expectEqual(tags[16], .l_brace); + std.testing.expectEqual(tags[17], .identifier); + std.testing.expectEqual(tags[18], .period); + std.testing.expectEqual(tags[19], .identifier); + std.testing.expectEqual(tags[20], .period); + std.testing.expectEqual(tags[21], .identifier); + std.testing.expectEqual(tags[22], .l_paren); + std.testing.expectEqual(tags[23], .string_literal); + std.testing.expectEqual(tags[24], .comma); + std.testing.expectEqual(tags[25], .period); + std.testing.expectEqual(tags[26], .l_brace); + std.testing.expectEqual(tags[27], .r_brace); + std.testing.expectEqual(tags[28], .r_paren); + std.testing.expectEqual(tags[29], .semicolon); + std.testing.expectEqual(tags[30], .r_brace); + std.testing.expectEqual(tags[31], .eof); +} diff --git a/lib/std/mutex.zig b/lib/std/mutex.zig deleted file mode 100644 index fb54e04289..0000000000 --- a/lib/std/mutex.zig +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const builtin = @import("builtin"); -const os = std.os; -const assert = std.debug.assert; -const windows = os.windows; -const testing = std.testing; -const SpinLock = std.SpinLock; -const ResetEvent = std.ResetEvent; - -/// Lock may be held only once. If the same thread tries to acquire -/// the same mutex twice, it deadlocks. This type supports static -/// initialization and is at most `@sizeOf(usize)` in size. When an -/// application is built in single threaded release mode, all the -/// functions are no-ops. In single threaded debug mode, there is -/// deadlock detection. -/// -/// Example usage: -/// var m = Mutex{}; -/// -/// const lock = m.acquire(); -/// defer lock.release(); -/// ... critical code -/// -/// Non-blocking: -/// if (m.tryAcquire) |lock| { -/// defer lock.release(); -/// // ... critical section -/// } else { -/// // ... lock not acquired -/// } -pub const Mutex = if (builtin.single_threaded) - Dummy -else if (builtin.os.tag == .windows) - WindowsMutex -else if (builtin.link_libc or builtin.os.tag == .linux) - // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs - struct { - state: usize = 0, - - /// number of times to spin trying to acquire the lock. - /// https://webkit.org/blog/6161/locking-in-webkit/ - const SPIN_COUNT = 40; - - const MUTEX_LOCK: usize = 1 << 0; - const QUEUE_LOCK: usize = 1 << 1; - const QUEUE_MASK: usize = ~(MUTEX_LOCK | QUEUE_LOCK); - - const Node = struct { - next: ?*Node, - event: ResetEvent, - }; - - pub fn tryAcquire(self: *Mutex) ?Held { - if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null) - return null; - return Held{ .mutex = self }; - } - - pub fn acquire(self: *Mutex) Held { - return self.tryAcquire() orelse { - self.acquireSlow(); - return Held{ .mutex = self }; - }; - } - - fn acquireSlow(self: *Mutex) void { - // inlining the fast path and hiding *Slow() - // calls behind a @setCold(true) appears to - // improve performance in release builds. - @setCold(true); - while (true) { - - // try and spin for a bit to acquire the mutex if theres currently no queue - var spin_count: u32 = SPIN_COUNT; - var state = @atomicLoad(usize, &self.state, .Monotonic); - while (spin_count != 0) : (spin_count -= 1) { - if (state & MUTEX_LOCK == 0) { - _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return; - } else if (state & QUEUE_MASK == 0) { - break; - } - SpinLock.yield(); - state = @atomicLoad(usize, &self.state, .Monotonic); - } - - // create the ResetEvent node on the stack - // (faster than threadlocal on platforms like OSX) - var node: Node = undefined; - node.event = ResetEvent.init(); - defer node.event.deinit(); - - // we've spun too long, try and add our node to the LIFO queue. - // if the mutex becomes available in the process, try and grab it instead. - while (true) { - if (state & MUTEX_LOCK == 0) { - _ = @cmpxchgWeak(usize, &self.state, state, state | MUTEX_LOCK, .Acquire, .Monotonic) orelse return; - } else { - node.next = @intToPtr(?*Node, state & QUEUE_MASK); - const new_state = @ptrToInt(&node) | (state & ~QUEUE_MASK); - _ = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Monotonic) orelse { - node.event.wait(); - break; - }; - } - SpinLock.yield(); - state = @atomicLoad(usize, &self.state, .Monotonic); - } - } - } - - /// Returned when the lock is acquired. Call release to - /// release. - pub const Held = struct { - mutex: *Mutex, - - /// Release the held lock. - pub fn release(self: Held) void { - // first, remove the lock bit so another possibly parallel acquire() can succeed. - // use .Sub since it can be usually compiled down more efficiency - // (`lock sub` on x86) vs .And ~MUTEX_LOCK (`lock cmpxchg` loop on x86) - const state = @atomicRmw(usize, &self.mutex.state, .Sub, MUTEX_LOCK, .Release); - - // if the LIFO queue isnt locked and it has a node, try and wake up the node. - if ((state & QUEUE_LOCK) == 0 and (state & QUEUE_MASK) != 0) - self.mutex.releaseSlow(); - } - }; - - fn releaseSlow(self: *Mutex) void { - @setCold(true); - - // try and lock the LFIO queue to pop a node off, - // stopping altogether if its already locked or the queue is empty - var state = @atomicLoad(usize, &self.state, .Monotonic); - while (true) : (SpinLock.loopHint(1)) { - if (state & QUEUE_LOCK != 0 or state & QUEUE_MASK == 0) - return; - state = @cmpxchgWeak(usize, &self.state, state, state | QUEUE_LOCK, .Acquire, .Monotonic) orelse break; - } - - // acquired the QUEUE_LOCK, try and pop a node to wake it. - // if the mutex is locked, then unset QUEUE_LOCK and let - // the thread who holds the mutex do the wake-up on unlock() - while (true) : (SpinLock.loopHint(1)) { - if ((state & MUTEX_LOCK) != 0) { - state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Acquire) orelse return; - } else { - const node = @intToPtr(*Node, state & QUEUE_MASK); - const new_state = @ptrToInt(node.next); - state = @cmpxchgWeak(usize, &self.state, state, new_state, .Release, .Acquire) orelse { - node.event.set(); - return; - }; - } - } - } - } - - // for platforms without a known OS blocking - // primitive, default to SpinLock for correctness -else - SpinLock; - -/// This has the sematics as `Mutex`, however it does not actually do any -/// synchronization. Operations are safety-checked no-ops. -pub const Dummy = struct { - lock: @TypeOf(lock_init) = lock_init, - - const lock_init = if (std.debug.runtime_safety) false else {}; - - pub const Held = struct { - mutex: *Dummy, - - pub fn release(self: Held) void { - if (std.debug.runtime_safety) { - self.mutex.lock = false; - } - } - }; - - /// Create a new mutex in unlocked state. - pub const init = Dummy{}; - - /// Try to acquire the mutex without blocking. Returns null if - /// the mutex is unavailable. Otherwise returns Held. Call - /// release on Held. - pub fn tryAcquire(self: *Dummy) ?Held { - if (std.debug.runtime_safety) { - if (self.lock) return null; - self.lock = true; - } - return Held{ .mutex = self }; - } - - /// Acquire the mutex. Will deadlock if the mutex is already - /// held by the calling thread. - pub fn acquire(self: *Dummy) Held { - return self.tryAcquire() orelse @panic("deadlock detected"); - } -}; - -// https://locklessinc.com/articles/keyed_events/ -const WindowsMutex = struct { - state: State = State{ .waiters = 0 }, - - const State = extern union { - locked: u8, - waiters: u32, - }; - - const WAKE = 1 << 8; - const WAIT = 1 << 9; - - pub fn tryAcquire(self: *WindowsMutex) ?Held { - if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) != 0) - return null; - return Held{ .mutex = self }; - } - - pub fn acquire(self: *WindowsMutex) Held { - return self.tryAcquire() orelse self.acquireSlow(); - } - - fn acquireSpinning(self: *WindowsMutex) Held { - @setCold(true); - while (true) : (SpinLock.yield()) { - return self.tryAcquire() orelse continue; - } - } - - fn acquireSlow(self: *WindowsMutex) Held { - // try to use NT keyed events for blocking, falling back to spinlock if unavailable - @setCold(true); - const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning(); - const key = @ptrCast(*const c_void, &self.state.waiters); - - while (true) : (SpinLock.loopHint(1)) { - const waiters = @atomicLoad(u32, &self.state.waiters, .Monotonic); - - // try and take lock if unlocked - if ((waiters & 1) == 0) { - if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) == 0) { - return Held{ .mutex = self }; - } - - // otherwise, try and update the waiting count. - // then unset the WAKE bit so that another unlocker can wake up a thread. - } else if (@cmpxchgWeak(u32, &self.state.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) { - const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); - assert(rc == .SUCCESS); - _ = @atomicRmw(u32, &self.state.waiters, .Sub, WAKE, .Monotonic); - } - } - } - - pub const Held = struct { - mutex: *WindowsMutex, - - pub fn release(self: Held) void { - // unlock without a rmw/cmpxchg instruction - @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release); - const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return; - const key = @ptrCast(*const c_void, &self.mutex.state.waiters); - - while (true) : (SpinLock.loopHint(1)) { - const waiters = @atomicLoad(u32, &self.mutex.state.waiters, .Monotonic); - - // no one is waiting - if (waiters < WAIT) return; - // someone grabbed the lock and will do the wake instead - if (waiters & 1 != 0) return; - // someone else is currently waking up - if (waiters & WAKE != 0) return; - - // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up - if (@cmpxchgWeak(u32, &self.mutex.state.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) { - const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); - assert(rc == .SUCCESS); - return; - } - } - } - }; -}; - -const TestContext = struct { - mutex: *Mutex, - data: i128, - - const incr_count = 10000; -}; - -test "std.Mutex" { - var mutex = Mutex{}; - - var context = TestContext{ - .mutex = &mutex, - .data = 0, - }; - - if (builtin.single_threaded) { - worker(&context); - testing.expect(context.data == TestContext.incr_count); - } else { - const thread_count = 10; - var threads: [thread_count]*std.Thread = undefined; - for (threads) |*t| { - t.* = try std.Thread.spawn(&context, worker); - } - for (threads) |t| - t.wait(); - - testing.expect(context.data == thread_count * TestContext.incr_count); - } -} - -fn worker(ctx: *TestContext) void { - var i: usize = 0; - while (i != TestContext.incr_count) : (i += 1) { - const held = ctx.mutex.acquire(); - defer held.release(); - - ctx.data += 1; - } -} diff --git a/lib/std/net.zig b/lib/std/net.zig index 7e285b1fac..636596c117 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -10,8 +10,13 @@ const net = @This(); const mem = std.mem; const os = std.os; const fs = std.fs; +const io = std.io; -pub const has_unix_sockets = @hasDecl(os, "sockaddr_un"); +// Windows 10 added support for unix sockets in build 17063, redstone 4 is the +// first release to support them. +pub const has_unix_sockets = @hasDecl(os, "sockaddr_un") and + (builtin.os.tag != .windows or + std.Target.current.os.version_range.windows.isAtLeast(.win10_rs4) orelse false); pub const Address = extern union { any: os.sockaddr, @@ -154,7 +159,7 @@ pub const Address = extern union { unreachable; } - try std.fmt.format(out_stream, "{}", .{&self.un.path}); + try std.fmt.format(out_stream, "{s}", .{&self.un.path}); }, else => unreachable, } @@ -596,7 +601,7 @@ pub const Ip6Address = extern struct { } }; -pub fn connectUnixSocket(path: []const u8) !fs.File { +pub fn connectUnixSocket(path: []const u8) !Stream { const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; const sockfd = try os.socket( os.AF_UNIX, @@ -614,7 +619,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { try os.connect(sockfd, &addr.any, addr.getOsSockLen()); } - return fs.File{ + return Stream{ .handle = sockfd, }; } @@ -648,7 +653,7 @@ pub const AddressList = struct { }; /// All memory allocated with `allocator` will be freed before this function returns. -pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !fs.File { +pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !Stream { const list = try getAddressList(allocator, name, port); defer list.deinit(); @@ -665,7 +670,7 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) return std.os.ConnectError.ConnectionRefused; } -pub fn tcpConnectToAddress(address: Address) !fs.File { +pub fn tcpConnectToAddress(address: Address) !Stream { const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; const sock_flags = os.SOCK_STREAM | nonblock | (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC); @@ -679,7 +684,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { try os.connect(sockfd, &address.any, address.getOsSockLen()); } - return fs.File{ .handle = sockfd }; + return Stream{ .handle = sockfd }; } /// Call `AddressList.deinit` on the result. @@ -783,13 +788,13 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* var lookup_addrs = std.ArrayList(LookupAddr).init(allocator); defer lookup_addrs.deinit(); - var canon = std.ArrayListSentineled(u8, 0).initNull(arena); + var canon = std.ArrayList(u8).init(arena); defer canon.deinit(); try linuxLookupName(&lookup_addrs, &canon, name, family, flags, port); result.addrs = try arena.alloc(Address, lookup_addrs.items.len); - if (!canon.isNull()) { + if (canon.items.len != 0) { result.canon_name = canon.toOwnedSlice(); } @@ -818,7 +823,7 @@ const DAS_ORDER_SHIFT = 0; fn linuxLookupName( addrs: *std.ArrayList(LookupAddr), - canon: *std.ArrayListSentineled(u8, 0), + canon: *std.ArrayList(u8), opt_name: ?[]const u8, family: os.sa_family_t, flags: u32, @@ -826,7 +831,8 @@ fn linuxLookupName( ) !void { if (opt_name) |name| { // reject empty name and check len so it fits into temp bufs - try canon.replaceContents(name); + canon.items.len = 0; + try canon.appendSlice(name); if (Address.parseExpectingFamily(name, family, port)) |addr| { try addrs.append(LookupAddr{ .addr = addr }); } else |name_err| if ((flags & std.c.AI_NUMERICHOST) != 0) { @@ -1091,7 +1097,7 @@ fn linuxLookupNameFromNull( fn linuxLookupNameFromHosts( addrs: *std.ArrayList(LookupAddr), - canon: *std.ArrayListSentineled(u8, 0), + canon: *std.ArrayList(u8), name: []const u8, family: os.sa_family_t, port: u16, @@ -1105,7 +1111,7 @@ fn linuxLookupNameFromHosts( }; defer file.close(); - const stream = std.io.bufferedInStream(file.inStream()).inStream(); + const stream = std.io.bufferedReader(file.reader()).reader(); var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { @@ -1142,7 +1148,8 @@ fn linuxLookupNameFromHosts( // first name is canonical name const name_text = first_name_text.?; if (isValidHostName(name_text)) { - try canon.replaceContents(name_text); + canon.items.len = 0; + try canon.appendSlice(name_text); } } } @@ -1161,7 +1168,7 @@ pub fn isValidHostName(hostname: []const u8) bool { fn linuxLookupNameFromDnsSearch( addrs: *std.ArrayList(LookupAddr), - canon: *std.ArrayListSentineled(u8, 0), + canon: *std.ArrayList(u8), name: []const u8, family: os.sa_family_t, port: u16, @@ -1177,10 +1184,10 @@ fn linuxLookupNameFromDnsSearch( if (byte == '.') dots += 1; } - const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) + const search = if (dots >= rc.ndots or mem.endsWith(u8, name, ".")) "" else - rc.search.span(); + rc.search.items; var canon_name = name; @@ -1193,30 +1200,30 @@ fn linuxLookupNameFromDnsSearch( // name is not a CNAME record) and serves as a buffer for passing // the full requested name to name_from_dns. try canon.resize(canon_name.len); - mem.copy(u8, canon.span(), canon_name); + mem.copy(u8, canon.items, canon_name); try canon.append('.'); var tok_it = mem.tokenize(search, " \t"); while (tok_it.next()) |tok| { - canon.shrink(canon_name.len + 1); + canon.shrinkRetainingCapacity(canon_name.len + 1); try canon.appendSlice(tok); - try linuxLookupNameFromDns(addrs, canon, canon.span(), family, rc, port); + try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port); if (addrs.items.len != 0) return; } - canon.shrink(canon_name.len); + canon.shrinkRetainingCapacity(canon_name.len); return linuxLookupNameFromDns(addrs, canon, name, family, rc, port); } const dpc_ctx = struct { addrs: *std.ArrayList(LookupAddr), - canon: *std.ArrayListSentineled(u8, 0), + canon: *std.ArrayList(u8), port: u16, }; fn linuxLookupNameFromDns( addrs: *std.ArrayList(LookupAddr), - canon: *std.ArrayListSentineled(u8, 0), + canon: *std.ArrayList(u8), name: []const u8, family: os.sa_family_t, rc: ResolvConf, @@ -1271,7 +1278,7 @@ const ResolvConf = struct { attempts: u32, ndots: u32, timeout: u32, - search: std.ArrayListSentineled(u8, 0), + search: std.ArrayList(u8), ns: std.ArrayList(LookupAddr), fn deinit(rc: *ResolvConf) void { @@ -1286,7 +1293,7 @@ const ResolvConf = struct { fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { rc.* = ResolvConf{ .ns = std.ArrayList(LookupAddr).init(allocator), - .search = std.ArrayListSentineled(u8, 0).initNull(allocator), + .search = std.ArrayList(u8).init(allocator), .ndots = 1, .timeout = 5, .attempts = 2, @@ -1302,7 +1309,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { }; defer file.close(); - const stream = std.io.bufferedInStream(file.inStream()).inStream(); + const stream = std.io.bufferedReader(file.reader()).reader(); var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { @@ -1338,7 +1345,8 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { const ip_txt = line_it.next() orelse continue; try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt, 53); } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) { - try rc.search.replaceContents(line_it.rest()); + rc.search.items.len = 0; + try rc.search.appendSlice(line_it.rest()); } } @@ -1569,13 +1577,100 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) _ = try os.dn_expand(packet, data, &tmp); const canon_name = mem.spanZ(std.meta.assumeSentinel(&tmp, 0)); if (isValidHostName(canon_name)) { - try ctx.canon.replaceContents(canon_name); + ctx.canon.items.len = 0; + try ctx.canon.appendSlice(canon_name); } }, else => return, } } +pub const Stream = struct { + // Underlying socket descriptor. + // Note that on some platforms this may not be interchangeable with a + // regular files descriptor. + handle: os.socket_t, + + pub fn close(self: Stream) void { + os.closeSocket(self.handle); + } + + pub const ReadError = os.ReadError; + pub const WriteError = os.WriteError; + + pub const Reader = io.Reader(Stream, ReadError, read); + pub const Writer = io.Writer(Stream, WriteError, write); + + pub fn reader(self: Stream) Reader { + return .{ .context = self }; + } + + pub fn writer(self: Stream) Writer { + return .{ .context = self }; + } + + pub fn read(self: Stream, buffer: []u8) ReadError!usize { + if (std.Target.current.os.tag == .windows) { + return os.windows.ReadFile(self.handle, buffer, null, io.default_mode); + } + + if (std.io.is_async) { + return std.event.Loop.instance.?.read(self.handle, buffer, false); + } else { + return os.read(self.handle, buffer); + } + } + + /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's + /// file system thread instead of non-blocking. It needs to be reworked to properly + /// use non-blocking I/O. + pub fn write(self: Stream, buffer: []const u8) WriteError!usize { + if (std.Target.current.os.tag == .windows) { + return os.windows.WriteFile(self.handle, buffer, null, io.default_mode); + } + + if (std.io.is_async) { + return std.event.Loop.instance.?.write(self.handle, buffer, false); + } else { + return os.write(self.handle, buffer); + } + } + + /// See https://github.com/ziglang/zig/issues/7699 + /// See equivalent function: `std.fs.File.writev`. + pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { + if (std.io.is_async) { + // TODO improve to actually take advantage of writev syscall, if available. + if (iovecs.len == 0) return 0; + const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len]; + try self.write(first_buffer); + return first_buffer.len; + } else { + return os.writev(self.handle, iovecs); + } + } + + /// The `iovecs` parameter is mutable because this function needs to mutate the fields in + /// order to handle partial writes from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 + /// See equivalent function: `std.fs.File.writevAll`. + pub fn writevAll(self: Stream, iovecs: []os.iovec_const) WriteError!void { + if (iovecs.len == 0) return; + + var i: usize = 0; + while (true) { + var amt = try self.writev(iovecs[i..]); + while (amt >= iovecs[i].iov_len) { + amt -= iovecs[i].iov_len; + i += 1; + if (i >= iovecs.len) return; + } + iovecs[i].iov_base += amt; + iovecs[i].iov_len -= amt; + } + } +}; + pub const StreamServer = struct { /// Copied from `Options` on `init`. kernel_backlog: u31, @@ -1682,7 +1777,7 @@ pub const StreamServer = struct { } || os.UnexpectedError; pub const Connection = struct { - file: fs.File, + stream: Stream, address: Address, }; @@ -1701,7 +1796,7 @@ pub const StreamServer = struct { if (accept_result) |fd| { return Connection{ - .file = fs.File{ .handle = fd }, + .stream = Stream{ .handle = fd }, .address = accepted_addr, }; } else |err| switch (err) { @@ -1711,6 +1806,6 @@ pub const StreamServer = struct { } }; -test "" { +test { _ = @import("net/test.zig"); } diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 9f40bb5a3b..10a9c4e18b 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -145,7 +145,7 @@ test "listen on a port, send bytes, receive bytes" { // Try only the IPv4 variant as some CI builders have no IPv6 localhost // configured. - const localhost = try net.Address.parseIp("127.0.0.1", 8080); + const localhost = try net.Address.parseIp("127.0.0.1", 0); var server = net.StreamServer.init(.{}); defer server.deinit(); @@ -165,8 +165,9 @@ test "listen on a port, send bytes, receive bytes" { defer t.wait(); var client = try server.accept(); + defer client.stream.close(); var buf: [16]u8 = undefined; - const n = try client.file.reader().read(&buf); + const n = try client.stream.reader().read(&buf); testing.expectEqual(@as(usize, 12), n); testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); @@ -249,6 +250,49 @@ fn testServer(server: *net.StreamServer) anyerror!void { var client = try server.accept(); - const stream = client.file.outStream(); + const stream = client.stream.writer(); try stream.print("hello from server\n", .{}); } + +test "listen on a unix socket, send bytes, receive bytes" { + if (builtin.single_threaded) return error.SkipZigTest; + if (!net.has_unix_sockets) return error.SkipZigTest; + + if (std.builtin.os.tag == .windows) { + _ = try std.os.windows.WSAStartup(2, 2); + } + defer { + if (std.builtin.os.tag == .windows) { + std.os.windows.WSACleanup() catch unreachable; + } + } + + var server = net.StreamServer.init(.{}); + defer server.deinit(); + + const socket_path = "socket.unix"; + + var socket_addr = try net.Address.initUnix(socket_path); + defer std.fs.cwd().deleteFile(socket_path) catch {}; + try server.listen(socket_addr); + + const S = struct { + fn clientFn(_: void) !void { + const socket = try net.connectUnixSocket(socket_path); + defer socket.close(); + + _ = try socket.writer().writeAll("Hello world!"); + } + }; + + const t = try std.Thread.spawn({}, S.clientFn); + defer t.wait(); + + var client = try server.accept(); + defer client.stream.close(); + var buf: [16]u8 = undefined; + const n = try client.stream.reader().read(&buf); + + testing.expectEqual(@as(usize, 12), n); + testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); +} diff --git a/lib/std/once.zig b/lib/std/once.zig index 6e0e4867d8..efa99060d3 100644 --- a/lib/std/once.zig +++ b/lib/std/once.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,7 +15,7 @@ pub fn once(comptime f: fn () void) Once(f) { pub fn Once(comptime f: fn () void) type { return struct { done: bool = false, - mutex: std.Mutex = std.Mutex{}, + mutex: std.Thread.Mutex = std.Thread.Mutex{}, /// Call the function `f`. /// If `call` is invoked multiple times `f` will be executed only the diff --git a/lib/std/os.zig b/lib/std/os.zig index b42cdf756f..24d9041639 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -32,6 +32,7 @@ const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; pub const darwin = @import("os/darwin.zig"); pub const dragonfly = @import("os/dragonfly.zig"); pub const freebsd = @import("os/freebsd.zig"); +pub const haiku = @import("os/haiku.zig"); pub const netbsd = @import("os/netbsd.zig"); pub const openbsd = @import("os/openbsd.zig"); pub const linux = @import("os/linux.zig"); @@ -43,7 +44,7 @@ comptime { assert(@import("std") == std); // std lib tests require --override-lib-dir } -test "" { +test { _ = darwin; _ = freebsd; _ = linux; @@ -52,6 +53,7 @@ test "" { _ = uefi; _ = wasi; _ = windows; + _ = haiku; _ = @import("os/test.zig"); } @@ -66,6 +68,7 @@ else if (builtin.link_libc) else switch (builtin.os.tag) { .macos, .ios, .watchos, .tvos => darwin, .freebsd => freebsd, + .haiku => haiku, .linux => linux, .netbsd => netbsd, .openbsd => openbsd, @@ -191,7 +194,7 @@ fn getRandomBytesDevURandom(buf: []u8) !void { .capable_io_mode = .blocking, .intended_io_mode = .blocking, }; - const stream = file.inStream(); + const stream = file.reader(); stream.readNoEof(buf) catch return error.Unexpected; } @@ -324,7 +327,7 @@ pub const ReadError = error{ /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as /// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page. /// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. -/// For POSIX the limit is `math.maxInt(isize)`. +/// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, null, std.io.default_mode); @@ -447,6 +450,12 @@ pub const PReadError = ReadError || error{Unseekable}; /// return error.WouldBlock when EAGAIN is received. /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. +/// +/// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000` +/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as +/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page. +/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL. +/// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, offset, std.io.default_mode); @@ -478,8 +487,16 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { } } + // Prevent EINVAL. + const max_count = switch (std.Target.current.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos => math.maxInt(i32), + else => math.maxInt(isize), + }; + const adjusted_len = math.min(max_count, buf.len); + while (true) { - const rc = system.pread(fd, buf.ptr, buf.len, offset); + const rc = system.pread(fd, buf.ptr, adjusted_len, offset); switch (errno(rc)) { 0 => return @intCast(usize, rc), EINTR => continue, @@ -585,7 +602,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { const have_pread_but_not_preadv = switch (std.Target.current.os.tag) { - .windows, .macos, .ios, .watchos, .tvos => true, + .windows, .macos, .ios, .watchos, .tvos, .haiku => true, else => false, }; if (have_pread_but_not_preadv) { @@ -918,7 +935,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { /// If `iov.len` is larger than will fit in a `u31`, a partial write will occur. pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) { - .windows, .macos, .ios, .watchos, .tvos => true, + .windows, .macos, .ios, .watchos, .tvos, .haiku => true, else => false, }; @@ -1348,89 +1365,10 @@ pub fn execvpeZ_expandArg0( /// If `file` is an absolute path, this is the same as `execveZ`. pub fn execvpeZ( file: [*:0]const u8, - argv: [*:null]const ?[*:0]const u8, + argv_ptr: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, ) ExecveError { - return execvpeZ_expandArg0(.no_expand, file, argv, envp); -} - -/// This is the same as `execvpe` except if the `arg0_expand` parameter is set to `.expand`, -/// then argv[0] will be replaced with the expanded version of it, after resolving in accordance -/// with the PATH environment variable. -pub fn execvpe_expandArg0( - allocator: *mem.Allocator, - arg0_expand: Arg0Expand, - argv_slice: []const []const u8, - env_map: *const std.BufMap, -) (ExecveError || error{OutOfMemory}) { - const argv_buf = try allocator.alloc(?[*:0]u8, argv_slice.len + 1); - mem.set(?[*:0]u8, argv_buf, null); - defer { - for (argv_buf) |arg| { - const arg_buf = mem.spanZ(arg) orelse break; - allocator.free(arg_buf); - } - allocator.free(argv_buf); - } - for (argv_slice) |arg, i| { - const arg_buf = try allocator.alloc(u8, arg.len + 1); - @memcpy(arg_buf.ptr, arg.ptr, arg.len); - arg_buf[arg.len] = 0; - argv_buf[i] = arg_buf[0..arg.len :0].ptr; - } - argv_buf[argv_slice.len] = null; - const argv_ptr = argv_buf[0..argv_slice.len :null].ptr; - - const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); - defer freeNullDelimitedEnvMap(allocator, envp_buf); - - switch (arg0_expand) { - .expand => return execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), - .no_expand => return execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr), - } -} - -/// This function must allocate memory to add a null terminating bytes on path and each arg. -/// It must also convert to KEY=VALUE\0 format for environment variables, and include null -/// pointers after the args and after the environment variables. -/// `argv_slice[0]` is the executable path. -/// This function also uses the PATH environment variable to get the full path to the executable. -pub fn execvpe( - allocator: *mem.Allocator, - argv_slice: []const []const u8, - env_map: *const std.BufMap, -) (ExecveError || error{OutOfMemory}) { - return execvpe_expandArg0(allocator, .no_expand, argv_slice, env_map); -} - -pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![:null]?[*:0]u8 { - const envp_count = env_map.count(); - const envp_buf = try allocator.alloc(?[*:0]u8, envp_count + 1); - mem.set(?[*:0]u8, envp_buf, null); - errdefer freeNullDelimitedEnvMap(allocator, envp_buf); - { - var it = env_map.iterator(); - var i: usize = 0; - while (it.next()) |pair| : (i += 1) { - const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2); - @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len); - env_buf[pair.key.len] = '='; - @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); - const len = env_buf.len - 1; - env_buf[len] = 0; - envp_buf[i] = env_buf[0..len :0].ptr; - } - assert(i == envp_count); - } - return envp_buf[0..envp_count :null]; -} - -pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) void { - for (envp_buf) |env| { - const env_buf = if (env) |ptr| ptr[0 .. mem.len(ptr) + 1] else break; - allocator.free(env_buf); - } - allocator.free(envp_buf); + return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); } /// Get an environment variable. @@ -1699,6 +1637,92 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*: } } +pub const LinkError = UnexpectedError || error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotSameFileSystem, +}; + +pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { + switch (errno(system.link(oldpath, newpath, flags))) { + 0 => return, + EACCES => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EROFS => return error.ReadOnlyFileSystem, + EXDEV => return error.NotSameFileSystem, + EINVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkZ(&old, &new, flags); +} + +pub const LinkatError = LinkError || error{NotDir}; + +pub fn linkatZ( + olddir: fd_t, + oldpath: [*:0]const u8, + newdir: fd_t, + newpath: [*:0]const u8, + flags: i32, +) LinkatError!void { + switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { + 0 => return, + EACCES => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EPERM => return error.AccessDenied, + EROFS => return error.ReadOnlyFileSystem, + EXDEV => return error.NotSameFileSystem, + EINVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub fn linkat( + olddir: fd_t, + oldpath: []const u8, + newdir: fd_t, + newpath: []const u8, + flags: i32, +) LinkatError!void { + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkatZ(olddir, &old, newdir, &new, flags); +} + pub const UnlinkError = error{ FileNotFound, @@ -3225,6 +3249,9 @@ pub const ConnectError = error{ /// The given path for the unix socket does not exist. FileNotFound, + + /// Connection was reset by peer before connect could complete. + ConnectionResetByPeer, } || UnexpectedError; /// Initiate a connection on a socket. @@ -3239,8 +3266,9 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne .WSAEADDRNOTAVAIL => return error.AddressNotAvailable, .WSAECONNREFUSED => return error.ConnectionRefused, .WSAETIMEDOUT => return error.ConnectionTimedOut, - .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well? - , .WSAENETUNREACH => return error.NetworkUnreachable, + .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well? + .WSAENETUNREACH, + => return error.NetworkUnreachable, .WSAEFAULT => unreachable, .WSAEINVAL => unreachable, .WSAEISCONN => unreachable, @@ -3302,6 +3330,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. ETIMEDOUT => return error.ConnectionTimedOut, + ECONNRESET => return error.ConnectionResetByPeer, else => |err| return unexpectedErrno(err), }, EBADF => unreachable, // The argument sockfd is not a valid file descriptor. @@ -3830,31 +3859,54 @@ pub fn pipe() PipeError![2]fd_t { } pub fn pipe2(flags: u32) PipeError![2]fd_t { - if (comptime std.Target.current.isDarwin()) { - var fds: [2]fd_t = try pipe(); - if (flags == 0) return fds; - errdefer { - close(fds[0]); - close(fds[1]); - } - for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) { - 0 => {}, + if (@hasDecl(system, "pipe2")) { + var fds: [2]fd_t = undefined; + switch (errno(system.pipe2(&fds, flags))) { + 0 => return fds, EINVAL => unreachable, // Invalid flags - EBADF => unreachable, // Always a race condition + EFAULT => unreachable, // Invalid fds pointer + ENFILE => return error.SystemFdQuotaExceeded, + EMFILE => return error.ProcessFdQuotaExceeded, else => |err| return unexpectedErrno(err), - }; + } + } + + var fds: [2]fd_t = try pipe(); + errdefer { + close(fds[0]); + close(fds[1]); + } + + if (flags == 0) return fds; + + // O_CLOEXEC is special, it's a file descriptor flag and must be set using + // F_SETFD. + if (flags & O_CLOEXEC != 0) { + for (fds) |fd| { + switch (errno(system.fcntl(fd, F_SETFD, @as(u32, FD_CLOEXEC)))) { + 0 => {}, + EINVAL => unreachable, // Invalid flags + EBADF => unreachable, // Always a race condition + else => |err| return unexpectedErrno(err), + } + } } - var fds: [2]fd_t = undefined; - switch (errno(system.pipe2(&fds, flags))) { - 0 => return fds, - EINVAL => unreachable, // Invalid flags - EFAULT => unreachable, // Invalid fds pointer - ENFILE => return error.SystemFdQuotaExceeded, - EMFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), + const new_flags = flags & ~@as(u32, O_CLOEXEC); + // Set every other flag affecting the file status using F_SETFL. + if (new_flags != 0) { + for (fds) |fd| { + switch (errno(system.fcntl(fd, F_SETFL, new_flags))) { + 0 => {}, + EINVAL => unreachable, // Invalid flags + EBADF => unreachable, // Always a race condition + else => |err| return unexpectedErrno(err), + } + } } + + return fds; } pub const SysCtlError = error{ @@ -3872,7 +3924,10 @@ pub fn sysctl( newlen: usize, ) SysCtlError!void { if (builtin.os.tag == .wasi) { - @panic("unsupported"); + @panic("unsupported"); // TODO should be compile error, not panic + } + if (builtin.os.tag == .haiku) { + @panic("unsupported"); // TODO should be compile error, not panic } const name_len = math.cast(c_uint, name.len) catch return error.NameTooLong; @@ -3896,7 +3951,10 @@ pub fn sysctlbynameZ( newlen: usize, ) SysCtlError!void { if (builtin.os.tag == .wasi) { - @panic("unsupported"); + @panic("unsupported"); // TODO should be compile error, not panic + } + if (builtin.os.tag == .haiku) { + @panic("unsupported"); // TODO should be compile error, not panic } switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { @@ -4335,7 +4393,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { }, .linux => { var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; - const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; + const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{d}\x00", .{fd}) catch unreachable; const target = readlinkZ(std.meta.assumeSentinel(proc_path.ptr, 0), out_buffer) catch |err| { switch (err) { @@ -4566,7 +4624,7 @@ pub const UnexpectedError = error{ /// and you get an unexpected error. pub fn unexpectedErrno(err: usize) UnexpectedError { if (unexpected_error_tracing) { - std.debug.warn("unexpected errno: {}\n", .{err}); + std.debug.warn("unexpected errno: {d}\n", .{err}); std.debug.dumpCurrentStackTrace(null); } return error.Unexpected; @@ -4774,6 +4832,12 @@ pub const SendError = error{ BrokenPipe, FileDescriptorNotASocket, + + /// Network is unreachable. + NetworkUnreachable, + + /// The local network interface used to reach the destination is down. + NetworkSubsystemFailed, } || UnexpectedError; pub const SendToError = SendError || error{ @@ -4790,15 +4854,8 @@ pub const SendToError = SendError || error{ FileNotFound, NotDir, - /// Network is unreachable. - NetworkUnreachable, - - /// Insufficient memory was available to fulfill the request. - SystemResources, - /// The socket is not connected (connection-oriented sockets only). SocketNotConnected, - WouldBlock, AddressNotAvailable, }; @@ -4853,7 +4910,7 @@ pub fn sendto( .WSAEHOSTUNREACH => return error.NetworkUnreachable, // TODO: WSAEINPROGRESS, WSAEINTR .WSAEINVAL => unreachable, - .WSAENETDOWN => return error.NetworkUnreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, .WSAENETRESET => return error.ConnectionResetByPeer, .WSAENETUNREACH => return error.NetworkUnreachable, .WSAENOTCONN => return error.SocketNotConnected, @@ -4882,7 +4939,6 @@ pub fn sendto( EMSGSIZE => return error.MessageTooBig, ENOBUFS => return error.SystemResources, ENOMEM => return error.SystemResources, - ENOTCONN => unreachable, // The socket is not connected, and no target has been given. ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. EOPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. EPIPE => return error.BrokenPipe, @@ -4892,6 +4948,9 @@ pub fn sendto( ENOENT => return error.FileNotFound, ENOTDIR => return error.NotDir, EHOSTUNREACH => return error.NetworkUnreachable, + ENETUNREACH => return error.NetworkUnreachable, + ENOTCONN => return error.SocketNotConnected, + ENETDOWN => return error.NetworkSubsystemFailed, else => |err| return unexpectedErrno(err), } } @@ -4923,7 +4982,17 @@ pub fn send( buf: []const u8, flags: u32, ) SendError!usize { - return sendto(sockfd, buf, flags, null, 0); + return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) { + error.AddressFamilyNotSupported => unreachable, + error.SymLinkLoop => unreachable, + error.NameTooLong => unreachable, + error.FileNotFound => unreachable, + error.NotDir => unreachable, + error.NetworkUnreachable => unreachable, + error.AddressNotAvailable => unreachable, + error.SocketNotConnected => unreachable, + else => |e| return e, + }; } pub const SendFileError = PReadError || WriteError || SendError; @@ -4967,7 +5036,7 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { /// /// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000` /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as -/// well as stuffing the errno codes into the last `4096` values. This is cited on the `sendfile` man page. +/// well as stuffing the errno codes into the last `4096` values. This is noted on the `sendfile` man page. /// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. /// The corresponding POSIX limit on this is `math.maxInt(isize)`. pub fn sendfile( @@ -5338,7 +5407,8 @@ pub const PollError = error{ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { while (true) { - const rc = system.poll(fds.ptr, fds.len, timeout); + const fds_count = math.cast(nfds_t, fds.len) catch return error.SystemResources; + const rc = system.poll(fds.ptr, fds_count, timeout); if (builtin.os.tag == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { @@ -5549,6 +5619,9 @@ pub const SetSockOptError = error{ /// Insufficient resources are available in the system to complete the call. SystemResources, + // Setting the socket option requires more elevated permissions. + PermissionDenied, + NetworkSubsystemFailed, FileDescriptorNotASocket, SocketNotBound, @@ -5581,6 +5654,7 @@ pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSo ENOPROTOOPT => return error.InvalidProtocolOption, ENOMEM => return error.SystemResources, ENOBUFS => return error.SystemResources, + EPERM => return error.PermissionDenied, else => |err| return unexpectedErrno(err), } } @@ -5845,3 +5919,51 @@ pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void else => |err| return unexpectedErrno(err), } } + +pub const MadviseError = error{ + /// advice is MADV_REMOVE, but the specified address range is not a shared writable mapping. + AccessDenied, + /// advice is MADV_HWPOISON, but the caller does not have the CAP_SYS_ADMIN capability. + PermissionDenied, + /// A kernel resource was temporarily unavailable. + SystemResources, + /// One of the following: + /// * addr is not page-aligned or length is negative + /// * advice is not valid + /// * advice is MADV_DONTNEED or MADV_REMOVE and the specified address range + /// includes locked, Huge TLB pages, or VM_PFNMAP pages. + /// * advice is MADV_MERGEABLE or MADV_UNMERGEABLE, but the kernel was not + /// configured with CONFIG_KSM. + /// * advice is MADV_FREE or MADV_WIPEONFORK but the specified address range + /// includes file, Huge TLB, MAP_SHARED, or VM_PFNMAP ranges. + InvalidSyscall, + /// (for MADV_WILLNEED) Paging in this area would exceed the process's + /// maximum resident set size. + WouldExceedMaximumResidentSetSize, + /// One of the following: + /// * (for MADV_WILLNEED) Not enough memory: paging in failed. + /// * Addresses in the specified range are not currently mapped, or + /// are outside the address space of the process. + OutOfMemory, + /// The madvise syscall is not available on this version and configuration + /// of the Linux kernel. + MadviseUnavailable, + /// The operating system returned an undocumented error code. + Unexpected, +}; + +/// Give advice about use of memory. +/// This syscall is optional and is sometimes configured to be disabled. +pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void { + switch (errno(system.madvise(ptr, length, advice))) { + 0 => return, + EACCES => return error.AccessDenied, + EAGAIN => return error.SystemResources, + EBADF => unreachable, // The map exists, but the area maps something that isn't a file. + EINVAL => return error.InvalidSyscall, + EIO => return error.WouldExceedMaximumResidentSetSize, + ENOMEM => return error.OutOfMemory, + ENOSYS => return error.MadviseUnavailable, + else => |err| return unexpectedErrno(err), + } +} diff --git a/lib/std/os/bits.zig b/lib/std/os/bits.zig index a9d65370ad..5d1de28bad 100644 --- a/lib/std/os/bits.zig +++ b/lib/std/os/bits.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -15,6 +15,7 @@ pub usingnamespace switch (std.Target.current.os.tag) { .macos, .ios, .tvos, .watchos => @import("bits/darwin.zig"), .dragonfly => @import("bits/dragonfly.zig"), .freebsd => @import("bits/freebsd.zig"), + .haiku => @import("bits/haiku.zig"), .linux => @import("bits/linux.zig"), .netbsd => @import("bits/netbsd.zig"), .openbsd => @import("bits/openbsd.zig"), @@ -34,3 +35,22 @@ pub const iovec_const = extern struct { iov_base: [*]const u8, iov_len: usize, }; + +// syslog + +/// system is unusable +pub const LOG_EMERG = 0; +/// action must be taken immediately +pub const LOG_ALERT = 1; +/// critical conditions +pub const LOG_CRIT = 2; +/// error conditions +pub const LOG_ERR = 3; +/// warning conditions +pub const LOG_WARNING = 4; +/// normal but significant condition +pub const LOG_NOTICE = 5; +/// informational +pub const LOG_INFO = 6; +/// debug-level messages +pub const LOG_DEBUG = 7; diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index 8bd40ed9a3..aca24b1c0c 100644 --- a/lib/std/os/bits/darwin.zig +++ b/lib/std/os/bits/darwin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -1461,7 +1461,7 @@ pub const LOCK_EX = 2; pub const LOCK_UN = 8; pub const LOCK_NB = 4; -pub const nfds_t = usize; +pub const nfds_t = u32; pub const pollfd = extern struct { fd: fd_t, events: i16, @@ -1554,3 +1554,186 @@ pub const rlimit = extern struct { pub const SHUT_RD = 0; pub const SHUT_WR = 1; pub const SHUT_RDWR = 2; + +// Term +pub const VEOF = 0; +pub const VEOL = 1; +pub const VEOL2 = 2; +pub const VERASE = 3; +pub const VWERASE = 4; +pub const VKILL = 5; +pub const VREPRINT = 6; +pub const VINTR = 8; +pub const VQUIT = 9; +pub const VSUSP = 10; +pub const VDSUSP = 11; +pub const VSTART = 12; +pub const VSTOP = 13; +pub const VLNEXT = 14; +pub const VDISCARD = 15; +pub const VMIN = 16; +pub const VTIME = 17; +pub const VSTATUS = 18; +pub const NCCS = 20; // 2 spares (7, 19) + +pub const IGNBRK = 0x00000001; // ignore BREAK condition +pub const BRKINT = 0x00000002; // map BREAK to SIGINTR +pub const IGNPAR = 0x00000004; // ignore (discard) parity errors +pub const PARMRK = 0x00000008; // mark parity and framing errors +pub const INPCK = 0x00000010; // enable checking of parity errors +pub const ISTRIP = 0x00000020; // strip 8th bit off chars +pub const INLCR = 0x00000040; // map NL into CR +pub const IGNCR = 0x00000080; // ignore CR +pub const ICRNL = 0x00000100; // map CR to NL (ala CRMOD) +pub const IXON = 0x00000200; // enable output flow control +pub const IXOFF = 0x00000400; // enable input flow control +pub const IXANY = 0x00000800; // any char will restart after stop +pub const IMAXBEL = 0x00002000; // ring bell on input queue full +pub const IUTF8 = 0x00004000; // maintain state for UTF-8 VERASE + +pub const OPOST = 0x00000001; //enable following output processing +pub const ONLCR = 0x00000002; // map NL to CR-NL (ala CRMOD) +pub const OXTABS = 0x00000004; // expand tabs to spaces +pub const ONOEOT = 0x00000008; // discard EOT's (^D) on output) + +pub const OCRNL = 0x00000010; // map CR to NL on output +pub const ONOCR = 0x00000020; // no CR output at column 0 +pub const ONLRET = 0x00000040; // NL performs CR function +pub const OFILL = 0x00000080; // use fill characters for delay +pub const NLDLY = 0x00000300; // \n delay +pub const TABDLY = 0x00000c04; // horizontal tab delay +pub const CRDLY = 0x00003000; // \r delay +pub const FFDLY = 0x00004000; // form feed delay +pub const BSDLY = 0x00008000; // \b delay +pub const VTDLY = 0x00010000; // vertical tab delay +pub const OFDEL = 0x00020000; // fill is DEL, else NUL + +pub const NL0 = 0x00000000; +pub const NL1 = 0x00000100; +pub const NL2 = 0x00000200; +pub const NL3 = 0x00000300; +pub const TAB0 = 0x00000000; +pub const TAB1 = 0x00000400; +pub const TAB2 = 0x00000800; +pub const TAB3 = 0x00000004; +pub const CR0 = 0x00000000; +pub const CR1 = 0x00001000; +pub const CR2 = 0x00002000; +pub const CR3 = 0x00003000; +pub const FF0 = 0x00000000; +pub const FF1 = 0x00004000; +pub const BS0 = 0x00000000; +pub const BS1 = 0x00008000; +pub const VT0 = 0x00000000; +pub const VT1 = 0x00010000; + +pub const CIGNORE = 0x00000001; // ignore control flags +pub const CSIZE = 0x00000300; // character size mask +pub const CS5 = 0x00000000; // 5 bits (pseudo) +pub const CS6 = 0x00000100; // 6 bits +pub const CS7 = 0x00000200; // 7 bits +pub const CS8 = 0x00000300; // 8 bits +pub const CSTOPB = 0x0000040; // send 2 stop bits +pub const CREAD = 0x00000800; // enable receiver +pub const PARENB = 0x00001000; // parity enable +pub const PARODD = 0x00002000; // odd parity, else even +pub const HUPCL = 0x00004000; // hang up on last close +pub const CLOCAL = 0x00008000; // ignore modem status lines +pub const CCTS_OFLOW = 0x00010000; // CTS flow control of output +pub const CRTSCTS = (CCTS_OFLOW | CRTS_IFLOW); +pub const CRTS_IFLOW = 0x00020000; // RTS flow control of input +pub const CDTR_IFLOW = 0x00040000; // DTR flow control of input +pub const CDSR_OFLOW = 0x00080000; // DSR flow control of output +pub const CCAR_OFLOW = 0x00100000; // DCD flow control of output +pub const MDMBUF = 0x00100000; // old name for CCAR_OFLOW + +pub const ECHOKE = 0x00000001; // visual erase for line kill +pub const ECHOE = 0x00000002; // visually erase chars +pub const ECHOK = 0x00000004; // echo NL after line kill +pub const ECHO = 0x00000008; // enable echoing +pub const ECHONL = 0x00000010; // echo NL even if ECHO is off +pub const ECHOPRT = 0x00000020; // visual erase mode for hardcopy +pub const ECHOCTL = 0x00000040; // echo control chars as ^(Char) +pub const ISIG = 0x00000080; // enable signals INTR, QUIT, [D]SUSP +pub const ICANON = 0x00000100; // canonicalize input lines +pub const ALTWERASE = 0x00000200; // use alternate WERASE algorithm +pub const IEXTEN = 0x00000400; // enable DISCARD and LNEXT +pub const EXTPROC = 0x00000800; // external processing +pub const TOSTOP = 0x00400000; // stop background jobs from output +pub const FLUSHO = 0x00800000; // output being flushed (state) +pub const NOKERNINFO = 0x02000000; // no kernel output from VSTATUS +pub const PENDIN = 0x20000000; // XXX retype pending input (state) +pub const NOFLSH = 0x80000000; // don't flush after interrupt + +pub const TCSANOW = 0; // make change immediate +pub const TCSADRAIN = 1; // drain output, then change +pub const TCSAFLUSH = 2; // drain output, flush input +pub const TCSASOFT = 0x10; // flag - don't alter h.w. state +pub const TCSA = extern enum(c_uint) { + NOW, + DRAIN, + FLUSH, + _, +}; + +pub const B0 = 0; +pub const B50 = 50; +pub const B75 = 75; +pub const B110 = 110; +pub const B134 = 134; +pub const B150 = 150; +pub const B200 = 200; +pub const B300 = 300; +pub const B600 = 600; +pub const B1200 = 1200; +pub const B1800 = 1800; +pub const B2400 = 2400; +pub const B4800 = 4800; +pub const B9600 = 9600; +pub const B19200 = 19200; +pub const B38400 = 38400; +pub const B7200 = 7200; +pub const B14400 = 14400; +pub const B28800 = 28800; +pub const B57600 = 57600; +pub const B76800 = 76800; +pub const B115200 = 115200; +pub const B230400 = 230400; +pub const EXTA = 19200; +pub const EXTB = 38400; + +pub const TCIFLUSH = 1; +pub const TCOFLUSH = 2; +pub const TCIOFLUSH = 3; +pub const TCOOFF = 1; +pub const TCOON = 2; +pub const TCIOFF = 3; +pub const TCION = 4; + +pub const cc_t = u8; +pub const speed_t = u64; +pub const tcflag_t = u64; + +pub const termios = extern struct { + iflag: tcflag_t, // input flags + oflag: tcflag_t, // output flags + cflag: tcflag_t, // control flags + lflag: tcflag_t, // local flags + cc: [NCCS]cc_t, // control chars + ispeed: speed_t align(8), // input speed + ospeed: speed_t, // output speed +}; + +pub const winsize = extern struct { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +}; + +pub const TIOCGWINSZ = ior(0x40000000, 't', 104, @sizeOf(winsize)); +pub const IOCPARM_MASK = 0x1fff; + +fn ior(inout: u32, group: usize, num: usize, len: usize) usize { + return (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)); +} diff --git a/lib/std/os/bits/dragonfly.zig b/lib/std/os/bits/dragonfly.zig index 61b6b9f363..3df6eb43de 100644 --- a/lib/std/os/bits/dragonfly.zig +++ b/lib/std/os/bits/dragonfly.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -20,6 +20,7 @@ pub const off_t = c_long; pub const mode_t = c_uint; pub const uid_t = u32; pub const gid_t = u32; +pub const suseconds_t = c_long; pub const ENOTSUP = EOPNOTSUPP; pub const EWOULDBLOCK = EAGAIN; @@ -190,6 +191,13 @@ pub const timespec = extern struct { tv_nsec: c_long, }; +pub const timeval = extern struct { + /// seconds + tv_sec: time_t, + /// microseconds + tv_usec: suseconds_t, +}; + pub const CTL_UNSPEC = 0; pub const CTL_KERN = 1; pub const CTL_VM = 2; diff --git a/lib/std/os/bits/freebsd.zig b/lib/std/os/bits/freebsd.zig index 6378dcaf8d..8529c5e3db 100644 --- a/lib/std/os/bits/freebsd.zig +++ b/lib/std/os/bits/freebsd.zig @@ -1,20 +1,32 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("../../std.zig"); +const builtin = std.builtin; const maxInt = std.math.maxInt; -// See https://svnweb.freebsd.org/base/head/sys/sys/_types.h?view=co -// TODO: audit pid_t/mode_t. They should likely be i32 and u16, respectively -pub const fd_t = c_int; -pub const pid_t = c_int; +pub const blksize_t = i32; +pub const blkcnt_t = i64; +pub const clockid_t = i32; +pub const fsblkcnt_t = u64; +pub const fsfilcnt_t = u64; +pub const nlink_t = u64; +pub const fd_t = i32; +pub const pid_t = i32; pub const uid_t = u32; pub const gid_t = u32; -pub const mode_t = c_uint; +pub const mode_t = u16; +pub const off_t = i64; +pub const ino_t = u64; +pub const dev_t = u64; +pub const time_t = i64; +// The signedness is not constant across different architectures. +pub const clock_t = isize; pub const socklen_t = u32; +pub const suseconds_t = c_long; /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. pub const Kevent = extern struct { @@ -116,20 +128,17 @@ pub const msghdr_const = extern struct { msg_flags: i32, }; -pub const off_t = i64; -pub const ino_t = u64; - pub const libc_stat = extern struct { - dev: u64, + dev: dev_t, ino: ino_t, - nlink: usize, + nlink: nlink_t, - mode: u16, + mode: mode_t, __pad0: u16, uid: uid_t, gid: gid_t, __pad1: u32, - rdev: u64, + rdev: dev_t, atim: timespec, mtim: timespec, @@ -161,6 +170,13 @@ pub const timespec = extern struct { tv_nsec: isize, }; +pub const timeval = extern struct { + /// seconds + tv_sec: time_t, + /// microseconds + tv_usec: suseconds_t, +}; + pub const dirent = extern struct { d_fileno: usize, d_off: i64, @@ -553,6 +569,9 @@ pub const EV_ONESHOT = 0x0010; /// clear event state after reporting pub const EV_CLEAR = 0x0020; +/// error, event data contains errno +pub const EV_ERROR = 0x4000; + /// force immediate event output /// ... with or without EV_ERROR /// ... use KEVENT_FLAG_ERROR_EVENTS @@ -796,16 +815,16 @@ pub const sigval = extern union { pub const _SIG_WORDS = 4; pub const _SIG_MAXSIG = 128; -pub inline fn _SIG_IDX(sig: usize) usize { +pub fn _SIG_IDX(sig: usize) callconv(.Inline) usize { return sig - 1; } -pub inline fn _SIG_WORD(sig: usize) usize { +pub fn _SIG_WORD(sig: usize) callconv(.Inline) usize { return_SIG_IDX(sig) >> 5; } -pub inline fn _SIG_BIT(sig: usize) usize { +pub fn _SIG_BIT(sig: usize) callconv(.Inline) usize { return 1 << (_SIG_IDX(sig) & 31); } -pub inline fn _SIG_VALID(sig: usize) usize { +pub fn _SIG_VALID(sig: usize) callconv(.Inline) usize { return sig <= _SIG_MAXSIG and sig > 0; } @@ -815,6 +834,53 @@ pub const sigset_t = extern struct { pub const empty_sigset = sigset_t{ .__bits = [_]u32{0} ** _SIG_WORDS }; +pub usingnamespace switch (builtin.arch) { + .x86_64 => struct { + pub const ucontext_t = extern struct { + sigmask: sigset_t, + mcontext: mcontext_t, + link: ?*ucontext_t, + stack: stack_t, + flags: c_int, + __spare__: [4]c_int, + }; + + /// XXX x86_64 specific + pub const mcontext_t = extern struct { + onstack: u64, + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + r8: u64, + r9: u64, + rax: u64, + rbx: u64, + rbp: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + trapno: u32, + fs: u16, + gs: u16, + addr: u64, + flags: u32, + es: u16, + ds: u16, + err: u64, + rip: u64, + cs: u64, + rflags: u64, + rsp: u64, + ss: u64, + }; + }, + else => struct {}, +}; + pub const EPERM = 1; // Operation not permitted pub const ENOENT = 2; // No such file or directory pub const ESRCH = 3; // No such process @@ -1432,3 +1498,37 @@ pub const rlimit = extern struct { pub const SHUT_RD = 0; pub const SHUT_WR = 1; pub const SHUT_RDWR = 2; + +pub const nfds_t = u32; + +pub const pollfd = extern struct { + fd: fd_t, + events: i16, + revents: i16, +}; + +/// any readable data available. +pub const POLLIN = 0x0001; +/// OOB/Urgent readable data. +pub const POLLPRI = 0x0002; +/// file descriptor is writeable. +pub const POLLOUT = 0x0004; +/// non-OOB/URG data available. +pub const POLLRDNORM = 0x0040; +/// no write type differentiation. +pub const POLLWRNORM = POLLOUT; +/// OOB/Urgent readable data. +pub const POLLRDBAND = 0x0080; +/// OOB/Urgent data can be written. +pub const POLLWRBAND = 0x0100; +/// like POLLIN, except ignore EOF. +pub const POLLINIGNEOF = 0x2000; +/// some poll error occurred. +pub const POLLERR = 0x0008; +/// file descriptor was "hung up". +pub const POLLHUP = 0x0010; +/// requested events "invalid". +pub const POLLNVAL = 0x0020; + +pub const POLLSTANDARD = POLLIN | POLLPRI | POLLOUT | POLLRDNORM | POLLRDBAND | + POLLWRBAND | POLLERR | POLLHUP | POLLNVAL; diff --git a/lib/std/os/bits/haiku.zig b/lib/std/os/bits/haiku.zig new file mode 100644 index 0000000000..59631fd40e --- /dev/null +++ b/lib/std/os/bits/haiku.zig @@ -0,0 +1,1450 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("../../std.zig"); +const maxInt = std.math.maxInt; + +pub const fd_t = c_int; +pub const pid_t = c_int; +pub const uid_t = u32; +pub const gid_t = u32; +pub const mode_t = c_uint; + +pub const socklen_t = u32; + +/// Renamed from `kevent` to `Kevent` to avoid conflict with function name. +pub const Kevent = extern struct { + ident: usize, + filter: i16, + flags: u16, + fflags: u32, + data: i64, + udata: usize, + // TODO ext +}; + +// Modes and flags for dlopen() +// include/dlfcn.h + +pub const POLLIN = 0x0001; +pub const POLLERR = 0x0004; +pub const POLLNVAL = 0x1000; +pub const POLLHUP = 0x0080; + +/// Bind function calls lazily. +pub const RTLD_LAZY = 1; + +/// Bind function calls immediately. +pub const RTLD_NOW = 2; + +pub const RTLD_MODEMASK = 0x3; + +/// Make symbols globally available. +pub const RTLD_GLOBAL = 0x100; + +/// Opposite of RTLD_GLOBAL, and the default. +pub const RTLD_LOCAL = 0; + +/// Trace loaded objects and exit. +pub const RTLD_TRACE = 0x200; + +/// Do not remove members. +pub const RTLD_NODELETE = 0x01000; + +/// Do not load if not already loaded. +pub const RTLD_NOLOAD = 0x02000; + +pub const dl_phdr_info = extern struct { + dlpi_addr: usize, + dlpi_name: ?[*:0]const u8, + dlpi_phdr: [*]std.elf.Phdr, + dlpi_phnum: u16, +}; + +pub const Flock = extern struct { + l_start: off_t, + l_len: off_t, + l_pid: pid_t, + l_type: i16, + l_whence: i16, + l_sysid: i32, + __unused: [4]u8, +}; + +pub const msghdr = extern struct { + /// optional address + msg_name: ?*sockaddr, + + /// size of address + msg_namelen: socklen_t, + + /// scatter/gather array + msg_iov: [*]iovec, + + /// # elements in msg_iov + msg_iovlen: i32, + + /// ancillary data + msg_control: ?*c_void, + + /// ancillary data buffer len + msg_controllen: socklen_t, + + /// flags on received message + msg_flags: i32, +}; + +pub const msghdr_const = extern struct { + /// optional address + msg_name: ?*const sockaddr, + + /// size of address + msg_namelen: socklen_t, + + /// scatter/gather array + msg_iov: [*]iovec_const, + + /// # elements in msg_iov + msg_iovlen: i32, + + /// ancillary data + msg_control: ?*c_void, + + /// ancillary data buffer len + msg_controllen: socklen_t, + + /// flags on received message + msg_flags: i32, +}; + +pub const off_t = i64; +pub const ino_t = u64; + +pub const nfds_t = u32; + +pub const pollfd = extern struct { + fd: i32, + events: i16, + revents: i16, +}; + +pub const libc_stat = extern struct { + dev: i32, + ino: u64, + mode: u32, + nlink: i32, + uid: i32, + gid: i32, + size: i64, + rdev: i32, + blksize: i32, + atim: timespec, + mtim: timespec, + ctim: timespec, + crtim: timespec, + st_type: u32, + blocks: i64, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + pub fn ctime(self: @This()) timespec { + return self.ctim; + } + pub fn crtime(self: @This()) timespec { + return self.crtim; + } +}; + +pub const timespec = extern struct { + tv_sec: isize, + tv_nsec: isize, +}; + +pub const dirent = extern struct { + d_dev: i32, + d_pdev: i32, + d_ino: i64, + d_pino: i64, + d_reclen: u16, + d_name: [256]u8, + + pub fn reclen(self: dirent) u16 { + return self.d_reclen; + } +}; + +pub const image_info = extern struct { + id: u32, //image_id + type: u32, // image_type + sequence: i32, + init_order: i32, + init_routine: *c_void, + term_routine: *c_void, + device: i32, + node: i32, + name: [1024]u8, + text: *c_void, + data: *c_void, + text_size: i32, + data_size: i32, + api_version: i32, + abi: i32, +}; + +pub const system_info = extern struct { + boot_time: i64, + cpu_count: u32, + max_pages: u64, + used_pages: u64, + cached_pages: u64, + block_cache_pages: u64, + ignored_pages: u64, + needed_memory: u64, + free_memory: u64, + max_swap_pages: u64, + free_swap_pages: u64, + page_faults: u32, + max_sems: u32, + used_sems: u32, + max_ports: u32, + used_ports: u32, + max_threads: u32, + used_threads: u32, + max_teams: u32, + used_teams: u32, + kernel_name: [256]u8, + kernel_build_date: [32]u8, + kernel_build_time: [32]u8, + kernel_version: i64, + abi: u32, +}; + +pub const in_port_t = u16; +pub const sa_family_t = u8; + +pub const sockaddr = extern struct { + /// total length + len: u8, + + /// address family + family: sa_family_t, + + /// actually longer; address value + data: [14]u8, +}; + +pub const sockaddr_in = extern struct { + len: u8 = @sizeOf(sockaddr_in), + family: sa_family_t = AF_INET, + port: in_port_t, + addr: u32, + zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +pub const sockaddr_in6 = extern struct { + len: u8 = @sizeOf(sockaddr_in6), + family: sa_family_t = AF_INET6, + port: in_port_t, + flowinfo: u32, + addr: [16]u8, + scope_id: u32, +}; + +pub const sockaddr_un = extern struct { + len: u8 = @sizeOf(sockaddr_un), + family: sa_family_t = AF_UNIX, + path: [104]u8, +}; + +pub const CTL_KERN = 1; +pub const CTL_DEBUG = 5; + +pub const KERN_PROC = 14; // struct: process entries +pub const KERN_PROC_PATHNAME = 12; // path to executable + +pub const PATH_MAX = 1024; + +pub const STDIN_FILENO = 0; +pub const STDOUT_FILENO = 1; +pub const STDERR_FILENO = 2; + +pub const PROT_NONE = 0; +pub const PROT_READ = 1; +pub const PROT_WRITE = 2; +pub const PROT_EXEC = 4; + +pub const CLOCK_REALTIME = 0; +pub const CLOCK_VIRTUAL = 1; +pub const CLOCK_PROF = 2; +pub const CLOCK_MONOTONIC = 4; +pub const CLOCK_UPTIME = 5; +pub const CLOCK_UPTIME_PRECISE = 7; +pub const CLOCK_UPTIME_FAST = 8; +pub const CLOCK_REALTIME_PRECISE = 9; +pub const CLOCK_REALTIME_FAST = 10; +pub const CLOCK_MONOTONIC_PRECISE = 11; +pub const CLOCK_MONOTONIC_FAST = 12; +pub const CLOCK_SECOND = 13; +pub const CLOCK_THREAD_CPUTIME_ID = 14; +pub const CLOCK_PROCESS_CPUTIME_ID = 15; + +pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize)); +pub const MAP_SHARED = 0x0001; +pub const MAP_PRIVATE = 0x0002; +pub const MAP_FIXED = 0x0010; +pub const MAP_STACK = 0x0400; +pub const MAP_NOSYNC = 0x0800; +pub const MAP_ANON = 0x1000; +pub const MAP_ANONYMOUS = MAP_ANON; +pub const MAP_FILE = 0; + +pub const MAP_GUARD = 0x00002000; +pub const MAP_EXCL = 0x00004000; +pub const MAP_NOCORE = 0x00020000; +pub const MAP_PREFAULT_READ = 0x00040000; +pub const MAP_32BIT = 0x00080000; + +pub const WNOHANG = 1; +pub const WUNTRACED = 2; +pub const WSTOPPED = WUNTRACED; +pub const WCONTINUED = 4; +pub const WNOWAIT = 8; +pub const WEXITED = 16; +pub const WTRAPPED = 32; + +pub const SA_ONSTACK = 0x0001; +pub const SA_RESTART = 0x0002; +pub const SA_RESETHAND = 0x0004; +pub const SA_NOCLDSTOP = 0x0008; +pub const SA_NODEFER = 0x0010; +pub const SA_NOCLDWAIT = 0x0020; +pub const SA_SIGINFO = 0x0040; + +pub const SIGHUP = 1; +pub const SIGINT = 2; +pub const SIGQUIT = 3; +pub const SIGILL = 4; +pub const SIGTRAP = 5; +pub const SIGABRT = 6; +pub const SIGIOT = SIGABRT; +pub const SIGEMT = 7; +pub const SIGFPE = 8; +pub const SIGKILL = 9; +pub const SIGBUS = 10; +pub const SIGSEGV = 11; +pub const SIGSYS = 12; +pub const SIGPIPE = 13; +pub const SIGALRM = 14; +pub const SIGTERM = 15; +pub const SIGURG = 16; +pub const SIGSTOP = 17; +pub const SIGTSTP = 18; +pub const SIGCONT = 19; +pub const SIGCHLD = 20; +pub const SIGTTIN = 21; +pub const SIGTTOU = 22; +pub const SIGIO = 23; +pub const SIGXCPU = 24; +pub const SIGXFSZ = 25; +pub const SIGVTALRM = 26; +pub const SIGPROF = 27; +pub const SIGWINCH = 28; +pub const SIGINFO = 29; +pub const SIGUSR1 = 30; +pub const SIGUSR2 = 31; +pub const SIGTHR = 32; +pub const SIGLWP = SIGTHR; +pub const SIGLIBRT = 33; + +pub const SIGRTMIN = 65; +pub const SIGRTMAX = 126; + +// access function +pub const F_OK = 0; // test for existence of file +pub const X_OK = 1; // test for execute or search permission +pub const W_OK = 2; // test for write permission +pub const R_OK = 4; // test for read permission + +pub const O_RDONLY = 0x0000; +pub const O_WRONLY = 0x0001; +pub const O_RDWR = 0x0002; +pub const O_ACCMODE = 0x0003; + +pub const O_SHLOCK = 0x0010; +pub const O_EXLOCK = 0x0020; + +pub const O_CREAT = 0x0200; +pub const O_EXCL = 0x0800; +pub const O_NOCTTY = 0x8000; +pub const O_TRUNC = 0x0400; +pub const O_APPEND = 0x0008; +pub const O_NONBLOCK = 0x0004; +pub const O_DSYNC = 0o10000; +pub const O_SYNC = 0x0080; +pub const O_RSYNC = 0o4010000; +pub const O_DIRECTORY = 0x20000; +pub const O_NOFOLLOW = 0x0100; +pub const O_CLOEXEC = 0x00100000; + +pub const O_ASYNC = 0x0040; +pub const O_DIRECT = 0x00010000; +pub const O_NOATIME = 0o1000000; +pub const O_PATH = 0o10000000; +pub const O_TMPFILE = 0o20200000; +pub const O_NDELAY = O_NONBLOCK; + +pub const F_DUPFD = 0; +pub const F_GETFD = 1; +pub const F_SETFD = 2; +pub const F_GETFL = 3; +pub const F_SETFL = 4; + +pub const F_GETOWN = 5; +pub const F_SETOWN = 6; + +pub const F_GETLK = 11; +pub const F_SETLK = 12; +pub const F_SETLKW = 13; + +pub const F_RDLCK = 1; +pub const F_WRLCK = 3; +pub const F_UNLCK = 2; + +pub const LOCK_SH = 1; +pub const LOCK_EX = 2; +pub const LOCK_UN = 8; +pub const LOCK_NB = 4; + +pub const F_SETOWN_EX = 15; +pub const F_GETOWN_EX = 16; + +pub const F_GETOWNER_UIDS = 17; + +pub const FD_CLOEXEC = 1; + +pub const SEEK_SET = 0; +pub const SEEK_CUR = 1; +pub const SEEK_END = 2; + +pub const SIG_BLOCK = 1; +pub const SIG_UNBLOCK = 2; +pub const SIG_SETMASK = 3; + +pub const SOCK_STREAM = 1; +pub const SOCK_DGRAM = 2; +pub const SOCK_RAW = 3; +pub const SOCK_RDM = 4; +pub const SOCK_SEQPACKET = 5; + +pub const SOCK_CLOEXEC = 0x10000000; +pub const SOCK_NONBLOCK = 0x20000000; + +pub const SO_DEBUG = 0x00000001; +pub const SO_ACCEPTCONN = 0x00000002; +pub const SO_REUSEADDR = 0x00000004; +pub const SO_KEEPALIVE = 0x00000008; +pub const SO_DONTROUTE = 0x00000010; +pub const SO_BROADCAST = 0x00000020; +pub const SO_USELOOPBACK = 0x00000040; +pub const SO_LINGER = 0x00000080; +pub const SO_OOBINLINE = 0x00000100; +pub const SO_REUSEPORT = 0x00000200; +pub const SO_TIMESTAMP = 0x00000400; +pub const SO_NOSIGPIPE = 0x00000800; +pub const SO_ACCEPTFILTER = 0x00001000; +pub const SO_BINTIME = 0x00002000; +pub const SO_NO_OFFLOAD = 0x00004000; +pub const SO_NO_DDP = 0x00008000; +pub const SO_REUSEPORT_LB = 0x00010000; + +pub const SO_SNDBUF = 0x1001; +pub const SO_RCVBUF = 0x1002; +pub const SO_SNDLOWAT = 0x1003; +pub const SO_RCVLOWAT = 0x1004; +pub const SO_SNDTIMEO = 0x1005; +pub const SO_RCVTIMEO = 0x1006; +pub const SO_ERROR = 0x1007; +pub const SO_TYPE = 0x1008; +pub const SO_LABEL = 0x1009; +pub const SO_PEERLABEL = 0x1010; +pub const SO_LISTENQLIMIT = 0x1011; +pub const SO_LISTENQLEN = 0x1012; +pub const SO_LISTENINCQLEN = 0x1013; +pub const SO_SETFIB = 0x1014; +pub const SO_USER_COOKIE = 0x1015; +pub const SO_PROTOCOL = 0x1016; +pub const SO_PROTOTYPE = SO_PROTOCOL; +pub const SO_TS_CLOCK = 0x1017; +pub const SO_MAX_PACING_RATE = 0x1018; +pub const SO_DOMAIN = 0x1019; + +pub const SOL_SOCKET = 0xffff; + +pub const PF_UNSPEC = AF_UNSPEC; +pub const PF_LOCAL = AF_LOCAL; +pub const PF_UNIX = PF_LOCAL; +pub const PF_INET = AF_INET; +pub const PF_IMPLINK = AF_IMPLINK; +pub const PF_PUP = AF_PUP; +pub const PF_CHAOS = AF_CHAOS; +pub const PF_NETBIOS = AF_NETBIOS; +pub const PF_ISO = AF_ISO; +pub const PF_OSI = AF_ISO; +pub const PF_ECMA = AF_ECMA; +pub const PF_DATAKIT = AF_DATAKIT; +pub const PF_CCITT = AF_CCITT; +pub const PF_DECnet = AF_DECnet; +pub const PF_DLI = AF_DLI; +pub const PF_LAT = AF_LAT; +pub const PF_HYLINK = AF_HYLINK; +pub const PF_APPLETALK = AF_APPLETALK; +pub const PF_ROUTE = AF_ROUTE; +pub const PF_LINK = AF_LINK; +pub const PF_XTP = pseudo_AF_XTP; +pub const PF_COIP = AF_COIP; +pub const PF_CNT = AF_CNT; +pub const PF_SIP = AF_SIP; +pub const PF_IPX = AF_IPX; +pub const PF_RTIP = pseudo_AF_RTIP; +pub const PF_PIP = psuedo_AF_PIP; +pub const PF_ISDN = AF_ISDN; +pub const PF_KEY = pseudo_AF_KEY; +pub const PF_INET6 = pseudo_AF_INET6; +pub const PF_NATM = AF_NATM; +pub const PF_ATM = AF_ATM; +pub const PF_NETGRAPH = AF_NETGRAPH; +pub const PF_SLOW = AF_SLOW; +pub const PF_SCLUSTER = AF_SCLUSTER; +pub const PF_ARP = AF_ARP; +pub const PF_BLUETOOTH = AF_BLUETOOTH; +pub const PF_IEEE80211 = AF_IEEE80211; +pub const PF_INET_SDP = AF_INET_SDP; +pub const PF_INET6_SDP = AF_INET6_SDP; +pub const PF_MAX = AF_MAX; + +pub const AF_UNSPEC = 0; +pub const AF_UNIX = 1; +pub const AF_LOCAL = AF_UNIX; +pub const AF_FILE = AF_LOCAL; +pub const AF_INET = 2; +pub const AF_IMPLINK = 3; +pub const AF_PUP = 4; +pub const AF_CHAOS = 5; +pub const AF_NETBIOS = 6; +pub const AF_ISO = 7; +pub const AF_OSI = AF_ISO; +pub const AF_ECMA = 8; +pub const AF_DATAKIT = 9; +pub const AF_CCITT = 10; +pub const AF_SNA = 11; +pub const AF_DECnet = 12; +pub const AF_DLI = 13; +pub const AF_LAT = 14; +pub const AF_HYLINK = 15; +pub const AF_APPLETALK = 16; +pub const AF_ROUTE = 17; +pub const AF_LINK = 18; +pub const pseudo_AF_XTP = 19; +pub const AF_COIP = 20; +pub const AF_CNT = 21; +pub const pseudo_AF_RTIP = 22; +pub const AF_IPX = 23; +pub const AF_SIP = 24; +pub const pseudo_AF_PIP = 25; +pub const AF_ISDN = 26; +pub const AF_E164 = AF_ISDN; +pub const pseudo_AF_KEY = 27; +pub const AF_INET6 = 28; +pub const AF_NATM = 29; +pub const AF_ATM = 30; +pub const pseudo_AF_HDRCMPLT = 31; +pub const AF_NETGRAPH = 32; +pub const AF_SLOW = 33; +pub const AF_SCLUSTER = 34; +pub const AF_ARP = 35; +pub const AF_BLUETOOTH = 36; +pub const AF_IEEE80211 = 37; +pub const AF_INET_SDP = 40; +pub const AF_INET6_SDP = 42; +pub const AF_MAX = 42; + +pub const DT_UNKNOWN = 0; +pub const DT_FIFO = 1; +pub const DT_CHR = 2; +pub const DT_DIR = 4; +pub const DT_BLK = 6; +pub const DT_REG = 8; +pub const DT_LNK = 10; +pub const DT_SOCK = 12; +pub const DT_WHT = 14; + +/// add event to kq (implies enable) +pub const EV_ADD = 0x0001; + +/// delete event from kq +pub const EV_DELETE = 0x0002; + +/// enable event +pub const EV_ENABLE = 0x0004; + +/// disable event (not reported) +pub const EV_DISABLE = 0x0008; + +/// only report one occurrence +pub const EV_ONESHOT = 0x0010; + +/// clear event state after reporting +pub const EV_CLEAR = 0x0020; + +/// force immediate event output +/// ... with or without EV_ERROR +/// ... use KEVENT_FLAG_ERROR_EVENTS +/// on syscalls supporting flags +pub const EV_RECEIPT = 0x0040; + +/// disable event after reporting +pub const EV_DISPATCH = 0x0080; + +pub const EVFILT_READ = -1; +pub const EVFILT_WRITE = -2; + +/// attached to aio requests +pub const EVFILT_AIO = -3; + +/// attached to vnodes +pub const EVFILT_VNODE = -4; + +/// attached to struct proc +pub const EVFILT_PROC = -5; + +/// attached to struct proc +pub const EVFILT_SIGNAL = -6; + +/// timers +pub const EVFILT_TIMER = -7; + +/// Process descriptors +pub const EVFILT_PROCDESC = -8; + +/// Filesystem events +pub const EVFILT_FS = -9; + +pub const EVFILT_LIO = -10; + +/// User events +pub const EVFILT_USER = -11; + +/// Sendfile events +pub const EVFILT_SENDFILE = -12; + +pub const EVFILT_EMPTY = -13; + +/// On input, NOTE_TRIGGER causes the event to be triggered for output. +pub const NOTE_TRIGGER = 0x01000000; + +/// ignore input fflags +pub const NOTE_FFNOP = 0x00000000; + +/// and fflags +pub const NOTE_FFAND = 0x40000000; + +/// or fflags +pub const NOTE_FFOR = 0x80000000; + +/// copy fflags +pub const NOTE_FFCOPY = 0xc0000000; + +/// mask for operations +pub const NOTE_FFCTRLMASK = 0xc0000000; +pub const NOTE_FFLAGSMASK = 0x00ffffff; + +/// low water mark +pub const NOTE_LOWAT = 0x00000001; + +/// behave like poll() +pub const NOTE_FILE_POLL = 0x00000002; + +/// vnode was removed +pub const NOTE_DELETE = 0x00000001; + +/// data contents changed +pub const NOTE_WRITE = 0x00000002; + +/// size increased +pub const NOTE_EXTEND = 0x00000004; + +/// attributes changed +pub const NOTE_ATTRIB = 0x00000008; + +/// link count changed +pub const NOTE_LINK = 0x00000010; + +/// vnode was renamed +pub const NOTE_RENAME = 0x00000020; + +/// vnode access was revoked +pub const NOTE_REVOKE = 0x00000040; + +/// vnode was opened +pub const NOTE_OPEN = 0x00000080; + +/// file closed, fd did not allow write +pub const NOTE_CLOSE = 0x00000100; + +/// file closed, fd did allow write +pub const NOTE_CLOSE_WRITE = 0x00000200; + +/// file was read +pub const NOTE_READ = 0x00000400; + +/// process exited +pub const NOTE_EXIT = 0x80000000; + +/// process forked +pub const NOTE_FORK = 0x40000000; + +/// process exec'd +pub const NOTE_EXEC = 0x20000000; + +/// mask for signal & exit status +pub const NOTE_PDATAMASK = 0x000fffff; +pub const NOTE_PCTRLMASK = (~NOTE_PDATAMASK); + +/// data is seconds +pub const NOTE_SECONDS = 0x00000001; + +/// data is milliseconds +pub const NOTE_MSECONDS = 0x00000002; + +/// data is microseconds +pub const NOTE_USECONDS = 0x00000004; + +/// data is nanoseconds +pub const NOTE_NSECONDS = 0x00000008; + +/// timeout is absolute +pub const NOTE_ABSTIME = 0x00000010; + +pub const TIOCEXCL = 0x2000740d; +pub const TIOCNXCL = 0x2000740e; +pub const TIOCSCTTY = 0x20007461; +pub const TIOCGPGRP = 0x40047477; +pub const TIOCSPGRP = 0x80047476; +pub const TIOCOUTQ = 0x40047473; +pub const TIOCSTI = 0x80017472; +pub const TIOCGWINSZ = 0x40087468; +pub const TIOCSWINSZ = 0x80087467; +pub const TIOCMGET = 0x4004746a; +pub const TIOCMBIS = 0x8004746c; +pub const TIOCMBIC = 0x8004746b; +pub const TIOCMSET = 0x8004746d; +pub const FIONREAD = 0x4004667f; +pub const TIOCCONS = 0x80047462; +pub const TIOCPKT = 0x80047470; +pub const FIONBIO = 0x8004667e; +pub const TIOCNOTTY = 0x20007471; +pub const TIOCSETD = 0x8004741b; +pub const TIOCGETD = 0x4004741a; +pub const TIOCSBRK = 0x2000747b; +pub const TIOCCBRK = 0x2000747a; +pub const TIOCGSID = 0x40047463; +pub const TIOCGPTN = 0x4004740f; +pub const TIOCSIG = 0x2004745f; + +pub fn WEXITSTATUS(s: u32) u32 { + return (s & 0xff00) >> 8; +} +pub fn WTERMSIG(s: u32) u32 { + return s & 0x7f; +} +pub fn WSTOPSIG(s: u32) u32 { + return WEXITSTATUS(s); +} +pub fn WIFEXITED(s: u32) bool { + return WTERMSIG(s) == 0; +} +pub fn WIFSTOPPED(s: u32) bool { + return @intCast(u16, (((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00; +} +pub fn WIFSIGNALED(s: u32) bool { + return (s & 0xffff) -% 1 < 0xff; +} + +pub const winsize = extern struct { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +}; + +const NSIG = 32; + +pub const SIG_ERR = @intToPtr(fn (i32) callconv(.C) void, maxInt(usize)); +pub const SIG_DFL = @intToPtr(fn (i32) callconv(.C) void, 0); +pub const SIG_IGN = @intToPtr(fn (i32) callconv(.C) void, 1); + +/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall. +pub const Sigaction = extern struct { + /// signal handler + __sigaction_u: extern union { + __sa_handler: fn (i32) callconv(.C) void, + __sa_sigaction: fn (i32, *__siginfo, usize) callconv(.C) void, + }, + + /// see signal options + sa_flags: u32, + + /// signal mask to apply + sa_mask: sigset_t, +}; + +pub const _SIG_WORDS = 4; +pub const _SIG_MAXSIG = 128; + +pub inline fn _SIG_IDX(sig: usize) usize { + return sig - 1; +} +pub inline fn _SIG_WORD(sig: usize) usize { + return_SIG_IDX(sig) >> 5; +} +pub inline fn _SIG_BIT(sig: usize) usize { + return 1 << (_SIG_IDX(sig) & 31); +} +pub inline fn _SIG_VALID(sig: usize) usize { + return sig <= _SIG_MAXSIG and sig > 0; +} + +pub const sigset_t = extern struct { + __bits: [_SIG_WORDS]u32, +}; + +pub const EPERM = 1; // Operation not permitted +pub const ENOENT = 2; // No such file or directory +pub const ESRCH = 3; // No such process +pub const EINTR = 4; // Interrupted system call +pub const EIO = 5; // Input/output error +pub const ENXIO = 6; // Device not configured +pub const E2BIG = 7; // Argument list too long +pub const ENOEXEC = 8; // Exec format error +pub const EBADF = 9; // Bad file descriptor +pub const ECHILD = 10; // No child processes +pub const EDEADLK = 11; // Resource deadlock avoided +// 11 was EAGAIN +pub const ENOMEM = 12; // Cannot allocate memory +pub const EACCES = 13; // Permission denied +pub const EFAULT = 14; // Bad address +pub const ENOTBLK = 15; // Block device required +pub const EBUSY = 16; // Device busy +pub const EEXIST = 17; // File exists +pub const EXDEV = 18; // Cross-device link +pub const ENODEV = 19; // Operation not supported by device +pub const ENOTDIR = 20; // Not a directory +pub const EISDIR = 21; // Is a directory +pub const EINVAL = 22; // Invalid argument +pub const ENFILE = 23; // Too many open files in system +pub const EMFILE = 24; // Too many open files +pub const ENOTTY = 25; // Inappropriate ioctl for device +pub const ETXTBSY = 26; // Text file busy +pub const EFBIG = 27; // File too large +pub const ENOSPC = 28; // No space left on device +pub const ESPIPE = 29; // Illegal seek +pub const EROFS = 30; // Read-only filesystem +pub const EMLINK = 31; // Too many links +pub const EPIPE = 32; // Broken pipe + +// math software +pub const EDOM = 33; // Numerical argument out of domain +pub const ERANGE = 34; // Result too large + +// non-blocking and interrupt i/o +pub const EAGAIN = 35; // Resource temporarily unavailable +pub const EWOULDBLOCK = EAGAIN; // Operation would block +pub const EINPROGRESS = 36; // Operation now in progress +pub const EALREADY = 37; // Operation already in progress + +// ipc/network software -- argument errors +pub const ENOTSOCK = 38; // Socket operation on non-socket +pub const EDESTADDRREQ = 39; // Destination address required +pub const EMSGSIZE = 40; // Message too long +pub const EPROTOTYPE = 41; // Protocol wrong type for socket +pub const ENOPROTOOPT = 42; // Protocol not available +pub const EPROTONOSUPPORT = 43; // Protocol not supported +pub const ESOCKTNOSUPPORT = 44; // Socket type not supported +pub const EOPNOTSUPP = 45; // Operation not supported +pub const ENOTSUP = EOPNOTSUPP; // Operation not supported +pub const EPFNOSUPPORT = 46; // Protocol family not supported +pub const EAFNOSUPPORT = 47; // Address family not supported by protocol family +pub const EADDRINUSE = 48; // Address already in use +pub const EADDRNOTAVAIL = 49; // Can't assign requested address + +// ipc/network software -- operational errors +pub const ENETDOWN = 50; // Network is down +pub const ENETUNREACH = 51; // Network is unreachable +pub const ENETRESET = 52; // Network dropped connection on reset +pub const ECONNABORTED = 53; // Software caused connection abort +pub const ECONNRESET = 54; // Connection reset by peer +pub const ENOBUFS = 55; // No buffer space available +pub const EISCONN = 56; // Socket is already connected +pub const ENOTCONN = 57; // Socket is not connected +pub const ESHUTDOWN = 58; // Can't send after socket shutdown +pub const ETOOMANYREFS = 59; // Too many references: can't splice +pub const ETIMEDOUT = 60; // Operation timed out +pub const ECONNREFUSED = 61; // Connection refused + +pub const ELOOP = 62; // Too many levels of symbolic links +pub const ENAMETOOLONG = 63; // File name too long + +// should be rearranged +pub const EHOSTDOWN = 64; // Host is down +pub const EHOSTUNREACH = 65; // No route to host +pub const ENOTEMPTY = 66; // Directory not empty + +// quotas & mush +pub const EPROCLIM = 67; // Too many processes +pub const EUSERS = 68; // Too many users +pub const EDQUOT = 69; // Disc quota exceeded + +// Network File System +pub const ESTALE = 70; // Stale NFS file handle +pub const EREMOTE = 71; // Too many levels of remote in path +pub const EBADRPC = 72; // RPC struct is bad +pub const ERPCMISMATCH = 73; // RPC version wrong +pub const EPROGUNAVAIL = 74; // RPC prog. not avail +pub const EPROGMISMATCH = 75; // Program version wrong +pub const EPROCUNAVAIL = 76; // Bad procedure for program + +pub const ENOLCK = 77; // No locks available +pub const ENOSYS = 78; // Function not implemented + +pub const EFTYPE = 79; // Inappropriate file type or format +pub const EAUTH = 80; // Authentication error +pub const ENEEDAUTH = 81; // Need authenticator +pub const EIDRM = 82; // Identifier removed +pub const ENOMSG = 83; // No message of desired type +pub const EOVERFLOW = 84; // Value too large to be stored in data type +pub const ECANCELED = 85; // Operation canceled +pub const EILSEQ = 86; // Illegal byte sequence +pub const ENOATTR = 87; // Attribute not found + +pub const EDOOFUS = 88; // Programming error + +pub const EBADMSG = 89; // Bad message +pub const EMULTIHOP = 90; // Multihop attempted +pub const ENOLINK = 91; // Link has been severed +pub const EPROTO = 92; // Protocol error + +pub const ENOTCAPABLE = 93; // Capabilities insufficient +pub const ECAPMODE = 94; // Not permitted in capability mode +pub const ENOTRECOVERABLE = 95; // State not recoverable +pub const EOWNERDEAD = 96; // Previous owner died + +pub const ELAST = 96; // Must be equal largest errno + +pub const MINSIGSTKSZ = switch (builtin.arch) { + .i386, .x86_64 => 2048, + .arm, .aarch64 => 4096, + else => @compileError("MINSIGSTKSZ not defined for this architecture"), +}; +pub const SIGSTKSZ = MINSIGSTKSZ + 32768; + +pub const SS_ONSTACK = 1; +pub const SS_DISABLE = 4; + +pub const stack_t = extern struct { + ss_sp: [*]u8, + ss_size: isize, + ss_flags: i32, +}; + +pub const S_IFMT = 0o170000; + +pub const S_IFIFO = 0o010000; +pub const S_IFCHR = 0o020000; +pub const S_IFDIR = 0o040000; +pub const S_IFBLK = 0o060000; +pub const S_IFREG = 0o100000; +pub const S_IFLNK = 0o120000; +pub const S_IFSOCK = 0o140000; +pub const S_IFWHT = 0o160000; + +pub const S_ISUID = 0o4000; +pub const S_ISGID = 0o2000; +pub const S_ISVTX = 0o1000; +pub const S_IRWXU = 0o700; +pub const S_IRUSR = 0o400; +pub const S_IWUSR = 0o200; +pub const S_IXUSR = 0o100; +pub const S_IRWXG = 0o070; +pub const S_IRGRP = 0o040; +pub const S_IWGRP = 0o020; +pub const S_IXGRP = 0o010; +pub const S_IRWXO = 0o007; +pub const S_IROTH = 0o004; +pub const S_IWOTH = 0o002; +pub const S_IXOTH = 0o001; + +pub fn S_ISFIFO(m: u32) bool { + return m & S_IFMT == S_IFIFO; +} + +pub fn S_ISCHR(m: u32) bool { + return m & S_IFMT == S_IFCHR; +} + +pub fn S_ISDIR(m: u32) bool { + return m & S_IFMT == S_IFDIR; +} + +pub fn S_ISBLK(m: u32) bool { + return m & S_IFMT == S_IFBLK; +} + +pub fn S_ISREG(m: u32) bool { + return m & S_IFMT == S_IFREG; +} + +pub fn S_ISLNK(m: u32) bool { + return m & S_IFMT == S_IFLNK; +} + +pub fn S_ISSOCK(m: u32) bool { + return m & S_IFMT == S_IFSOCK; +} + +pub fn S_IWHT(m: u32) bool { + return m & S_IFMT == S_IFWHT; +} + +pub const HOST_NAME_MAX = 255; + +/// Magic value that specify the use of the current working directory +/// to determine the target of relative file paths in the openat() and +/// similar syscalls. +pub const AT_FDCWD = -100; + +/// Check access using effective user and group ID +pub const AT_EACCESS = 0x0100; + +/// Do not follow symbolic links +pub const AT_SYMLINK_NOFOLLOW = 0x0200; + +/// Follow symbolic link +pub const AT_SYMLINK_FOLLOW = 0x0400; + +/// Remove directory instead of file +pub const AT_REMOVEDIR = 0x0800; + +pub const addrinfo = extern struct { + flags: i32, + family: i32, + socktype: i32, + protocol: i32, + addrlen: socklen_t, + canonname: ?[*:0]u8, + addr: ?*sockaddr, + next: ?*addrinfo, +}; + +/// Fail if not under dirfd +pub const AT_BENEATH = 0x1000; + +/// dummy for IP +pub const IPPROTO_IP = 0; + +/// control message protocol +pub const IPPROTO_ICMP = 1; + +/// tcp +pub const IPPROTO_TCP = 6; + +/// user datagram protocol +pub const IPPROTO_UDP = 17; + +/// IP6 header +pub const IPPROTO_IPV6 = 41; + +/// raw IP packet +pub const IPPROTO_RAW = 255; + +/// IP6 hop-by-hop options +pub const IPPROTO_HOPOPTS = 0; + +/// group mgmt protocol +pub const IPPROTO_IGMP = 2; + +/// gateway^2 (deprecated) +pub const IPPROTO_GGP = 3; + +/// IPv4 encapsulation +pub const IPPROTO_IPV4 = 4; + +/// for compatibility +pub const IPPROTO_IPIP = IPPROTO_IPV4; + +/// Stream protocol II +pub const IPPROTO_ST = 7; + +/// exterior gateway protocol +pub const IPPROTO_EGP = 8; + +/// private interior gateway +pub const IPPROTO_PIGP = 9; + +/// BBN RCC Monitoring +pub const IPPROTO_RCCMON = 10; + +/// network voice protocol +pub const IPPROTO_NVPII = 11; + +/// pup +pub const IPPROTO_PUP = 12; + +/// Argus +pub const IPPROTO_ARGUS = 13; + +/// EMCON +pub const IPPROTO_EMCON = 14; + +/// Cross Net Debugger +pub const IPPROTO_XNET = 15; + +/// Chaos +pub const IPPROTO_CHAOS = 16; + +/// Multiplexing +pub const IPPROTO_MUX = 18; + +/// DCN Measurement Subsystems +pub const IPPROTO_MEAS = 19; + +/// Host Monitoring +pub const IPPROTO_HMP = 20; + +/// Packet Radio Measurement +pub const IPPROTO_PRM = 21; + +/// xns idp +pub const IPPROTO_IDP = 22; + +/// Trunk-1 +pub const IPPROTO_TRUNK1 = 23; + +/// Trunk-2 +pub const IPPROTO_TRUNK2 = 24; + +/// Leaf-1 +pub const IPPROTO_LEAF1 = 25; + +/// Leaf-2 +pub const IPPROTO_LEAF2 = 26; + +/// Reliable Data +pub const IPPROTO_RDP = 27; + +/// Reliable Transaction +pub const IPPROTO_IRTP = 28; + +/// tp-4 w/ class negotiation +pub const IPPROTO_TP = 29; + +/// Bulk Data Transfer +pub const IPPROTO_BLT = 30; + +/// Network Services +pub const IPPROTO_NSP = 31; + +/// Merit Internodal +pub const IPPROTO_INP = 32; + +/// Datagram Congestion Control Protocol +pub const IPPROTO_DCCP = 33; + +/// Third Party Connect +pub const IPPROTO_3PC = 34; + +/// InterDomain Policy Routing +pub const IPPROTO_IDPR = 35; + +/// XTP +pub const IPPROTO_XTP = 36; + +/// Datagram Delivery +pub const IPPROTO_DDP = 37; + +/// Control Message Transport +pub const IPPROTO_CMTP = 38; + +/// TP++ Transport +pub const IPPROTO_TPXX = 39; + +/// IL transport protocol +pub const IPPROTO_IL = 40; + +/// Source Demand Routing +pub const IPPROTO_SDRP = 42; + +/// IP6 routing header +pub const IPPROTO_ROUTING = 43; + +/// IP6 fragmentation header +pub const IPPROTO_FRAGMENT = 44; + +/// InterDomain Routing +pub const IPPROTO_IDRP = 45; + +/// resource reservation +pub const IPPROTO_RSVP = 46; + +/// General Routing Encap. +pub const IPPROTO_GRE = 47; + +/// Mobile Host Routing +pub const IPPROTO_MHRP = 48; + +/// BHA +pub const IPPROTO_BHA = 49; + +/// IP6 Encap Sec. Payload +pub const IPPROTO_ESP = 50; + +/// IP6 Auth Header +pub const IPPROTO_AH = 51; + +/// Integ. Net Layer Security +pub const IPPROTO_INLSP = 52; + +/// IP with encryption +pub const IPPROTO_SWIPE = 53; + +/// Next Hop Resolution +pub const IPPROTO_NHRP = 54; + +/// IP Mobility +pub const IPPROTO_MOBILE = 55; + +/// Transport Layer Security +pub const IPPROTO_TLSP = 56; + +/// SKIP +pub const IPPROTO_SKIP = 57; + +/// ICMP6 +pub const IPPROTO_ICMPV6 = 58; + +/// IP6 no next header +pub const IPPROTO_NONE = 59; + +/// IP6 destination option +pub const IPPROTO_DSTOPTS = 60; + +/// any host internal protocol +pub const IPPROTO_AHIP = 61; + +/// CFTP +pub const IPPROTO_CFTP = 62; + +/// "hello" routing protocol +pub const IPPROTO_HELLO = 63; + +/// SATNET/Backroom EXPAK +pub const IPPROTO_SATEXPAK = 64; + +/// Kryptolan +pub const IPPROTO_KRYPTOLAN = 65; + +/// Remote Virtual Disk +pub const IPPROTO_RVD = 66; + +/// Pluribus Packet Core +pub const IPPROTO_IPPC = 67; + +/// Any distributed FS +pub const IPPROTO_ADFS = 68; + +/// Satnet Monitoring +pub const IPPROTO_SATMON = 69; + +/// VISA Protocol +pub const IPPROTO_VISA = 70; + +/// Packet Core Utility +pub const IPPROTO_IPCV = 71; + +/// Comp. Prot. Net. Executive +pub const IPPROTO_CPNX = 72; + +/// Comp. Prot. HeartBeat +pub const IPPROTO_CPHB = 73; + +/// Wang Span Network +pub const IPPROTO_WSN = 74; + +/// Packet Video Protocol +pub const IPPROTO_PVP = 75; + +/// BackRoom SATNET Monitoring +pub const IPPROTO_BRSATMON = 76; + +/// Sun net disk proto (temp.) +pub const IPPROTO_ND = 77; + +/// WIDEBAND Monitoring +pub const IPPROTO_WBMON = 78; + +/// WIDEBAND EXPAK +pub const IPPROTO_WBEXPAK = 79; + +/// ISO cnlp +pub const IPPROTO_EON = 80; + +/// VMTP +pub const IPPROTO_VMTP = 81; + +/// Secure VMTP +pub const IPPROTO_SVMTP = 82; + +/// Banyon VINES +pub const IPPROTO_VINES = 83; + +/// TTP +pub const IPPROTO_TTP = 84; + +/// NSFNET-IGP +pub const IPPROTO_IGP = 85; + +/// dissimilar gateway prot. +pub const IPPROTO_DGP = 86; + +/// TCF +pub const IPPROTO_TCF = 87; + +/// Cisco/GXS IGRP +pub const IPPROTO_IGRP = 88; + +/// OSPFIGP +pub const IPPROTO_OSPFIGP = 89; + +/// Strite RPC protocol +pub const IPPROTO_SRPC = 90; + +/// Locus Address Resoloution +pub const IPPROTO_LARP = 91; + +/// Multicast Transport +pub const IPPROTO_MTP = 92; + +/// AX.25 Frames +pub const IPPROTO_AX25 = 93; + +/// IP encapsulated in IP +pub const IPPROTO_IPEIP = 94; + +/// Mobile Int.ing control +pub const IPPROTO_MICP = 95; + +/// Semaphore Comm. security +pub const IPPROTO_SCCSP = 96; + +/// Ethernet IP encapsulation +pub const IPPROTO_ETHERIP = 97; + +/// encapsulation header +pub const IPPROTO_ENCAP = 98; + +/// any private encr. scheme +pub const IPPROTO_APES = 99; + +/// GMTP +pub const IPPROTO_GMTP = 100; + +/// payload compression (IPComp) +pub const IPPROTO_IPCOMP = 108; + +/// SCTP +pub const IPPROTO_SCTP = 132; + +/// IPv6 Mobility Header +pub const IPPROTO_MH = 135; + +/// UDP-Lite +pub const IPPROTO_UDPLITE = 136; + +/// IP6 Host Identity Protocol +pub const IPPROTO_HIP = 139; + +/// IP6 Shim6 Protocol +pub const IPPROTO_SHIM6 = 140; + +/// Protocol Independent Mcast +pub const IPPROTO_PIM = 103; + +/// CARP +pub const IPPROTO_CARP = 112; + +/// PGM +pub const IPPROTO_PGM = 113; + +/// MPLS-in-IP +pub const IPPROTO_MPLS = 137; + +/// PFSYNC +pub const IPPROTO_PFSYNC = 240; + +/// Reserved +pub const IPPROTO_RESERVED_253 = 253; + +/// Reserved +pub const IPPROTO_RESERVED_254 = 254; + +pub const rlimit_resource = extern enum(c_int) { + CPU = 0, + FSIZE = 1, + DATA = 2, + STACK = 3, + CORE = 4, + RSS = 5, + MEMLOCK = 6, + NPROC = 7, + NOFILE = 8, + SBSIZE = 9, + VMEM = 10, + AS = 10, + NPTS = 11, + SWAP = 12, + KQUEUES = 13, + UMTXP = 14, + + _, +}; + +pub const rlim_t = i64; + +/// No limit +pub const RLIM_INFINITY: rlim_t = (1 << 63) - 1; + +pub const RLIM_SAVED_MAX = RLIM_INFINITY; +pub const RLIM_SAVED_CUR = RLIM_INFINITY; + +pub const rlimit = extern struct { + /// Soft limit + cur: rlim_t, + /// Hard limit + max: rlim_t, +}; + +pub const SHUT_RD = 0; +pub const SHUT_WR = 1; +pub const SHUT_RDWR = 2; + +// TODO fill out if needed +pub const directory_which = extern enum(c_int) { + B_USER_SETTINGS_DIRECTORY = 0xbbe, + + _, +}; diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index e86a08e861..8d3d5c49a3 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -533,6 +533,59 @@ pub const SOL_TLS = 282; pub const SOMAXCONN = 128; +pub const IP_TOS = 1; +pub const IP_TTL = 2; +pub const IP_HDRINCL = 3; +pub const IP_OPTIONS = 4; +pub const IP_ROUTER_ALERT = 5; +pub const IP_RECVOPTS = 6; +pub const IP_RETOPTS = 7; +pub const IP_PKTINFO = 8; +pub const IP_PKTOPTIONS = 9; +pub const IP_PMTUDISC = 10; +pub const IP_MTU_DISCOVER = 10; +pub const IP_RECVERR = 11; +pub const IP_RECVTTL = 12; +pub const IP_RECVTOS = 13; +pub const IP_MTU = 14; +pub const IP_FREEBIND = 15; +pub const IP_IPSEC_POLICY = 16; +pub const IP_XFRM_POLICY = 17; +pub const IP_PASSSEC = 18; +pub const IP_TRANSPARENT = 19; +pub const IP_ORIGDSTADDR = 20; +pub const IP_RECVORIGDSTADDR = IP_ORIGDSTADDR; +pub const IP_MINTTL = 21; +pub const IP_NODEFRAG = 22; +pub const IP_CHECKSUM = 23; +pub const IP_BIND_ADDRESS_NO_PORT = 24; +pub const IP_RECVFRAGSIZE = 25; +pub const IP_MULTICAST_IF = 32; +pub const IP_MULTICAST_TTL = 33; +pub const IP_MULTICAST_LOOP = 34; +pub const IP_ADD_MEMBERSHIP = 35; +pub const IP_DROP_MEMBERSHIP = 36; +pub const IP_UNBLOCK_SOURCE = 37; +pub const IP_BLOCK_SOURCE = 38; +pub const IP_ADD_SOURCE_MEMBERSHIP = 39; +pub const IP_DROP_SOURCE_MEMBERSHIP = 40; +pub const IP_MSFILTER = 41; +pub const IP_MULTICAST_ALL = 49; +pub const IP_UNICAST_IF = 50; + +pub const IP_RECVRETOPTS = IP_RETOPTS; + +pub const IP_PMTUDISC_DONT = 0; +pub const IP_PMTUDISC_WANT = 1; +pub const IP_PMTUDISC_DO = 2; +pub const IP_PMTUDISC_PROBE = 3; +pub const IP_PMTUDISC_INTERFACE = 4; +pub const IP_PMTUDISC_OMIT = 5; + +pub const IP_DEFAULT_MULTICAST_TTL = 1; +pub const IP_DEFAULT_MULTICAST_LOOP = 1; +pub const IP_MAX_MEMBERSHIPS = 20; + pub const MSG_OOB = 0x0001; pub const MSG_PEEK = 0x0002; pub const MSG_DONTROUTE = 0x0004; @@ -1284,6 +1337,9 @@ pub const IORING_SETUP_CLAMP = 1 << 4; /// attach to existing wq pub const IORING_SETUP_ATTACH_WQ = 1 << 5; +/// start with ring disabled +pub const IORING_SETUP_R_DISABLED = 1 << 6; + pub const io_sqring_offsets = extern struct { /// offset of ring head head: u32, @@ -1430,6 +1486,11 @@ pub const io_uring_cqe = extern struct { flags: u32, }; +// io_uring_cqe.flags + +/// If set, the upper 16 bits are the buffer ID +pub const IORING_CQE_F_BUFFER = 1 << 0; + pub const IORING_OFF_SQ_RING = 0; pub const IORING_OFF_CQ_RING = 0x8000000; pub const IORING_OFF_SQES = 0x10000000; @@ -1439,7 +1500,7 @@ pub const IORING_ENTER_GETEVENTS = 1 << 0; pub const IORING_ENTER_SQ_WAKEUP = 1 << 1; // io_uring_register opcodes and arguments -pub const IORING_REGISTER = extern enum(u32) { +pub const IORING_REGISTER = extern enum(u8) { REGISTER_BUFFERS, UNREGISTER_BUFFERS, REGISTER_FILES, @@ -1451,11 +1512,13 @@ pub const IORING_REGISTER = extern enum(u32) { REGISTER_PROBE, REGISTER_PERSONALITY, UNREGISTER_PERSONALITY, + REGISTER_RESTRICTIONS, + REGISTER_ENABLE_RINGS, _, }; -pub const io_uring_files_update = struct { +pub const io_uring_files_update = extern struct { offset: u32, resv: u32, fds: u64, @@ -1463,7 +1526,7 @@ pub const io_uring_files_update = struct { pub const IO_URING_OP_SUPPORTED = 1 << 0; -pub const io_uring_probe_op = struct { +pub const io_uring_probe_op = extern struct { op: IORING_OP, resv: u8, @@ -1474,7 +1537,7 @@ pub const io_uring_probe_op = struct { resv2: u32, }; -pub const io_uring_probe = struct { +pub const io_uring_probe = extern struct { /// last opcode supported last_op: IORING_OP, @@ -1487,6 +1550,39 @@ pub const io_uring_probe = struct { // Followed by up to `ops_len` io_uring_probe_op structures }; +pub const io_uring_restriction = extern struct { + opcode: u16, + arg: extern union { + /// IORING_RESTRICTION_REGISTER_OP + register_op: IORING_REGISTER, + + /// IORING_RESTRICTION_SQE_OP + sqe_op: IORING_OP, + + /// IORING_RESTRICTION_SQE_FLAGS_* + sqe_flags: u8, + }, + resv: u8, + resv2: u32[3], +}; + +/// io_uring_restriction->opcode values +pub const IORING_RESTRICTION = extern enum(u8) { + /// Allow an io_uring_register(2) opcode + REGISTER_OP = 0, + + /// Allow an sqe opcode + SQE_OP = 1, + + /// Allow sqe flags + SQE_FLAGS_ALLOWED = 2, + + /// Require sqe flags (these flags must be set on each submission) + SQE_FLAGS_REQUIRED = 3, + + _, +}; + pub const utsname = extern struct { sysname: [64:0]u8, nodename: [64:0]u8, @@ -1837,6 +1933,120 @@ pub const tcflag_t = u32; pub const NCCS = 32; +pub const B0 = 0o0000000; +pub const B50 = 0o0000001; +pub const B75 = 0o0000002; +pub const B110 = 0o0000003; +pub const B134 = 0o0000004; +pub const B150 = 0o0000005; +pub const B200 = 0o0000006; +pub const B300 = 0o0000007; +pub const B600 = 0o0000010; +pub const B1200 = 0o0000011; +pub const B1800 = 0o0000012; +pub const B2400 = 0o0000013; +pub const B4800 = 0o0000014; +pub const B9600 = 0o0000015; +pub const B19200 = 0o0000016; +pub const B38400 = 0o0000017; +pub const BOTHER = 0o0010000; +pub const B57600 = 0o0010001; +pub const B115200 = 0o0010002; +pub const B230400 = 0o0010003; +pub const B460800 = 0o0010004; +pub const B500000 = 0o0010005; +pub const B576000 = 0o0010006; +pub const B921600 = 0o0010007; +pub const B1000000 = 0o0010010; +pub const B1152000 = 0o0010011; +pub const B1500000 = 0o0010012; +pub const B2000000 = 0o0010013; +pub const B2500000 = 0o0010014; +pub const B3000000 = 0o0010015; +pub const B3500000 = 0o0010016; +pub const B4000000 = 0o0010017; + +pub usingnamespace switch (builtin.arch) { + .powerpc, .powerpc64, .powerpc64le => struct { + pub const VINTR = 0; + pub const VQUIT = 1; + pub const VERASE = 2; + pub const VKILL = 3; + pub const VEOF = 4; + pub const VMIN = 5; + pub const VEOL = 6; + pub const VTIME = 7; + pub const VEOL2 = 8; + pub const VSWTC = 9; + pub const VWERASE = 10; + pub const VREPRINT = 11; + pub const VSUSP = 12; + pub const VSTART = 13; + pub const VSTOP = 14; + pub const VLNEXT = 15; + pub const VDISCARD = 16; + }, + .sparc, .sparcv9 => struct { + pub const VINTR = 0; + pub const VQUIT = 1; + pub const VERASE = 2; + pub const VKILL = 3; + pub const VEOF = 4; + pub const VEOL = 5; + pub const VEOL2 = 6; + pub const VSWTC = 7; + pub const VSTART = 8; + pub const VSTOP = 9; + pub const VSUSP = 10; + pub const VDSUSP = 11; + pub const VREPRINT = 12; + pub const VDISCARD = 13; + pub const VWERASE = 14; + pub const VLNEXT = 15; + pub const VMIN = VEOF; + pub const VTIME = VEOL; + }, + .mips, .mipsel, .mips64, .mips64el => struct { + pub const VINTR = 0; + pub const VQUIT = 1; + pub const VERASE = 2; + pub const VKILL = 3; + pub const VMIN = 4; + pub const VTIME = 5; + pub const VEOL2 = 6; + pub const VSWTC = 7; + pub const VSWTCH = 7; + pub const VSTART = 8; + pub const VSTOP = 9; + pub const VSUSP = 10; + pub const VREPRINT = 12; + pub const VDISCARD = 13; + pub const VWERASE = 14; + pub const VLNEXT = 15; + pub const VEOF = 16; + pub const VEOL = 17; + }, + else => struct { + pub const VINTR = 0; + pub const VQUIT = 1; + pub const VERASE = 2; + pub const VKILL = 3; + pub const VEOF = 4; + pub const VTIME = 5; + pub const VMIN = 6; + pub const VSWTC = 7; + pub const VSTART = 8; + pub const VSTOP = 9; + pub const VSUSP = 10; + pub const VEOL = 11; + pub const VREPRINT = 12; + pub const VDISCARD = 13; + pub const VWERASE = 14; + pub const VLNEXT = 15; + pub const VEOL2 = 16; + }, +}; + pub const IGNBRK = 1; pub const BRKINT = 2; pub const IGNPAR = 4; @@ -2012,3 +2222,25 @@ pub const rlimit = extern struct { /// Hard limit max: rlim_t, }; + +pub const MADV_NORMAL = 0; +pub const MADV_RANDOM = 1; +pub const MADV_SEQUENTIAL = 2; +pub const MADV_WILLNEED = 3; +pub const MADV_DONTNEED = 4; +pub const MADV_FREE = 8; +pub const MADV_REMOVE = 9; +pub const MADV_DONTFORK = 10; +pub const MADV_DOFORK = 11; +pub const MADV_MERGEABLE = 12; +pub const MADV_UNMERGEABLE = 13; +pub const MADV_HUGEPAGE = 14; +pub const MADV_NOHUGEPAGE = 15; +pub const MADV_DONTDUMP = 16; +pub const MADV_DODUMP = 17; +pub const MADV_WIPEONFORK = 18; +pub const MADV_KEEPONFORK = 19; +pub const MADV_COLD = 20; +pub const MADV_PAGEOUT = 21; +pub const MADV_HWPOISON = 100; +pub const MADV_SOFT_OFFLINE = 101; diff --git a/lib/std/os/bits/linux/arm-eabi.zig b/lib/std/os/bits/linux/arm-eabi.zig index d89e75ccd6..5673cb952b 100644 --- a/lib/std/os/bits/linux/arm-eabi.zig +++ b/lib/std/os/bits/linux/arm-eabi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -411,6 +411,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, breakpoint = 0x0f0001, cacheflush = 0x0f0002, diff --git a/lib/std/os/bits/linux/arm64.zig b/lib/std/os/bits/linux/arm64.zig index 22955aa896..a069b6adf1 100644 --- a/lib/std/os/bits/linux/arm64.zig +++ b/lib/std/os/bits/linux/arm64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -312,6 +312,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/linux/errno-generic.zig b/lib/std/os/bits/linux/errno-generic.zig index a99f20a1a8..f55aa3698e 100644 --- a/lib/std/os/bits/linux/errno-generic.zig +++ b/lib/std/os/bits/linux/errno-generic.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/linux/errno-mips.zig b/lib/std/os/bits/linux/errno-mips.zig index 1258863086..2c74fa6f8c 100644 --- a/lib/std/os/bits/linux/errno-mips.zig +++ b/lib/std/os/bits/linux/errno-mips.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/linux/i386.zig b/lib/std/os/bits/linux/i386.zig index 92b21727c1..7ef34eb96b 100644 --- a/lib/std/os/bits/linux/i386.zig +++ b/lib/std/os/bits/linux/i386.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -447,6 +447,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/linux/mips.zig b/lib/std/os/bits/linux/mips.zig index cfd9c7adce..412a1f48be 100644 --- a/lib/std/os/bits/linux/mips.zig +++ b/lib/std/os/bits/linux/mips.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -430,6 +430,7 @@ pub const SYS = extern enum(usize) { pidfd_getfd = Linux + 438, faccessat2 = Linux + 439, process_madvise = Linux + 440, + epoll_pwait2 = Linux + 441, _, }; diff --git a/lib/std/os/bits/linux/netlink.zig b/lib/std/os/bits/linux/netlink.zig index 3e75733b9a..72596cb1ab 100644 --- a/lib/std/os/bits/linux/netlink.zig +++ b/lib/std/os/bits/linux/netlink.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/linux/powerpc64.zig b/lib/std/os/bits/linux/powerpc64.zig index 4313a609e4..e0e9347aa1 100644 --- a/lib/std/os/bits/linux/powerpc64.zig +++ b/lib/std/os/bits/linux/powerpc64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -408,6 +408,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/linux/prctl.zig b/lib/std/os/bits/linux/prctl.zig index 6a601784ef..151acf4e71 100644 --- a/lib/std/os/bits/linux/prctl.zig +++ b/lib/std/os/bits/linux/prctl.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/linux/riscv64.zig b/lib/std/os/bits/linux/riscv64.zig index a61b56db38..0cbdea415c 100644 --- a/lib/std/os/bits/linux/riscv64.zig +++ b/lib/std/os/bits/linux/riscv64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -309,6 +309,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/linux/securebits.zig b/lib/std/os/bits/linux/securebits.zig index 0086a694d9..374f7c9f02 100644 --- a/lib/std/os/bits/linux/securebits.zig +++ b/lib/std/os/bits/linux/securebits.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/linux/sparc64.zig b/lib/std/os/bits/linux/sparc64.zig index d4c65de36d..5644256a95 100644 --- a/lib/std/os/bits/linux/sparc64.zig +++ b/lib/std/os/bits/linux/sparc64.zig @@ -386,6 +386,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/linux/x86_64.zig b/lib/std/os/bits/linux/x86_64.zig index 665c8f6eba..52fee679c5 100644 --- a/lib/std/os/bits/linux/x86_64.zig +++ b/lib/std/os/bits/linux/x86_64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -374,6 +374,8 @@ pub const SYS = extern enum(usize) { openat2 = 437, pidfd_getfd = 438, faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, _, }; diff --git a/lib/std/os/bits/netbsd.zig b/lib/std/os/bits/netbsd.zig index be25284b73..57ae70ddbf 100644 --- a/lib/std/os/bits/netbsd.zig +++ b/lib/std/os/bits/netbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -22,6 +22,7 @@ pub const socklen_t = u32; pub const time_t = i64; pub const uid_t = u32; pub const lwpid_t = i32; +pub const suseconds_t = c_int; /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. pub const Kevent = extern struct { @@ -190,6 +191,13 @@ pub const timespec = extern struct { tv_nsec: isize, }; +pub const timeval = extern struct { + /// seconds + tv_sec: time_t, + /// microseconds + tv_usec: suseconds_t, +}; + pub const MAXNAMLEN = 511; pub const dirent = extern struct { @@ -788,16 +796,16 @@ pub const _ksiginfo = extern struct { pub const _SIG_WORDS = 4; pub const _SIG_MAXSIG = 128; -pub inline fn _SIG_IDX(sig: usize) usize { +pub fn _SIG_IDX(sig: usize) callconv(.Inline) usize { return sig - 1; } -pub inline fn _SIG_WORD(sig: usize) usize { +pub fn _SIG_WORD(sig: usize) callconv(.Inline) usize { return_SIG_IDX(sig) >> 5; } -pub inline fn _SIG_BIT(sig: usize) usize { +pub fn _SIG_BIT(sig: usize) callconv(.Inline) usize { return 1 << (_SIG_IDX(sig) & 31); } -pub inline fn _SIG_VALID(sig: usize) usize { +pub fn _SIG_VALID(sig: usize) callconv(.Inline) usize { return sig <= _SIG_MAXSIG and sig > 0; } @@ -828,13 +836,15 @@ pub const ucontext_t = extern struct { sigmask: sigset_t, stack: stack_t, mcontext: mcontext_t, - __pad: [switch (builtin.arch) { - .i386 => 4, - .mips, .mipsel, .mips64, .mips64el => 14, - .arm, .armeb, .thumb, .thumbeb => 1, - .sparc, .sparcel, .sparcv9 => if (@sizeOf(usize) == 4) 43 else 8, - else => 0, - }]u32, + __pad: [ + switch (builtin.arch) { + .i386 => 4, + .mips, .mipsel, .mips64, .mips64el => 14, + .arm, .armeb, .thumb, .thumbeb => 1, + .sparc, .sparcel, .sparcv9 => if (@sizeOf(usize) == 4) 43 else 8, + else => 0, + } + ]u32, }; pub const EPERM = 1; // Operation not permitted diff --git a/lib/std/os/bits/openbsd.zig b/lib/std/os/bits/openbsd.zig index 2aae0cef09..8d1e74ec20 100644 --- a/lib/std/os/bits/openbsd.zig +++ b/lib/std/os/bits/openbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -781,11 +781,17 @@ pub const siginfo_t = extern struct { data: extern union { proc: extern struct { pid: pid_t, - uid: uid_t, - value: sigval, - utime: clock_t, - stime: clock_t, - status: c_int, + pdata: extern union { + kill: extern struct { + uid: uid_t, + value: sigval, + }, + cld: extern struct { + utime: clock_t, + stime: clock_t, + status: c_int, + }, + }, }, fault: extern struct { addr: ?*c_void, @@ -803,6 +809,60 @@ comptime { std.debug.assert(@sizeOf(siginfo_t) == 136); } +pub usingnamespace switch (builtin.arch) { + .x86_64 => struct { + pub const ucontext_t = extern struct { + sc_rdi: c_long, + sc_rsi: c_long, + sc_rdx: c_long, + sc_rcx: c_long, + sc_r8: c_long, + sc_r9: c_long, + sc_r10: c_long, + sc_r11: c_long, + sc_r12: c_long, + sc_r13: c_long, + sc_r14: c_long, + sc_r15: c_long, + sc_rbp: c_long, + sc_rbx: c_long, + sc_rax: c_long, + sc_gs: c_long, + sc_fs: c_long, + sc_es: c_long, + sc_ds: c_long, + sc_trapno: c_long, + sc_err: c_long, + sc_rip: c_long, + sc_cs: c_long, + sc_rflags: c_long, + sc_rsp: c_long, + sc_ss: c_long, + + sc_fpstate: fxsave64, + __sc_unused: c_int, + sc_mask: c_int, + sc_cookie: c_long, + }; + + pub const fxsave64 = packed struct { + fx_fcw: u16, + fx_fsw: u16, + fx_ftw: u8, + fx_unused1: u8, + fx_fop: u16, + fx_rip: u64, + fx_rdp: u64, + fx_mxcsr: u32, + fx_mxcsr_mask: u32, + fx_st: [8][2]u64, + fx_xmm: [16][2]u64, + fx_unused3: [96]u8, + }; + }, + else => struct {}, +}; + pub const sigset_t = c_uint; pub const empty_sigset: sigset_t = 0; @@ -1149,3 +1209,23 @@ pub const rlimit = extern struct { pub const SHUT_RD = 0; pub const SHUT_WR = 1; pub const SHUT_RDWR = 2; + +pub const nfds_t = c_uint; + +pub const pollfd = extern struct { + fd: fd_t, + events: c_short, + revents: c_short, +}; + +pub const POLLIN = 0x0001; +pub const POLLPRI = 0x0002; +pub const POLLOUT = 0x0004; +pub const POLLERR = 0x0008; +pub const POLLHUP = 0x0010; +pub const POLLNVAL = 0x0020; +pub const POLLRDNORM = 0x0040; +pub const POLLNORM = POLLRDNORM; +pub const POLLWRNORM = POLLOUT; +pub const POLLRDBAND = 0x0080; +pub const POLLWRBAND = 0x0100; diff --git a/lib/std/os/bits/wasi.zig b/lib/std/os/bits/wasi.zig index 07275fc229..8b2f5c3351 100644 --- a/lib/std/os/bits/wasi.zig +++ b/lib/std/os/bits/wasi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig index dda57208f8..28a6a251f8 100644 --- a/lib/std/os/bits/windows.zig +++ b/lib/std/os/bits/windows.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -243,6 +243,7 @@ pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; pub const IPPROTO_RM = ws2_32.IPPROTO_RM; +pub const nfds_t = c_ulong; pub const pollfd = ws2_32.pollfd; pub const POLLRDNORM = ws2_32.POLLRDNORM; diff --git a/lib/std/os/darwin.zig b/lib/std/os/darwin.zig index 1bd983398b..87a9ed12ac 100644 --- a/lib/std/os/darwin.zig +++ b/lib/std/os/darwin.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/dragonfly.zig b/lib/std/os/dragonfly.zig index a713a009ad..572b470239 100644 --- a/lib/std/os/dragonfly.zig +++ b/lib/std/os/dragonfly.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/freebsd.zig b/lib/std/os/freebsd.zig index a713a009ad..572b470239 100644 --- a/lib/std/os/freebsd.zig +++ b/lib/std/os/freebsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/io/out_stream.zig b/lib/std/os/haiku.zig index c937ccf16a..a713a009ad 100644 --- a/lib/std/io/out_stream.zig +++ b/lib/std/os/haiku.zig @@ -3,5 +3,6 @@ // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -/// Deprecated: use `std.io.writer.Writer` -pub const OutStream = @import("./writer.zig").Writer; +const std = @import("../std.zig"); +pub usingnamespace std.c; +pub usingnamespace @import("bits.zig"); diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index f840f6a255..035cdabe63 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -126,7 +126,7 @@ pub fn fork() usize { /// It is advised to avoid this function and use clone instead, because /// the compiler is not aware of how vfork affects control flow and you may /// see different results in optimized builds. -pub inline fn vfork() usize { +pub fn vfork() callconv(.Inline) usize { return @call(.{ .modifier = .always_inline }, syscall0, .{.vfork}); } @@ -634,6 +634,37 @@ pub fn tgkill(tgid: pid_t, tid: pid_t, sig: i32) usize { return syscall2(.tgkill, @bitCast(usize, @as(isize, tgid)), @bitCast(usize, @as(isize, tid)), @bitCast(usize, @as(isize, sig))); } +pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) usize { + if (@hasField(SYS, "link")) { + return syscall3( + .link, + @ptrToInt(oldpath), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); + } else { + return syscall5( + .linkat, + @bitCast(usize, @as(isize, AT_FDCWD)), + @ptrToInt(oldpath), + @bitCast(usize, @as(isize, AT_FDCWD)), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); + } +} + +pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize { + return syscall5( + .linkat, + @bitCast(usize, @as(isize, oldfd)), + @ptrToInt(oldpath), + @bitCast(usize, @as(isize, newfd)), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); +} + pub fn unlink(path: [*:0]const u8) usize { if (@hasField(SYS, "unlink")) { return syscall1(.unlink, @ptrToInt(path)); @@ -745,33 +776,33 @@ pub fn setregid(rgid: gid_t, egid: gid_t) usize { pub fn getuid() uid_t { if (@hasField(SYS, "getuid32")) { - return @as(uid_t, syscall0(.getuid32)); + return @intCast(uid_t, syscall0(.getuid32)); } else { - return @as(uid_t, syscall0(.getuid)); + return @intCast(uid_t, syscall0(.getuid)); } } pub fn getgid() gid_t { if (@hasField(SYS, "getgid32")) { - return @as(gid_t, syscall0(.getgid32)); + return @intCast(gid_t, syscall0(.getgid32)); } else { - return @as(gid_t, syscall0(.getgid)); + return @intCast(gid_t, syscall0(.getgid)); } } pub fn geteuid() uid_t { if (@hasField(SYS, "geteuid32")) { - return @as(uid_t, syscall0(.geteuid32)); + return @intCast(uid_t, syscall0(.geteuid32)); } else { - return @as(uid_t, syscall0(.geteuid)); + return @intCast(uid_t, syscall0(.geteuid)); } } pub fn getegid() gid_t { if (@hasField(SYS, "getegid32")) { - return @as(gid_t, syscall0(.getegid32)); + return @intCast(gid_t, syscall0(.getegid32)); } else { - return @as(gid_t, syscall0(.getegid)); + return @intCast(gid_t, syscall0(.getegid)); } } @@ -1351,7 +1382,11 @@ pub fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: ?*const rlimit, ); } -test "" { +pub fn madvise(address: [*]u8, len: usize, advice: u32) usize { + return syscall3(.madvise, @ptrToInt(address), len, advice); +} + +test { if (builtin.os.tag == .linux) { _ = @import("linux/test.zig"); } diff --git a/lib/std/os/linux/arm-eabi.zig b/lib/std/os/linux/arm-eabi.zig index a72799a26a..bac7048615 100644 --- a/lib/std/os/linux/arm-eabi.zig +++ b/lib/std/os/linux/arm-eabi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/arm64.zig b/lib/std/os/linux/arm64.zig index 6727cbce8e..dd5c3ef3af 100644 --- a/lib/std/os/linux/arm64.zig +++ b/lib/std/os/linux/arm64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/bpf.zig b/lib/std/os/linux/bpf.zig index 44c938feb8..0d7e0a19ed 100644 --- a/lib/std/os/linux/bpf.zig +++ b/lib/std/os/linux/bpf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/bpf/btf.zig b/lib/std/os/linux/bpf/btf.zig index 5338994aff..b28f65945a 100644 --- a/lib/std/os/linux/bpf/btf.zig +++ b/lib/std/os/linux/bpf/btf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/bpf/btf_ext.zig b/lib/std/os/linux/bpf/btf_ext.zig index ce412fdf4e..ca713f1910 100644 --- a/lib/std/os/linux/bpf/btf_ext.zig +++ b/lib/std/os/linux/bpf/btf_ext.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/bpf/helpers.zig b/lib/std/os/linux/bpf/helpers.zig index 9228e1f1fd..c6f0bc0b5e 100644 --- a/lib/std/os/linux/bpf/helpers.zig +++ b/lib/std/os/linux/bpf/helpers.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/bpf/kern.zig b/lib/std/os/linux/bpf/kern.zig index a2e9d36aa1..d1b4347d85 100644 --- a/lib/std/os/linux/bpf/kern.zig +++ b/lib/std/os/linux/bpf/kern.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/i386.zig b/lib/std/os/linux/i386.zig index ed5bc88f0f..c1ac6938fb 100644 --- a/lib/std/os/linux/i386.zig +++ b/lib/std/os/linux/i386.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig index 0fdc7651d2..b47d4c7b32 100644 --- a/lib/std/os/linux/io_uring.zig +++ b/lib/std/os/linux/io_uring.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -28,7 +28,7 @@ pub const IO_Uring = struct { /// call on how many entries the submission and completion queues will ultimately have, /// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050. /// Matches the interface of io_uring_queue_init() in liburing. - pub fn init(entries: u12, flags: u32) !IO_Uring { + pub fn init(entries: u13, flags: u32) !IO_Uring { var params = mem.zeroInit(io_uring_params, .{ .flags = flags, .sq_thread_idle = 1000, @@ -39,17 +39,15 @@ pub const IO_Uring = struct { /// A powerful way to setup an io_uring, if you want to tweak io_uring_params such as submission /// queue thread cpu affinity or thread idle timeout (the kernel and our default is 1 second). /// `params` is passed by reference because the kernel needs to modify the parameters. - /// You may only set the `flags`, `sq_thread_cpu` and `sq_thread_idle` parameters. - /// Every other parameter belongs to the kernel and must be zeroed. /// Matches the interface of io_uring_queue_init_params() in liburing. - pub fn init_params(entries: u12, p: *io_uring_params) !IO_Uring { + pub fn init_params(entries: u13, p: *io_uring_params) !IO_Uring { if (entries == 0) return error.EntriesZero; if (!std.math.isPowerOfTwo(entries)) return error.EntriesNotPowerOfTwo; assert(p.sq_entries == 0); - assert(p.cq_entries == 0); + assert(p.cq_entries == 0 or p.flags & linux.IORING_SETUP_CQSIZE != 0); assert(p.features == 0); - assert(p.wq_fd == 0); + assert(p.wq_fd == 0 or p.flags & linux.IORING_SETUP_ATTACH_WQ != 0); assert(p.resv[0] == 0); assert(p.resv[1] == 0); assert(p.resv[2] == 0); @@ -558,6 +556,22 @@ pub const IO_Uring = struct { return sqe; } + /// Queues (but does not submit) an SQE to perform an `fallocate(2)`. + /// Returns a pointer to the SQE. + pub fn fallocate( + self: *IO_Uring, + user_data: u64, + fd: os.fd_t, + mode: i32, + offset: u64, + len: u64, + ) !*io_uring_sqe { + const sqe = try self.get_sqe(); + io_uring_prep_fallocate(sqe, fd, mode, offset, len); + sqe.user_data = user_data; + return sqe; + } + /// Registers an array of file descriptors. /// Every time a file descriptor is put in an SQE and submitted to the kernel, the kernel must /// retrieve a reference to the file, and once I/O has completed the file reference must be @@ -896,6 +910,30 @@ pub fn io_uring_prep_timeout_remove(sqe: *io_uring_sqe, timeout_user_data: u64, }; } +pub fn io_uring_prep_fallocate( + sqe: *io_uring_sqe, + fd: os.fd_t, + mode: i32, + offset: u64, + len: u64, +) void { + sqe.* = .{ + .opcode = .FALLOCATE, + .flags = 0, + .ioprio = 0, + .fd = fd, + .off = offset, + .addr = len, + .len = @intCast(u32, mode), + .rw_flags = 0, + .user_data = 0, + .buf_index = 0, + .personality = 0, + .splice_fd_in = 0, + .__pad2 = [2]u64{ 0, 0 }, + }; +} + test "structs/offsets/entries" { if (builtin.os.tag != .linux) return error.SkipZigTest; @@ -1378,11 +1416,10 @@ test "timeout_remove" { // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL. // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6. // We don't want to skip this test for newer kernels. - if ( - cqe_timeout.user_data == 0x99999999 and + if (cqe_timeout.user_data == 0x99999999 and cqe_timeout.res == -linux.EBADF and - (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0 - ) { + (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) + { return error.SkipZigTest; } testing.expectEqual(linux.io_uring_cqe{ @@ -1398,3 +1435,47 @@ test "timeout_remove" { .flags = 0, }, cqe_timeout_remove); } + +test "fallocate" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + var ring = IO_Uring.init(1, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const path = "test_io_uring_fallocate"; + const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); + defer file.close(); + defer std.fs.cwd().deleteFile(path) catch {}; + + testing.expectEqual(@as(u64, 0), (try file.stat()).size); + + const len: u64 = 65536; + const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len); + testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode); + testing.expectEqual(file.handle, sqe.fd); + testing.expectEqual(@as(u32, 1), try ring.submit()); + + const cqe = try ring.copy_cqe(); + switch (-cqe.res) { + 0 => {}, + // This kernel's io_uring does not yet implement fallocate(): + linux.EINVAL => return error.SkipZigTest, + // This kernel does not implement fallocate(): + linux.ENOSYS => return error.SkipZigTest, + // The filesystem containing the file referred to by fd does not support this operation; + // or the mode is not supported by the filesystem containing the file referred to by fd: + linux.EOPNOTSUPP => return error.SkipZigTest, + else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), + } + testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0xaaaaaaaa, + .res = 0, + .flags = 0, + }, cqe); + + testing.expectEqual(len, (try file.stat()).size); +} diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index ad673e06f9..2622628533 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index 7252000f24..567ad2bc1f 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index 034340d0b3..d58e080407 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 7b3840288a..039678e405 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,6 +9,7 @@ const linux = std.os.linux; const mem = std.mem; const elf = std.elf; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const fs = std.fs; test "fallocate" { @@ -99,3 +100,11 @@ test "statx" { expect(@bitCast(u64, @as(i64, stat_buf.blksize)) == statx_buf.blksize); expect(@bitCast(u64, @as(i64, stat_buf.blocks)) == statx_buf.blocks); } + +test "user and group ids" { + if (builtin.link_libc) return error.SkipZigTest; + expectEqual(linux.getauxval(elf.AT_UID), linux.getuid()); + expectEqual(linux.getauxval(elf.AT_GID), linux.getgid()); + expectEqual(linux.getauxval(elf.AT_EUID), linux.geteuid()); + expectEqual(linux.getauxval(elf.AT_EGID), linux.getegid()); +} diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index f6c339bc2c..614f5b4395 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -300,7 +300,7 @@ fn initTLS() void { }; } -inline fn alignPtrCast(comptime T: type, ptr: [*]u8) *T { +fn alignPtrCast(comptime T: type, ptr: [*]u8) callconv(.Inline) *T { return @ptrCast(*T, @alignCast(@alignOf(*T), ptr)); } @@ -327,32 +327,43 @@ pub fn prepareTLS(area: []u8) usize { if (tls_tp_points_past_tcb) tls_image.data_offset else tls_image.tcb_offset; } -var main_thread_tls_buffer: [256]u8 = undefined; +// The main motivation for the size chosen here is this is how much ends up being +// requested for the thread local variables of the std.crypto.random implementation. +// I'm not sure why it ends up being so much; the struct itself is only 64 bytes. +// I think it has to do with being page aligned and LLVM or LLD is not smart enough +// to lay out the TLS data in a space conserving way. Anyway I think it's fine +// because it's less than 3 pages of memory, and putting it in the ELF like this +// is equivalent to moving the mmap call below into the kernel, avoiding syscall +// overhead. +var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined; pub fn initStaticTLS() void { initTLS(); - const alloc_tls_area: []u8 = blk: { - const full_alloc_size = tls_image.alloc_size + tls_image.alloc_align - 1; - + const tls_area = blk: { // Fast path for the common case where the TLS data is really small, - // avoid an allocation and use our local buffer - if (full_alloc_size < main_thread_tls_buffer.len) - break :blk main_thread_tls_buffer[0..]; + // avoid an allocation and use our local buffer. + if (tls_image.alloc_align <= mem.page_size and + tls_image.alloc_size <= main_thread_tls_buffer.len) + { + break :blk main_thread_tls_buffer[0..tls_image.alloc_size]; + } - break :blk os.mmap( + const alloc_tls_area = os.mmap( null, - full_alloc_size, + tls_image.alloc_size + tls_image.alloc_align - 1, os.PROT_READ | os.PROT_WRITE, os.MAP_PRIVATE | os.MAP_ANONYMOUS, -1, 0, ) catch os.abort(); - }; - // Make sure the slice is correctly aligned - const start = @ptrToInt(alloc_tls_area.ptr) & (tls_image.alloc_align - 1); - const tls_area = alloc_tls_area[start .. start + tls_image.alloc_size]; + // Make sure the slice is correctly aligned. + const begin_addr = @ptrToInt(alloc_tls_area.ptr); + const begin_aligned_addr = mem.alignForward(begin_addr, tls_image.alloc_align); + const start = begin_aligned_addr - begin_addr; + break :blk alloc_tls_area[start .. start + tls_image.alloc_size]; + }; const tp_value = prepareTLS(tls_area); setThreadPointer(tp_value); diff --git a/lib/std/os/linux/vdso.zig b/lib/std/os/linux/vdso.zig index eb99c7407b..f2e4f1f5bc 100644 --- a/lib/std/os/linux/vdso.zig +++ b/lib/std/os/linux/vdso.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 8987e1aab3..b9b3ff47eb 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/netbsd.zig b/lib/std/os/netbsd.zig index a713a009ad..572b470239 100644 --- a/lib/std/os/netbsd.zig +++ b/lib/std/os/netbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/openbsd.zig b/lib/std/os/openbsd.zig index a713a009ad..572b470239 100644 --- a/lib/std/os/openbsd.zig +++ b/lib/std/os/openbsd.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 8ad172679b..f08d4d58fa 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -189,6 +189,75 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { expect(mem.eql(u8, target_path, given)); } +test "link with relative paths" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + var cwd = fs.cwd(); + + cwd.deleteFile("example.txt") catch {}; + cwd.deleteFile("new.txt") catch {}; + + try cwd.writeFile("example.txt", "example"); + try os.link("example.txt", "new.txt", 0); + + const efd = try cwd.openFile("example.txt", .{}); + defer efd.close(); + + const nfd = try cwd.openFile("new.txt", .{}); + defer nfd.close(); + + { + const estat = try os.fstat(efd.handle); + const nstat = try os.fstat(nfd.handle); + + testing.expectEqual(estat.ino, nstat.ino); + testing.expectEqual(@as(usize, 2), nstat.nlink); + } + + try os.unlink("new.txt"); + + { + const estat = try os.fstat(efd.handle); + testing.expectEqual(@as(usize, 1), estat.nlink); + } + + try cwd.deleteFile("example.txt"); +} + +test "linkat with different directories" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + var cwd = fs.cwd(); + var tmp = tmpDir(.{}); + + cwd.deleteFile("example.txt") catch {}; + tmp.dir.deleteFile("new.txt") catch {}; + + try cwd.writeFile("example.txt", "example"); + try os.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); + + const efd = try cwd.openFile("example.txt", .{}); + defer efd.close(); + + const nfd = try tmp.dir.openFile("new.txt", .{}); + + { + defer nfd.close(); + const estat = try os.fstat(efd.handle); + const nstat = try os.fstat(nfd.handle); + + testing.expectEqual(estat.ino, nstat.ino); + testing.expectEqual(@as(usize, 2), nstat.nlink); + } + + try os.unlinkat(tmp.dir.fd, "new.txt", 0); + + { + const estat = try os.fstat(efd.handle); + testing.expectEqual(@as(usize, 1), estat.nlink); + } + + try cwd.deleteFile("example.txt"); +} + test "fstatat" { // enable when `fstat` and `fstatat` are implemented on Windows if (builtin.os.tag == .windows) return error.SkipZigTest; @@ -475,7 +544,7 @@ test "mmap" { const file = try tmp.dir.createFile(test_out_file, .{}); defer file.close(); - const stream = file.outStream(); + const stream = file.writer(); var i: u32 = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { @@ -499,7 +568,7 @@ test "mmap" { defer os.munmap(data); var mem_stream = io.fixedBufferStream(data); - const stream = mem_stream.inStream(); + const stream = mem_stream.reader(); var i: u32 = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { @@ -523,7 +592,7 @@ test "mmap" { defer os.munmap(data); var mem_stream = io.fixedBufferStream(data); - const stream = mem_stream.inStream(); + const stream = mem_stream.reader(); var i: u32 = alloc_size / 2 / @sizeOf(u32); while (i < alloc_size / @sizeOf(u32)) : (i += 1) { diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index ba1544105c..1942165999 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. +const std = @import("../std.zig"); + /// A protocol is an interface identified by a GUID. pub const protocols = @import("uefi/protocols.zig"); @@ -33,10 +35,10 @@ pub const Guid = extern struct { self: @This(), comptime f: []const u8, options: std.fmt.FormatOptions, - out_stream: anytype, - ) Errors!void { + writer: anytype, + ) !void { if (f.len == 0) { - return std.fmt.format(out_stream, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ + return std.fmt.format(writer, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ self.time_low, self.time_mid, self.time_high_and_version, @@ -48,6 +50,15 @@ pub const Guid = extern struct { @compileError("Unknown format character: '" ++ f ++ "'"); } } + + pub fn eql(a: std.os.uefi.Guid, b: std.os.uefi.Guid) bool { + return a.time_low == b.time_low and + a.time_mid == b.time_mid and + a.time_high_and_version == b.time_high_and_version and + a.clock_seq_high_and_reserved == b.clock_seq_high_and_reserved and + a.clock_seq_low == b.clock_seq_low and + std.mem.eql(u8, &a.node, &b.node); + } }; /// An EFI Handle represents a collection of related interfaces. diff --git a/lib/std/os/uefi/protocols.zig b/lib/std/os/uefi/protocols.zig index 1519092b84..68dafdcecb 100644 --- a/lib/std/os/uefi/protocols.zig +++ b/lib/std/os/uefi/protocols.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/absolute_pointer_protocol.zig b/lib/std/os/uefi/protocols/absolute_pointer_protocol.zig index 3ec6aab5b9..8edc11e24d 100644 --- a/lib/std/os/uefi/protocols/absolute_pointer_protocol.zig +++ b/lib/std/os/uefi/protocols/absolute_pointer_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/device_path_protocol.zig b/lib/std/os/uefi/protocols/device_path_protocol.zig index 1a998f0f78..0d1d028f60 100644 --- a/lib/std/os/uefi/protocols/device_path_protocol.zig +++ b/lib/std/os/uefi/protocols/device_path_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/edid_active_protocol.zig b/lib/std/os/uefi/protocols/edid_active_protocol.zig index dc8057b4f8..750ff2833b 100644 --- a/lib/std/os/uefi/protocols/edid_active_protocol.zig +++ b/lib/std/os/uefi/protocols/edid_active_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/edid_discovered_protocol.zig b/lib/std/os/uefi/protocols/edid_discovered_protocol.zig index 1ed2b6277d..fdbe594563 100644 --- a/lib/std/os/uefi/protocols/edid_discovered_protocol.zig +++ b/lib/std/os/uefi/protocols/edid_discovered_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/edid_override_protocol.zig b/lib/std/os/uefi/protocols/edid_override_protocol.zig index 83260f7b88..e451d41f32 100644 --- a/lib/std/os/uefi/protocols/edid_override_protocol.zig +++ b/lib/std/os/uefi/protocols/edid_override_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/file_protocol.zig b/lib/std/os/uefi/protocols/file_protocol.zig index ce34a2d6e5..782ac70810 100644 --- a/lib/std/os/uefi/protocols/file_protocol.zig +++ b/lib/std/os/uefi/protocols/file_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -16,7 +16,7 @@ pub const FileProtocol = extern struct { _read: fn (*const FileProtocol, *usize, [*]u8) callconv(.C) Status, _write: fn (*const FileProtocol, *usize, [*]const u8) callconv(.C) Status, _get_position: fn (*const FileProtocol, *u64) callconv(.C) Status, - _set_position: fn (*const FileProtocol, *const u64) callconv(.C) Status, + _set_position: fn (*const FileProtocol, u64) callconv(.C) Status, _get_info: fn (*const FileProtocol, *align(8) const Guid, *const usize, [*]u8) callconv(.C) Status, _set_info: fn (*const FileProtocol, *align(8) const Guid, usize, [*]const u8) callconv(.C) Status, _flush: fn (*const FileProtocol) callconv(.C) Status, @@ -41,11 +41,19 @@ pub const FileProtocol = extern struct { return self._write(self, buffer_size, buffer); } - pub fn get_info(self: *const FileProtocol, information_type: *align(8) Guid, buffer_size: *usize, buffer: [*]u8) Status { + pub fn getPosition(self: *const FileProtocol, position: *u64) Status { + return self._get_position(self, position); + } + + pub fn setPosition(self: *const FileProtocol, position: u64) Status { + return self._set_position(self, position); + } + + pub fn getInfo(self: *const FileProtocol, information_type: *align(8) Guid, buffer_size: *usize, buffer: [*]u8) Status { return self._get_info(self, information_type, buffer_size, buffer); } - pub fn set_info(self: *const FileProtocol, information_type: *align(8) Guid, buffer_size: usize, buffer: [*]const u8) Status { + pub fn setInfo(self: *const FileProtocol, information_type: *align(8) Guid, buffer_size: usize, buffer: [*]const u8) Status { return self._set_info(self, information_type, buffer_size, buffer); } @@ -73,6 +81,8 @@ pub const FileProtocol = extern struct { pub const efi_file_directory: u64 = 0x0000000000000010; pub const efi_file_archive: u64 = 0x0000000000000020; pub const efi_file_valid_attr: u64 = 0x0000000000000037; + + pub const efi_file_position_end_of_file: u64 = 0xffffffffffffffff; }; pub const FileInfo = extern struct { diff --git a/lib/std/os/uefi/protocols/graphics_output_protocol.zig b/lib/std/os/uefi/protocols/graphics_output_protocol.zig index 3ec1c39ab8..a5e784b597 100644 --- a/lib/std/os/uefi/protocols/graphics_output_protocol.zig +++ b/lib/std/os/uefi/protocols/graphics_output_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/hii.zig b/lib/std/os/uefi/protocols/hii.zig index ed7c40d6ac..9d85f293b3 100644 --- a/lib/std/os/uefi/protocols/hii.zig +++ b/lib/std/os/uefi/protocols/hii.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/hii_database_protocol.zig b/lib/std/os/uefi/protocols/hii_database_protocol.zig index d0b16ff943..33014e1cb7 100644 --- a/lib/std/os/uefi/protocols/hii_database_protocol.zig +++ b/lib/std/os/uefi/protocols/hii_database_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/hii_popup_protocol.zig b/lib/std/os/uefi/protocols/hii_popup_protocol.zig index 1f5c5ce0f4..22bae95449 100644 --- a/lib/std/os/uefi/protocols/hii_popup_protocol.zig +++ b/lib/std/os/uefi/protocols/hii_popup_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/ip6_config_protocol.zig b/lib/std/os/uefi/protocols/ip6_config_protocol.zig index 16002f62a6..8dd0caf31a 100644 --- a/lib/std/os/uefi/protocols/ip6_config_protocol.zig +++ b/lib/std/os/uefi/protocols/ip6_config_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/ip6_protocol.zig b/lib/std/os/uefi/protocols/ip6_protocol.zig index 578a3cfb01..011517ba2a 100644 --- a/lib/std/os/uefi/protocols/ip6_protocol.zig +++ b/lib/std/os/uefi/protocols/ip6_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/ip6_service_binding_protocol.zig b/lib/std/os/uefi/protocols/ip6_service_binding_protocol.zig index 59605cc11b..69a410c01c 100644 --- a/lib/std/os/uefi/protocols/ip6_service_binding_protocol.zig +++ b/lib/std/os/uefi/protocols/ip6_service_binding_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/loaded_image_protocol.zig b/lib/std/os/uefi/protocols/loaded_image_protocol.zig index 96aa10f08d..a5c5610f9b 100644 --- a/lib/std/os/uefi/protocols/loaded_image_protocol.zig +++ b/lib/std/os/uefi/protocols/loaded_image_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/managed_network_protocol.zig b/lib/std/os/uefi/protocols/managed_network_protocol.zig index 9202c6f139..6652107a1a 100644 --- a/lib/std/os/uefi/protocols/managed_network_protocol.zig +++ b/lib/std/os/uefi/protocols/managed_network_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/managed_network_service_binding_protocol.zig b/lib/std/os/uefi/protocols/managed_network_service_binding_protocol.zig index 0c684336c8..f0b8c5fb15 100644 --- a/lib/std/os/uefi/protocols/managed_network_service_binding_protocol.zig +++ b/lib/std/os/uefi/protocols/managed_network_service_binding_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/rng_protocol.zig b/lib/std/os/uefi/protocols/rng_protocol.zig index 713b76b371..25f7c936c3 100644 --- a/lib/std/os/uefi/protocols/rng_protocol.zig +++ b/lib/std/os/uefi/protocols/rng_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/shell_parameters_protocol.zig b/lib/std/os/uefi/protocols/shell_parameters_protocol.zig index 7ec46b732c..338d88fc9b 100644 --- a/lib/std/os/uefi/protocols/shell_parameters_protocol.zig +++ b/lib/std/os/uefi/protocols/shell_parameters_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_file_system_protocol.zig b/lib/std/os/uefi/protocols/simple_file_system_protocol.zig index 946c88a89b..68f08ebff8 100644 --- a/lib/std/os/uefi/protocols/simple_file_system_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_file_system_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_network_protocol.zig b/lib/std/os/uefi/protocols/simple_network_protocol.zig index f74f11a857..d29cd68873 100644 --- a/lib/std/os/uefi/protocols/simple_network_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_network_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_pointer_protocol.zig b/lib/std/os/uefi/protocols/simple_pointer_protocol.zig index 5f8ca7569e..b76b5bc512 100644 --- a/lib/std/os/uefi/protocols/simple_pointer_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_pointer_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_text_input_ex_protocol.zig b/lib/std/os/uefi/protocols/simple_text_input_ex_protocol.zig index 096013bfb0..0cc1416641 100644 --- a/lib/std/os/uefi/protocols/simple_text_input_ex_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_text_input_ex_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_text_input_protocol.zig b/lib/std/os/uefi/protocols/simple_text_input_protocol.zig index 00fae88472..47e632021b 100644 --- a/lib/std/os/uefi/protocols/simple_text_input_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_text_input_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/simple_text_output_protocol.zig b/lib/std/os/uefi/protocols/simple_text_output_protocol.zig index f9bbc37140..6fb56724c7 100644 --- a/lib/std/os/uefi/protocols/simple_text_output_protocol.zig +++ b/lib/std/os/uefi/protocols/simple_text_output_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/udp6_protocol.zig b/lib/std/os/uefi/protocols/udp6_protocol.zig index 50b7dae7c4..c2e4228998 100644 --- a/lib/std/os/uefi/protocols/udp6_protocol.zig +++ b/lib/std/os/uefi/protocols/udp6_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/protocols/udp6_service_binding_protocol.zig b/lib/std/os/uefi/protocols/udp6_service_binding_protocol.zig index 4f4e0a2638..620f015722 100644 --- a/lib/std/os/uefi/protocols/udp6_service_binding_protocol.zig +++ b/lib/std/os/uefi/protocols/udp6_service_binding_protocol.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index 3e86962202..ccf50d8515 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables.zig b/lib/std/os/uefi/tables.zig index b796eb6e06..649fe95cd2 100644 --- a/lib/std/os/uefi/tables.zig +++ b/lib/std/os/uefi/tables.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables/boot_services.zig b/lib/std/os/uefi/tables/boot_services.zig index 09431cf3f4..b96881fcc2 100644 --- a/lib/std/os/uefi/tables/boot_services.zig +++ b/lib/std/os/uefi/tables/boot_services.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables/configuration_table.zig b/lib/std/os/uefi/tables/configuration_table.zig index c7dedad5fb..00c8f2f429 100644 --- a/lib/std/os/uefi/tables/configuration_table.zig +++ b/lib/std/os/uefi/tables/configuration_table.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables/runtime_services.zig b/lib/std/os/uefi/tables/runtime_services.zig index a2012168ee..7436ab530c 100644 --- a/lib/std/os/uefi/tables/runtime_services.zig +++ b/lib/std/os/uefi/tables/runtime_services.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables/system_table.zig b/lib/std/os/uefi/tables/system_table.zig index 3f0624d2ce..c5b6c5f1e9 100644 --- a/lib/std/os/uefi/tables/system_table.zig +++ b/lib/std/os/uefi/tables/system_table.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/uefi/tables/table_header.zig b/lib/std/os/uefi/tables/table_header.zig index a8343c967a..8af1895cad 100644 --- a/lib/std/os/uefi/tables/table_header.zig +++ b/lib/std/os/uefi/tables/table_header.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index 899541f3fe..3965ae77a0 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b994720ce9..6f67b65252 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -109,7 +109,12 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN 0, ); switch (rc) { - .SUCCESS => return result, + .SUCCESS => { + if (std.io.is_async and options.io_mode == .evented) { + _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined; + } + return result; + }, .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, @@ -418,8 +423,6 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64, io_mode: std.io.Mo }, }, }; - // TODO only call create io completion port once per fd - _ = CreateIoCompletionPort(in_hFile, loop.os_data.io_port, undefined, undefined) catch undefined; loop.beginOneEvent(); suspend { // TODO handle buffer bigger than DWORD can hold @@ -500,8 +503,6 @@ pub fn WriteFile( }, }, }; - // TODO only call create io completion port once per fd - _ = CreateIoCompletionPort(handle, loop.os_data.io_port, undefined, undefined) catch undefined; loop.beginOneEvent(); suspend { const adjusted_len = math.cast(DWORD, bytes.len) catch maxInt(DWORD); @@ -857,6 +858,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil .SUCCESS => return CloseHandle(tmp_handle), .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, .INVALID_PARAMETER => unreachable, .FILE_IS_A_DIRECTORY => return error.IsDir, .NOT_A_DIRECTORY => return error.NotDir, @@ -952,7 +954,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { return @bitCast(u64, result); } +pub fn QueryObjectName( + handle: HANDLE, + out_buffer: []u16, +) ![]u16 { + const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; + + const info = @ptrCast(*OBJECT_NAME_INFORMATION, out_buffer_aligned); + //buffer size is specified in bytes + const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) catch |e| switch (e) { + error.Overflow => std.math.maxInt(ULONG), + }; + //last argument would return the length required for full_buffer, not exposed here + const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null); + switch (rc) { + .SUCCESS => { + // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) + // if the object was "unnamed", not sure if this can happen for file handles + if (info.Name.MaximumLength == 0) return error.Unexpected; + // resulting string length is specified in bytes + const path_length_unterminated = @divExact(info.Name.Length, 2); + return info.Name.Buffer[0..path_length_unterminated]; + }, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_HANDLE => return error.InvalidHandle, + // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), + // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) + .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, + else => |e| return unexpectedStatus(e), + } +} +test "QueryObjectName" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var out_buffer: [PATH_MAX_WIDE]u16 = undefined; + + var result_path = try QueryObjectName(handle, &out_buffer); + const required_len_in_u16 = result_path.len + @divExact(@ptrToInt(result_path.ptr) - @ptrToInt(&out_buffer), 2) + 1; + //insufficient size + std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); + //exactly-sufficient size + _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); +} + pub const GetFinalPathNameByHandleError = error{ + AccessDenied, BadPathName, FileNotFound, NameTooLong, @@ -980,32 +1031,31 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { - // Get normalized path; doesn't include volume name though. - var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); - - // Get NT volume name. - var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); - - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); - const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; - - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { + // we assume InvalidHandle is close enough to FileNotFound in semantics + // to not further complicate the error set + error.InvalidHandle => return error.FileNotFound, + else => |e| return e, + }; switch (fmt.volume_name) { .Nt => { - // Nothing to do, we simply copy the bytes to the user-provided buffer. - const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2]; + // the returned path is already in .Nt format + return final_path; + }, + .Dos => { + // parse the string to separate volume path from file path + const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; + // TODO find out if a path can start with something besides `\Device\<volume name>`, + // and if we need to handle it differently + // (i.e. how to determine the start and end of the volume name in that case) + if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; - std.mem.copy(u16, out_buffer[0..], volume_name_u16); - std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + const volume_name_u16 = final_path[0..file_path_begin_index]; + const file_name_u16 = final_path[file_path_begin_index..]; - return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; - }, - .Dos => { // Get DOS volume name. DOS volume names are actually symbolic link objects to the // actual NT volume. For example: // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: @@ -1038,10 +1088,10 @@ pub fn GetFinalPathNameByHandle( var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]); input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); - input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); - @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); + input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); + @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); - DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { + DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) { error.AccessDenied => unreachable, else => |e| return e, }; @@ -1061,22 +1111,20 @@ pub fn GetFinalPathNameByHandle( // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks // with traditional DOS drive letters, so pick the first one available. - const prefix_u8 = "\\DosDevices\\"; - var prefix_buf_u16: [prefix_u8.len]u16 = undefined; - const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable; - const prefix = prefix_buf_u16[0..prefix_len_u16]; + var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); + const prefix = prefix_buf[0..prefix_buf.len]; - if (std.mem.startsWith(u16, symlink, prefix)) { + if (mem.startsWith(u16, symlink, prefix)) { const drive_letter = symlink[prefix.len..]; if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; - std.mem.copy(u16, out_buffer[0..], drive_letter); - std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); + mem.copy(u16, out_buffer, drive_letter); + mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); const total_len = drive_letter.len + file_name_u16.len; // Validate that DOS does not contain any spurious nul bytes. - if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { + if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { return error.BadPathName; } @@ -1091,6 +1139,30 @@ pub fn GetFinalPathNameByHandle( } } +test "GetFinalPathNameByHandle" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var buffer: [PATH_MAX_WIDE]u16 = undefined; + + //check with sufficient size + const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); + + const required_len_in_u16 = nt_path.len + @divExact(@ptrToInt(nt_path.ptr) - @ptrToInt(&buffer), 2) + 1; + //check with insufficient size + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); + + //check with exactly-sufficient size + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); +} + pub const QueryInformationFileError = error{Unexpected}; pub fn QueryInformationFile( @@ -1240,8 +1312,8 @@ pub fn recvfrom(s: ws2_32.SOCKET, buf: [*]u8, len: usize, flags: u32, from: ?*ws } } -pub fn poll(fds: [*]ws2_32.pollfd, n: usize, timeout: i32) i32 { - return ws2_32.WSAPoll(fds, @intCast(u32, n), timeout); +pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 { + return ws2_32.WSAPoll(fds, n, timeout); } pub fn WSAIoctl( @@ -1597,7 +1669,7 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { return path_space; } -inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID { +fn MAKELANGID(p: c_ushort, s: c_ushort) callconv(.Inline) LANGID { return (s << 10) | p; } @@ -1618,7 +1690,7 @@ pub fn unexpectedError(err: Win32Error) std.os.UnexpectedError { null, ); _ = std.unicode.utf16leToUtf8(&buf_u8, buf_u16[0..len]) catch unreachable; - std.debug.warn("error.Unexpected: GetLastError({}): {}\n", .{ @enumToInt(err), buf_u8[0..len] }); + std.debug.warn("error.Unexpected: GetLastError({}): {s}\n", .{ @enumToInt(err), buf_u8[0..len] }); std.debug.dumpCurrentStackTrace(null); } return error.Unexpected; diff --git a/lib/std/os/windows/advapi32.zig b/lib/std/os/windows/advapi32.zig index 177449a70e..6fa9ae2b45 100644 --- a/lib/std/os/windows/advapi32.zig +++ b/lib/std/os/windows/advapi32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index f5d520c580..cbeb0b483d 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -37,6 +37,7 @@ pub const UCHAR = u8; pub const FLOAT = f32; pub const HANDLE = *c_void; pub const HCRYPTPROV = ULONG_PTR; +pub const ATOM = u16; pub const HBRUSH = *opaque {}; pub const HCURSOR = *opaque {}; pub const HICON = *opaque {}; @@ -84,8 +85,8 @@ pub const HLOCAL = HANDLE; pub const LANGID = c_ushort; pub const WPARAM = usize; -pub const LPARAM = ?*c_void; -pub const LRESULT = ?*c_void; +pub const LPARAM = LONG_PTR; +pub const LRESULT = LONG_PTR; pub const va_list = *opaque {}; @@ -770,6 +771,13 @@ pub const FILE_FLAG_SESSION_AWARE = 0x00800000; pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; pub const FILE_FLAG_WRITE_THROUGH = 0x80000000; +pub const RECT = extern struct { + left: LONG, + top: LONG, + right: LONG, + bottom: LONG, +}; + pub const SMALL_RECT = extern struct { Left: SHORT, Top: SHORT, @@ -777,6 +785,11 @@ pub const SMALL_RECT = extern struct { Bottom: SHORT, }; +pub const POINT = extern struct { + x: LONG, + y: LONG, +}; + pub const COORD = extern struct { X: SHORT, Y: SHORT, @@ -813,7 +826,8 @@ pub const FILE_NOTIFY_INFORMATION = extern struct { NextEntryOffset: DWORD, Action: DWORD, FileNameLength: DWORD, - FileName: [1]WCHAR, + // Flexible array member + // FileName: [1]WCHAR, }; pub const FILE_ACTION_ADDED = 0x00000001; @@ -1301,11 +1315,13 @@ pub const PEB = extern struct { ImageSubSystemMinorVersion: ULONG, // note: there is padding here on 64 bit ActiveProcessAffinityMask: KAFFINITY, - GdiHandleBuffer: [switch (@sizeOf(usize)) { - 4 => 0x22, - 8 => 0x3C, - else => unreachable, - }]ULONG, + GdiHandleBuffer: [ + switch (@sizeOf(usize)) { + 4 => 0x22, + 8 => 0x3C, + else => unreachable, + } + ]ULONG, // Fields appended in 5.0 (Windows 2000): PostProcessInitRoutine: PVOID, @@ -1606,3 +1622,23 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; pub const SD_RECEIVE = 0; pub const SD_SEND = 1; pub const SD_BOTH = 2; + +pub const OBJECT_INFORMATION_CLASS = extern enum { + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2, + ObjectTypesInformation = 3, + ObjectHandleFlagInformation = 4, + ObjectSessionInformation = 5, + MaxObjectInfoClass, +}; + +pub const OBJECT_NAME_INFORMATION = extern struct { + Name: UNICODE_STRING, +}; +pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION; + +pub const SRWLOCK = usize; +pub const SRWLOCK_INIT: SRWLOCK = 0; +pub const CONDITION_VARIABLE = usize; +pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0; diff --git a/lib/std/os/windows/gdi32.zig b/lib/std/os/windows/gdi32.zig index 35ebfb7789..c91e1d487c 100644 --- a/lib/std/os/windows/gdi32.zig +++ b/lib/std/os/windows/gdi32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 444234876c..734059a08a 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -8,7 +8,7 @@ usingnamespace @import("bits.zig"); pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*c_void; pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong; -pub extern "kernel32" fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) callconv(WINAPI) BOOL; +pub extern "kernel32" fn CancelIoEx(hFile: HANDLE, lpOverlapped: ?LPOVERLAPPED) callconv(WINAPI) BOOL; pub extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; @@ -115,6 +115,7 @@ pub extern "kernel32" fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u1 pub extern "kernel32" fn GetModuleHandleW(lpModuleName: ?[*:0]const WCHAR) callconv(WINAPI) ?HMODULE; pub extern "kernel32" fn GetLastError() callconv(WINAPI) Win32Error; +pub extern "kernel32" fn SetLastError(dwErrCode: Win32Error) callconv(WINAPI) void; pub extern "kernel32" fn GetFileInformationByHandle( hFile: HANDLE, @@ -288,3 +289,16 @@ pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWO pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(WINAPI) BOOL; pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(WINAPI) BOOL; + +pub extern "kernel32" fn WakeAllConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void; +pub extern "kernel32" fn WakeConditionVariable(c: *CONDITION_VARIABLE) callconv(WINAPI) void; +pub extern "kernel32" fn SleepConditionVariableSRW( + c: *CONDITION_VARIABLE, + s: *SRWLOCK, + t: DWORD, + f: ULONG, +) callconv(WINAPI) BOOL; + +pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOLEAN; +pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; +pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void; diff --git a/lib/std/os/windows/lang.zig b/lib/std/os/windows/lang.zig index 61efa3bdb3..40b363cfae 100644 --- a/lib/std/os/windows/lang.zig +++ b/lib/std/os/windows/lang.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index fc485f87f2..3f76036762 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( ) callconv(WINAPI) NTSTATUS; pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS; + +pub extern "NtDll" fn NtQueryObject( + Handle: HANDLE, + ObjectInformationClass: OBJECT_INFORMATION_CLASS, + ObjectInformation: PVOID, + ObjectInformationLength: ULONG, + ReturnLength: ?*ULONG, +) callconv(WINAPI) NTSTATUS; diff --git a/lib/std/os/windows/ntstatus.zig b/lib/std/os/windows/ntstatus.zig index 0e567df510..b86cd1186b 100644 --- a/lib/std/os/windows/ntstatus.zig +++ b/lib/std/os/windows/ntstatus.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/ole32.zig b/lib/std/os/windows/ole32.zig index 13920dd510..bf1eabd63e 100644 --- a/lib/std/os/windows/ole32.zig +++ b/lib/std/os/windows/ole32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/psapi.zig b/lib/std/os/windows/psapi.zig index 0d19117c30..2952df1635 100644 --- a/lib/std/os/windows/psapi.zig +++ b/lib/std/os/windows/psapi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/shell32.zig b/lib/std/os/windows/shell32.zig index 812cbd6cfc..d184ba1036 100644 --- a/lib/std/os/windows/shell32.zig +++ b/lib/std/os/windows/shell32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/sublang.zig b/lib/std/os/windows/sublang.zig index 5249e8ed0a..ecc46dbfc4 100644 --- a/lib/std/os/windows/sublang.zig +++ b/lib/std/os/windows/sublang.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/user32.zig b/lib/std/os/windows/user32.zig index f4533faaa6..186a1af59f 100644 --- a/lib/std/os/windows/user32.zig +++ b/lib/std/os/windows/user32.zig @@ -1,48 +1,82 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. usingnamespace @import("bits.zig"); +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const windows = @import("../windows.zig"); +const unexpectedError = windows.unexpectedError; +const GetLastError = windows.kernel32.GetLastError; +const SetLastError = windows.kernel32.SetLastError; -// PM -pub const PM_REMOVE = 0x0001; -pub const PM_NOREMOVE = 0x0000; -pub const PM_NOYIELD = 0x0002; +fn selectSymbol(comptime function_static: anytype, function_dynamic: @TypeOf(function_static), comptime os: std.Target.Os.WindowsVersion) @TypeOf(function_static) { + comptime { + const sym_ok = builtin.Target.current.os.isAtLeast(.windows, os); + if (sym_ok == true) return function_static; + if (sym_ok == null) return function_dynamic; + if (sym_ok == false) @compileError("Target OS range does not support function, at least " ++ @tagName(os) ++ " is required"); + } +} + +// === Messages === + +pub const WNDPROC = fn (hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(WINAPI) LRESULT; + +pub const MSG = extern struct { + hWnd: ?HWND, + message: UINT, + wParam: WPARAM, + lParam: LPARAM, + time: DWORD, + pt: POINT, + lPrivate: DWORD, +}; -// WM pub const WM_NULL = 0x0000; pub const WM_CREATE = 0x0001; pub const WM_DESTROY = 0x0002; +pub const WM_NCDESTROY = WM_DESTROY; pub const WM_MOVE = 0x0003; pub const WM_SIZE = 0x0005; - pub const WM_ACTIVATE = 0x0006; -pub const WM_PAINT = 0x000F; -pub const WM_CLOSE = 0x0010; -pub const WM_QUIT = 0x0012; pub const WM_SETFOCUS = 0x0007; - pub const WM_KILLFOCUS = 0x0008; pub const WM_ENABLE = 0x000A; pub const WM_SETREDRAW = 0x000B; - -pub const WM_SYSCOLORCHANGE = 0x0015; +pub const WM_SETTEXT = 0x000C; +pub const WM_GETTEXT = 0x000D; +pub const WM_GETTEXTLENGTH = 0x000E; +pub const WM_PAINT = 0x000F; +pub const WM_CLOSE = 0x0010; +pub const WM_QUIT = 0x0012; +pub const WM_ERASEBKGND = 0x0014; pub const WM_SHOWWINDOW = 0x0018; - -pub const WM_WINDOWPOSCHANGING = 0x0046; +pub const WM_CTLCOLOR = 0x0019; +pub const WM_NEXTDLGCTL = 0x0028; +pub const WM_DRAWITEM = 0x002B; +pub const WM_MEASUREITEM = 0x002C; +pub const WM_DELETEITEM = 0x002D; +pub const WM_VKEYTOITEM = 0x002E; +pub const WM_CHARTOITEM = 0x002F; +pub const WM_SETFONT = 0x0030; +pub const WM_GETFONT = 0x0031; +pub const WM_COMPAREITEM = 0x0039; pub const WM_WINDOWPOSCHANGED = 0x0047; -pub const WM_POWER = 0x0048; - -pub const WM_CONTEXTMENU = 0x007B; -pub const WM_STYLECHANGING = 0x007C; -pub const WM_STYLECHANGED = 0x007D; -pub const WM_DISPLAYCHANGE = 0x007E; -pub const WM_GETICON = 0x007F; -pub const WM_SETICON = 0x0080; - -pub const WM_INPUT_DEVICE_CHANGE = 0x00fe; -pub const WM_INPUT = 0x00FF; +pub const WM_NOTIFY = 0x004E; +pub const WM_NCCALCSIZE = 0x0083; +pub const WM_NCHITTEST = 0x0084; +pub const WM_NCPAINT = 0x0085; +pub const WM_GETDLGCODE = 0x0087; +pub const WM_NCMOUSEMOVE = 0x00A0; +pub const WM_NCLBUTTONDOWN = 0x00A1; +pub const WM_NCLBUTTONUP = 0x00A2; +pub const WM_NCLBUTTONDBLCLK = 0x00A3; +pub const WM_NCRBUTTONDOWN = 0x00A4; +pub const WM_NCRBUTTONUP = 0x00A5; +pub const WM_NCRBUTTONDBLCLK = 0x00A6; pub const WM_KEYFIRST = 0x0100; pub const WM_KEYDOWN = 0x0100; pub const WM_KEYUP = 0x0101; @@ -54,11 +88,20 @@ pub const WM_SYSCHAR = 0x0106; pub const WM_SYSDEADCHAR = 0x0107; pub const WM_UNICHAR = 0x0109; pub const WM_KEYLAST = 0x0109; - +pub const WM_INITDIALOG = 0x0110; pub const WM_COMMAND = 0x0111; pub const WM_SYSCOMMAND = 0x0112; pub const WM_TIMER = 0x0113; - +pub const WM_HSCROLL = 0x0114; +pub const WM_VSCROLL = 0x0115; +pub const WM_ENTERIDLE = 0x0121; +pub const WM_CTLCOLORMSGBOX = 0x0132; +pub const WM_CTLCOLOREDIT = 0x0133; +pub const WM_CTLCOLORLISTBOX = 0x0134; +pub const WM_CTLCOLORBTN = 0x0135; +pub const WM_CTLCOLORDLG = 0x0136; +pub const WM_CTLCOLORSCROLLBAR = 0x0137; +pub const WM_CTLCOLORSTATIC = 0x0138; pub const WM_MOUSEFIRST = 0x0200; pub const WM_MOUSEMOVE = 0x0200; pub const WM_LBUTTONDOWN = 0x0201; @@ -71,105 +114,561 @@ pub const WM_MBUTTONDOWN = 0x0207; pub const WM_MBUTTONUP = 0x0208; pub const WM_MBUTTONDBLCLK = 0x0209; pub const WM_MOUSEWHEEL = 0x020A; -pub const WM_XBUTTONDOWN = 0x020B; -pub const WM_XBUTTONUP = 0x020C; -pub const WM_XBUTTONDBLCLK = 0x020D; +pub const WM_MOUSELAST = 0x020A; +pub const WM_HOTKEY = 0x0312; +pub const WM_CARET_CREATE = 0x03E0; +pub const WM_CARET_DESTROY = 0x03E1; +pub const WM_CARET_BLINK = 0x03E2; +pub const WM_FDINPUT = 0x03F0; +pub const WM_FDOUTPUT = 0x03F1; +pub const WM_FDEXCEPT = 0x03F2; +pub const WM_USER = 0x0400; -// WA -pub const WA_INACTIVE = 0; -pub const WA_ACTIVE = 0x0006; +pub extern "user32" fn GetMessageA(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) callconv(WINAPI) BOOL; +pub fn getMessageA(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: u32, wMsgFilterMax: u32) !void { + const r = GetMessageA(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax); + if (r == 0) return error.Quit; + if (r != -1) return; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} -// WS -pub const WS_OVERLAPPED = 0x00000000; -pub const WS_CAPTION = 0x00C00000; -pub const WS_SYSMENU = 0x00080000; -pub const WS_THICKFRAME = 0x00040000; -pub const WS_MINIMIZEBOX = 0x00020000; -pub const WS_MAXIMIZEBOX = 0x00010000; +pub extern "user32" fn GetMessageW(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) callconv(WINAPI) BOOL; +pub var pfnGetMessageW: @TypeOf(GetMessageW) = undefined; +pub fn getMessageW(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: u32, wMsgFilterMax: u32) !void { + const function = selectSymbol(GetMessageW, pfnGetMessageW, .win2k); -// PFD -pub const PFD_DRAW_TO_WINDOW = 0x00000004; -pub const PFD_SUPPORT_OPENGL = 0x00000020; -pub const PFD_DOUBLEBUFFER = 0x00000001; -pub const PFD_MAIN_PLANE = 0; -pub const PFD_TYPE_RGBA = 0; + const r = function(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax); + if (r == 0) return error.Quit; + if (r != -1) return; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} -// CS -pub const CS_HREDRAW = 0x0002; -pub const CS_VREDRAW = 0x0001; -pub const CS_OWNDC = 0x0020; +pub const PM_NOREMOVE = 0x0000; +pub const PM_REMOVE = 0x0001; +pub const PM_NOYIELD = 0x0002; -// SW -pub const SW_HIDE = 0; -pub const SW_SHOW = 5; +pub extern "user32" fn PeekMessageA(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) callconv(WINAPI) BOOL; +pub fn peekMessageA(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: u32, wMsgFilterMax: u32, wRemoveMsg: u32) !bool { + const r = PeekMessageA(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + if (r == 0) return false; + if (r != -1) return true; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn PeekMessageW(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) callconv(WINAPI) BOOL; +pub var pfnPeekMessageW: @TypeOf(PeekMessageW) = undefined; +pub fn peekMessageW(lpMsg: *MSG, hWnd: ?HWND, wMsgFilterMin: u32, wMsgFilterMax: u32, wRemoveMsg: u32) !bool { + const function = selectSymbol(PeekMessageW, pfnPeekMessageW, .win2k); + + const r = function(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + if (r == 0) return false; + if (r != -1) return true; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn TranslateMessage(lpMsg: *const MSG) callconv(WINAPI) BOOL; +pub fn translateMessage(lpMsg: *const MSG) bool { + return if (TranslateMessage(lpMsg) == 0) false else true; +} + +pub extern "user32" fn DispatchMessageA(lpMsg: *const MSG) callconv(WINAPI) LRESULT; +pub fn dispatchMessageA(lpMsg: *const MSG) LRESULT { + return DispatchMessageA(lpMsg); +} + +pub extern "user32" fn DispatchMessageW(lpMsg: *const MSG) callconv(WINAPI) LRESULT; +pub var pfnDispatchMessageW: @TypeOf(DispatchMessageW) = undefined; +pub fn dispatchMessageW(lpMsg: *const MSG) LRESULT { + const function = selectSymbol(DispatchMessageW, pfnDispatchMessageW, .win2k); + return function(lpMsg); +} -pub const WNDPROC = fn (HWND, UINT, WPARAM, LPARAM) callconv(WINAPI) LRESULT; +pub extern "user32" fn PostQuitMessage(nExitCode: i32) callconv(WINAPI) void; +pub fn postQuitMessage(nExitCode: i32) void { + PostQuitMessage(nExitCode); +} + +pub extern "user32" fn DefWindowProcA(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(WINAPI) LRESULT; +pub fn defWindowProcA(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) LRESULT { + return DefWindowProcA(hWnd, Msg, wParam, lParam); +} + +pub extern "user32" fn DefWindowProcW(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) callconv(WINAPI) LRESULT; +pub var pfnDefWindowProcW: @TypeOf(DefWindowProcW) = undefined; +pub fn defWindowProcW(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) LRESULT { + const function = selectSymbol(DefWindowProcW, pfnDefWindowProcW, .win2k); + return function(hWnd, Msg, wParam, lParam); +} + +// === Windows === + +pub const CS_VREDRAW = 0x0001; +pub const CS_HREDRAW = 0x0002; +pub const CS_DBLCLKS = 0x0008; +pub const CS_OWNDC = 0x0020; +pub const CS_CLASSDC = 0x0040; +pub const CS_PARENTDC = 0x0080; +pub const CS_NOCLOSE = 0x0200; +pub const CS_SAVEBITS = 0x0800; +pub const CS_BYTEALIGNCLIENT = 0x1000; +pub const CS_BYTEALIGNWINDOW = 0x2000; +pub const CS_GLOBALCLASS = 0x4000; pub const WNDCLASSEXA = extern struct { cbSize: UINT = @sizeOf(WNDCLASSEXA), style: UINT, lpfnWndProc: WNDPROC, - cbClsExtra: i32, - cbWndExtra: i32, + cbClsExtra: i32 = 0, + cbWndExtra: i32 = 0, hInstance: HINSTANCE, hIcon: ?HICON, hCursor: ?HCURSOR, hbrBackground: ?HBRUSH, - lpszMenuName: ?LPCSTR, - lpszClassName: LPCSTR, + lpszMenuName: ?[*:0]const u8, + lpszClassName: [*:0]const u8, hIconSm: ?HICON, }; -pub const POINT = extern struct { - x: c_long, y: c_long +pub const WNDCLASSEXW = extern struct { + cbSize: UINT = @sizeOf(WNDCLASSEXW), + style: UINT, + lpfnWndProc: WNDPROC, + cbClsExtra: i32 = 0, + cbWndExtra: i32 = 0, + hInstance: HINSTANCE, + hIcon: ?HICON, + hCursor: ?HCURSOR, + hbrBackground: ?HBRUSH, + lpszMenuName: ?[*:0]const u16, + lpszClassName: [*:0]const u16, + hIconSm: ?HICON, }; -pub const MSG = extern struct { - hWnd: ?HWND, - message: UINT, - wParam: WPARAM, - lParam: LPARAM, - time: DWORD, - pt: POINT, - lPrivate: DWORD, -}; +pub extern "user32" fn RegisterClassExA(*const WNDCLASSEXA) callconv(WINAPI) ATOM; +pub fn registerClassExA(window_class: *const WNDCLASSEXA) !ATOM { + const atom = RegisterClassExA(window_class); + if (atom != 0) return atom; + switch (GetLastError()) { + .CLASS_ALREADY_EXISTS => return error.AlreadyExists, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} -pub extern "user32" fn CreateWindowExA( - dwExStyle: DWORD, - lpClassName: LPCSTR, - lpWindowName: LPCSTR, - dwStyle: DWORD, - X: i32, - Y: i32, - nWidth: i32, - nHeight: i32, - hWindParent: ?HWND, - hMenu: ?HMENU, - hInstance: HINSTANCE, - lpParam: ?LPVOID, -) callconv(WINAPI) ?HWND; +pub extern "user32" fn RegisterClassExW(*const WNDCLASSEXW) callconv(WINAPI) ATOM; +pub var pfnRegisterClassExW: @TypeOf(RegisterClassExW) = undefined; +pub fn registerClassExW(window_class: *const WNDCLASSEXW) !ATOM { + const function = selectSymbol(RegisterClassExW, pfnRegisterClassExW, .win2k); + const atom = function(window_class); + if (atom != 0) return atom; + switch (GetLastError()) { + .CLASS_ALREADY_EXISTS => return error.AlreadyExists, + .CALL_NOT_IMPLEMENTED => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn UnregisterClassA(lpClassName: [*:0]const u8, hInstance: HINSTANCE) callconv(WINAPI) BOOL; +pub fn unregisterClassA(lpClassName: [*:0]const u8, hInstance: HINSTANCE) !void { + if (UnregisterClassA(lpClassName, hInstance) == 0) { + switch (GetLastError()) { + .CLASS_DOES_NOT_EXIST => return error.ClassDoesNotExist, + else => |err| return windows.unexpectedError(err), + } + } +} + +pub extern "user32" fn UnregisterClassW(lpClassName: [*:0]const u16, hInstance: HINSTANCE) callconv(WINAPI) BOOL; +pub var pfnUnregisterClassW: @TypeOf(UnregisterClassW) = undefined; +pub fn unregisterClassW(lpClassName: [*:0]const u16, hInstance: HINSTANCE) !void { + const function = selectSymbol(UnregisterClassW, pfnUnregisterClassW, .win2k); + if (function(lpClassName, hInstance) == 0) { + switch (GetLastError()) { + .CLASS_DOES_NOT_EXIST => return error.ClassDoesNotExist, + else => |err| return windows.unexpectedError(err), + } + } +} + +pub const WS_OVERLAPPED = 0x00000000; +pub const WS_POPUP = 0x80000000; +pub const WS_CHILD = 0x40000000; +pub const WS_MINIMIZE = 0x20000000; +pub const WS_VISIBLE = 0x10000000; +pub const WS_DISABLED = 0x08000000; +pub const WS_CLIPSIBLINGS = 0x04000000; +pub const WS_CLIPCHILDREN = 0x02000000; +pub const WS_MAXIMIZE = 0x01000000; +pub const WS_CAPTION = WS_BORDER | WS_DLGFRAME; +pub const WS_BORDER = 0x00800000; +pub const WS_DLGFRAME = 0x00400000; +pub const WS_VSCROLL = 0x00200000; +pub const WS_HSCROLL = 0x00100000; +pub const WS_SYSMENU = 0x00080000; +pub const WS_THICKFRAME = 0x00040000; +pub const WS_GROUP = 0x00020000; +pub const WS_TABSTOP = 0x00010000; +pub const WS_MINIMIZEBOX = 0x00020000; +pub const WS_MAXIMIZEBOX = 0x00010000; +pub const WS_TILED = WS_OVERLAPPED; +pub const WS_ICONIC = WS_MINIMIZE; +pub const WS_SIZEBOX = WS_THICKFRAME; +pub const WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; +pub const WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; +pub const WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; +pub const WS_CHILDWINDOW = WS_CHILD; + +pub const WS_EX_DLGMODALFRAME = 0x00000001; +pub const WS_EX_NOPARENTNOTIFY = 0x00000004; +pub const WS_EX_TOPMOST = 0x00000008; +pub const WS_EX_ACCEPTFILES = 0x00000010; +pub const WS_EX_TRANSPARENT = 0x00000020; +pub const WS_EX_MDICHILD = 0x00000040; +pub const WS_EX_TOOLWINDOW = 0x00000080; +pub const WS_EX_WINDOWEDGE = 0x00000100; +pub const WS_EX_CLIENTEDGE = 0x00000200; +pub const WS_EX_CONTEXTHELP = 0x00000400; +pub const WS_EX_RIGHT = 0x00001000; +pub const WS_EX_LEFT = 0x00000000; +pub const WS_EX_RTLREADING = 0x00002000; +pub const WS_EX_LTRREADING = 0x00000000; +pub const WS_EX_LEFTSCROLLBAR = 0x00004000; +pub const WS_EX_RIGHTSCROLLBAR = 0x00000000; +pub const WS_EX_CONTROLPARENT = 0x00010000; +pub const WS_EX_STATICEDGE = 0x00020000; +pub const WS_EX_APPWINDOW = 0x00040000; +pub const WS_EX_LAYERED = 0x00080000; +pub const WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE; +pub const WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + +pub const CW_USEDEFAULT = @bitCast(i32, @as(u32, 0x80000000)); + +pub extern "user32" fn CreateWindowExA(dwExStyle: DWORD, lpClassName: [*:0]const u8, lpWindowName: [*:0]const u8, dwStyle: DWORD, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?LPVOID) callconv(WINAPI) ?HWND; +pub fn createWindowExA(dwExStyle: u32, lpClassName: [*:0]const u8, lpWindowName: [*:0]const u8, dwStyle: u32, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?*c_void) !HWND { + const window = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWindParent, hMenu, hInstance, lpParam); + if (window) |win| return win; + + switch (GetLastError()) { + .CLASS_DOES_NOT_EXIST => return error.ClassDoesNotExist, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn CreateWindowExW(dwExStyle: DWORD, lpClassName: [*:0]const u16, lpWindowName: [*:0]const u16, dwStyle: DWORD, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?LPVOID) callconv(WINAPI) ?HWND; +pub var pfnCreateWindowExW: @TypeOf(RegisterClassExW) = undefined; +pub fn createWindowExW(dwExStyle: u32, lpClassName: [*:0]const u16, lpWindowName: [*:0]const u16, dwStyle: u32, X: i32, Y: i32, nWidth: i32, nHeight: i32, hWindParent: ?HWND, hMenu: ?HMENU, hInstance: HINSTANCE, lpParam: ?*c_void) !HWND { + const function = selectSymbol(CreateWindowExW, pfnCreateWindowExW, .win2k); + const window = function(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWindParent, hMenu, hInstance, lpParam); + if (window) |win| return win; + + switch (GetLastError()) { + .CLASS_DOES_NOT_EXIST => return error.ClassDoesNotExist, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn DestroyWindow(hWnd: HWND) callconv(WINAPI) BOOL; +pub fn destroyWindow(hWnd: HWND) !void { + if (DestroyWindow(hWnd) == 0) { + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } + } +} + +pub const SW_HIDE = 0; +pub const SW_SHOWNORMAL = 1; +pub const SW_NORMAL = 1; +pub const SW_SHOWMINIMIZED = 2; +pub const SW_SHOWMAXIMIZED = 3; +pub const SW_MAXIMIZE = 3; +pub const SW_SHOWNOACTIVATE = 4; +pub const SW_SHOW = 5; +pub const SW_MINIMIZE = 6; +pub const SW_SHOWMINNOACTIVE = 7; +pub const SW_SHOWNA = 8; +pub const SW_RESTORE = 9; +pub const SW_SHOWDEFAULT = 10; +pub const SW_FORCEMINIMIZE = 11; +pub const SW_MAX = 11; + +pub extern "user32" fn ShowWindow(hWnd: HWND, nCmdShow: i32) callconv(WINAPI) BOOL; +pub fn showWindow(hWnd: HWND, nCmdShow: i32) bool { + return (ShowWindow(hWnd, nCmdShow) == TRUE); +} + +pub extern "user32" fn UpdateWindow(hWnd: HWND) callconv(WINAPI) BOOL; +pub fn updateWindow(hWnd: HWND) !void { + if (ShowWindow(hWnd, nCmdShow) == 0) { + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } + } +} + +pub extern "user32" fn AdjustWindowRectEx(lpRect: *RECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) callconv(WINAPI) BOOL; +pub fn adjustWindowRectEx(lpRect: *RECT, dwStyle: u32, bMenu: bool, dwExStyle: u32) !void { + assert(dwStyle & WS_OVERLAPPED == 0); + + if (AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle) == 0) { + switch (GetLastError()) { + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } + } +} + +pub const GWL_WNDPROC = -4; +pub const GWL_HINSTANCE = -6; +pub const GWL_HWNDPARENT = -8; +pub const GWL_STYLE = -16; +pub const GWL_EXSTYLE = -20; +pub const GWL_USERDATA = -21; +pub const GWL_ID = -12; + +pub extern "user32" fn GetWindowLongA(hWnd: HWND, nIndex: i32) callconv(WINAPI) LONG; +pub fn getWindowLongA(hWnd: HWND, nIndex: i32) !i32 { + const value = GetWindowLongA(hWnd, nIndex); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn GetWindowLongW(hWnd: HWND, nIndex: i32) callconv(WINAPI) LONG; +pub var pfnGetWindowLongW: @TypeOf(GetWindowLongW) = undefined; +pub fn getWindowLongW(hWnd: HWND, nIndex: i32) !i32 { + const function = selectSymbol(GetWindowLongW, pfnGetWindowLongW, .win2k); + + const value = function(hWnd, nIndex); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn GetWindowLongPtrA(hWnd: HWND, nIndex: i32) callconv(WINAPI) LONG_PTR; +pub fn getWindowLongPtrA(hWnd: HWND, nIndex: i32) !isize { + // "When compiling for 32-bit Windows, GetWindowLongPtr is defined as a call to the GetWindowLong function." + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw + if (@sizeOf(LONG_PTR) == 4) return getWindowLongA(hWnd, nIndex); + + const value = GetWindowLongPtrA(hWnd, nIndex); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn GetWindowLongPtrW(hWnd: HWND, nIndex: i32) callconv(WINAPI) LONG_PTR; +pub var pfnGetWindowLongPtrW: @TypeOf(GetWindowLongPtrW) = undefined; +pub fn getWindowLongPtrW(hWnd: HWND, nIndex: i32) !isize { + if (@sizeOf(LONG_PTR) == 4) return getWindowLongW(hWnd, nIndex); + const function = selectSymbol(GetWindowLongPtrW, pfnGetWindowLongPtrW, .win2k); + + const value = function(hWnd, nIndex); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn SetWindowLongA(hWnd: HWND, nIndex: i32, dwNewLong: LONG) callconv(WINAPI) LONG; +pub fn setWindowLongA(hWnd: HWND, nIndex: i32, dwNewLong: i32) !i32 { + // [...] you should clear the last error information by calling SetLastError with 0 before calling SetWindowLong. + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlonga + SetLastError(.SUCCESS); + + const value = SetWindowLongA(hWnd, nIndex, dwNewLong); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn SetWindowLongW(hWnd: HWND, nIndex: i32, dwNewLong: LONG) callconv(WINAPI) LONG; +pub var pfnSetWindowLongW: @TypeOf(SetWindowLongW) = undefined; +pub fn setWindowLongW(hWnd: HWND, nIndex: i32, dwNewLong: i32) !i32 { + const function = selectSymbol(SetWindowLongW, pfnSetWindowLongW, .win2k); + + SetLastError(.SUCCESS); + const value = function(hWnd, nIndex, dwNewLong); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn SetWindowLongPtrA(hWnd: HWND, nIndex: i32, dwNewLong: LONG_PTR) callconv(WINAPI) LONG_PTR; +pub fn setWindowLongPtrA(hWnd: HWND, nIndex: i32, dwNewLong: isize) !isize { + // "When compiling for 32-bit Windows, GetWindowLongPtr is defined as a call to the GetWindowLong function." + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw + if (@sizeOf(LONG_PTR) == 4) return setWindowLongA(hWnd, nIndex, dwNewLong); + + SetLastError(.SUCCESS); + const value = SetWindowLongPtrA(hWnd, nIndex, dwNewLong); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn SetWindowLongPtrW(hWnd: HWND, nIndex: i32, dwNewLong: LONG_PTR) callconv(WINAPI) LONG_PTR; +pub var pfnSetWindowLongPtrW: @TypeOf(SetWindowLongPtrW) = undefined; +pub fn setWindowLongPtrW(hWnd: HWND, nIndex: i32, dwNewLong: isize) !isize { + if (@sizeOf(LONG_PTR) == 4) return setWindowLongW(hWnd, nIndex, dwNewLong); + const function = selectSymbol(SetWindowLongPtrW, pfnSetWindowLongPtrW, .win2k); + + SetLastError(.SUCCESS); + const value = function(hWnd, nIndex, dwNewLong); + if (value != 0) return value; + + switch (GetLastError()) { + .SUCCESS => return 0, + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} -pub extern "user32" fn RegisterClassExA(*const WNDCLASSEXA) callconv(WINAPI) c_ushort; -pub extern "user32" fn DefWindowProcA(HWND, Msg: UINT, WPARAM, LPARAM) callconv(WINAPI) LRESULT; -pub extern "user32" fn ShowWindow(hWnd: ?HWND, nCmdShow: i32) callconv(WINAPI) bool; -pub extern "user32" fn UpdateWindow(hWnd: ?HWND) callconv(WINAPI) bool; pub extern "user32" fn GetDC(hWnd: ?HWND) callconv(WINAPI) ?HDC; +pub fn getDC(hWnd: ?HWND) !HDC { + const hdc = GetDC(hWnd); + if (hdc) |h| return h; -pub extern "user32" fn PeekMessageA( - lpMsg: ?*MSG, - hWnd: ?HWND, - wMsgFilterMin: UINT, - wMsgFilterMax: UINT, - wRemoveMsg: UINT, -) callconv(WINAPI) bool; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} -pub extern "user32" fn GetMessageA( - lpMsg: ?*MSG, - hWnd: ?HWND, - wMsgFilterMin: UINT, - wMsgFilterMax: UINT, -) callconv(WINAPI) bool; +pub extern "user32" fn ReleaseDC(hWnd: ?HWND, hDC: HDC) callconv(WINAPI) i32; +pub fn releaseDC(hWnd: ?HWND, hDC: HDC) bool { + return if (ReleaseDC(hWnd, hDC) == 1) true else false; +} -pub extern "user32" fn TranslateMessage(lpMsg: *const MSG) callconv(WINAPI) bool; -pub extern "user32" fn DispatchMessageA(lpMsg: *const MSG) callconv(WINAPI) LRESULT; -pub extern "user32" fn PostQuitMessage(nExitCode: i32) callconv(WINAPI) void; +// === Modal dialogue boxes === + +pub const MB_OK = 0x00000000; +pub const MB_OKCANCEL = 0x00000001; +pub const MB_ABORTRETRYIGNORE = 0x00000002; +pub const MB_YESNOCANCEL = 0x00000003; +pub const MB_YESNO = 0x00000004; +pub const MB_RETRYCANCEL = 0x00000005; +pub const MB_CANCELTRYCONTINUE = 0x00000006; +pub const MB_ICONHAND = 0x00000010; +pub const MB_ICONQUESTION = 0x00000020; +pub const MB_ICONEXCLAMATION = 0x00000030; +pub const MB_ICONASTERISK = 0x00000040; +pub const MB_USERICON = 0x00000080; +pub const MB_ICONWARNING = MB_ICONEXCLAMATION; +pub const MB_ICONERROR = MB_ICONHAND; +pub const MB_ICONINFORMATION = MB_ICONASTERISK; +pub const MB_ICONSTOP = MB_ICONHAND; +pub const MB_DEFBUTTON1 = 0x00000000; +pub const MB_DEFBUTTON2 = 0x00000100; +pub const MB_DEFBUTTON3 = 0x00000200; +pub const MB_DEFBUTTON4 = 0x00000300; +pub const MB_APPLMODAL = 0x00000000; +pub const MB_SYSTEMMODAL = 0x00001000; +pub const MB_TASKMODAL = 0x00002000; +pub const MB_HELP = 0x00004000; +pub const MB_NOFOCUS = 0x00008000; +pub const MB_SETFOREGROUND = 0x00010000; +pub const MB_DEFAULT_DESKTOP_ONLY = 0x00020000; +pub const MB_TOPMOST = 0x00040000; +pub const MB_RIGHT = 0x00080000; +pub const MB_RTLREADING = 0x00100000; +pub const MB_TYPEMASK = 0x0000000F; +pub const MB_ICONMASK = 0x000000F0; +pub const MB_DEFMASK = 0x00000F00; +pub const MB_MODEMASK = 0x00003000; +pub const MB_MISCMASK = 0x0000C000; + +pub const IDOK = 1; +pub const IDCANCEL = 2; +pub const IDABORT = 3; +pub const IDRETRY = 4; +pub const IDIGNORE = 5; +pub const IDYES = 6; +pub const IDNO = 7; +pub const IDCLOSE = 8; +pub const IDHELP = 9; +pub const IDTRYAGAIN = 10; +pub const IDCONTINUE = 11; + +pub extern "user32" fn MessageBoxA(hWnd: ?HWND, lpText: [*:0]const u8, lpCaption: [*:0]const u8, uType: UINT) callconv(WINAPI) i32; +pub fn messageBoxA(hWnd: ?HWND, lpText: [*:0]const u8, lpCaption: [*:0]const u8, uType: u32) !i32 { + const value = MessageBoxA(hWnd, lpText, lpCaption, uType); + if (value != 0) return value; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} + +pub extern "user32" fn MessageBoxW(hWnd: ?HWND, lpText: [*:0]const u16, lpCaption: ?[*:0]const u16, uType: UINT) callconv(WINAPI) i32; +pub var pfnMessageBoxW: @TypeOf(MessageBoxW) = undefined; +pub fn messageBoxW(hWnd: ?HWND, lpText: [*:0]const u16, lpCaption: [*:0]const u16, uType: u32) !i32 { + const function = selectSymbol(pfnMessageBoxW, MessageBoxW, .win2k); + const value = function(hWnd, lpText, lpCaption, uType); + if (value != 0) return value; + switch (GetLastError()) { + .INVALID_WINDOW_HANDLE => unreachable, + .INVALID_PARAMETER => unreachable, + else => |err| return windows.unexpectedError(err), + } +} diff --git a/lib/std/os/windows/win32error.zig b/lib/std/os/windows/win32error.zig index 2e1f111d92..61bbcac8bb 100644 --- a/lib/std/os/windows/win32error.zig +++ b/lib/std/os/windows/win32error.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index 7123869d65..1dd2ce738b 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index c3222c483c..c53d6f0505 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -203,6 +203,14 @@ pub fn PackedIntArrayEndian(comptime Int: type, comptime endian: builtin.Endian, return self; } + ///Initialize all entries of a packed array to the same value + pub fn initAllTo(int: Int) Self { + // TODO: use `var self = @as(Self, undefined);` https://github.com/ziglang/zig/issues/7635 + var self = Self{ .bytes = [_]u8{0} ** total_bytes }; + self.setAll(int); + return self; + } + ///Return the Int stored at index pub fn get(self: Self, index: usize) Int { debug.assert(index < int_count); @@ -215,6 +223,14 @@ pub fn PackedIntArrayEndian(comptime Int: type, comptime endian: builtin.Endian, return Io.set(&self.bytes, index, 0, int); } + ///Set all entries of a packed array to the same value + pub fn setAll(self: *Self, int: Int) void { + var i: usize = 0; + while (i < int_count) : (i += 1) { + self.set(i, int); + } + } + ///Create a PackedIntSlice of the array from given start to given end pub fn slice(self: *Self, start: usize, end: usize) PackedIntSliceEndian(Int, endian) { debug.assert(start < int_count); @@ -365,7 +381,15 @@ test "PackedIntArray init" { const PackedArray = PackedIntArray(u3, 8); var packed_array = PackedArray.init([_]u3{ 0, 1, 2, 3, 4, 5, 6, 7 }); var i = @as(usize, 0); - while (i < packed_array.len()) : (i += 1) testing.expect(packed_array.get(i) == i); + while (i < packed_array.len()) : (i += 1) testing.expectEqual(@intCast(u3, i), packed_array.get(i)); +} + +test "PackedIntArray initAllTo" { + if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; + const PackedArray = PackedIntArray(u3, 8); + var packed_array = PackedArray.initAllTo(5); + var i = @as(usize, 0); + while (i < packed_array.len()) : (i += 1) testing.expectEqual(@as(u3, 5), packed_array.get(i)); } test "PackedIntSlice" { diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index c35ab6f723..6a47cd6e8b 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -662,6 +662,7 @@ const MsfStream = struct { fn read(self: *MsfStream, buffer: []u8) !usize { var block_id = @intCast(usize, self.pos / self.block_size); + if (block_id >= self.blocks.len) return 0; // End of Stream var block = self.blocks[block_id]; var offset = self.pos % self.block_size; @@ -680,6 +681,7 @@ const MsfStream = struct { if (offset == self.block_size) { offset = 0; block_id += 1; + if (block_id >= self.blocks.len) break; // End of Stream block = self.blocks[block_id]; try self.in_file.seekTo(block * self.block_size); } @@ -716,8 +718,4 @@ const MsfStream = struct { pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) { return .{ .context = self }; } - /// Deprecated: use `reader` - pub fn inStream(self: *MsfStream) std.io.InStream(*MsfStream, Error, read) { - return .{ .context = self }; - } }; diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig index 951e07bfb6..f5c01edff5 100644 --- a/lib/std/priority_queue.zig +++ b/lib/std/priority_queue.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/process.zig b/lib/std/process.zig index e12bc28c0c..3ad73db420 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -13,6 +13,7 @@ const math = std.math; const Allocator = mem.Allocator; const assert = std.debug.assert; const testing = std.testing; +const child_process = @import("child_process.zig"); pub const abort = os.abort; pub const exit = os.exit; @@ -516,7 +517,8 @@ test "args iterator" { const given_suffix = std.fs.path.basename(prog_name); testing.expect(mem.eql(u8, expected_suffix, given_suffix)); - testing.expectEqual(it.next(ga), null); + testing.expect(it.skip()); // Skip over zig_exe_path, passed to the test runner + testing.expect(it.next(ga) == null); testing.expect(!it.skip()); } @@ -594,7 +596,7 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u16, expected_args: []const []con for (expected_args) |expected_arg| { const arg = it.next(std.testing.allocator).? catch unreachable; defer std.testing.allocator.free(arg); - testing.expectEqualSlices(u8, expected_arg, arg); + testing.expectEqualStrings(expected_arg, arg); } testing.expect(it.next(std.testing.allocator) == null); } @@ -607,7 +609,7 @@ pub const UserInfo = struct { /// POSIX function which gets a uid from username. pub fn getUserInfo(name: []const u8) !UserInfo { return switch (builtin.os.tag) { - .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd => posixGetUserInfo(name), + .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku => posixGetUserInfo(name), else => @compileError("Unsupported OS"), }; } @@ -775,6 +777,82 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0] } return paths.toOwnedSlice(); }, + // revisit if Haiku implements dl_iterat_phdr (https://dev.haiku-os.org/ticket/15743) + .haiku => { + var paths = List.init(allocator); + errdefer { + const slice = paths.toOwnedSlice(); + for (slice) |item| { + allocator.free(item); + } + allocator.free(slice); + } + + var b = "/boot/system/runtime_loader"; + const item = try allocator.dupeZ(u8, mem.spanZ(b)); + errdefer allocator.free(item); + try paths.append(item); + + return paths.toOwnedSlice(); + }, else => @compileError("getSelfExeSharedLibPaths unimplemented for this target"), } } + +/// Tells whether calling the `execv` or `execve` functions will be a compile error. +pub const can_execv = std.builtin.os.tag != .windows; + +pub const ExecvError = std.os.ExecveError || error{OutOfMemory}; + +/// Replaces the current process image with the executed process. +/// This function must allocate memory to add a null terminating bytes on path and each arg. +/// It must also convert to KEY=VALUE\0 format for environment variables, and include null +/// pointers after the args and after the environment variables. +/// `argv[0]` is the executable path. +/// This function also uses the PATH environment variable to get the full path to the executable. +/// Due to the heap-allocation, it is illegal to call this function in a fork() child. +/// For that use case, use the `std.os` functions directly. +pub fn execv(allocator: *mem.Allocator, argv: []const []const u8) ExecvError { + return execve(allocator, argv, null); +} + +/// Replaces the current process image with the executed process. +/// This function must allocate memory to add a null terminating bytes on path and each arg. +/// It must also convert to KEY=VALUE\0 format for environment variables, and include null +/// pointers after the args and after the environment variables. +/// `argv[0]` is the executable path. +/// This function also uses the PATH environment variable to get the full path to the executable. +/// Due to the heap-allocation, it is illegal to call this function in a fork() child. +/// For that use case, use the `std.os` functions directly. +pub fn execve( + allocator: *mem.Allocator, + argv: []const []const u8, + env_map: ?*const std.BufMap, +) ExecvError { + if (!can_execv) @compileError("The target OS does not support execv"); + + var arena_allocator = std.heap.ArenaAllocator.init(allocator); + defer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + const argv_buf = try arena.allocSentinel(?[*:0]u8, argv.len, null); + for (argv) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const envp = m: { + if (env_map) |m| { + const envp_buf = try child_process.createNullDelimitedEnvMap(arena, m); + break :m envp_buf.ptr; + } else if (std.builtin.link_libc) { + break :m std.c.environ; + } else if (std.builtin.output_mode == .Exe) { + // Then we have Zig start code and this works. + // TODO type-safety for null-termination of `os.environ`. + break :m @ptrCast([*:null]?[*:0]u8, os.environ.ptr); + } else { + // TODO come up with a solution for this. + @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process"); + } + }; + + return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); +} diff --git a/lib/std/progress.zig b/lib/std/progress.zig deleted file mode 100644 index 82f2801fa1..0000000000 --- a/lib/std/progress.zig +++ /dev/null @@ -1,310 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std"); -const windows = std.os.windows; -const testing = std.testing; -const assert = std.debug.assert; - -/// This API is non-allocating and non-fallible. The tradeoff is that users of -/// this API must provide the storage for each `Progress.Node`. -/// Initialize the struct directly, overriding these fields as desired: -/// * `refresh_rate_ms` -/// * `initial_delay_ms` -pub const Progress = struct { - /// `null` if the current node (and its children) should - /// not print on update() - terminal: ?std.fs.File = undefined, - - /// Whether the terminal supports ANSI escape codes. - supports_ansi_escape_codes: bool = false, - - root: Node = undefined, - - /// Keeps track of how much time has passed since the beginning. - /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`. - timer: std.time.Timer = undefined, - - /// When the previous refresh was written to the terminal. - /// Used to compare with `refresh_rate_ms`. - prev_refresh_timestamp: u64 = undefined, - - /// This buffer represents the maximum number of bytes written to the terminal - /// with each refresh. - output_buffer: [100]u8 = undefined, - - /// How many nanoseconds between writing updates to the terminal. - refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, - - /// How many nanoseconds to keep the output hidden - initial_delay_ns: u64 = 500 * std.time.ns_per_ms, - - done: bool = true, - - /// Keeps track of how many columns in the terminal have been output, so that - /// we can move the cursor back later. - columns_written: usize = undefined, - - /// Represents one unit of progress. Each node can have children nodes, or - /// one can use integers with `update`. - pub const Node = struct { - context: *Progress, - parent: ?*Node, - completed_items: usize, - name: []const u8, - recently_updated_child: ?*Node = null, - - /// This field may be updated freely. - estimated_total_items: ?usize, - - /// Create a new child progress node. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to set `self.parent.recently_updated_child` with the return value. - /// Until that is fixed you probably want to call `activate` on the return value. - pub fn start(self: *Node, name: []const u8, estimated_total_items: ?usize) Node { - return Node{ - .context = self.context, - .parent = self, - .completed_items = 0, - .name = name, - .estimated_total_items = estimated_total_items, - }; - } - - /// This is the same as calling `start` and then `end` on the returned `Node`. - pub fn completeOne(self: *Node) void { - if (self.parent) |parent| parent.recently_updated_child = self; - self.completed_items += 1; - self.context.maybeRefresh(); - } - - pub fn end(self: *Node) void { - self.context.maybeRefresh(); - if (self.parent) |parent| { - if (parent.recently_updated_child) |parent_child| { - if (parent_child == self) { - parent.recently_updated_child = null; - } - } - parent.completeOne(); - } else { - self.context.done = true; - self.context.refresh(); - } - } - - /// Tell the parent node that this node is actively being worked on. - pub fn activate(self: *Node) void { - if (self.parent) |parent| parent.recently_updated_child = self; - } - }; - - /// Create a new progress node. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to return Progress rather than accept it as a parameter. - pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node { - const stderr = std.io.getStdErr(); - self.terminal = null; - if (stderr.supportsAnsiEscapeCodes()) { - self.terminal = stderr; - self.supports_ansi_escape_codes = true; - } else if (std.builtin.os.tag == .windows and stderr.isTty()) { - self.terminal = stderr; - } - self.root = Node{ - .context = self, - .parent = null, - .completed_items = 0, - .name = name, - .estimated_total_items = estimated_total_items, - }; - self.columns_written = 0; - self.prev_refresh_timestamp = 0; - self.timer = try std.time.Timer.start(); - self.done = false; - return &self.root; - } - - /// Updates the terminal if enough time has passed since last update. - pub fn maybeRefresh(self: *Progress) void { - const now = self.timer.read(); - if (now < self.initial_delay_ns) return; - if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; - self.refresh(); - } - - /// Updates the terminal and resets `self.next_refresh_timestamp`. - pub fn refresh(self: *Progress) void { - const file = self.terminal orelse return; - - const prev_columns_written = self.columns_written; - var end: usize = 0; - if (self.columns_written > 0) { - // restore the cursor position by moving the cursor - // `columns_written` cells to the left, then clear the rest of the - // line - if (self.supports_ansi_escape_codes) { - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len; - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; - } else if (std.builtin.os.tag == .windows) winapi: { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) - unreachable; - - var cursor_pos = windows.COORD{ - .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written), - .Y = info.dwCursorPosition.Y, - }; - - if (cursor_pos.X < 0) - cursor_pos.X = 0; - - const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X); - - var written: windows.DWORD = undefined; - if (windows.kernel32.FillConsoleOutputAttribute( - file.handle, - info.wAttributes, - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) { - // Stop trying to write to this file. - self.terminal = null; - break :winapi; - } - if (windows.kernel32.FillConsoleOutputCharacterA( - file.handle, - ' ', - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) unreachable; - - if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) - unreachable; - } else unreachable; - - self.columns_written = 0; - } - - if (!self.done) { - var need_ellipse = false; - var maybe_node: ?*Node = &self.root; - while (maybe_node) |node| { - if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); - } - need_ellipse = false; - if (node.name.len != 0 or node.estimated_total_items != null) { - if (node.name.len != 0) { - self.bufWrite(&end, "{}", .{node.name}); - need_ellipse = true; - } - if (node.estimated_total_items) |total| { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{}/{}] ", .{ node.completed_items + 1, total }); - need_ellipse = false; - } else if (node.completed_items != 0) { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{}] ", .{node.completed_items + 1}); - need_ellipse = false; - } - } - maybe_node = node.recently_updated_child; - } - if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); - } - } - - _ = file.write(self.output_buffer[0..end]) catch |e| { - // Stop trying to write to this file once it errors. - self.terminal = null; - }; - self.prev_refresh_timestamp = self.timer.read(); - } - - pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { - const file = self.terminal orelse return; - self.refresh(); - file.outStream().print(format, args) catch { - self.terminal = null; - return; - }; - self.columns_written = 0; - } - - fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { - if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { - const amt = written.len; - end.* += amt; - self.columns_written += amt; - } else |err| switch (err) { - error.NoSpaceLeft => { - self.columns_written += self.output_buffer.len - end.*; - end.* = self.output_buffer.len; - }, - } - const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; - const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; - if (end.* > max_end) { - const suffix = "... "; - self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; - std.mem.copy(u8, self.output_buffer[max_end..], suffix); - end.* = max_end + suffix.len; - } - } -}; - -test "basic functionality" { - var disable = true; - if (disable) { - // This test is disabled because it uses time.sleep() and is therefore slow. It also - // prints bogus progress data to stderr. - return error.SkipZigTest; - } - var progress = Progress{}; - const root_node = try progress.start("", 100); - defer root_node.end(); - - const sub_task_names = [_][]const u8{ - "reticulating splines", - "adjusting shoes", - "climbing towers", - "pouring juice", - }; - var next_sub_task: usize = 0; - - var i: usize = 0; - while (i < 100) : (i += 1) { - var node = root_node.start(sub_task_names[next_sub_task], 5); - node.activate(); - next_sub_task = (next_sub_task + 1) % sub_task_names.len; - - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - node.completeOne(); - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - node.completeOne(); - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - - node.end(); - - std.time.sleep(5 * std.time.ns_per_ms); - } - { - var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null); - node.activate(); - std.time.sleep(10 * std.time.ns_per_ms); - progress.refresh(); - std.time.sleep(10 * std.time.ns_per_ms); - node.end(); - } -} diff --git a/lib/std/rand.zig b/lib/std/rand.zig index a201968a5f..d0d400b5b0 100644 --- a/lib/std/rand.zig +++ b/lib/std/rand.zig @@ -1,22 +1,14 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -//! The engines provided here should be initialized from an external source. For now, randomBytes -//! from the crypto package is the most suitable. Be sure to use a CSPRNG when required, otherwise using -//! a normal PRNG will be faster and use substantially less stack space. -//! -//! ``` -//! var buf: [8]u8 = undefined; -//! try std.crypto.randomBytes(buf[0..]); -//! const seed = mem.readIntLittle(u64, buf[0..8]); -//! -//! var r = DefaultPrng.init(seed); -//! -//! const s = r.random.int(u64); -//! ``` +//! The engines provided here should be initialized from an external source. +//! For a thread-local cryptographically secure pseudo random number generator, +//! use `std.crypto.random`. +//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will +//! be faster and use substantially less stack space. //! //! TODO(tiehuis): Benchmark these against other reference implementations. @@ -36,6 +28,12 @@ pub const DefaultPrng = Xoroshiro128; /// Cryptographically secure random numbers. pub const DefaultCsprng = Gimli; +pub const Isaac64 = @import("rand/Isaac64.zig"); +pub const Gimli = @import("rand/Gimli.zig"); +pub const Pcg = @import("rand/Pcg.zig"); +pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig"); +pub const Sfc64 = @import("rand/Sfc64.zig"); + pub const Random = struct { fillFn: fn (r: *Random, buf: []u8) void, @@ -491,7 +489,7 @@ test "Random Biased" { // // The number of cycles is thus limited to 64-bits regardless of the engine, but this // is still plenty for practical purposes. -const SplitMix64 = struct { +pub const SplitMix64 = struct { s: u64, pub fn init(seed: u64) SplitMix64 { @@ -525,557 +523,6 @@ test "splitmix64 sequence" { } } -// PCG32 - http://www.pcg-random.org/ -// -// PRNG -pub const Pcg = struct { - const default_multiplier = 6364136223846793005; - - random: Random, - - s: u64, - i: u64, - - pub fn init(init_s: u64) Pcg { - var pcg = Pcg{ - .random = Random{ .fillFn = fill }, - .s = undefined, - .i = undefined, - }; - - pcg.seed(init_s); - return pcg; - } - - fn next(self: *Pcg) u32 { - const l = self.s; - self.s = l *% default_multiplier +% (self.i | 1); - - const xor_s = @truncate(u32, ((l >> 18) ^ l) >> 27); - const rot = @intCast(u32, l >> 59); - - return (xor_s >> @intCast(u5, rot)) | (xor_s << @intCast(u5, (0 -% rot) & 31)); - } - - fn seed(self: *Pcg, init_s: u64) void { - // Pcg requires 128-bits of seed. - var gen = SplitMix64.init(init_s); - self.seedTwo(gen.next(), gen.next()); - } - - fn seedTwo(self: *Pcg, init_s: u64, init_i: u64) void { - self.s = 0; - self.i = (init_s << 1) | 1; - self.s = self.s *% default_multiplier +% self.i; - self.s +%= init_i; - self.s = self.s *% default_multiplier +% self.i; - } - - fn fill(r: *Random, buf: []u8) void { - const self = @fieldParentPtr(Pcg, "random", r); - - var i: usize = 0; - const aligned_len = buf.len - (buf.len & 7); - - // Complete 4 byte segments. - while (i < aligned_len) : (i += 4) { - var n = self.next(); - comptime var j: usize = 0; - inline while (j < 4) : (j += 1) { - buf[i + j] = @truncate(u8, n); - n >>= 8; - } - } - - // Remaining. (cuts the stream) - if (i != buf.len) { - var n = self.next(); - while (i < buf.len) : (i += 1) { - buf[i] = @truncate(u8, n); - n >>= 4; - } - } - } -}; - -test "pcg sequence" { - var r = Pcg.init(0); - const s0: u64 = 0x9394bf54ce5d79de; - const s1: u64 = 0x84e9c579ef59bbf7; - r.seedTwo(s0, s1); - - const seq = [_]u32{ - 2881561918, - 3063928540, - 1199791034, - 2487695858, - 1479648952, - 3247963454, - }; - - for (seq) |s| { - expect(s == r.next()); - } -} - -// Xoroshiro128+ - http://xoroshiro.di.unimi.it/ -// -// PRNG -pub const Xoroshiro128 = struct { - random: Random, - - s: [2]u64, - - pub fn init(init_s: u64) Xoroshiro128 { - var x = Xoroshiro128{ - .random = Random{ .fillFn = fill }, - .s = undefined, - }; - - x.seed(init_s); - return x; - } - - fn next(self: *Xoroshiro128) u64 { - const s0 = self.s[0]; - var s1 = self.s[1]; - const r = s0 +% s1; - - s1 ^= s0; - self.s[0] = math.rotl(u64, s0, @as(u8, 55)) ^ s1 ^ (s1 << 14); - self.s[1] = math.rotl(u64, s1, @as(u8, 36)); - - return r; - } - - // Skip 2^64 places ahead in the sequence - fn jump(self: *Xoroshiro128) void { - var s0: u64 = 0; - var s1: u64 = 0; - - const table = [_]u64{ - 0xbeac0467eba5facb, - 0xd86b048b86aa9922, - }; - - inline for (table) |entry| { - var b: usize = 0; - while (b < 64) : (b += 1) { - if ((entry & (@as(u64, 1) << @intCast(u6, b))) != 0) { - s0 ^= self.s[0]; - s1 ^= self.s[1]; - } - _ = self.next(); - } - } - - self.s[0] = s0; - self.s[1] = s1; - } - - pub fn seed(self: *Xoroshiro128, init_s: u64) void { - // Xoroshiro requires 128-bits of seed. - var gen = SplitMix64.init(init_s); - - self.s[0] = gen.next(); - self.s[1] = gen.next(); - } - - fn fill(r: *Random, buf: []u8) void { - const self = @fieldParentPtr(Xoroshiro128, "random", r); - - var i: usize = 0; - const aligned_len = buf.len - (buf.len & 7); - - // Complete 8 byte segments. - while (i < aligned_len) : (i += 8) { - var n = self.next(); - comptime var j: usize = 0; - inline while (j < 8) : (j += 1) { - buf[i + j] = @truncate(u8, n); - n >>= 8; - } - } - - // Remaining. (cuts the stream) - if (i != buf.len) { - var n = self.next(); - while (i < buf.len) : (i += 1) { - buf[i] = @truncate(u8, n); - n >>= 8; - } - } - } -}; - -test "xoroshiro sequence" { - var r = Xoroshiro128.init(0); - r.s[0] = 0xaeecf86f7878dd75; - r.s[1] = 0x01cd153642e72622; - - const seq1 = [_]u64{ - 0xb0ba0da5bb600397, - 0x18a08afde614dccc, - 0xa2635b956a31b929, - 0xabe633c971efa045, - 0x9ac19f9706ca3cac, - 0xf62b426578c1e3fb, - }; - - for (seq1) |s| { - expect(s == r.next()); - } - - r.jump(); - - const seq2 = [_]u64{ - 0x95344a13556d3e22, - 0xb4fb32dafa4d00df, - 0xb2011d9ccdcfe2dd, - 0x05679a9b2119b908, - 0xa860a1da7c9cd8a0, - 0x658a96efe3f86550, - }; - - for (seq2) |s| { - expect(s == r.next()); - } -} - -// Gimli -// -// CSPRNG -pub const Gimli = struct { - random: Random, - state: std.crypto.core.Gimli, - - pub const secret_seed_length = 32; - - /// The seed must be uniform, secret and `secret_seed_length` bytes long. - /// It can be generated using `std.crypto.randomBytes()`. - pub fn init(secret_seed: [secret_seed_length]u8) Gimli { - var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; - mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed); - mem.set(u8, initial_state[secret_seed_length..], 0); - var self = Gimli{ - .random = Random{ .fillFn = fill }, - .state = std.crypto.core.Gimli.init(initial_state), - }; - return self; - } - - fn fill(r: *Random, buf: []u8) void { - const self = @fieldParentPtr(Gimli, "random", r); - - if (buf.len != 0) { - self.state.squeeze(buf); - } else { - self.state.permute(); - } - mem.set(u8, self.state.toSlice()[0..std.crypto.core.Gimli.RATE], 0); - } -}; - -// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html -// -// Follows the general idea of the implementation from here with a few shortcuts. -// https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html -pub const Isaac64 = struct { - random: Random, - - r: [256]u64, - m: [256]u64, - a: u64, - b: u64, - c: u64, - i: usize, - - pub fn init(init_s: u64) Isaac64 { - var isaac = Isaac64{ - .random = Random{ .fillFn = fill }, - .r = undefined, - .m = undefined, - .a = undefined, - .b = undefined, - .c = undefined, - .i = undefined, - }; - - // seed == 0 => same result as the unseeded reference implementation - isaac.seed(init_s, 1); - return isaac; - } - - fn step(self: *Isaac64, mix: u64, base: usize, comptime m1: usize, comptime m2: usize) void { - const x = self.m[base + m1]; - self.a = mix +% self.m[base + m2]; - - const y = self.a +% self.b +% self.m[@intCast(usize, (x >> 3) % self.m.len)]; - self.m[base + m1] = y; - - self.b = x +% self.m[@intCast(usize, (y >> 11) % self.m.len)]; - self.r[self.r.len - 1 - base - m1] = self.b; - } - - fn refill(self: *Isaac64) void { - const midpoint = self.r.len / 2; - - self.c +%= 1; - self.b +%= self.c; - - { - var i: usize = 0; - while (i < midpoint) : (i += 4) { - self.step(~(self.a ^ (self.a << 21)), i + 0, 0, midpoint); - self.step(self.a ^ (self.a >> 5), i + 1, 0, midpoint); - self.step(self.a ^ (self.a << 12), i + 2, 0, midpoint); - self.step(self.a ^ (self.a >> 33), i + 3, 0, midpoint); - } - } - - { - var i: usize = 0; - while (i < midpoint) : (i += 4) { - self.step(~(self.a ^ (self.a << 21)), i + 0, midpoint, 0); - self.step(self.a ^ (self.a >> 5), i + 1, midpoint, 0); - self.step(self.a ^ (self.a << 12), i + 2, midpoint, 0); - self.step(self.a ^ (self.a >> 33), i + 3, midpoint, 0); - } - } - - self.i = 0; - } - - fn next(self: *Isaac64) u64 { - if (self.i >= self.r.len) { - self.refill(); - } - - const value = self.r[self.i]; - self.i += 1; - return value; - } - - fn seed(self: *Isaac64, init_s: u64, comptime rounds: usize) void { - // We ignore the multi-pass requirement since we don't currently expose full access to - // seeding the self.m array completely. - mem.set(u64, self.m[0..], 0); - self.m[0] = init_s; - - // prescrambled golden ratio constants - var a = [_]u64{ - 0x647c4677a2884b7c, - 0xb9f8b322c73ac862, - 0x8c0ea5053d4712a0, - 0xb29b2e824a595524, - 0x82f053db8355e0ce, - 0x48fe4a0fa5a09315, - 0xae985bf2cbfc89ed, - 0x98f5704f6c44c0ab, - }; - - comptime var i: usize = 0; - inline while (i < rounds) : (i += 1) { - var j: usize = 0; - while (j < self.m.len) : (j += 8) { - comptime var x1: usize = 0; - inline while (x1 < 8) : (x1 += 1) { - a[x1] +%= self.m[j + x1]; - } - - a[0] -%= a[4]; - a[5] ^= a[7] >> 9; - a[7] +%= a[0]; - a[1] -%= a[5]; - a[6] ^= a[0] << 9; - a[0] +%= a[1]; - a[2] -%= a[6]; - a[7] ^= a[1] >> 23; - a[1] +%= a[2]; - a[3] -%= a[7]; - a[0] ^= a[2] << 15; - a[2] +%= a[3]; - a[4] -%= a[0]; - a[1] ^= a[3] >> 14; - a[3] +%= a[4]; - a[5] -%= a[1]; - a[2] ^= a[4] << 20; - a[4] +%= a[5]; - a[6] -%= a[2]; - a[3] ^= a[5] >> 17; - a[5] +%= a[6]; - a[7] -%= a[3]; - a[4] ^= a[6] << 14; - a[6] +%= a[7]; - - comptime var x2: usize = 0; - inline while (x2 < 8) : (x2 += 1) { - self.m[j + x2] = a[x2]; - } - } - } - - mem.set(u64, self.r[0..], 0); - self.a = 0; - self.b = 0; - self.c = 0; - self.i = self.r.len; // trigger refill on first value - } - - fn fill(r: *Random, buf: []u8) void { - const self = @fieldParentPtr(Isaac64, "random", r); - - var i: usize = 0; - const aligned_len = buf.len - (buf.len & 7); - - // Fill complete 64-byte segments - while (i < aligned_len) : (i += 8) { - var n = self.next(); - comptime var j: usize = 0; - inline while (j < 8) : (j += 1) { - buf[i + j] = @truncate(u8, n); - n >>= 8; - } - } - - // Fill trailing, ignoring excess (cut the stream). - if (i != buf.len) { - var n = self.next(); - while (i < buf.len) : (i += 1) { - buf[i] = @truncate(u8, n); - n >>= 8; - } - } - } -}; - -test "isaac64 sequence" { - var r = Isaac64.init(0); - - // from reference implementation - const seq = [_]u64{ - 0xf67dfba498e4937c, - 0x84a5066a9204f380, - 0xfee34bd5f5514dbb, - 0x4d1664739b8f80d6, - 0x8607459ab52a14aa, - 0x0e78bc5a98529e49, - 0xfe5332822ad13777, - 0x556c27525e33d01a, - 0x08643ca615f3149f, - 0xd0771faf3cb04714, - 0x30e86f68a37b008d, - 0x3074ebc0488a3adf, - 0x270645ea7a2790bc, - 0x5601a0a8d3763c6a, - 0x2f83071f53f325dd, - 0xb9090f3d42d2d2ea, - }; - - for (seq) |s| { - expect(s == r.next()); - } -} - -/// Sfc64 pseudo-random number generator from Practically Random. -/// Fastest engine of pracrand and smallest footprint. -/// See http://pracrand.sourceforge.net/ -pub const Sfc64 = struct { - random: Random, - - a: u64 = undefined, - b: u64 = undefined, - c: u64 = undefined, - counter: u64 = undefined, - - const Rotation = 24; - const RightShift = 11; - const LeftShift = 3; - - pub fn init(init_s: u64) Sfc64 { - var x = Sfc64{ - .random = Random{ .fillFn = fill }, - }; - - x.seed(init_s); - return x; - } - - fn next(self: *Sfc64) u64 { - const tmp = self.a +% self.b +% self.counter; - self.counter += 1; - self.a = self.b ^ (self.b >> RightShift); - self.b = self.c +% (self.c << LeftShift); - self.c = math.rotl(u64, self.c, Rotation) +% tmp; - return tmp; - } - - fn seed(self: *Sfc64, init_s: u64) void { - self.a = init_s; - self.b = init_s; - self.c = init_s; - self.counter = 1; - var i: u32 = 0; - while (i < 12) : (i += 1) { - _ = self.next(); - } - } - - fn fill(r: *Random, buf: []u8) void { - const self = @fieldParentPtr(Sfc64, "random", r); - - var i: usize = 0; - const aligned_len = buf.len - (buf.len & 7); - - // Complete 8 byte segments. - while (i < aligned_len) : (i += 8) { - var n = self.next(); - comptime var j: usize = 0; - inline while (j < 8) : (j += 1) { - buf[i + j] = @truncate(u8, n); - n >>= 8; - } - } - - // Remaining. (cuts the stream) - if (i != buf.len) { - var n = self.next(); - while (i < buf.len) : (i += 1) { - buf[i] = @truncate(u8, n); - n >>= 8; - } - } - } -}; - -test "Sfc64 sequence" { - // Unfortunately there does not seem to be an official test sequence. - var r = Sfc64.init(0); - - const seq = [_]u64{ - 0x3acfa029e3cc6041, - 0xf5b6515bf2ee419c, - 0x1259635894a29b61, - 0xb6ae75395f8ebd6, - 0x225622285ce302e2, - 0x520d28611395cb21, - 0xdb909c818901599d, - 0x8ffd195365216f57, - 0xe8c4ad5e258ac04a, - 0x8f8ef2c89fdb63ca, - 0xf9865b01d98d8e2f, - 0x46555871a65d08ba, - 0x66868677c6298fcd, - 0x2ce15a7e6329f57d, - 0xb2f1833ca91ca79, - 0x4b0890ac9bf453ca, - }; - - for (seq) |s| { - expectEqual(s, r.next()); - } -} - // Actual Random helper function tests, pcg engine is assumed correct. test "Random float" { var prng = DefaultPrng.init(0); @@ -1147,7 +594,7 @@ fn testRangeBias(r: *Random, start: i8, end: i8, biased: bool) void { test "CSPRNG" { var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined; - try std.crypto.randomBytes(&secret_seed); + std.crypto.random.bytes(&secret_seed); var csprng = DefaultCsprng.init(secret_seed); const a = csprng.random.int(u64); const b = csprng.random.int(u64); @@ -1155,6 +602,6 @@ test "CSPRNG" { expect(a ^ b ^ c != 0); } -test "" { +test { std.testing.refAllDecls(@This()); } diff --git a/lib/std/rand/Gimli.zig b/lib/std/rand/Gimli.zig new file mode 100644 index 0000000000..8356c7afde --- /dev/null +++ b/lib/std/rand/Gimli.zig @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! CSPRNG + +const std = @import("std"); +const Random = std.rand.Random; +const mem = std.mem; +const Gimli = @This(); + +random: Random, +state: std.crypto.core.Gimli, + +pub const secret_seed_length = 32; + +/// The seed must be uniform, secret and `secret_seed_length` bytes long. +pub fn init(secret_seed: [secret_seed_length]u8) Gimli { + var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; + mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed); + mem.set(u8, initial_state[secret_seed_length..], 0); + var self = Gimli{ + .random = Random{ .fillFn = fill }, + .state = std.crypto.core.Gimli.init(initial_state), + }; + return self; +} + +fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Gimli, "random", r); + + if (buf.len != 0) { + self.state.squeeze(buf); + } else { + self.state.permute(); + } + mem.set(u8, self.state.toSlice()[0..std.crypto.core.Gimli.RATE], 0); +} diff --git a/lib/std/rand/Isaac64.zig b/lib/std/rand/Isaac64.zig new file mode 100644 index 0000000000..e1d4dedf5a --- /dev/null +++ b/lib/std/rand/Isaac64.zig @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html +//! +//! Follows the general idea of the implementation from here with a few shortcuts. +//! https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html + +const std = @import("std"); +const Random = std.rand.Random; +const mem = std.mem; +const Isaac64 = @This(); + +random: Random, + +r: [256]u64, +m: [256]u64, +a: u64, +b: u64, +c: u64, +i: usize, + +pub fn init(init_s: u64) Isaac64 { + var isaac = Isaac64{ + .random = Random{ .fillFn = fill }, + .r = undefined, + .m = undefined, + .a = undefined, + .b = undefined, + .c = undefined, + .i = undefined, + }; + + // seed == 0 => same result as the unseeded reference implementation + isaac.seed(init_s, 1); + return isaac; +} + +fn step(self: *Isaac64, mix: u64, base: usize, comptime m1: usize, comptime m2: usize) void { + const x = self.m[base + m1]; + self.a = mix +% self.m[base + m2]; + + const y = self.a +% self.b +% self.m[@intCast(usize, (x >> 3) % self.m.len)]; + self.m[base + m1] = y; + + self.b = x +% self.m[@intCast(usize, (y >> 11) % self.m.len)]; + self.r[self.r.len - 1 - base - m1] = self.b; +} + +fn refill(self: *Isaac64) void { + const midpoint = self.r.len / 2; + + self.c +%= 1; + self.b +%= self.c; + + { + var i: usize = 0; + while (i < midpoint) : (i += 4) { + self.step(~(self.a ^ (self.a << 21)), i + 0, 0, midpoint); + self.step(self.a ^ (self.a >> 5), i + 1, 0, midpoint); + self.step(self.a ^ (self.a << 12), i + 2, 0, midpoint); + self.step(self.a ^ (self.a >> 33), i + 3, 0, midpoint); + } + } + + { + var i: usize = 0; + while (i < midpoint) : (i += 4) { + self.step(~(self.a ^ (self.a << 21)), i + 0, midpoint, 0); + self.step(self.a ^ (self.a >> 5), i + 1, midpoint, 0); + self.step(self.a ^ (self.a << 12), i + 2, midpoint, 0); + self.step(self.a ^ (self.a >> 33), i + 3, midpoint, 0); + } + } + + self.i = 0; +} + +fn next(self: *Isaac64) u64 { + if (self.i >= self.r.len) { + self.refill(); + } + + const value = self.r[self.i]; + self.i += 1; + return value; +} + +fn seed(self: *Isaac64, init_s: u64, comptime rounds: usize) void { + // We ignore the multi-pass requirement since we don't currently expose full access to + // seeding the self.m array completely. + mem.set(u64, self.m[0..], 0); + self.m[0] = init_s; + + // prescrambled golden ratio constants + var a = [_]u64{ + 0x647c4677a2884b7c, + 0xb9f8b322c73ac862, + 0x8c0ea5053d4712a0, + 0xb29b2e824a595524, + 0x82f053db8355e0ce, + 0x48fe4a0fa5a09315, + 0xae985bf2cbfc89ed, + 0x98f5704f6c44c0ab, + }; + + comptime var i: usize = 0; + inline while (i < rounds) : (i += 1) { + var j: usize = 0; + while (j < self.m.len) : (j += 8) { + comptime var x1: usize = 0; + inline while (x1 < 8) : (x1 += 1) { + a[x1] +%= self.m[j + x1]; + } + + a[0] -%= a[4]; + a[5] ^= a[7] >> 9; + a[7] +%= a[0]; + a[1] -%= a[5]; + a[6] ^= a[0] << 9; + a[0] +%= a[1]; + a[2] -%= a[6]; + a[7] ^= a[1] >> 23; + a[1] +%= a[2]; + a[3] -%= a[7]; + a[0] ^= a[2] << 15; + a[2] +%= a[3]; + a[4] -%= a[0]; + a[1] ^= a[3] >> 14; + a[3] +%= a[4]; + a[5] -%= a[1]; + a[2] ^= a[4] << 20; + a[4] +%= a[5]; + a[6] -%= a[2]; + a[3] ^= a[5] >> 17; + a[5] +%= a[6]; + a[7] -%= a[3]; + a[4] ^= a[6] << 14; + a[6] +%= a[7]; + + comptime var x2: usize = 0; + inline while (x2 < 8) : (x2 += 1) { + self.m[j + x2] = a[x2]; + } + } + } + + mem.set(u64, self.r[0..], 0); + self.a = 0; + self.b = 0; + self.c = 0; + self.i = self.r.len; // trigger refill on first value +} + +fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Isaac64, "random", r); + + var i: usize = 0; + const aligned_len = buf.len - (buf.len & 7); + + // Fill complete 64-byte segments + while (i < aligned_len) : (i += 8) { + var n = self.next(); + comptime var j: usize = 0; + inline while (j < 8) : (j += 1) { + buf[i + j] = @truncate(u8, n); + n >>= 8; + } + } + + // Fill trailing, ignoring excess (cut the stream). + if (i != buf.len) { + var n = self.next(); + while (i < buf.len) : (i += 1) { + buf[i] = @truncate(u8, n); + n >>= 8; + } + } +} + +test "isaac64 sequence" { + var r = Isaac64.init(0); + + // from reference implementation + const seq = [_]u64{ + 0xf67dfba498e4937c, + 0x84a5066a9204f380, + 0xfee34bd5f5514dbb, + 0x4d1664739b8f80d6, + 0x8607459ab52a14aa, + 0x0e78bc5a98529e49, + 0xfe5332822ad13777, + 0x556c27525e33d01a, + 0x08643ca615f3149f, + 0xd0771faf3cb04714, + 0x30e86f68a37b008d, + 0x3074ebc0488a3adf, + 0x270645ea7a2790bc, + 0x5601a0a8d3763c6a, + 0x2f83071f53f325dd, + 0xb9090f3d42d2d2ea, + }; + + for (seq) |s| { + std.testing.expect(s == r.next()); + } +} diff --git a/lib/std/rand/Pcg.zig b/lib/std/rand/Pcg.zig new file mode 100644 index 0000000000..6be17b3bb8 --- /dev/null +++ b/lib/std/rand/Pcg.zig @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! PCG32 - http://www.pcg-random.org/ +//! +//! PRNG + +const std = @import("std"); +const Random = std.rand.Random; +const Pcg = @This(); + +const default_multiplier = 6364136223846793005; + +random: Random, + +s: u64, +i: u64, + +pub fn init(init_s: u64) Pcg { + var pcg = Pcg{ + .random = Random{ .fillFn = fill }, + .s = undefined, + .i = undefined, + }; + + pcg.seed(init_s); + return pcg; +} + +fn next(self: *Pcg) u32 { + const l = self.s; + self.s = l *% default_multiplier +% (self.i | 1); + + const xor_s = @truncate(u32, ((l >> 18) ^ l) >> 27); + const rot = @intCast(u32, l >> 59); + + return (xor_s >> @intCast(u5, rot)) | (xor_s << @intCast(u5, (0 -% rot) & 31)); +} + +fn seed(self: *Pcg, init_s: u64) void { + // Pcg requires 128-bits of seed. + var gen = std.rand.SplitMix64.init(init_s); + self.seedTwo(gen.next(), gen.next()); +} + +fn seedTwo(self: *Pcg, init_s: u64, init_i: u64) void { + self.s = 0; + self.i = (init_s << 1) | 1; + self.s = self.s *% default_multiplier +% self.i; + self.s +%= init_i; + self.s = self.s *% default_multiplier +% self.i; +} + +fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Pcg, "random", r); + + var i: usize = 0; + const aligned_len = buf.len - (buf.len & 7); + + // Complete 4 byte segments. + while (i < aligned_len) : (i += 4) { + var n = self.next(); + comptime var j: usize = 0; + inline while (j < 4) : (j += 1) { + buf[i + j] = @truncate(u8, n); + n >>= 8; + } + } + + // Remaining. (cuts the stream) + if (i != buf.len) { + var n = self.next(); + while (i < buf.len) : (i += 1) { + buf[i] = @truncate(u8, n); + n >>= 4; + } + } +} + +test "pcg sequence" { + var r = Pcg.init(0); + const s0: u64 = 0x9394bf54ce5d79de; + const s1: u64 = 0x84e9c579ef59bbf7; + r.seedTwo(s0, s1); + + const seq = [_]u32{ + 2881561918, + 3063928540, + 1199791034, + 2487695858, + 1479648952, + 3247963454, + }; + + for (seq) |s| { + std.testing.expect(s == r.next()); + } +} diff --git a/lib/std/rand/Sfc64.zig b/lib/std/rand/Sfc64.zig new file mode 100644 index 0000000000..3b5f1eda82 --- /dev/null +++ b/lib/std/rand/Sfc64.zig @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Sfc64 pseudo-random number generator from Practically Random. +//! Fastest engine of pracrand and smallest footprint. +//! See http://pracrand.sourceforge.net/ + +const std = @import("std"); +const Random = std.rand.Random; +const math = std.math; +const Sfc64 = @This(); + +random: Random, + +a: u64 = undefined, +b: u64 = undefined, +c: u64 = undefined, +counter: u64 = undefined, + +const Rotation = 24; +const RightShift = 11; +const LeftShift = 3; + +pub fn init(init_s: u64) Sfc64 { + var x = Sfc64{ + .random = Random{ .fillFn = fill }, + }; + + x.seed(init_s); + return x; +} + +fn next(self: *Sfc64) u64 { + const tmp = self.a +% self.b +% self.counter; + self.counter += 1; + self.a = self.b ^ (self.b >> RightShift); + self.b = self.c +% (self.c << LeftShift); + self.c = math.rotl(u64, self.c, Rotation) +% tmp; + return tmp; +} + +fn seed(self: *Sfc64, init_s: u64) void { + self.a = init_s; + self.b = init_s; + self.c = init_s; + self.counter = 1; + var i: u32 = 0; + while (i < 12) : (i += 1) { + _ = self.next(); + } +} + +fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Sfc64, "random", r); + + var i: usize = 0; + const aligned_len = buf.len - (buf.len & 7); + + // Complete 8 byte segments. + while (i < aligned_len) : (i += 8) { + var n = self.next(); + comptime var j: usize = 0; + inline while (j < 8) : (j += 1) { + buf[i + j] = @truncate(u8, n); + n >>= 8; + } + } + + // Remaining. (cuts the stream) + if (i != buf.len) { + var n = self.next(); + while (i < buf.len) : (i += 1) { + buf[i] = @truncate(u8, n); + n >>= 8; + } + } +} + +test "Sfc64 sequence" { + // Unfortunately there does not seem to be an official test sequence. + var r = Sfc64.init(0); + + const seq = [_]u64{ + 0x3acfa029e3cc6041, + 0xf5b6515bf2ee419c, + 0x1259635894a29b61, + 0xb6ae75395f8ebd6, + 0x225622285ce302e2, + 0x520d28611395cb21, + 0xdb909c818901599d, + 0x8ffd195365216f57, + 0xe8c4ad5e258ac04a, + 0x8f8ef2c89fdb63ca, + 0xf9865b01d98d8e2f, + 0x46555871a65d08ba, + 0x66868677c6298fcd, + 0x2ce15a7e6329f57d, + 0xb2f1833ca91ca79, + 0x4b0890ac9bf453ca, + }; + + for (seq) |s| { + std.testing.expectEqual(s, r.next()); + } +} diff --git a/lib/std/rand/Xoroshiro128.zig b/lib/std/rand/Xoroshiro128.zig new file mode 100644 index 0000000000..816bb9f58c --- /dev/null +++ b/lib/std/rand/Xoroshiro128.zig @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Xoroshiro128+ - http://xoroshiro.di.unimi.it/ +//! +//! PRNG + +const std = @import("std"); +const Random = std.rand.Random; +const math = std.math; +const Xoroshiro128 = @This(); + +random: Random, + +s: [2]u64, + +pub fn init(init_s: u64) Xoroshiro128 { + var x = Xoroshiro128{ + .random = Random{ .fillFn = fill }, + .s = undefined, + }; + + x.seed(init_s); + return x; +} + +fn next(self: *Xoroshiro128) u64 { + const s0 = self.s[0]; + var s1 = self.s[1]; + const r = s0 +% s1; + + s1 ^= s0; + self.s[0] = math.rotl(u64, s0, @as(u8, 55)) ^ s1 ^ (s1 << 14); + self.s[1] = math.rotl(u64, s1, @as(u8, 36)); + + return r; +} + +// Skip 2^64 places ahead in the sequence +fn jump(self: *Xoroshiro128) void { + var s0: u64 = 0; + var s1: u64 = 0; + + const table = [_]u64{ + 0xbeac0467eba5facb, + 0xd86b048b86aa9922, + }; + + inline for (table) |entry| { + var b: usize = 0; + while (b < 64) : (b += 1) { + if ((entry & (@as(u64, 1) << @intCast(u6, b))) != 0) { + s0 ^= self.s[0]; + s1 ^= self.s[1]; + } + _ = self.next(); + } + } + + self.s[0] = s0; + self.s[1] = s1; +} + +pub fn seed(self: *Xoroshiro128, init_s: u64) void { + // Xoroshiro requires 128-bits of seed. + var gen = std.rand.SplitMix64.init(init_s); + + self.s[0] = gen.next(); + self.s[1] = gen.next(); +} + +fn fill(r: *Random, buf: []u8) void { + const self = @fieldParentPtr(Xoroshiro128, "random", r); + + var i: usize = 0; + const aligned_len = buf.len - (buf.len & 7); + + // Complete 8 byte segments. + while (i < aligned_len) : (i += 8) { + var n = self.next(); + comptime var j: usize = 0; + inline while (j < 8) : (j += 1) { + buf[i + j] = @truncate(u8, n); + n >>= 8; + } + } + + // Remaining. (cuts the stream) + if (i != buf.len) { + var n = self.next(); + while (i < buf.len) : (i += 1) { + buf[i] = @truncate(u8, n); + n >>= 8; + } + } +} + +test "xoroshiro sequence" { + var r = Xoroshiro128.init(0); + r.s[0] = 0xaeecf86f7878dd75; + r.s[1] = 0x01cd153642e72622; + + const seq1 = [_]u64{ + 0xb0ba0da5bb600397, + 0x18a08afde614dccc, + 0xa2635b956a31b929, + 0xabe633c971efa045, + 0x9ac19f9706ca3cac, + 0xf62b426578c1e3fb, + }; + + for (seq1) |s| { + std.testing.expect(s == r.next()); + } + + r.jump(); + + const seq2 = [_]u64{ + 0x95344a13556d3e22, + 0xb4fb32dafa4d00df, + 0xb2011d9ccdcfe2dd, + 0x05679a9b2119b908, + 0xa860a1da7c9cd8a0, + 0x658a96efe3f86550, + }; + + for (seq2) |s| { + std.testing.expect(s == r.next()); + } +} diff --git a/lib/std/rand/ziggurat.zig b/lib/std/rand/ziggurat.zig index da189637bf..fe120943d7 100644 --- a/lib/std/rand/ziggurat.zig +++ b/lib/std/rand/ziggurat.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -131,7 +131,11 @@ fn norm_zero_case(random: *Random, u: f64) f64 { } } -test "ziggurant normal dist sanity" { +const please_windows_dont_oom = std.Target.current.os.tag == .windows; + +test "normal dist sanity" { + if (please_windows_dont_oom) return error.SkipZigTest; + var prng = std.rand.DefaultPrng.init(0); var i: usize = 0; while (i < 1000) : (i += 1) { @@ -158,7 +162,9 @@ fn exp_zero_case(random: *Random, _: f64) f64 { return exp_r - math.ln(random.float(f64)); } -test "ziggurant exp dist sanity" { +test "exp dist sanity" { + if (please_windows_dont_oom) return error.SkipZigTest; + var prng = std.rand.DefaultPrng.init(0); var i: usize = 0; while (i < 1000) : (i += 1) { @@ -166,6 +172,8 @@ test "ziggurant exp dist sanity" { } } -test "ziggurat table gen" { +test "table gen" { + if (please_windows_dont_oom) return error.SkipZigTest; + const table = NormDist; } diff --git a/lib/std/sort.zig b/lib/std/sort.zig index e2e4cc662d..b30fb6ae57 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -1129,7 +1129,7 @@ fn swap( } } -/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, asc(u8))`. +/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, comptime asc(u8))`. pub fn asc(comptime T: type) fn (void, T, T) bool { const impl = struct { fn inner(context: void, a: T, b: T) bool { @@ -1140,7 +1140,7 @@ pub fn asc(comptime T: type) fn (void, T, T) bool { return impl.inner; } -/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, asc(u8))`. +/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, comptime desc(u8))`. pub fn desc(comptime T: type) fn (void, T, T) bool { const impl = struct { fn inner(context: void, a: T, b: T) bool { diff --git a/lib/std/special/build_runner.zig b/lib/std/special/build_runner.zig index 7d9ac17499..0b7baf0fc1 100644 --- a/lib/std/special/build_runner.zig +++ b/lib/std/special/build_runner.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -57,8 +57,8 @@ pub fn main() !void { var targets = ArrayList([]const u8).init(allocator); - const stderr_stream = io.getStdErr().outStream(); - const stdout_stream = io.getStdOut().outStream(); + const stderr_stream = io.getStdErr().writer(); + const stdout_stream = io.getStdOut().writer(); while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-D")) { @@ -98,7 +98,7 @@ pub fn main() !void { return usageAndErr(builder, false, stderr_stream); }; builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse { - warn("expected [auto|on|off] after --color, found '{}'", .{next_arg}); + warn("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); return usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--override-lib-dir")) { @@ -126,7 +126,7 @@ pub fn main() !void { builder.args = argsRest(args, arg_idx); break; } else { - warn("Unrecognized argument: {}\n\n", .{arg}); + warn("Unrecognized argument: {s}\n\n", .{arg}); return usageAndErr(builder, false, stderr_stream); } } else { @@ -168,7 +168,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void } try out_stream.print( - \\Usage: {} build [steps] [options] + \\Usage: {s} build [steps] [options] \\ \\Steps: \\ @@ -177,10 +177,10 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void const allocator = builder.allocator; for (builder.top_level_steps.items) |top_level_step| { const name = if (&top_level_step.step == builder.default_step) - try fmt.allocPrint(allocator, "{} (default)", .{top_level_step.step.name}) + try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name}) else top_level_step.step.name; - try out_stream.print(" {s:<27} {}\n", .{ name, top_level_step.description }); + try out_stream.print(" {s:<27} {s}\n", .{ name, top_level_step.description }); } try out_stream.writeAll( @@ -200,12 +200,12 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void try out_stream.print(" (none)\n", .{}); } else { for (builder.available_options_list.items) |option| { - const name = try fmt.allocPrint(allocator, " -D{}=[{}]", .{ + const name = try fmt.allocPrint(allocator, " -D{s}=[{s}]", .{ option.name, Builder.typeIdName(option.type_id), }); defer allocator.free(name); - try out_stream.print("{s:<29} {}\n", .{ name, option.description }); + try out_stream.print("{s:<29} {s}\n", .{ name, option.description }); } } diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig index 449b70d6b0..51cbafc133 100644 --- a/lib/std/special/c.zig +++ b/lib/std/special/c.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -172,7 +172,7 @@ test "strncmp" { pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { if (builtin.is_test) { @setCold(true); - std.debug.panic("{}", .{msg}); + std.debug.panic("{s}", .{msg}); } if (builtin.os.tag != .freestanding and builtin.os.tag != .other) { std.os.abort(); @@ -634,6 +634,16 @@ export fn cosf(a: f32) f32 { return math.cos(a); } +export fn sincos(a: f64, r_sin: *f64, r_cos: *f64) void { + r_sin.* = math.sin(a); + r_cos.* = math.cos(a); +} + +export fn sincosf(a: f32, r_sin: *f32, r_cos: *f32) void { + r_sin.* = math.sin(a); + r_cos.* = math.cos(a); +} + export fn exp(a: f64) f64 { return math.exp(a); } diff --git a/lib/std/special/compiler_rt.zig b/lib/std/special/compiler_rt.zig index 98d292cce9..4f12d21957 100644 --- a/lib/std/special/compiler_rt.zig +++ b/lib/std/special/compiler_rt.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -167,6 +167,10 @@ comptime { @export(@import("compiler_rt/clzsi2.zig").__clzsi2, .{ .name = "__clzsi2", .linkage = linkage }); + if (builtin.link_libc and builtin.os.tag == .openbsd) { + @export(@import("compiler_rt/emutls.zig").__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = linkage }); + } + if ((builtin.arch.isARM() or builtin.arch.isThumb()) and !is_test) { @export(@import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr0, .{ .name = "__aeabi_unwind_cpp_pr0", .linkage = linkage }); @export(@import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr1, .{ .name = "__aeabi_unwind_cpp_pr1", .linkage = linkage }); @@ -273,6 +277,25 @@ comptime { @export(@import("compiler_rt/aullrem.zig")._aullrem, .{ .name = "\x01__aullrem", .linkage = strong_linkage }); } + if (builtin.arch.isSPARC()) { + // SPARC systems use a different naming scheme + @export(@import("compiler_rt/sparc.zig")._Qp_add, .{ .name = "_Qp_add", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_div, .{ .name = "_Qp_div", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_mul, .{ .name = "_Qp_mul", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_sub, .{ .name = "_Qp_sub", .linkage = linkage }); + + @export(@import("compiler_rt/sparc.zig")._Qp_cmp, .{ .name = "_Qp_cmp", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_feq, .{ .name = "_Qp_feq", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_fne, .{ .name = "_Qp_fne", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_flt, .{ .name = "_Qp_flt", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_fle, .{ .name = "_Qp_fle", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_fgt, .{ .name = "_Qp_fgt", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_fge, .{ .name = "_Qp_fge", .linkage = linkage }); + + @export(@import("compiler_rt/sparc.zig")._Qp_dtoq, .{ .name = "_Qp_dtoq", .linkage = linkage }); + @export(@import("compiler_rt/sparc.zig")._Qp_qtod, .{ .name = "_Qp_qtod", .linkage = linkage }); + } + if (builtin.os.tag == .windows) { // Default stack-probe functions emitted by LLVM if (is_mingw) { @@ -324,7 +347,7 @@ pub usingnamespace @import("compiler_rt/atomics.zig"); pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); if (is_test) { - std.debug.panic("{}", .{msg}); + std.debug.panic("{s}", .{msg}); } else { unreachable; } diff --git a/lib/std/special/compiler_rt/addXf3.zig b/lib/std/special/compiler_rt/addXf3.zig index 27dbd440c2..5a2f3c976c 100644 --- a/lib/std/special/compiler_rt/addXf3.zig +++ b/lib/std/special/compiler_rt/addXf3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/addXf3_test.zig b/lib/std/special/compiler_rt/addXf3_test.zig index 3d75309507..a8f454384c 100644 --- a/lib/std/special/compiler_rt/addXf3_test.zig +++ b/lib/std/special/compiler_rt/addXf3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/arm.zig b/lib/std/special/compiler_rt/arm.zig index 1eecd3ceac..f100f8293c 100644 --- a/lib/std/special/compiler_rt/arm.zig +++ b/lib/std/special/compiler_rt/arm.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -66,10 +66,7 @@ pub fn __aeabi_uidivmod() callconv(.Naked) void { \\ ldr r1, [sp] \\ add sp, #4 \\ pop {pc} - : - : - : "memory" - ); + ::: "memory"); unreachable; } @@ -86,10 +83,7 @@ pub fn __aeabi_uldivmod() callconv(.Naked) void { \\ ldr r3, [sp, #12] \\ add sp, #16 \\ pop {r4, pc} - : - : - : "memory" - ); + ::: "memory"); unreachable; } @@ -104,10 +98,7 @@ pub fn __aeabi_idivmod() callconv(.Naked) void { \\ ldr r1, [sp] \\ add sp, #4 \\ pop {pc} - : - : - : "memory" - ); + ::: "memory"); unreachable; } @@ -124,9 +115,6 @@ pub fn __aeabi_ldivmod() callconv(.Naked) void { \\ ldr r3, [sp, #12] \\ add sp, #16 \\ pop {r4, pc} - : - : - : "memory" - ); + ::: "memory"); unreachable; } diff --git a/lib/std/special/compiler_rt/ashldi3_test.zig b/lib/std/special/compiler_rt/ashldi3_test.zig index 874681a79a..dfc3712e39 100644 --- a/lib/std/special/compiler_rt/ashldi3_test.zig +++ b/lib/std/special/compiler_rt/ashldi3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/ashlti3_test.zig b/lib/std/special/compiler_rt/ashlti3_test.zig index 42cb3a47bb..453fa9e77b 100644 --- a/lib/std/special/compiler_rt/ashlti3_test.zig +++ b/lib/std/special/compiler_rt/ashlti3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/ashrdi3_test.zig b/lib/std/special/compiler_rt/ashrdi3_test.zig index e80b95af9e..77fe286185 100644 --- a/lib/std/special/compiler_rt/ashrdi3_test.zig +++ b/lib/std/special/compiler_rt/ashrdi3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/ashrti3_test.zig b/lib/std/special/compiler_rt/ashrti3_test.zig index 958b8a8a74..5f4e166001 100644 --- a/lib/std/special/compiler_rt/ashrti3_test.zig +++ b/lib/std/special/compiler_rt/ashrti3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/atomics.zig b/lib/std/special/compiler_rt/atomics.zig index cf9854c3c6..cda87236a9 100644 --- a/lib/std/special/compiler_rt/atomics.zig +++ b/lib/std/special/compiler_rt/atomics.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/aulldiv.zig b/lib/std/special/compiler_rt/aulldiv.zig index 321ff288bb..196c218e24 100644 --- a/lib/std/special/compiler_rt/aulldiv.zig +++ b/lib/std/special/compiler_rt/aulldiv.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/aullrem.zig b/lib/std/special/compiler_rt/aullrem.zig index a14eb99be3..7d0eef5921 100644 --- a/lib/std/special/compiler_rt/aullrem.zig +++ b/lib/std/special/compiler_rt/aullrem.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/clear_cache.zig b/lib/std/special/compiler_rt/clear_cache.zig index 4b00721868..568373aabe 100644 --- a/lib/std/special/compiler_rt/clear_cache.zig +++ b/lib/std/special/compiler_rt/clear_cache.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/clzsi2.zig b/lib/std/special/compiler_rt/clzsi2.zig index e4739d47c4..c10786b462 100644 --- a/lib/std/special/compiler_rt/clzsi2.zig +++ b/lib/std/special/compiler_rt/clzsi2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/clzsi2_test.zig b/lib/std/special/compiler_rt/clzsi2_test.zig index 2d9ba3d1b3..2b860afd22 100644 --- a/lib/std/special/compiler_rt/clzsi2_test.zig +++ b/lib/std/special/compiler_rt/clzsi2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/compareXf2.zig b/lib/std/special/compiler_rt/compareXf2.zig index eba6abb003..c903321669 100644 --- a/lib/std/special/compiler_rt/compareXf2.zig +++ b/lib/std/special/compiler_rt/compareXf2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/comparedf2_test.zig b/lib/std/special/compiler_rt/comparedf2_test.zig index 9d681b8f81..f5e8cfe372 100644 --- a/lib/std/special/compiler_rt/comparedf2_test.zig +++ b/lib/std/special/compiler_rt/comparedf2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/comparesf2_test.zig b/lib/std/special/compiler_rt/comparesf2_test.zig index da7fe940a0..0a1f5e74f6 100644 --- a/lib/std/special/compiler_rt/comparesf2_test.zig +++ b/lib/std/special/compiler_rt/comparesf2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divdf3.zig b/lib/std/special/compiler_rt/divdf3.zig index 31d6ff0993..10a548090a 100644 --- a/lib/std/special/compiler_rt/divdf3.zig +++ b/lib/std/special/compiler_rt/divdf3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divdf3_test.zig b/lib/std/special/compiler_rt/divdf3_test.zig index 04cac956d2..8bdecc7c6a 100644 --- a/lib/std/special/compiler_rt/divdf3_test.zig +++ b/lib/std/special/compiler_rt/divdf3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divsf3.zig b/lib/std/special/compiler_rt/divsf3.zig index 779506d85e..3f89f12313 100644 --- a/lib/std/special/compiler_rt/divsf3.zig +++ b/lib/std/special/compiler_rt/divsf3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divsf3_test.zig b/lib/std/special/compiler_rt/divsf3_test.zig index 30dcba462b..a14e8e9163 100644 --- a/lib/std/special/compiler_rt/divsf3_test.zig +++ b/lib/std/special/compiler_rt/divsf3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divtf3.zig b/lib/std/special/compiler_rt/divtf3.zig index 152ffa9926..9c18e79dd5 100644 --- a/lib/std/special/compiler_rt/divtf3.zig +++ b/lib/std/special/compiler_rt/divtf3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divtf3_test.zig b/lib/std/special/compiler_rt/divtf3_test.zig index cf6f2f2eaf..98910e9994 100644 --- a/lib/std/special/compiler_rt/divtf3_test.zig +++ b/lib/std/special/compiler_rt/divtf3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divti3.zig b/lib/std/special/compiler_rt/divti3.zig index a065111510..03bae3f3f8 100644 --- a/lib/std/special/compiler_rt/divti3.zig +++ b/lib/std/special/compiler_rt/divti3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/divti3_test.zig b/lib/std/special/compiler_rt/divti3_test.zig index 18fab24ed1..a20be340c6 100644 --- a/lib/std/special/compiler_rt/divti3_test.zig +++ b/lib/std/special/compiler_rt/divti3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/emutls.zig b/lib/std/special/compiler_rt/emutls.zig new file mode 100644 index 0000000000..2b0fba5b34 --- /dev/null +++ b/lib/std/special/compiler_rt/emutls.zig @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2018 LLVM Compiler Infrastructure +// Copyright (c) 2020 Sebastien Marie <semarie@online.fr> +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +// __emutls_get_address specific builtin +// +// derived work from LLVM Compiler Infrastructure - release 8.0 (MIT) +// https://github.com/llvm-mirror/compiler-rt/blob/release_80/lib/builtins/emutls.c +// + +const std = @import("std"); + +const abort = std.os.abort; +const assert = std.debug.assert; +const expect = std.testing.expect; + +// defined in C as: +// typedef unsigned int gcc_word __attribute__((mode(word))); +const gcc_word = usize; + +comptime { + assert(std.builtin.link_libc); +} + +/// public entrypoint for generated code using EmulatedTLS +pub fn __emutls_get_address(control: *emutls_control) callconv(.C) *c_void { + return control.getPointer(); +} + +/// Simple allocator interface, to avoid pulling in the while +/// std allocator implementation. +const simple_allocator = struct { + /// Allocate a memory chunk for requested type. Return a pointer on the data. + pub fn alloc(comptime T: type) *T { + return @ptrCast(*T, @alignCast( + @alignOf(T), + advancedAlloc(@alignOf(T), @sizeOf(T)), + )); + } + + /// Allocate a slice of T, with len elements. + pub fn allocSlice(comptime T: type, len: usize) []T { + return @ptrCast([*]T, @alignCast( + @alignOf(T), + advancedAlloc(@alignOf(T), @sizeOf(T) * len), + ))[0 .. len - 1]; + } + + /// Allocate a memory chunk. + pub fn advancedAlloc(alignment: u29, size: usize) [*]u8 { + const minimal_alignment = std.math.max(@alignOf(usize), alignment); + + var aligned_ptr: ?*c_void = undefined; + if (std.c.posix_memalign(&aligned_ptr, minimal_alignment, size) != 0) { + abort(); + } + + return @ptrCast([*]u8, aligned_ptr); + } + + /// Resize a slice. + pub fn reallocSlice(comptime T: type, slice: []T, len: usize) []T { + var c_ptr: *c_void = @ptrCast(*c_void, slice.ptr); + var new_array: [*]T = @ptrCast([*]T, @alignCast( + @alignOf(T), + std.c.realloc(c_ptr, @sizeOf(T) * len) orelse abort(), + )); + return new_array[0..len]; + } + + /// Free a memory chunk allocated with simple_allocator. + pub fn free(ptr: anytype) void { + std.c.free(@ptrCast(*c_void, ptr)); + } +}; + +/// Simple array of ?ObjectPointer with automatic resizing and +/// automatic storage allocation. +const ObjectArray = struct { + const ObjectPointer = *c_void; + + // content of the array + slots: []?ObjectPointer, + + /// create a new ObjectArray with n slots. must call deinit() to deallocate. + pub fn init(n: usize) *ObjectArray { + var array = simple_allocator.alloc(ObjectArray); + errdefer simple_allocator.free(array); + + array.* = ObjectArray{ + .slots = simple_allocator.allocSlice(?ObjectPointer, n), + }; + errdefer simple_allocator.free(array.slots); + + for (array.slots) |*object| { + object.* = null; + } + + return array; + } + + /// deallocate the ObjectArray. + pub fn deinit(self: *ObjectArray) void { + // deallocated used objects in the array + for (self.slots) |*object| { + simple_allocator.free(object.*); + } + simple_allocator.free(self.slots); + simple_allocator.free(self); + } + + /// resize the ObjectArray if needed. + pub fn ensureLength(self: *ObjectArray, new_len: usize) *ObjectArray { + const old_len = self.slots.len; + + if (old_len > new_len) { + return self; + } + + // reallocate + self.slots = simple_allocator.reallocSlice(?ObjectPointer, self.slots, new_len); + + // init newly added slots + for (self.slots[old_len..]) |*object| { + object.* = null; + } + + return self; + } + + /// Retrieve the pointer at request index, using control to initialize it if needed. + pub fn getPointer(self: *ObjectArray, index: usize, control: *emutls_control) ObjectPointer { + if (self.slots[index] == null) { + // initialize the slot + const size = control.size; + const alignment = @truncate(u29, control.alignment); + + var data = simple_allocator.advancedAlloc(alignment, size); + errdefer simple_allocator.free(data); + + if (control.default_value) |value| { + // default value: copy the content to newly allocated object. + @memcpy(data, @ptrCast([*]u8, value), size); + } else { + // no default: return zeroed memory. + @memset(data, 0, size); + } + + self.slots[index] = @ptrCast(*c_void, data); + } + + return self.slots[index].?; + } +}; + +// Global stucture for Thread Storage. +// It provides thread-safety for on-demand storage of Thread Objects. +const current_thread_storage = struct { + var key: std.c.pthread_key_t = undefined; + var init_once = std.once(current_thread_storage.init); + + /// Return a per thread ObjectArray with at least the expected index. + pub fn getArray(index: usize) *ObjectArray { + if (current_thread_storage.getspecific()) |array| { + // we already have a specific. just ensure the array is + // big enough for the wanted index. + return array.ensureLength(index); + } + + // no specific. we need to create a new array. + + // make it to contains at least 16 objects (to avoid too much + // reallocation at startup). + const size = std.math.max(16, index); + + // create a new array and store it. + var array: *ObjectArray = ObjectArray.init(size); + current_thread_storage.setspecific(array); + return array; + } + + /// Return casted thread specific value. + fn getspecific() ?*ObjectArray { + return @ptrCast( + ?*ObjectArray, + @alignCast( + @alignOf(ObjectArray), + std.c.pthread_getspecific(current_thread_storage.key), + ), + ); + } + + /// Set casted thread specific value. + fn setspecific(new: ?*ObjectArray) void { + if (std.c.pthread_setspecific(current_thread_storage.key, @ptrCast(*c_void, new)) != 0) { + abort(); + } + } + + /// Initialize pthread_key_t. + fn init() void { + if (std.c.pthread_key_create(¤t_thread_storage.key, current_thread_storage.deinit) != 0) { + abort(); + } + } + + /// Invoked by pthread specific destructor. the passed argument is the ObjectArray pointer. + fn deinit(arrayPtr: *c_void) callconv(.C) void { + var array = @ptrCast( + *ObjectArray, + @alignCast(@alignOf(ObjectArray), arrayPtr), + ); + array.deinit(); + } +}; + +const emutls_control = extern struct { + // A emutls_control value is a global value across all + // threads. The threads shares the index of TLS variable. The data + // array (containing address of allocated variables) is thread + // specific and stored using pthread_setspecific(). + + // size of the object in bytes + size: gcc_word, + + // alignment of the object in bytes + alignment: gcc_word, + + object: extern union { + // data[index-1] is the object address / 0 = uninit + index: usize, + + // object address, when in single thread env (not used) + address: *c_void, + }, + + // null or non-zero initial value for the object + default_value: ?*c_void, + + // global Mutex used to serialize control.index initialization. + var mutex: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER; + + // global counter for keeping track of requested indexes. + // access should be done with mutex held. + var next_index: usize = 1; + + /// Simple wrapper for global lock. + fn lock() void { + if (std.c.pthread_mutex_lock(&emutls_control.mutex) != 0) { + abort(); + } + } + + /// Simple wrapper for global unlock. + fn unlock() void { + if (std.c.pthread_mutex_unlock(&emutls_control.mutex) != 0) { + abort(); + } + } + + /// Helper to retrieve nad initialize global unique index per emutls variable. + pub fn getIndex(self: *emutls_control) usize { + // Two threads could race against the same emutls_control. + + // Use atomic for reading coherent value lockless. + const index_lockless = @atomicLoad(usize, &self.object.index, .Acquire); + + if (index_lockless != 0) { + // index is already initialized, return it. + return index_lockless; + } + + // index is uninitialized: take global lock to avoid possible race. + emutls_control.lock(); + defer emutls_control.unlock(); + + const index_locked = self.object.index; + if (index_locked != 0) { + // we lost a race, but index is already initialized: nothing particular to do. + return index_locked; + } + + // Store a new index atomically (for having coherent index_lockless reading). + @atomicStore(usize, &self.object.index, emutls_control.next_index, .Release); + + // Increment the next available index + emutls_control.next_index += 1; + + return self.object.index; + } + + /// Simple helper for testing purpose. + pub fn init(comptime T: type, default_value: ?*T) emutls_control { + return emutls_control{ + .size = @sizeOf(T), + .alignment = @alignOf(T), + .object = .{ .index = 0 }, + .default_value = @ptrCast(?*c_void, default_value), + }; + } + + /// Get the pointer on allocated storage for emutls variable. + pub fn getPointer(self: *emutls_control) *c_void { + // ensure current_thread_storage initialization is done + current_thread_storage.init_once.call(); + + const index = self.getIndex(); + var array = current_thread_storage.getArray(index); + + return array.getPointer(index - 1, self); + } + + /// Testing helper for retrieving typed pointer. + pub fn get_typed_pointer(self: *emutls_control, comptime T: type) *T { + assert(self.size == @sizeOf(T)); + assert(self.alignment == @alignOf(T)); + return @ptrCast( + *T, + @alignCast(@alignOf(T), self.getPointer()), + ); + } +}; + +test "simple_allocator" { + var data1: *[64]u8 = simple_allocator.alloc([64]u8); + defer simple_allocator.free(data1); + for (data1) |*c| { + c.* = 0xff; + } + + var data2: [*]u8 = simple_allocator.advancedAlloc(@alignOf(u8), 64); + defer simple_allocator.free(data2); + for (data2[0..63]) |*c| { + c.* = 0xff; + } +} + +test "__emutls_get_address zeroed" { + var ctl = emutls_control.init(usize, null); + expect(ctl.object.index == 0); + + // retrieve a variable from ctl + var x = @ptrCast(*usize, @alignCast(@alignOf(usize), __emutls_get_address(&ctl))); + expect(ctl.object.index != 0); // index has been allocated for this ctl + expect(x.* == 0); // storage has been zeroed + + // modify the storage + x.* = 1234; + + // retrieve a variable from ctl (same ctl) + var y = @ptrCast(*usize, @alignCast(@alignOf(usize), __emutls_get_address(&ctl))); + + expect(y.* == 1234); // same content that x.* + expect(x == y); // same pointer +} + +test "__emutls_get_address with default_value" { + var value: usize = 5678; // default value + var ctl = emutls_control.init(usize, &value); + expect(ctl.object.index == 0); + + var x: *usize = @ptrCast(*usize, @alignCast(@alignOf(usize), __emutls_get_address(&ctl))); + expect(ctl.object.index != 0); + expect(x.* == 5678); // storage initialized with default value + + // modify the storage + x.* = 9012; + + expect(value == 5678); // the default value didn't change + + var y = @ptrCast(*usize, @alignCast(@alignOf(usize), __emutls_get_address(&ctl))); + expect(y.* == 9012); // the modified storage persists +} + +test "test default_value with differents sizes" { + const testType = struct { + fn _testType(comptime T: type, value: T) void { + var def: T = value; + var ctl = emutls_control.init(T, &def); + var x = ctl.get_typed_pointer(T); + expect(x.* == value); + } + }._testType; + + testType(usize, 1234); + testType(u32, 1234); + testType(i16, -12); + testType(f64, -12.0); + testType( + @TypeOf("012345678901234567890123456789"), + "012345678901234567890123456789", + ); +} diff --git a/lib/std/special/compiler_rt/extendXfYf2.zig b/lib/std/special/compiler_rt/extendXfYf2.zig index 53783d2b13..c5b93fa51e 100644 --- a/lib/std/special/compiler_rt/extendXfYf2.zig +++ b/lib/std/special/compiler_rt/extendXfYf2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/extendXfYf2_test.zig b/lib/std/special/compiler_rt/extendXfYf2_test.zig index d82a8baf4c..6a3f69d8e9 100644 --- a/lib/std/special/compiler_rt/extendXfYf2_test.zig +++ b/lib/std/special/compiler_rt/extendXfYf2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfdi.zig b/lib/std/special/compiler_rt/fixdfdi.zig index 28de1ecd23..f827f22a4a 100644 --- a/lib/std/special/compiler_rt/fixdfdi.zig +++ b/lib/std/special/compiler_rt/fixdfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfdi_test.zig b/lib/std/special/compiler_rt/fixdfdi_test.zig index 83835106cd..f085bdf665 100644 --- a/lib/std/special/compiler_rt/fixdfdi_test.zig +++ b/lib/std/special/compiler_rt/fixdfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfsi.zig b/lib/std/special/compiler_rt/fixdfsi.zig index 678c75d0c1..2e9fab2297 100644 --- a/lib/std/special/compiler_rt/fixdfsi.zig +++ b/lib/std/special/compiler_rt/fixdfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfsi_test.zig b/lib/std/special/compiler_rt/fixdfsi_test.zig index 8050a1b9c5..1445149546 100644 --- a/lib/std/special/compiler_rt/fixdfsi_test.zig +++ b/lib/std/special/compiler_rt/fixdfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfti.zig b/lib/std/special/compiler_rt/fixdfti.zig index 3d9266ae1f..88072de063 100644 --- a/lib/std/special/compiler_rt/fixdfti.zig +++ b/lib/std/special/compiler_rt/fixdfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixdfti_test.zig b/lib/std/special/compiler_rt/fixdfti_test.zig index 796855b716..3b5bac4b4e 100644 --- a/lib/std/special/compiler_rt/fixdfti_test.zig +++ b/lib/std/special/compiler_rt/fixdfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixint.zig b/lib/std/special/compiler_rt/fixint.zig index 889b599e62..2947154d20 100644 --- a/lib/std/special/compiler_rt/fixint.zig +++ b/lib/std/special/compiler_rt/fixint.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixint_test.zig b/lib/std/special/compiler_rt/fixint_test.zig index 49942e5382..139546c52b 100644 --- a/lib/std/special/compiler_rt/fixint_test.zig +++ b/lib/std/special/compiler_rt/fixint_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfdi.zig b/lib/std/special/compiler_rt/fixsfdi.zig index cc5731946d..9563af1a56 100644 --- a/lib/std/special/compiler_rt/fixsfdi.zig +++ b/lib/std/special/compiler_rt/fixsfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfdi_test.zig b/lib/std/special/compiler_rt/fixsfdi_test.zig index d93c3d4218..7c13d83da5 100644 --- a/lib/std/special/compiler_rt/fixsfdi_test.zig +++ b/lib/std/special/compiler_rt/fixsfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfsi.zig b/lib/std/special/compiler_rt/fixsfsi.zig index 62334574b0..f1a32d9f77 100644 --- a/lib/std/special/compiler_rt/fixsfsi.zig +++ b/lib/std/special/compiler_rt/fixsfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfsi_test.zig b/lib/std/special/compiler_rt/fixsfsi_test.zig index 56c28d91ab..07c080470d 100644 --- a/lib/std/special/compiler_rt/fixsfsi_test.zig +++ b/lib/std/special/compiler_rt/fixsfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfti.zig b/lib/std/special/compiler_rt/fixsfti.zig index 31dea953e8..75c0a2fe1d 100644 --- a/lib/std/special/compiler_rt/fixsfti.zig +++ b/lib/std/special/compiler_rt/fixsfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixsfti_test.zig b/lib/std/special/compiler_rt/fixsfti_test.zig index d0bdcc4e75..dbc30c5404 100644 --- a/lib/std/special/compiler_rt/fixsfti_test.zig +++ b/lib/std/special/compiler_rt/fixsfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfdi.zig b/lib/std/special/compiler_rt/fixtfdi.zig index edf70dbe49..a9e37b777f 100644 --- a/lib/std/special/compiler_rt/fixtfdi.zig +++ b/lib/std/special/compiler_rt/fixtfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfdi_test.zig b/lib/std/special/compiler_rt/fixtfdi_test.zig index b926f33d50..dfc08f84a3 100644 --- a/lib/std/special/compiler_rt/fixtfdi_test.zig +++ b/lib/std/special/compiler_rt/fixtfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfsi.zig b/lib/std/special/compiler_rt/fixtfsi.zig index cf614ec8b3..cd92a972c4 100644 --- a/lib/std/special/compiler_rt/fixtfsi.zig +++ b/lib/std/special/compiler_rt/fixtfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfsi_test.zig b/lib/std/special/compiler_rt/fixtfsi_test.zig index 86207f7dbc..e5605a3936 100644 --- a/lib/std/special/compiler_rt/fixtfsi_test.zig +++ b/lib/std/special/compiler_rt/fixtfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfti.zig b/lib/std/special/compiler_rt/fixtfti.zig index e796b86d50..cfae7c249b 100644 --- a/lib/std/special/compiler_rt/fixtfti.zig +++ b/lib/std/special/compiler_rt/fixtfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixtfti_test.zig b/lib/std/special/compiler_rt/fixtfti_test.zig index 65a64ac431..b01e3af9f9 100644 --- a/lib/std/special/compiler_rt/fixtfti_test.zig +++ b/lib/std/special/compiler_rt/fixtfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixuint.zig b/lib/std/special/compiler_rt/fixuint.zig index e10926177f..755e1b8bb2 100644 --- a/lib/std/special/compiler_rt/fixuint.zig +++ b/lib/std/special/compiler_rt/fixuint.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfdi.zig b/lib/std/special/compiler_rt/fixunsdfdi.zig index 19f94c95a8..24a88236e0 100644 --- a/lib/std/special/compiler_rt/fixunsdfdi.zig +++ b/lib/std/special/compiler_rt/fixunsdfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfdi_test.zig b/lib/std/special/compiler_rt/fixunsdfdi_test.zig index da6fef3376..b7bbe42fb9 100644 --- a/lib/std/special/compiler_rt/fixunsdfdi_test.zig +++ b/lib/std/special/compiler_rt/fixunsdfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfsi.zig b/lib/std/special/compiler_rt/fixunsdfsi.zig index 2f622aff39..416ffc59af 100644 --- a/lib/std/special/compiler_rt/fixunsdfsi.zig +++ b/lib/std/special/compiler_rt/fixunsdfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfsi_test.zig b/lib/std/special/compiler_rt/fixunsdfsi_test.zig index ddbb05c705..a083f97f0b 100644 --- a/lib/std/special/compiler_rt/fixunsdfsi_test.zig +++ b/lib/std/special/compiler_rt/fixunsdfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfti.zig b/lib/std/special/compiler_rt/fixunsdfti.zig index f11f6b937f..02836a6f75 100644 --- a/lib/std/special/compiler_rt/fixunsdfti.zig +++ b/lib/std/special/compiler_rt/fixunsdfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunsdfti_test.zig b/lib/std/special/compiler_rt/fixunsdfti_test.zig index 11ba3bc425..dbfeb0fc4b 100644 --- a/lib/std/special/compiler_rt/fixunsdfti_test.zig +++ b/lib/std/special/compiler_rt/fixunsdfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfdi.zig b/lib/std/special/compiler_rt/fixunssfdi.zig index 5dd9b27d10..77077b4344 100644 --- a/lib/std/special/compiler_rt/fixunssfdi.zig +++ b/lib/std/special/compiler_rt/fixunssfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfdi_test.zig b/lib/std/special/compiler_rt/fixunssfdi_test.zig index 018c94ce7c..d5e04292cb 100644 --- a/lib/std/special/compiler_rt/fixunssfdi_test.zig +++ b/lib/std/special/compiler_rt/fixunssfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfsi.zig b/lib/std/special/compiler_rt/fixunssfsi.zig index ce983cb78a..9c63424629 100644 --- a/lib/std/special/compiler_rt/fixunssfsi.zig +++ b/lib/std/special/compiler_rt/fixunssfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfsi_test.zig b/lib/std/special/compiler_rt/fixunssfsi_test.zig index 98cac8a1e4..c30c1d6804 100644 --- a/lib/std/special/compiler_rt/fixunssfsi_test.zig +++ b/lib/std/special/compiler_rt/fixunssfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfti.zig b/lib/std/special/compiler_rt/fixunssfti.zig index bbaeafd6a8..ab5b95ec7f 100644 --- a/lib/std/special/compiler_rt/fixunssfti.zig +++ b/lib/std/special/compiler_rt/fixunssfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunssfti_test.zig b/lib/std/special/compiler_rt/fixunssfti_test.zig index b5c79906b7..b148f5a35a 100644 --- a/lib/std/special/compiler_rt/fixunssfti_test.zig +++ b/lib/std/special/compiler_rt/fixunssfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfdi.zig b/lib/std/special/compiler_rt/fixunstfdi.zig index 3062e5322c..2053b948e0 100644 --- a/lib/std/special/compiler_rt/fixunstfdi.zig +++ b/lib/std/special/compiler_rt/fixunstfdi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfdi_test.zig b/lib/std/special/compiler_rt/fixunstfdi_test.zig index 299c509cea..b0297d4a2f 100644 --- a/lib/std/special/compiler_rt/fixunstfdi_test.zig +++ b/lib/std/special/compiler_rt/fixunstfdi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfsi.zig b/lib/std/special/compiler_rt/fixunstfsi.zig index 6836e5df36..3c317cd7fe 100644 --- a/lib/std/special/compiler_rt/fixunstfsi.zig +++ b/lib/std/special/compiler_rt/fixunstfsi.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfsi_test.zig b/lib/std/special/compiler_rt/fixunstfsi_test.zig index 2e5139e5e2..f1cb9f6de7 100644 --- a/lib/std/special/compiler_rt/fixunstfsi_test.zig +++ b/lib/std/special/compiler_rt/fixunstfsi_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfti.zig b/lib/std/special/compiler_rt/fixunstfti.zig index da3319ee5c..b089fedd3f 100644 --- a/lib/std/special/compiler_rt/fixunstfti.zig +++ b/lib/std/special/compiler_rt/fixunstfti.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/fixunstfti_test.zig b/lib/std/special/compiler_rt/fixunstfti_test.zig index 2fbde63e63..fcbf9d3b25 100644 --- a/lib/std/special/compiler_rt/fixunstfti_test.zig +++ b/lib/std/special/compiler_rt/fixunstfti_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatXisf.zig b/lib/std/special/compiler_rt/floatXisf.zig index fcbd02239e..4ce97c98f6 100644 --- a/lib/std/special/compiler_rt/floatXisf.zig +++ b/lib/std/special/compiler_rt/floatXisf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatdidf.zig b/lib/std/special/compiler_rt/floatdidf.zig index 2a1ba4cadd..2e07c91dd5 100644 --- a/lib/std/special/compiler_rt/floatdidf.zig +++ b/lib/std/special/compiler_rt/floatdidf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatdidf_test.zig b/lib/std/special/compiler_rt/floatdidf_test.zig index a2072cc922..41b851a306 100644 --- a/lib/std/special/compiler_rt/floatdidf_test.zig +++ b/lib/std/special/compiler_rt/floatdidf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatdisf_test.zig b/lib/std/special/compiler_rt/floatdisf_test.zig index 6676871035..845dc7b1ae 100644 --- a/lib/std/special/compiler_rt/floatdisf_test.zig +++ b/lib/std/special/compiler_rt/floatdisf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatditf.zig b/lib/std/special/compiler_rt/floatditf.zig index aa945ca5dd..a06f66e71e 100644 --- a/lib/std/special/compiler_rt/floatditf.zig +++ b/lib/std/special/compiler_rt/floatditf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatditf_test.zig b/lib/std/special/compiler_rt/floatditf_test.zig index ff4f10927c..13796efd69 100644 --- a/lib/std/special/compiler_rt/floatditf_test.zig +++ b/lib/std/special/compiler_rt/floatditf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatsiXf.zig b/lib/std/special/compiler_rt/floatsiXf.zig index 5941e0ca57..50fcdd748b 100644 --- a/lib/std/special/compiler_rt/floatsiXf.zig +++ b/lib/std/special/compiler_rt/floatsiXf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floattidf.zig b/lib/std/special/compiler_rt/floattidf.zig index 73d86f7747..2fa5fee400 100644 --- a/lib/std/special/compiler_rt/floattidf.zig +++ b/lib/std/special/compiler_rt/floattidf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floattidf_test.zig b/lib/std/special/compiler_rt/floattidf_test.zig index d299ed8087..ab6311c9ff 100644 --- a/lib/std/special/compiler_rt/floattidf_test.zig +++ b/lib/std/special/compiler_rt/floattidf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floattisf_test.zig b/lib/std/special/compiler_rt/floattisf_test.zig index c92db9e150..2458e4bb76 100644 --- a/lib/std/special/compiler_rt/floattisf_test.zig +++ b/lib/std/special/compiler_rt/floattisf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floattitf.zig b/lib/std/special/compiler_rt/floattitf.zig index 87408ea445..a577b6dc10 100644 --- a/lib/std/special/compiler_rt/floattitf.zig +++ b/lib/std/special/compiler_rt/floattitf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floattitf_test.zig b/lib/std/special/compiler_rt/floattitf_test.zig index c4014a6298..3310875ecc 100644 --- a/lib/std/special/compiler_rt/floattitf_test.zig +++ b/lib/std/special/compiler_rt/floattitf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatundidf.zig b/lib/std/special/compiler_rt/floatundidf.zig index a88ca4a03d..e079dabced 100644 --- a/lib/std/special/compiler_rt/floatundidf.zig +++ b/lib/std/special/compiler_rt/floatundidf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatundidf_test.zig b/lib/std/special/compiler_rt/floatundidf_test.zig index c0651cb359..a0e18c4f5a 100644 --- a/lib/std/special/compiler_rt/floatundidf_test.zig +++ b/lib/std/special/compiler_rt/floatundidf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatundisf.zig b/lib/std/special/compiler_rt/floatundisf.zig index 67cd53b21c..ac7e576316 100644 --- a/lib/std/special/compiler_rt/floatundisf.zig +++ b/lib/std/special/compiler_rt/floatundisf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunditf.zig b/lib/std/special/compiler_rt/floatunditf.zig index 014a479c5f..59c433b372 100644 --- a/lib/std/special/compiler_rt/floatunditf.zig +++ b/lib/std/special/compiler_rt/floatunditf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunditf_test.zig b/lib/std/special/compiler_rt/floatunditf_test.zig index 19d1b4a2a3..e734355589 100644 --- a/lib/std/special/compiler_rt/floatunditf_test.zig +++ b/lib/std/special/compiler_rt/floatunditf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunsidf.zig b/lib/std/special/compiler_rt/floatunsidf.zig index c9a31eff8e..1b700b001d 100644 --- a/lib/std/special/compiler_rt/floatunsidf.zig +++ b/lib/std/special/compiler_rt/floatunsidf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunsisf.zig b/lib/std/special/compiler_rt/floatunsisf.zig index 17eae51092..1a0ef47b5c 100644 --- a/lib/std/special/compiler_rt/floatunsisf.zig +++ b/lib/std/special/compiler_rt/floatunsisf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunsitf.zig b/lib/std/special/compiler_rt/floatunsitf.zig index f59446abac..3cdadfc07e 100644 --- a/lib/std/special/compiler_rt/floatunsitf.zig +++ b/lib/std/special/compiler_rt/floatunsitf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatunsitf_test.zig b/lib/std/special/compiler_rt/floatunsitf_test.zig index deb95ca396..7e7b8b69b9 100644 --- a/lib/std/special/compiler_rt/floatunsitf_test.zig +++ b/lib/std/special/compiler_rt/floatunsitf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntidf.zig b/lib/std/special/compiler_rt/floatuntidf.zig index adb804d0ec..6e1fe3b117 100644 --- a/lib/std/special/compiler_rt/floatuntidf.zig +++ b/lib/std/special/compiler_rt/floatuntidf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntidf_test.zig b/lib/std/special/compiler_rt/floatuntidf_test.zig index cce3893860..427c7a08f2 100644 --- a/lib/std/special/compiler_rt/floatuntidf_test.zig +++ b/lib/std/special/compiler_rt/floatuntidf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntisf.zig b/lib/std/special/compiler_rt/floatuntisf.zig index d0c9a76562..dd173945ba 100644 --- a/lib/std/special/compiler_rt/floatuntisf.zig +++ b/lib/std/special/compiler_rt/floatuntisf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntisf_test.zig b/lib/std/special/compiler_rt/floatuntisf_test.zig index 42379d8084..78d45dc5e0 100644 --- a/lib/std/special/compiler_rt/floatuntisf_test.zig +++ b/lib/std/special/compiler_rt/floatuntisf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntitf.zig b/lib/std/special/compiler_rt/floatuntitf.zig index c87ff50e9a..9759268b93 100644 --- a/lib/std/special/compiler_rt/floatuntitf.zig +++ b/lib/std/special/compiler_rt/floatuntitf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/floatuntitf_test.zig b/lib/std/special/compiler_rt/floatuntitf_test.zig index 62c9b631df..fd57be51e6 100644 --- a/lib/std/special/compiler_rt/floatuntitf_test.zig +++ b/lib/std/special/compiler_rt/floatuntitf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/int.zig b/lib/std/special/compiler_rt/int.zig index 1fb2c263e1..b852139516 100644 --- a/lib/std/special/compiler_rt/int.zig +++ b/lib/std/special/compiler_rt/int.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/lshrdi3_test.zig b/lib/std/special/compiler_rt/lshrdi3_test.zig index de83f0a9c8..5443fd9bce 100644 --- a/lib/std/special/compiler_rt/lshrdi3_test.zig +++ b/lib/std/special/compiler_rt/lshrdi3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/lshrti3_test.zig b/lib/std/special/compiler_rt/lshrti3_test.zig index f831e8b132..bfd812f028 100644 --- a/lib/std/special/compiler_rt/lshrti3_test.zig +++ b/lib/std/special/compiler_rt/lshrti3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/modti3.zig b/lib/std/special/compiler_rt/modti3.zig index 9c3de44395..298a488dc2 100644 --- a/lib/std/special/compiler_rt/modti3.zig +++ b/lib/std/special/compiler_rt/modti3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/modti3_test.zig b/lib/std/special/compiler_rt/modti3_test.zig index cad60c015e..644c9027b7 100644 --- a/lib/std/special/compiler_rt/modti3_test.zig +++ b/lib/std/special/compiler_rt/modti3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/mulXf3.zig b/lib/std/special/compiler_rt/mulXf3.zig index d8dac5b1bc..a4c71529d1 100644 --- a/lib/std/special/compiler_rt/mulXf3.zig +++ b/lib/std/special/compiler_rt/mulXf3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/mulXf3_test.zig b/lib/std/special/compiler_rt/mulXf3_test.zig index c9994b089b..272c96522d 100644 --- a/lib/std/special/compiler_rt/mulXf3_test.zig +++ b/lib/std/special/compiler_rt/mulXf3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/muldi3.zig b/lib/std/special/compiler_rt/muldi3.zig index 2de96ea66c..607ac489fc 100644 --- a/lib/std/special/compiler_rt/muldi3.zig +++ b/lib/std/special/compiler_rt/muldi3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/muldi3_test.zig b/lib/std/special/compiler_rt/muldi3_test.zig index b4962189cd..78023f514b 100644 --- a/lib/std/special/compiler_rt/muldi3_test.zig +++ b/lib/std/special/compiler_rt/muldi3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/mulodi4.zig b/lib/std/special/compiler_rt/mulodi4.zig index fab345fa47..ed90b4d382 100644 --- a/lib/std/special/compiler_rt/mulodi4.zig +++ b/lib/std/special/compiler_rt/mulodi4.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/mulodi4_test.zig b/lib/std/special/compiler_rt/mulodi4_test.zig index a96f7a1996..7d7658e192 100644 --- a/lib/std/special/compiler_rt/mulodi4_test.zig +++ b/lib/std/special/compiler_rt/mulodi4_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/muloti4.zig b/lib/std/special/compiler_rt/muloti4.zig index b1ad82da29..30054ac751 100644 --- a/lib/std/special/compiler_rt/muloti4.zig +++ b/lib/std/special/compiler_rt/muloti4.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/muloti4_test.zig b/lib/std/special/compiler_rt/muloti4_test.zig index 44ab93a069..83722df6a5 100644 --- a/lib/std/special/compiler_rt/muloti4_test.zig +++ b/lib/std/special/compiler_rt/muloti4_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/multi3.zig b/lib/std/special/compiler_rt/multi3.zig index fad73789ac..d417c79ff2 100644 --- a/lib/std/special/compiler_rt/multi3.zig +++ b/lib/std/special/compiler_rt/multi3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/multi3_test.zig b/lib/std/special/compiler_rt/multi3_test.zig index 04b70d0538..674cf1cb9b 100644 --- a/lib/std/special/compiler_rt/multi3_test.zig +++ b/lib/std/special/compiler_rt/multi3_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/negXf2.zig b/lib/std/special/compiler_rt/negXf2.zig index 389b26584d..8c7010cccb 100644 --- a/lib/std/special/compiler_rt/negXf2.zig +++ b/lib/std/special/compiler_rt/negXf2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/popcountdi2.zig b/lib/std/special/compiler_rt/popcountdi2.zig index 5bb49ce402..8495068339 100644 --- a/lib/std/special/compiler_rt/popcountdi2.zig +++ b/lib/std/special/compiler_rt/popcountdi2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/popcountdi2_test.zig b/lib/std/special/compiler_rt/popcountdi2_test.zig index 6ea181bf0e..d0665bf278 100644 --- a/lib/std/special/compiler_rt/popcountdi2_test.zig +++ b/lib/std/special/compiler_rt/popcountdi2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/shift.zig b/lib/std/special/compiler_rt/shift.zig index 21e52d9db8..46712738ab 100644 --- a/lib/std/special/compiler_rt/shift.zig +++ b/lib/std/special/compiler_rt/shift.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -124,7 +124,7 @@ pub fn __aeabi_llsr(a: i64, b: i32) callconv(.AAPCS) i64 { return __lshrdi3(a, b); } -test "" { +test { _ = @import("ashrdi3_test.zig"); _ = @import("ashrti3_test.zig"); diff --git a/lib/std/special/compiler_rt/sparc.zig b/lib/std/special/compiler_rt/sparc.zig new file mode 100644 index 0000000000..e66bb25886 --- /dev/null +++ b/lib/std/special/compiler_rt/sparc.zig @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +// +// SPARC uses a different naming scheme for its support routines so we map it here to the x86 name. + +const std = @import("std"); +const builtin = @import("builtin"); + +// The SPARC Architecture Manual, Version 9: +// A.13 Floating-Point Compare +const FCMP = extern enum(i32) { + Equal = 0, + Less = 1, + Greater = 2, + Unordered = 3, +}; + +// Basic arithmetic + +pub fn _Qp_add(c: *f128, a: *f128, b: *f128) callconv(.C) void { + c.* = @import("addXf3.zig").__addtf3(a.*, b.*); +} + +pub fn _Qp_div(c: *f128, a: *f128, b: *f128) callconv(.C) void { + c.* = @import("divtf3.zig").__divtf3(a.*, b.*); +} + +pub fn _Qp_mul(c: *f128, a: *f128, b: *f128) callconv(.C) void { + c.* = @import("mulXf3.zig").__multf3(a.*, b.*); +} + +pub fn _Qp_sub(c: *f128, a: *f128, b: *f128) callconv(.C) void { + c.* = @import("addXf3.zig").__subtf3(a.*, b.*); +} + +// Comparison + +pub fn _Qp_cmp(a: *f128, b: *f128) callconv(.C) i32 { + return @enumToInt(@import("compareXf2.zig").cmp(f128, FCMP, a.*, b.*)); +} + +pub fn _Qp_feq(a: *f128, b: *f128) callconv(.C) bool { + return _Qp_cmp(a, b) == @enumToInt(FCMP.Equal); +} + +pub fn _Qp_fne(a: *f128, b: *f128) callconv(.C) bool { + return _Qp_cmp(a, b) != @enumToInt(FCMP.Equal); +} + +pub fn _Qp_flt(a: *f128, b: *f128) callconv(.C) bool { + return _Qp_cmp(a, b) == @enumToInt(FCMP.Less); +} + +pub fn _Qp_fle(a: *f128, b: *f128) callconv(.C) bool { + const cmp = _Qp_cmp(a, b); + return cmp == @enumToInt(FCMP.Less) or cmp == @enumToInt(FCMP.Equal); +} + +pub fn _Qp_fgt(a: *f128, b: *f128) callconv(.C) bool { + return _Qp_cmp(a, b) == @enumToInt(FCMP.Greater); +} + +pub fn _Qp_fge(a: *f128, b: *f128) callconv(.C) bool { + const cmp = _Qp_cmp(a, b); + return cmp == @enumToInt(FCMP.Greater) or cmp == @enumToInt(FCMP.Equal); +} + +// Casting + +pub fn _Qp_dtoq(c: *f128, a: f64) callconv(.C) void { + c.* = @import("extendXfYf2.zig").__extenddftf2(a); +} + +pub fn _Qp_qtod(a: *f128) callconv(.C) f64 { + return @import("truncXfYf2.zig").__trunctfdf2(a.*); +} diff --git a/lib/std/special/compiler_rt/stack_probe.zig b/lib/std/special/compiler_rt/stack_probe.zig index 58cce9fb6f..d0dcd70550 100644 --- a/lib/std/special/compiler_rt/stack_probe.zig +++ b/lib/std/special/compiler_rt/stack_probe.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/truncXfYf2.zig b/lib/std/special/compiler_rt/truncXfYf2.zig index a3885d1211..470ac17c2c 100644 --- a/lib/std/special/compiler_rt/truncXfYf2.zig +++ b/lib/std/special/compiler_rt/truncXfYf2.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/truncXfYf2_test.zig b/lib/std/special/compiler_rt/truncXfYf2_test.zig index 048005f86d..6426614b07 100644 --- a/lib/std/special/compiler_rt/truncXfYf2_test.zig +++ b/lib/std/special/compiler_rt/truncXfYf2_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/udivmod.zig b/lib/std/special/compiler_rt/udivmod.zig index aa3ca7ea30..265a365dc8 100644 --- a/lib/std/special/compiler_rt/udivmod.zig +++ b/lib/std/special/compiler_rt/udivmod.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/udivmoddi4_test.zig b/lib/std/special/compiler_rt/udivmoddi4_test.zig index 74a4a828e8..d3f39a0589 100644 --- a/lib/std/special/compiler_rt/udivmoddi4_test.zig +++ b/lib/std/special/compiler_rt/udivmoddi4_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/udivmodti4.zig b/lib/std/special/compiler_rt/udivmodti4.zig index ff33a35680..310f4dce42 100644 --- a/lib/std/special/compiler_rt/udivmodti4.zig +++ b/lib/std/special/compiler_rt/udivmodti4.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/udivmodti4_test.zig b/lib/std/special/compiler_rt/udivmodti4_test.zig index 1852d5c7af..667b27f0aa 100644 --- a/lib/std/special/compiler_rt/udivmodti4_test.zig +++ b/lib/std/special/compiler_rt/udivmodti4_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/udivti3.zig b/lib/std/special/compiler_rt/udivti3.zig index 187b4a1577..8d95624edc 100644 --- a/lib/std/special/compiler_rt/udivti3.zig +++ b/lib/std/special/compiler_rt/udivti3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/compiler_rt/umodti3.zig b/lib/std/special/compiler_rt/umodti3.zig index 3bc5be8ffc..98160039a1 100644 --- a/lib/std/special/compiler_rt/umodti3.zig +++ b/lib/std/special/compiler_rt/umodti3.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/ssp.zig b/lib/std/special/ssp.zig index 255487744d..81db44a534 100644 --- a/lib/std/special/ssp.zig +++ b/lib/std/special/ssp.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index 2b2fe78262..f5a93298b5 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -11,7 +11,15 @@ pub const io_mode: io.Mode = builtin.test_io_mode; var log_err_count: usize = 0; +var args_buffer: [std.fs.MAX_PATH_BYTES + std.mem.page_size]u8 = undefined; +var args_allocator = std.heap.FixedBufferAllocator.init(&args_buffer); + pub fn main() anyerror!void { + const args = std.process.argsAlloc(&args_allocator.allocator) catch { + @panic("Too many bytes passed over the CLI to the test runner"); + }; + std.testing.zig_exe_path = args[1]; + const test_fn_list = builtin.test_functions; var ok_count: usize = 0; var skip_count: usize = 0; @@ -36,11 +44,11 @@ pub fn main() anyerror!void { } std.testing.log_level = .warn; - var test_node = root_node.start(test_fn.name, null); + var test_node = root_node.start(test_fn.name, 0); test_node.activate(); progress.refresh(); if (progress.terminal == null) { - std.debug.print("{}/{} {}... ", .{ i + 1, test_fn_list.len, test_fn.name }); + std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name }); } const result = if (test_fn.async_frame_size) |size| switch (io_mode) { .evented => blk: { @@ -54,7 +62,7 @@ pub fn main() anyerror!void { .blocking => { skip_count += 1; test_node.end(); - progress.log("{}...SKIP (async test)\n", .{test_fn.name}); + progress.log("{s}...SKIP (async test)\n", .{test_fn.name}); if (progress.terminal == null) std.debug.print("SKIP (async test)\n", .{}); continue; }, @@ -67,7 +75,7 @@ pub fn main() anyerror!void { error.SkipZigTest => { skip_count += 1; test_node.end(); - progress.log("{}...SKIP\n", .{test_fn.name}); + progress.log("{s}...SKIP\n", .{test_fn.name}); if (progress.terminal == null) std.debug.print("SKIP\n", .{}); }, else => { @@ -78,15 +86,15 @@ pub fn main() anyerror!void { } root_node.end(); if (ok_count == test_fn_list.len) { - std.debug.print("All {} tests passed.\n", .{ok_count}); + std.debug.print("All {d} tests passed.\n", .{ok_count}); } else { - std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count }); + std.debug.print("{d} passed; {d} skipped.\n", .{ ok_count, skip_count }); } if (log_err_count != 0) { - std.debug.print("{} errors were logged.\n", .{log_err_count}); + std.debug.print("{d} errors were logged.\n", .{log_err_count}); } if (leaks != 0) { - std.debug.print("{} tests leaked memory.\n", .{leaks}); + std.debug.print("{d} tests leaked memory.\n", .{leaks}); } if (leaks != 0 or log_err_count != 0) { std.process.exit(1); @@ -103,6 +111,6 @@ pub fn log( log_err_count += 1; } if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) { - std.debug.print("[{}] ({}): " ++ format ++ "\n", .{ @tagName(scope), @tagName(message_level) } ++ args); + std.debug.print("[{s}] ({s}): " ++ format ++ "\n", .{ @tagName(scope), @tagName(message_level) } ++ args); } } diff --git a/lib/std/spinlock.zig b/lib/std/spinlock.zig deleted file mode 100644 index d72ac14ecf..0000000000 --- a/lib/std/spinlock.zig +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const builtin = @import("builtin"); - -pub const SpinLock = struct { - state: State, - - const State = enum(u8) { - Unlocked, - Locked, - }; - - pub const Held = struct { - spinlock: *SpinLock, - - pub fn release(self: Held) void { - @atomicStore(State, &self.spinlock.state, .Unlocked, .Release); - } - }; - - pub fn init() SpinLock { - return SpinLock{ .state = .Unlocked }; - } - - pub fn deinit(self: *SpinLock) void { - self.* = undefined; - } - - pub fn tryAcquire(self: *SpinLock) ?Held { - return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) { - .Unlocked => Held{ .spinlock = self }, - .Locked => null, - }; - } - - pub fn acquire(self: *SpinLock) Held { - while (true) { - return self.tryAcquire() orelse { - yield(); - continue; - }; - } - } - - pub fn yield() void { - // On native windows, SwitchToThread is too expensive, - // and yielding for 380-410 iterations was found to be - // a nice sweet spot. Posix systems on the other hand, - // especially linux, perform better by yielding the thread. - switch (builtin.os.tag) { - .windows => loopHint(400), - else => std.os.sched_yield() catch loopHint(1), - } - } - - /// Hint to the cpu that execution is spinning - /// for the given amount of iterations. - pub fn loopHint(iterations: usize) void { - var i = iterations; - while (i != 0) : (i -= 1) { - switch (builtin.arch) { - // these instructions use a memory clobber as they - // flush the pipeline of any speculated reads/writes. - .i386, .x86_64 => asm volatile ("pause" - : - : - : "memory" - ), - .arm, .aarch64 => asm volatile ("yield" - : - : - : "memory" - ), - else => std.os.sched_yield() catch {}, - } - } - } -}; - -test "spinlock" { - var lock = SpinLock.init(); - defer lock.deinit(); - - const held = lock.acquire(); - defer held.release(); -} diff --git a/lib/std/start.zig b/lib/std/start.zig index 7d3a9c45c7..0fb96c768f 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -10,6 +10,7 @@ const std = @import("std.zig"); const builtin = std.builtin; const assert = std.debug.assert; const uefi = std.os.uefi; +const tlcsprng = @import("crypto/tlcsprng.zig"); var argc_argv_ptr: [*]usize = undefined; @@ -261,11 +262,11 @@ const bad_main_ret = "expected return type of main to be 'void', '!void', 'noret // This is marked inline because for some reason LLVM in release mode fails to inline it, // and we want fewer call frames in stack traces. -inline fn initEventLoopAndCallMain() u8 { +fn initEventLoopAndCallMain() callconv(.Inline) u8 { if (std.event.Loop.instance) |loop| { if (!@hasDecl(root, "event_loop")) { loop.init() catch |err| { - std.log.err("{}", .{@errorName(err)}); + std.log.err("{s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } @@ -290,11 +291,11 @@ inline fn initEventLoopAndCallMain() u8 { // and we want fewer call frames in stack traces. // TODO This function is duplicated from initEventLoopAndCallMain instead of using generics // because it is working around stage1 compiler bugs. -inline fn initEventLoopAndCallWinMain() std.os.windows.INT { +fn initEventLoopAndCallWinMain() callconv(.Inline) std.os.windows.INT { if (std.event.Loop.instance) |loop| { if (!@hasDecl(root, "event_loop")) { loop.init() catch |err| { - std.log.err("{}", .{@errorName(err)}); + std.log.err("{s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } @@ -342,7 +343,7 @@ pub fn callMain() u8 { }, .ErrorUnion => { const result = root.main() catch |err| { - std.log.err("{}", .{@errorName(err)}); + std.log.err("{s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } diff --git a/lib/std/start_windows_tls.zig b/lib/std/start_windows_tls.zig index 18c016d206..1ad10126d2 100644 --- a/lib/std/start_windows_tls.zig +++ b/lib/std/start_windows_tls.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/std.zig b/lib/std/std.zig index f6da7afc55..c0d97a9d9c 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -8,13 +8,11 @@ pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged; pub const ArrayList = @import("array_list.zig").ArrayList; pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; -pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; pub const AutoHashMap = hash_map.AutoHashMap; pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; -pub const AutoResetEvent = @import("auto_reset_event.zig").AutoResetEvent; pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const ChildProcess = @import("child_process.zig").ChildProcess; @@ -22,25 +20,22 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM pub const DynLib = @import("dynamic_library.zig").DynLib; pub const HashMap = hash_map.HashMap; pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; -pub const mutex = @import("mutex.zig"); -pub const Mutex = mutex.Mutex; +pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; -pub const Progress = @import("progress.zig").Progress; -pub const ResetEvent = @import("reset_event.zig").ResetEvent; +pub const Progress = @import("Progress.zig"); pub const SemanticVersion = @import("SemanticVersion.zig"); pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; -pub const SpinLock = @import("spinlock.zig").SpinLock; pub const StringHashMap = hash_map.StringHashMap; pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; pub const TailQueue = @import("linked_list.zig").TailQueue; pub const Target = @import("target.zig").Target; -pub const Thread = @import("thread.zig").Thread; +pub const Thread = @import("Thread.zig"); pub const array_hash_map = @import("array_hash_map.zig"); pub const atomic = @import("atomic.zig"); @@ -83,6 +78,7 @@ pub const testing = @import("testing.zig"); pub const time = @import("time.zig"); pub const unicode = @import("unicode.zig"); pub const valgrind = @import("valgrind.zig"); +pub const wasm = @import("wasm.zig"); pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); @@ -92,6 +88,37 @@ comptime { _ = start; } -test "" { - testing.refAllDecls(@This()); +test { + if (builtin.os.tag == .windows) { + // We only test the Windows-relevant stuff to save memory because the CI + // server is hitting OOM. TODO revert this after stage2 arrives. + _ = ChildProcess; + _ = DynLib; + _ = Progress; + _ = Target; + _ = Thread; + + _ = atomic; + _ = build; + _ = builtin; + _ = debug; + _ = event; + _ = fs; + _ = heap; + _ = io; + _ = log; + _ = macho; + _ = net; + _ = os; + _ = once; + _ = pdb; + _ = process; + _ = testing; + _ = time; + _ = unicode; + _ = zig; + _ = start; + } else { + testing.refAllDecls(@This()); + } } diff --git a/lib/std/target.zig b/lib/std/target.zig index 08fecd7a82..ff7ee5d33c 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -57,6 +57,9 @@ pub const Target = struct { wasi, emscripten, uefi, + opencl, + glsl450, + vulkan, other, pub fn isDarwin(tag: Tag) bool { @@ -95,7 +98,7 @@ pub const Target = struct { win7 = 0x06010000, win8 = 0x06020000, win8_1 = 0x06030000, - win10 = 0x0A000000, + win10 = 0x0A000000, //aka win10_th1 win10_th2 = 0x0A000001, win10_rs1 = 0x0A000002, win10_rs2 = 0x0A000003, @@ -103,11 +106,34 @@ pub const Target = struct { win10_rs4 = 0x0A000005, win10_rs5 = 0x0A000006, win10_19h1 = 0x0A000007, - win10_20h1 = 0x0A000008, + win10_vb = 0x0A000008, //aka win10_19h2 + win10_mn = 0x0A000009, //aka win10_20h1 + win10_fe = 0x0A00000A, //aka win10_20h2 _, /// Latest Windows version that the Zig Standard Library is aware of - pub const latest = WindowsVersion.win10_20h1; + pub const latest = WindowsVersion.win10_fe; + + /// Compared against build numbers reported by the runtime to distinguish win10 versions, + /// where 0x0A000000 + index corresponds to the WindowsVersion u32 value. + pub const known_win10_build_numbers = [_]u32{ + 10240, //win10 aka win10_th1 + 10586, //win10_th2 + 14393, //win10_rs1 + 15063, //win10_rs2 + 16299, //win10_rs3 + 17134, //win10_rs4 + 17763, //win10_rs5 + 18362, //win10_19h1 + 18363, //win10_vb aka win10_19h2 + 19041, //win10_mn aka win10_20h1 + 19042, //win10_fe aka win10_20h2 + }; + + /// Returns whether the first version `self` is newer (greater) than or equal to the second version `ver`. + pub fn isAtLeast(self: WindowsVersion, ver: WindowsVersion) bool { + return @enumToInt(self) >= @enumToInt(ver); + } pub const Range = struct { min: WindowsVersion, @@ -136,14 +162,14 @@ pub const Target = struct { ) !void { if (fmt.len > 0 and fmt[0] == 's') { if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.latest)) { - try std.fmt.format(out_stream, ".{}", .{@tagName(self)}); + try std.fmt.format(out_stream, ".{s}", .{@tagName(self)}); } else { // TODO this code path breaks zig triples, but it is used in `builtin` try std.fmt.format(out_stream, "@intToEnum(Target.Os.WindowsVersion, 0x{X:0>8})", .{@enumToInt(self)}); } } else { if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.latest)) { - try std.fmt.format(out_stream, "WindowsVersion.{}", .{@tagName(self)}); + try std.fmt.format(out_stream, "WindowsVersion.{s}", .{@tagName(self)}); } else { try std.fmt.format(out_stream, "WindowsVersion(0x{X:0>8})", .{@enumToInt(self)}); } @@ -225,6 +251,9 @@ pub const Target = struct { .wasi, .emscripten, .uefi, + .opencl, // TODO: OpenCL versions + .glsl450, // TODO: GLSL versions + .vulkan, .other, => return .{ .none = {} }, @@ -237,7 +266,7 @@ pub const Target = struct { .macos => return .{ .semver = .{ .min = .{ .major = 10, .minor = 13 }, - .max = .{ .major = 10, .minor = 15, .patch = 7 }, + .max = .{ .major = 11, .minor = 1 }, }, }, .ios => return .{ @@ -337,6 +366,9 @@ pub const Target = struct { }; } + /// On Darwin, we always link libSystem which contains libc. + /// Similarly on FreeBSD and NetBSD we always link system libc + /// since this is the stable syscall interface. pub fn requiresLibC(os: Os) bool { return switch (os.tag) { .freebsd, @@ -347,6 +379,7 @@ pub const Target = struct { .watchos, .dragonfly, .openbsd, + .haiku, => true, .linux, @@ -359,7 +392,6 @@ pub const Target = struct { .lv2, .solaris, .zos, - .haiku, .minix, .rtems, .nacl, @@ -377,6 +409,9 @@ pub const Target = struct { .wasi, .emscripten, .uefi, + .opencl, + .glsl450, + .vulkan, .other, => false, }; @@ -395,6 +430,7 @@ pub const Target = struct { pub const powerpc = @import("target/powerpc.zig"); pub const riscv = @import("target/riscv.zig"); pub const sparc = @import("target/sparc.zig"); + pub const spirv = @import("target/spirv.zig"); pub const systemz = @import("target/systemz.zig"); pub const wasm = @import("target/wasm.zig"); pub const x86 = @import("target/x86.zig"); @@ -433,7 +469,6 @@ pub const Target = struct { .lv2, .solaris, .zos, - .haiku, .minix, .rtems, .nacl, @@ -459,6 +494,7 @@ pub const Target = struct { .kfreebsd, .netbsd, .hurd, + .haiku, => return .gnu, .windows, .uefi, @@ -467,6 +503,10 @@ pub const Target = struct { .wasi, .emscripten, => return .musl, + .opencl, // TODO: SPIR-V ABIs with Linkage capability + .glsl450, + .vulkan, + => return .none, } } @@ -502,6 +542,7 @@ pub const Target = struct { macho, wasm, c, + spirv, hex, raw, }; @@ -719,6 +760,8 @@ pub const Target = struct { // Stage1 currently assumes that architectures above this comment // map one-to-one with the ZigLLVM_ArchType enum. spu_2, + spirv32, + spirv64, pub fn isARM(arch: Arch) bool { return switch (arch) { @@ -833,6 +876,8 @@ pub const Target = struct { .s390x => ._S390, .ve => ._NONE, .spu_2 => ._SPU_2, + .spirv32 => ._NONE, + .spirv64 => ._NONE, }; } @@ -891,6 +936,8 @@ pub const Target = struct { .s390x => .Unknown, .ve => .Unknown, .spu_2 => .Unknown, + .spirv32 => .Unknown, + .spirv64 => .Unknown, }; } @@ -935,6 +982,9 @@ pub const Target = struct { .shave, .ve, .spu_2, + // GPU bitness is opaque. For now, assume little endian. + .spirv32, + .spirv64, => .Little, .arc, @@ -991,6 +1041,7 @@ pub const Target = struct { .wasm32, .renderscript32, .aarch64_32, + .spirv32, => return 32, .aarch64, @@ -1014,6 +1065,7 @@ pub const Target = struct { .sparcv9, .s390x, .ve, + .spirv64, => return 64, } } @@ -1037,6 +1089,7 @@ pub const Target = struct { .i386, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", .wasm32, .wasm64 => "wasm", + .spirv32, .spirv64 => "spir-v", else => @tagName(arch), }; } @@ -1180,7 +1233,7 @@ pub const Target = struct { } pub fn linuxTripleSimple(allocator: *mem.Allocator, cpu_arch: Cpu.Arch, os_tag: Os.Tag, abi: Abi) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi) }); + return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi) }); } pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 { @@ -1327,6 +1380,9 @@ pub const Target = struct { .uefi, .windows, .emscripten, + .opencl, + .glsl450, + .vulkan, .other, => return false, else => return true, @@ -1384,7 +1440,7 @@ pub const Target = struct { if (self.abi == .android) { const suffix = if (self.cpu.arch.ptrBitWidth() == 64) "64" else ""; - return print(&result, "/system/bin/linker{}", .{suffix}); + return print(&result, "/system/bin/linker{s}", .{suffix}); } if (self.abi.isMusl()) { @@ -1398,7 +1454,7 @@ pub const Target = struct { else => |arch| @tagName(arch), }; const arch_suffix = if (is_arm and self.abi.floatAbi() == .hard) "hf" else ""; - return print(&result, "/lib/ld-musl-{}{}.so.1", .{ arch_part, arch_suffix }); + return print(&result, "/lib/ld-musl-{s}{s}.so.1", .{ arch_part, arch_suffix }); } switch (self.os.tag) { @@ -1437,7 +1493,7 @@ pub const Target = struct { }; const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008); const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1"; - return print(&result, "/lib{}/{}", .{ lib_suffix, loader }); + return print(&result, "/lib{s}/{s}", .{ lib_suffix, loader }); }, .powerpc => return copy(&result, "/lib/ld.so.1"), @@ -1462,6 +1518,8 @@ pub const Target = struct { .nvptx64, .spu_2, .avr, + .spirv32, + .spirv64, => return result, // TODO go over each item in this list and either move it to the above list, or @@ -1505,9 +1563,15 @@ pub const Target = struct { .windows, .emscripten, .wasi, + .opencl, + .glsl450, + .vulkan, .other, => return result, + // TODO revisit when multi-arch for Haiku is available + .haiku => return copy(&result, "/system/runtime_loader"), + // TODO go over each item in this list and either move it to the above list, or // implement the standard dynamic linker path code for it. .ananas, @@ -1517,7 +1581,6 @@ pub const Target = struct { .lv2, .solaris, .zos, - .haiku, .minix, .rtems, .nacl, @@ -1558,6 +1621,6 @@ pub const Target = struct { } }; -test "" { +test { std.testing.refAllDecls(Target.Cpu.Arch); } diff --git a/lib/std/target/aarch64.zig b/lib/std/target/aarch64.zig index abf7f09943..b64f04d7ad 100644 --- a/lib/std/target/aarch64.zig +++ b/lib/std/target/aarch64.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/amdgpu.zig b/lib/std/target/amdgpu.zig index 368bd80dc8..135446c7df 100644 --- a/lib/std/target/amdgpu.zig +++ b/lib/std/target/amdgpu.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/arm.zig b/lib/std/target/arm.zig index 7243fc3805..b717120de3 100644 --- a/lib/std/target/arm.zig +++ b/lib/std/target/arm.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/avr.zig b/lib/std/target/avr.zig index 4517fd5abe..9513c1861f 100644 --- a/lib/std/target/avr.zig +++ b/lib/std/target/avr.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/bpf.zig b/lib/std/target/bpf.zig index 5e23c233c8..73287ec6a8 100644 --- a/lib/std/target/bpf.zig +++ b/lib/std/target/bpf.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/hexagon.zig b/lib/std/target/hexagon.zig index 34bbf70bb4..b1f565f52d 100644 --- a/lib/std/target/hexagon.zig +++ b/lib/std/target/hexagon.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/mips.zig b/lib/std/target/mips.zig index ccc207ff0f..59da13ac39 100644 --- a/lib/std/target/mips.zig +++ b/lib/std/target/mips.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/msp430.zig b/lib/std/target/msp430.zig index 38ea358f90..c1005a1d81 100644 --- a/lib/std/target/msp430.zig +++ b/lib/std/target/msp430.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/nvptx.zig b/lib/std/target/nvptx.zig index 9a35edc7e9..b025fbfcf7 100644 --- a/lib/std/target/nvptx.zig +++ b/lib/std/target/nvptx.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/powerpc.zig b/lib/std/target/powerpc.zig index ba59ee5da9..a2040e70b9 100644 --- a/lib/std/target/powerpc.zig +++ b/lib/std/target/powerpc.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -792,7 +792,7 @@ pub const cpu = struct { }; pub const ppc32 = CpuModel{ .name = "ppc32", - .llvm_name = "ppc32", + .llvm_name = "ppc", .features = featureSet(&[_]Feature{ .hard_float, }), diff --git a/lib/std/target/riscv.zig b/lib/std/target/riscv.zig index 3e26f0a094..a791f0105b 100644 --- a/lib/std/target/riscv.zig +++ b/lib/std/target/riscv.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/sparc.zig b/lib/std/target/sparc.zig index 4b6698e8de..a075160d59 100644 --- a/lib/std/target/sparc.zig +++ b/lib/std/target/sparc.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/systemz.zig b/lib/std/target/systemz.zig index 16b1471d55..8a78167e69 100644 --- a/lib/std/target/systemz.zig +++ b/lib/std/target/systemz.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/wasm.zig b/lib/std/target/wasm.zig index 6a2053c613..0a3281c692 100644 --- a/lib/std/target/wasm.zig +++ b/lib/std/target/wasm.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/target/x86.zig b/lib/std/target/x86.zig index d764bc2d21..1a52162969 100644 --- a/lib/std/target/x86.zig +++ b/lib/std/target/x86.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 69df01190d..1d89155a58 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -21,14 +21,18 @@ pub var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); /// TODO https://github.com/ziglang/zig/issues/5738 pub var log_level = std.log.Level.warn; +/// This is available to any test that wants to execute Zig in a child process. +/// It will be the same executable that is running `zig test`. +pub var zig_exe_path: []const u8 = undefined; + /// This function is intended to be used only in tests. It prints diagnostics to stderr /// and then aborts when actual_error_union is not expected_error. pub fn expectError(expected_error: anyerror, actual_error_union: anytype) void { if (actual_error_union) |actual_payload| { - std.debug.panic("expected error.{}, found {}", .{ @errorName(expected_error), actual_payload }); + std.debug.panic("expected error.{s}, found {any}", .{ @errorName(expected_error), actual_payload }); } else |actual_error| { if (expected_error != actual_error) { - std.debug.panic("expected error.{}, found error.{}", .{ + std.debug.panic("expected error.{s}, found error.{s}", .{ @errorName(expected_error), @errorName(actual_error), }); @@ -56,7 +60,7 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { .Type => { if (actual != expected) { - std.debug.panic("expected type {}, found type {}", .{ @typeName(expected), @typeName(actual) }); + std.debug.panic("expected type {s}, found type {s}", .{ @typeName(expected), @typeName(actual) }); } }, @@ -84,7 +88,7 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { }, .Slice => { if (actual.ptr != expected.ptr) { - std.debug.panic("expected slice ptr {}, found {}", .{ expected.ptr, actual.ptr }); + std.debug.panic("expected slice ptr {*}, found {*}", .{ expected.ptr, actual.ptr }); } if (actual.len != expected.len) { std.debug.panic("expected slice len {}, found {}", .{ expected.len, actual.len }); @@ -115,10 +119,10 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { @compileError("Unable to compare untagged union values"); } - const TagType = @TagType(@TypeOf(expected)); + const Tag = std.meta.Tag(@TypeOf(expected)); - const expectedTag = @as(TagType, expected); - const actualTag = @as(TagType, actual); + const expectedTag = @as(Tag, expected); + const actualTag = @as(Tag, actual); expectEqual(expectedTag, actualTag); @@ -141,11 +145,11 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { if (actual) |actual_payload| { expectEqual(expected_payload, actual_payload); } else { - std.debug.panic("expected {}, found null", .{expected_payload}); + std.debug.panic("expected {any}, found null", .{expected_payload}); } } else { if (actual) |actual_payload| { - std.debug.panic("expected null, found {}", .{actual_payload}); + std.debug.panic("expected null, found {any}", .{actual_payload}); } } }, @@ -155,11 +159,11 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { if (actual) |actual_payload| { expectEqual(expected_payload, actual_payload); } else |actual_err| { - std.debug.panic("expected {}, found {}", .{ expected_payload, actual_err }); + std.debug.panic("expected {any}, found {}", .{ expected_payload, actual_err }); } } else |expected_err| { if (actual) |actual_payload| { - std.debug.panic("expected {}, found {}", .{ expected_err, actual_payload }); + std.debug.panic("expected {}, found {any}", .{ expected_err, actual_payload }); } else |actual_err| { expectEqual(expected_err, actual_err); } @@ -180,6 +184,22 @@ test "expectEqual.union(enum)" { expectEqual(a10, a10); } +/// This function is intended to be used only in tests. When the formatted result of the template +/// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how +/// they are not equal, then returns an error. +pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { + const result = try std.fmt.allocPrint(allocator, template, args); + defer allocator.free(result); + if (std.mem.eql(u8, result, expected)) return; + + print("\n====== expected this output: =========\n", .{}); + print("{s}", .{expected}); + print("\n======== instead found this: =========\n", .{}); + print("{s}", .{result}); + print("\n======================================\n", .{}); + return error.TestFailed; +} + /// This function is intended to be used only in tests. When the actual value is not /// within the margin of the expected value, /// prints diagnostics to stderr to show exactly how they are not equal, then aborts. @@ -254,12 +274,12 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const // If the child type is u8 and no weird bytes, we could print it as strings // Even for the length difference, it would be useful to see the values of the slices probably. if (expected.len != actual.len) { - std.debug.panic("slice lengths differ. expected {}, found {}", .{ expected.len, actual.len }); + std.debug.panic("slice lengths differ. expected {d}, found {d}", .{ expected.len, actual.len }); } var i: usize = 0; while (i < expected.len) : (i += 1) { if (!std.meta.eql(expected[i], actual[i])) { - std.debug.panic("index {} incorrect. expected {}, found {}", .{ i, expected[i], actual[i] }); + std.debug.panic("index {} incorrect. expected {any}, found {any}", .{ i, expected[i], actual[i] }); } } } @@ -303,10 +323,9 @@ fn getCwdOrWasiPreopen() std.fs.Dir { pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir { var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; - std.crypto.randomBytes(&random_bytes) catch - @panic("unable to make tmp dir for testing: unable to get random bytes"); + std.crypto.random.bytes(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; - std.fs.base64_encoder.encode(&sub_path, &random_bytes); + _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); var cwd = getCwdOrWasiPreopen(); var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch @@ -357,7 +376,7 @@ pub fn expectEqualStrings(expected: []const u8, actual: []const u8) void { for (expected[0..diff_index]) |value| { if (value == '\n') diff_line_number += 1; } - print("First difference occurs on line {}:\n", .{diff_line_number}); + print("First difference occurs on line {d}:\n", .{diff_line_number}); print("expected:\n", .{}); printIndicatorLine(expected, diff_index); @@ -413,18 +432,18 @@ fn printWithVisibleNewlines(source: []const u8) void { while (std.mem.indexOf(u8, source[i..], "\n")) |nl| : (i += nl + 1) { printLine(source[i .. i + nl]); } - print("{}␃\n", .{source[i..]}); // End of Text symbol (ETX) + print("{s}␃\n", .{source[i..]}); // End of Text symbol (ETX) } fn printLine(line: []const u8) void { if (line.len != 0) switch (line[line.len - 1]) { - ' ', '\t' => print("{}⏎\n", .{line}), // Carriage return symbol, + ' ', '\t' => print("{s}⏎\n", .{line}), // Carriage return symbol, else => {}, }; - print("{}\n", .{line}); + print("{s}\n", .{line}); } -test "" { +test { expectEqualStrings("foo", "foo"); } diff --git a/lib/std/testing/failing_allocator.zig b/lib/std/testing/failing_allocator.zig index 61912e6933..570050762d 100644 --- a/lib/std/testing/failing_allocator.zig +++ b/lib/std/testing/failing_allocator.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/thread.zig b/lib/std/thread.zig deleted file mode 100644 index 83dfd7cb52..0000000000 --- a/lib/std/thread.zig +++ /dev/null @@ -1,496 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const builtin = std.builtin; -const os = std.os; -const mem = std.mem; -const windows = std.os.windows; -const c = std.c; -const assert = std.debug.assert; - -const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; - -pub const Thread = struct { - data: Data, - - pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; - - /// Represents a kernel thread handle. - /// May be an integer or a pointer depending on the platform. - /// On Linux and POSIX, this is the same as Id. - pub const Handle = if (use_pthreads) - c.pthread_t - else switch (std.Target.current.os.tag) { - .linux => i32, - .windows => windows.HANDLE, - else => void, - }; - - /// Represents a unique ID per thread. - /// May be an integer or pointer depending on the platform. - /// On Linux and POSIX, this is the same as Handle. - pub const Id = switch (std.Target.current.os.tag) { - .windows => windows.DWORD, - else => Handle, - }; - - pub const Data = if (use_pthreads) - struct { - handle: Thread.Handle, - memory: []u8, - } - else switch (std.Target.current.os.tag) { - .linux => struct { - handle: Thread.Handle, - memory: []align(mem.page_size) u8, - }, - .windows => struct { - handle: Thread.Handle, - alloc_start: *c_void, - heap_handle: windows.HANDLE, - }, - else => struct {}, - }; - - /// Returns the ID of the calling thread. - /// Makes a syscall every time the function is called. - /// On Linux and POSIX, this Id is the same as a Handle. - pub fn getCurrentId() Id { - if (use_pthreads) { - return c.pthread_self(); - } else - return switch (std.Target.current.os.tag) { - .linux => os.linux.gettid(), - .windows => windows.kernel32.GetCurrentThreadId(), - else => @compileError("Unsupported OS"), - }; - } - - /// Returns the handle of this thread. - /// On Linux and POSIX, this is the same as Id. - /// On Linux, it is possible that the thread spawned with `spawn` - /// finishes executing entirely before the clone syscall completes. In this - /// case, this function will return 0 rather than the no-longer-existing thread's - /// pid. - pub fn handle(self: Thread) Handle { - return self.data.handle; - } - - pub fn wait(self: *Thread) void { - if (use_pthreads) { - const err = c.pthread_join(self.data.handle, null); - switch (err) { - 0 => {}, - os.EINVAL => unreachable, - os.ESRCH => unreachable, - os.EDEADLK => unreachable, - else => unreachable, - } - std.heap.c_allocator.free(self.data.memory); - std.heap.c_allocator.destroy(self); - } else switch (std.Target.current.os.tag) { - .linux => { - while (true) { - const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); - if (pid_value == 0) break; - const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null); - switch (os.linux.getErrno(rc)) { - 0 => continue, - os.EINTR => continue, - os.EAGAIN => continue, - else => unreachable, - } - } - os.munmap(self.data.memory); - }, - .windows => { - windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable; - windows.CloseHandle(self.data.handle); - windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start); - }, - else => @compileError("Unsupported OS"), - } - } - - pub const SpawnError = error{ - /// A system-imposed limit on the number of threads was encountered. - /// There are a number of limits that may trigger this error: - /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), - /// which limits the number of processes and threads for a real - /// user ID, was reached; - /// * the kernel's system-wide limit on the number of processes and - /// threads, /proc/sys/kernel/threads-max, was reached (see - /// proc(5)); - /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was - /// reached (see proc(5)); or - /// * the PID limit (pids.max) imposed by the cgroup "process num‐ - /// ber" (PIDs) controller was reached. - ThreadQuotaExceeded, - - /// The kernel cannot allocate sufficient memory to allocate a task structure - /// for the child, or to copy those parts of the caller's context that need to - /// be copied. - SystemResources, - - /// Not enough userland memory to spawn the thread. - OutOfMemory, - - /// `mlockall` is enabled, and the memory needed to spawn the thread - /// would exceed the limit. - LockedMemoryLimitExceeded, - - Unexpected, - }; - - /// caller must call wait on the returned thread - /// fn startFn(@TypeOf(context)) T - /// where T is u8, noreturn, void, or !void - /// caller must call wait on the returned thread - pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread { - if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); - // TODO compile-time call graph analysis to determine stack upper bound - // https://github.com/ziglang/zig/issues/157 - const default_stack_size = 16 * 1024 * 1024; - - const Context = @TypeOf(context); - comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context); - - if (std.Target.current.os.tag == .windows) { - const WinThread = struct { - const OuterContext = struct { - thread: Thread, - inner: Context, - }; - fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; - - switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { - .NoReturn => { - startFn(arg); - }, - .Void => { - startFn(arg); - return 0; - }, - .Int => |info| { - if (info.bits != 8) { - @compileError(bad_startfn_ret); - } - return startFn(arg); - }, - .ErrorUnion => |info| { - if (info.payload != void) { - @compileError(bad_startfn_ret); - } - startFn(arg) catch |err| { - std.debug.warn("error: {}\n", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - }; - return 0; - }, - else => @compileError(bad_startfn_ret), - } - } - }; - - const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory; - const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); - const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory; - errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0); - const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count]; - const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; - outer_context.* = WinThread.OuterContext{ - .thread = Thread{ - .data = Thread.Data{ - .heap_handle = heap_handle, - .alloc_start = bytes_ptr, - .handle = undefined, - }, - }, - .inner = context, - }; - - const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner); - outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse { - switch (windows.kernel32.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - }; - return &outer_context.thread; - } - - const MainFuncs = struct { - fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { - const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; - - switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { - .NoReturn => { - startFn(arg); - }, - .Void => { - startFn(arg); - return 0; - }, - .Int => |info| { - if (info.bits != 8) { - @compileError(bad_startfn_ret); - } - return startFn(arg); - }, - .ErrorUnion => |info| { - if (info.payload != void) { - @compileError(bad_startfn_ret); - } - startFn(arg) catch |err| { - std.debug.warn("error: {}\n", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - }; - return 0; - }, - else => @compileError(bad_startfn_ret), - } - } - fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; - - switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { - .NoReturn => { - startFn(arg); - }, - .Void => { - startFn(arg); - return null; - }, - .Int => |info| { - if (info.bits != 8) { - @compileError(bad_startfn_ret); - } - // pthreads don't support exit status, ignore value - _ = startFn(arg); - return null; - }, - .ErrorUnion => |info| { - if (info.payload != void) { - @compileError(bad_startfn_ret); - } - startFn(arg) catch |err| { - std.debug.warn("error: {}\n", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - }; - return null; - }, - else => @compileError(bad_startfn_ret), - } - } - }; - - if (Thread.use_pthreads) { - var attr: c.pthread_attr_t = undefined; - if (c.pthread_attr_init(&attr) != 0) return error.SystemResources; - defer assert(c.pthread_attr_destroy(&attr) == 0); - - const thread_obj = try std.heap.c_allocator.create(Thread); - errdefer std.heap.c_allocator.destroy(thread_obj); - if (@sizeOf(Context) > 0) { - thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced( - u8, - @alignOf(Context), - @sizeOf(Context), - .at_least, - ); - errdefer std.heap.c_allocator.free(thread_obj.data.memory); - mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context)); - } else { - thread_obj.data.memory = @as([*]u8, undefined)[0..0]; - } - - // Use the same set of parameters used by the libc-less impl. - assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0); - assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0); - - const err = c.pthread_create( - &thread_obj.data.handle, - &attr, - MainFuncs.posixThreadMain, - thread_obj.data.memory.ptr, - ); - switch (err) { - 0 => return thread_obj, - os.EAGAIN => return error.SystemResources, - os.EPERM => unreachable, - os.EINVAL => unreachable, - else => return os.unexpectedErrno(@intCast(usize, err)), - } - - return thread_obj; - } - - var guard_end_offset: usize = undefined; - var stack_end_offset: usize = undefined; - var thread_start_offset: usize = undefined; - var context_start_offset: usize = undefined; - var tls_start_offset: usize = undefined; - const mmap_len = blk: { - var l: usize = mem.page_size; - // Allocate a guard page right after the end of the stack region - guard_end_offset = l; - // The stack itself, which grows downwards. - l = mem.alignForward(l + default_stack_size, mem.page_size); - stack_end_offset = l; - // Above the stack, so that it can be in the same mmap call, put the Thread object. - l = mem.alignForward(l, @alignOf(Thread)); - thread_start_offset = l; - l += @sizeOf(Thread); - // Next, the Context object. - if (@sizeOf(Context) != 0) { - l = mem.alignForward(l, @alignOf(Context)); - context_start_offset = l; - l += @sizeOf(Context); - } - // Finally, the Thread Local Storage, if any. - l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align); - tls_start_offset = l; - l += os.linux.tls.tls_image.alloc_size; - // Round the size to the page size. - break :blk mem.alignForward(l, mem.page_size); - }; - - const mmap_slice = mem: { - // Map the whole stack with no rw permissions to avoid - // committing the whole region right away - const mmap_slice = os.mmap( - null, - mmap_len, - os.PROT_NONE, - os.MAP_PRIVATE | os.MAP_ANONYMOUS, - -1, - 0, - ) catch |err| switch (err) { - error.MemoryMappingNotSupported => unreachable, - error.AccessDenied => unreachable, - error.PermissionDenied => unreachable, - else => |e| return e, - }; - errdefer os.munmap(mmap_slice); - - // Map everything but the guard page as rw - os.mprotect( - mmap_slice[guard_end_offset..], - os.PROT_READ | os.PROT_WRITE, - ) catch |err| switch (err) { - error.AccessDenied => unreachable, - else => |e| return e, - }; - - break :mem mmap_slice; - }; - - const mmap_addr = @ptrToInt(mmap_slice.ptr); - - const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); - thread_ptr.data.memory = mmap_slice; - - var arg: usize = undefined; - if (@sizeOf(Context) != 0) { - arg = mmap_addr + context_start_offset; - const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); - context_ptr.* = context; - } - - if (std.Target.current.os.tag == .linux) { - const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | - os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | - os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | - os.CLONE_DETACHED | os.CLONE_SETTLS; - // This structure is only needed when targeting i386 - var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; - - const tls_area = mmap_slice[tls_start_offset..]; - const tp_value = os.linux.tls.prepareTLS(tls_area); - - const newtls = blk: { - if (std.Target.current.cpu.arch == .i386) { - user_desc = os.linux.user_desc{ - .entry_number = os.linux.tls.tls_image.gdt_entry_number, - .base_addr = tp_value, - .limit = 0xfffff, - .seg_32bit = 1, - .contents = 0, // Data - .read_exec_only = 0, - .limit_in_pages = 1, - .seg_not_present = 0, - .useable = 1, - }; - break :blk @ptrToInt(&user_desc); - } else { - break :blk tp_value; - } - }; - - const rc = os.linux.clone( - MainFuncs.linuxThreadMain, - mmap_addr + stack_end_offset, - flags, - arg, - &thread_ptr.data.handle, - newtls, - &thread_ptr.data.handle, - ); - switch (os.errno(rc)) { - 0 => return thread_ptr, - os.EAGAIN => return error.ThreadQuotaExceeded, - os.EINVAL => unreachable, - os.ENOMEM => return error.SystemResources, - os.ENOSPC => unreachable, - os.EPERM => unreachable, - os.EUSERS => unreachable, - else => |err| return os.unexpectedErrno(err), - } - } else { - @compileError("Unsupported OS"); - } - } - - pub const CpuCountError = error{ - PermissionDenied, - SystemResources, - Unexpected, - }; - - pub fn cpuCount() CpuCountError!usize { - if (std.Target.current.os.tag == .linux) { - const cpu_set = try os.sched_getaffinity(0); - return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast - } - if (std.Target.current.os.tag == .windows) { - return os.windows.peb().NumberOfProcessors; - } - if (std.Target.current.os.tag == .openbsd) { - var count: c_int = undefined; - var count_size: usize = @sizeOf(c_int); - const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE }; - os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { - error.NameTooLong, error.UnknownName => unreachable, - else => |e| return e, - }; - return @intCast(usize, count); - } - var count: c_int = undefined; - var count_len: usize = @sizeOf(c_int); - const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; - os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { - error.NameTooLong, error.UnknownName => unreachable, - else => |e| return e, - }; - return @intCast(usize, count); - } -}; diff --git a/lib/std/time.zig b/lib/std/time.zig index 2f56e82f45..7435a67e3d 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/time/epoch.zig b/lib/std/time/epoch.zig index 3a42c85a6d..75bddc71c3 100644 --- a/lib/std/time/epoch.zig +++ b/lib/std/time/epoch.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index c791e07f78..f9ad6e3eb5 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/unicode/throughput_test.zig b/lib/std/unicode/throughput_test.zig index 5474124fd2..8f9f9d9cb7 100644 --- a/lib/std/unicode/throughput_test.zig +++ b/lib/std/unicode/throughput_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -45,7 +45,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount { } pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); const args = try std.process.argsAlloc(std.heap.page_allocator); diff --git a/lib/std/valgrind.zig b/lib/std/valgrind.zig index 5373a2d513..6930652fbc 100644 --- a/lib/std/valgrind.zig +++ b/lib/std/valgrind.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -262,7 +262,7 @@ pub fn monitorCommand(command: [*]u8) bool { pub const memcheck = @import("valgrind/memcheck.zig"); pub const callgrind = @import("valgrind/callgrind.zig"); -test "" { +test { _ = @import("valgrind/memcheck.zig"); _ = @import("valgrind/callgrind.zig"); } diff --git a/lib/std/valgrind/callgrind.zig b/lib/std/valgrind/callgrind.zig index 5e025c4ffe..3962ad3e3a 100644 --- a/lib/std/valgrind/callgrind.zig +++ b/lib/std/valgrind/callgrind.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/valgrind/memcheck.zig b/lib/std/valgrind/memcheck.zig index 449b4c4a46..3226beec53 100644 --- a/lib/std/valgrind/memcheck.zig +++ b/lib/std/valgrind/memcheck.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig new file mode 100644 index 0000000000..a04378d283 --- /dev/null +++ b/lib/std/wasm.zig @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const testing = @import("std.zig").testing; + +/// Wasm instruction opcodes +/// +/// All instructions are defined as per spec: +/// https://webassembly.github.io/spec/core/appendix/index-instructions.html +pub const Opcode = enum(u8) { + @"unreachable" = 0x00, + nop = 0x01, + block = 0x02, + loop = 0x03, + @"if" = 0x04, + @"else" = 0x05, + end = 0x0B, + br = 0x0C, + br_if = 0x0D, + br_table = 0x0E, + @"return" = 0x0F, + call = 0x10, + call_indirect = 0x11, + drop = 0x1A, + select = 0x1B, + local_get = 0x20, + local_set = 0x21, + local_tee = 0x22, + global_get = 0x23, + global_set = 0x24, + i32_load = 0x28, + i64_load = 0x29, + f32_load = 0x2A, + f64_load = 0x2B, + i32_load8_s = 0x2C, + i32_load8_u = 0x2D, + i32_load16_s = 0x2E, + i32_load16_u = 0x2F, + i64_load8_s = 0x30, + i64_load8_u = 0x31, + i64_load16_s = 0x32, + i64_load16_u = 0x33, + i64_load32_s = 0x34, + i64_load32_u = 0x35, + i32_store = 0x36, + i64_store = 0x37, + f32_store = 0x38, + f64_store = 0x39, + i32_store8 = 0x3A, + i32_store16 = 0x3B, + i64_store8 = 0x3C, + i64_store16 = 0x3D, + i64_store32 = 0x3E, + memory_size = 0x3F, + memory_grow = 0x40, + i32_const = 0x41, + i64_const = 0x42, + f32_const = 0x43, + f64_const = 0x44, + i32_eqz = 0x45, + i32_eq = 0x46, + i32_ne = 0x47, + i32_lt_s = 0x48, + i32_lt_u = 0x49, + i32_gt_s = 0x4A, + i32_gt_u = 0x4B, + i32_le_s = 0x4C, + i32_le_u = 0x4D, + i32_ge_s = 0x4E, + i32_ge_u = 0x4F, + i64_eqz = 0x50, + i64_eq = 0x51, + i64_ne = 0x52, + i64_lt_s = 0x53, + i64_lt_u = 0x54, + i64_gt_s = 0x55, + i64_gt_u = 0x56, + i64_le_s = 0x57, + i64_le_u = 0x58, + i64_ge_s = 0x59, + i64_ge_u = 0x5A, + f32_eq = 0x5B, + f32_ne = 0x5C, + f32_lt = 0x5D, + f32_gt = 0x5E, + f32_le = 0x5F, + f32_ge = 0x60, + f64_eq = 0x61, + f64_ne = 0x62, + f64_lt = 0x63, + f64_gt = 0x64, + f64_le = 0x65, + f64_ge = 0x66, + i32_clz = 0x67, + i32_ctz = 0x68, + i32_popcnt = 0x69, + i32_add = 0x6A, + i32_sub = 0x6B, + i32_mul = 0x6C, + i32_div_s = 0x6D, + i32_div_u = 0x6E, + i32_rem_s = 0x6F, + i32_rem_u = 0x70, + i32_and = 0x71, + i32_or = 0x72, + i32_xor = 0x73, + i32_shl = 0x74, + i32_shr_s = 0x75, + i32_shr_u = 0x76, + i32_rotl = 0x77, + i32_rotr = 0x78, + i64_clz = 0x79, + i64_ctz = 0x7A, + i64_popcnt = 0x7B, + i64_add = 0x7C, + i64_sub = 0x7D, + i64_mul = 0x7E, + i64_div_s = 0x7F, + i64_div_u = 0x80, + i64_rem_s = 0x81, + i64_rem_u = 0x82, + i64_and = 0x83, + i64_or = 0x84, + i64_xor = 0x85, + i64_shl = 0x86, + i64_shr_s = 0x87, + i64_shr_u = 0x88, + i64_rotl = 0x89, + i64_rotr = 0x8A, + f32_abs = 0x8B, + f32_neg = 0x8C, + f32_ceil = 0x8D, + f32_floor = 0x8E, + f32_trunc = 0x8F, + f32_nearest = 0x90, + f32_sqrt = 0x91, + f32_add = 0x92, + f32_sub = 0x93, + f32_mul = 0x94, + f32_div = 0x95, + f32_min = 0x96, + f32_max = 0x97, + f32_copysign = 0x98, + f64_abs = 0x99, + f64_neg = 0x9A, + f64_ceil = 0x9B, + f64_floor = 0x9C, + f64_trunc = 0x9D, + f64_nearest = 0x9E, + f64_sqrt = 0x9F, + f64_add = 0xA0, + f64_sub = 0xA1, + f64_mul = 0xA2, + f64_div = 0xA3, + f64_min = 0xA4, + f64_max = 0xA5, + f64_copysign = 0xA6, + i32_wrap_i64 = 0xA7, + i32_trunc_f32_s = 0xA8, + i32_trunc_f32_u = 0xA9, + i32_trunc_f64_s = 0xB0, + i32_trunc_f64_u = 0xB1, + f32_convert_i32_s = 0xB2, + f32_convert_i32_u = 0xB3, + f32_convert_i64_s = 0xB4, + f32_convert_i64_u = 0xB5, + f32_demote_f64 = 0xB6, + f64_convert_i32_s = 0xB7, + f64_convert_i32_u = 0xB8, + f64_convert_i64_s = 0xB9, + f64_convert_i64_u = 0xBA, + f64_promote_f32 = 0xBB, + i32_reinterpret_f32 = 0xBC, + i64_reinterpret_f64 = 0xBD, + f32_reinterpret_i32 = 0xBE, + i64_reinterpret_i64 = 0xBF, + i32_extend8_s = 0xC0, + i32_extend16_s = 0xC1, + i64_extend8_s = 0xC2, + i64_extend16_s = 0xC3, + i64_extend32_s = 0xC4, + _, +}; + +/// Returns the integer value of an `Opcode`. Used by the Zig compiler +/// to write instructions to the wasm binary file +pub fn opcode(op: Opcode) u8 { + return @enumToInt(op); +} + +test "Wasm - opcodes" { + // Ensure our opcodes values remain intact as certain values are skipped due to them being reserved + const i32_const = opcode(.i32_const); + const end = opcode(.end); + const drop = opcode(.drop); + const local_get = opcode(.local_get); + const i64_extend32_s = opcode(.i64_extend32_s); + + testing.expectEqual(@as(u16, 0x41), i32_const); + testing.expectEqual(@as(u16, 0x0B), end); + testing.expectEqual(@as(u16, 0x1A), drop); + testing.expectEqual(@as(u16, 0x20), local_get); + testing.expectEqual(@as(u16, 0xC4), i64_extend32_s); +} + +/// Enum representing all Wasm value types as per spec: +/// https://webassembly.github.io/spec/core/binary/types.html +pub const Valtype = enum(u8) { + i32 = 0x7F, + i64 = 0x7E, + f32 = 0x7D, + f64 = 0x7C, +}; + +/// Returns the integer value of a `Valtype` +pub fn valtype(value: Valtype) u8 { + return @enumToInt(value); +} + +test "Wasm - valtypes" { + const _i32 = valtype(.i32); + const _i64 = valtype(.i64); + const _f32 = valtype(.f32); + const _f64 = valtype(.f64); + + testing.expectEqual(@as(u8, 0x7F), _i32); + testing.expectEqual(@as(u8, 0x7E), _i64); + testing.expectEqual(@as(u8, 0x7D), _f32); + testing.expectEqual(@as(u8, 0x7C), _f64); +} + +/// Wasm module sections as per spec: +/// https://webassembly.github.io/spec/core/binary/modules.html +pub const Section = enum(u8) { + custom, + type, + import, + function, + table, + memory, + global, + @"export", + start, + element, + code, + data, +}; + +/// Returns the integer value of a given `Section` +pub fn section(val: Section) u8 { + return @enumToInt(val); +} + +/// The kind of the type when importing or exporting to/from the host environment +/// https://webassembly.github.io/spec/core/syntax/modules.html +pub const ExternalKind = enum(u8) { + function, + table, + memory, + global, +}; + +/// Returns the integer value of a given `ExternalKind` +pub fn externalKind(val: ExternalKind) u8 { + return @enumToInt(val); +} + +// types +pub const element_type: u8 = 0x70; +pub const function_type: u8 = 0x60; +pub const result_type: u8 = 0x40; + +/// Represents a block which will not return a value +pub const block_empty: u8 = 0x40; + +// binary constants +pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm +pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 06a74a9786..197d7c2c59 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -8,9 +8,10 @@ const tokenizer = @import("zig/tokenizer.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; +pub const fmtId = @import("zig/fmt.zig").fmtId; +pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes; pub const parse = @import("zig/parse.zig").parse; pub const parseStringLiteral = @import("zig/string_literal.zig").parse; -pub const render = @import("zig/render.zig").render; pub const ast = @import("zig/ast.zig"); pub const system = @import("zig/system.zig"); pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget; @@ -139,6 +140,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro .Lib => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}), }, .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}), + .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), } @@ -253,6 +255,6 @@ test "parseCharLiteral" { std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{FFFF}x'", &bad_index)); } -test "" { +test { @import("std").testing.refAllDecls(@This()); } diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 3e2f08cd06..46b58e9465 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -9,71 +9,78 @@ const testing = std.testing; const mem = std.mem; const Token = std.zig.Token; -pub const TokenIndex = usize; -pub const NodeIndex = usize; +pub const TokenIndex = u32; +pub const ByteOffset = u32; + +pub const TokenList = std.MultiArrayList(struct { + tag: Token.Tag, + start: ByteOffset, +}); +pub const NodeList = std.MultiArrayList(Node); pub const Tree = struct { /// Reference to externally-owned data. source: []const u8, - token_ids: []const Token.Id, - token_locs: []const Token.Loc, - errors: []const Error, - root_node: *Node.Root, - arena: std.heap.ArenaAllocator.State, - gpa: *mem.Allocator, + tokens: TokenList.Slice, + /// The root AST node is assumed to be index 0. Since there can be no + /// references to the root node, this means 0 is available to indicate null. + nodes: NodeList.Slice, + extra_data: []Node.Index, - /// translate-c uses this to avoid having to emit correct newlines - /// TODO get rid of this hack - generated: bool = false, + errors: []const Error, - pub fn deinit(self: *Tree) void { - self.gpa.free(self.token_ids); - self.gpa.free(self.token_locs); - self.gpa.free(self.errors); - self.arena.promote(self.gpa).deinit(); - } + pub const Location = struct { + line: usize, + column: usize, + line_start: usize, + line_end: usize, + }; - pub fn renderError(self: *Tree, parse_error: *const Error, stream: anytype) !void { - return parse_error.render(self.token_ids, stream); + pub fn deinit(tree: *Tree, gpa: *mem.Allocator) void { + tree.tokens.deinit(gpa); + tree.nodes.deinit(gpa); + gpa.free(tree.extra_data); + gpa.free(tree.errors); + tree.* = undefined; } - pub fn tokenSlice(self: *Tree, token_index: TokenIndex) []const u8 { - return self.tokenSliceLoc(self.token_locs[token_index]); - } + pub const RenderError = error{ + /// Ran out of memory allocating call stack frames to complete rendering, or + /// ran out of memory allocating space in the output buffer. + OutOfMemory, + }; - pub fn tokenSliceLoc(self: *Tree, token: Token.Loc) []const u8 { - return self.source[token.start..token.end]; - } + /// `gpa` is used for allocating the resulting formatted source code, as well as + /// for allocating extra stack memory if needed, because this function utilizes recursion. + /// Note: that's not actually true yet, see https://github.com/ziglang/zig/issues/1006. + /// Caller owns the returned slice of bytes, allocated with `gpa`. + pub fn render(tree: Tree, gpa: *mem.Allocator) RenderError![]u8 { + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); - pub fn getNodeSource(self: *const Tree, node: *const Node) []const u8 { - const first_token = self.token_locs[node.firstToken()]; - const last_token = self.token_locs[node.lastToken()]; - return self.source[first_token.start..last_token.end]; + try tree.renderToArrayList(&buffer); + return buffer.toOwnedSlice(); } - pub const Location = struct { - line: usize, - column: usize, - line_start: usize, - line_end: usize, - }; + pub fn renderToArrayList(tree: Tree, buffer: *std.ArrayList(u8)) RenderError!void { + return @import("./render.zig").renderTree(buffer, tree); + } - /// Return the Location of the token relative to the offset specified by `start_index`. - pub fn tokenLocationLoc(self: *Tree, start_index: usize, token: Token.Loc) Location { + pub fn tokenLocation(self: Tree, start_offset: ByteOffset, token_index: TokenIndex) Location { var loc = Location{ .line = 0, .column = 0, - .line_start = start_index, + .line_start = start_offset, .line_end = self.source.len, }; - if (self.generated) - return loc; - const token_start = token.start; - for (self.source[start_index..]) |c, i| { - if (i + start_index == token_start) { - loc.line_end = i + start_index; - while (loc.line_end < self.source.len and self.source[loc.line_end] != '\n') : (loc.line_end += 1) {} + const token_start = self.tokens.items(.start)[token_index]; + for (self.source[start_offset..]) |c, i| { + if (i + start_offset == token_start) { + loc.line_end = i + start_offset; + while (loc.line_end < self.source.len and self.source[loc.line_end] != '\n') { + loc.line_end += 1; + } return loc; } if (c == '\n') { @@ -87,3196 +94,2878 @@ pub const Tree = struct { return loc; } - pub fn tokenLocation(self: *Tree, start_index: usize, token_index: TokenIndex) Location { - return self.tokenLocationLoc(start_index, self.token_locs[token_index]); - } - - pub fn tokensOnSameLine(self: *Tree, token1_index: TokenIndex, token2_index: TokenIndex) bool { - return self.tokensOnSameLineLoc(self.token_locs[token1_index], self.token_locs[token2_index]); - } - - pub fn tokensOnSameLineLoc(self: *Tree, token1: Token.Loc, token2: Token.Loc) bool { - return mem.indexOfScalar(u8, self.source[token1.end..token2.start], '\n') == null; - } - - pub fn dump(self: *Tree) void { - self.root_node.base.dump(0); - } + pub fn tokenSlice(tree: Tree, token_index: TokenIndex) []const u8 { + const token_starts = tree.tokens.items(.start); + const token_tags = tree.tokens.items(.tag); + const token_tag = token_tags[token_index]; - /// Skips over comments - pub fn prevToken(self: *Tree, token_index: TokenIndex) TokenIndex { - var index = token_index - 1; - while (self.token_ids[index] == Token.Id.LineComment) { - index -= 1; + // Many tokens can be determined entirely by their tag. + if (token_tag.lexeme()) |lexeme| { + return lexeme; } - return index; - } - /// Skips over comments - pub fn nextToken(self: *Tree, token_index: TokenIndex) TokenIndex { - var index = token_index + 1; - while (self.token_ids[index] == Token.Id.LineComment) { - index += 1; - } - return index; + // For some tokens, re-tokenization is needed to find the end. + var tokenizer: std.zig.Tokenizer = .{ + .buffer = tree.source, + .index = token_starts[token_index], + .pending_invalid_token = null, + }; + const token = tokenizer.next(); + assert(token.tag == token_tag); + return tree.source[token.loc.start..token.loc.end]; } -}; -pub const Error = union(enum) { - InvalidToken: InvalidToken, - ExpectedContainerMembers: ExpectedContainerMembers, - ExpectedStringLiteral: ExpectedStringLiteral, - ExpectedIntegerLiteral: ExpectedIntegerLiteral, - ExpectedPubItem: ExpectedPubItem, - ExpectedIdentifier: ExpectedIdentifier, - ExpectedStatement: ExpectedStatement, - ExpectedVarDeclOrFn: ExpectedVarDeclOrFn, - ExpectedVarDecl: ExpectedVarDecl, - ExpectedFn: ExpectedFn, - ExpectedReturnType: ExpectedReturnType, - ExpectedAggregateKw: ExpectedAggregateKw, - UnattachedDocComment: UnattachedDocComment, - ExpectedEqOrSemi: ExpectedEqOrSemi, - ExpectedSemiOrLBrace: ExpectedSemiOrLBrace, - ExpectedSemiOrElse: ExpectedSemiOrElse, - ExpectedLabelOrLBrace: ExpectedLabelOrLBrace, - ExpectedLBrace: ExpectedLBrace, - ExpectedColonOrRParen: ExpectedColonOrRParen, - ExpectedLabelable: ExpectedLabelable, - ExpectedInlinable: ExpectedInlinable, - ExpectedAsmOutputReturnOrType: ExpectedAsmOutputReturnOrType, - ExpectedCall: ExpectedCall, - ExpectedCallOrFnProto: ExpectedCallOrFnProto, - ExpectedSliceOrRBracket: ExpectedSliceOrRBracket, - ExtraAlignQualifier: ExtraAlignQualifier, - ExtraConstQualifier: ExtraConstQualifier, - ExtraVolatileQualifier: ExtraVolatileQualifier, - ExtraAllowZeroQualifier: ExtraAllowZeroQualifier, - ExpectedTypeExpr: ExpectedTypeExpr, - ExpectedPrimaryTypeExpr: ExpectedPrimaryTypeExpr, - ExpectedParamType: ExpectedParamType, - ExpectedExpr: ExpectedExpr, - ExpectedPrimaryExpr: ExpectedPrimaryExpr, - ExpectedToken: ExpectedToken, - ExpectedCommaOrEnd: ExpectedCommaOrEnd, - ExpectedParamList: ExpectedParamList, - ExpectedPayload: ExpectedPayload, - ExpectedBlockOrAssignment: ExpectedBlockOrAssignment, - ExpectedBlockOrExpression: ExpectedBlockOrExpression, - ExpectedExprOrAssignment: ExpectedExprOrAssignment, - ExpectedPrefixExpr: ExpectedPrefixExpr, - ExpectedLoopExpr: ExpectedLoopExpr, - ExpectedDerefOrUnwrap: ExpectedDerefOrUnwrap, - ExpectedSuffixOp: ExpectedSuffixOp, - ExpectedBlockOrField: ExpectedBlockOrField, - DeclBetweenFields: DeclBetweenFields, - InvalidAnd: InvalidAnd, - AsteriskAfterPointerDereference: AsteriskAfterPointerDereference, - - pub fn render(self: *const Error, tokens: []const Token.Id, stream: anytype) !void { - switch (self.*) { - .InvalidToken => |*x| return x.render(tokens, stream), - .ExpectedContainerMembers => |*x| return x.render(tokens, stream), - .ExpectedStringLiteral => |*x| return x.render(tokens, stream), - .ExpectedIntegerLiteral => |*x| return x.render(tokens, stream), - .ExpectedPubItem => |*x| return x.render(tokens, stream), - .ExpectedIdentifier => |*x| return x.render(tokens, stream), - .ExpectedStatement => |*x| return x.render(tokens, stream), - .ExpectedVarDeclOrFn => |*x| return x.render(tokens, stream), - .ExpectedVarDecl => |*x| return x.render(tokens, stream), - .ExpectedFn => |*x| return x.render(tokens, stream), - .ExpectedReturnType => |*x| return x.render(tokens, stream), - .ExpectedAggregateKw => |*x| return x.render(tokens, stream), - .UnattachedDocComment => |*x| return x.render(tokens, stream), - .ExpectedEqOrSemi => |*x| return x.render(tokens, stream), - .ExpectedSemiOrLBrace => |*x| return x.render(tokens, stream), - .ExpectedSemiOrElse => |*x| return x.render(tokens, stream), - .ExpectedLabelOrLBrace => |*x| return x.render(tokens, stream), - .ExpectedLBrace => |*x| return x.render(tokens, stream), - .ExpectedColonOrRParen => |*x| return x.render(tokens, stream), - .ExpectedLabelable => |*x| return x.render(tokens, stream), - .ExpectedInlinable => |*x| return x.render(tokens, stream), - .ExpectedAsmOutputReturnOrType => |*x| return x.render(tokens, stream), - .ExpectedCall => |*x| return x.render(tokens, stream), - .ExpectedCallOrFnProto => |*x| return x.render(tokens, stream), - .ExpectedSliceOrRBracket => |*x| return x.render(tokens, stream), - .ExtraAlignQualifier => |*x| return x.render(tokens, stream), - .ExtraConstQualifier => |*x| return x.render(tokens, stream), - .ExtraVolatileQualifier => |*x| return x.render(tokens, stream), - .ExtraAllowZeroQualifier => |*x| return x.render(tokens, stream), - .ExpectedTypeExpr => |*x| return x.render(tokens, stream), - .ExpectedPrimaryTypeExpr => |*x| return x.render(tokens, stream), - .ExpectedParamType => |*x| return x.render(tokens, stream), - .ExpectedExpr => |*x| return x.render(tokens, stream), - .ExpectedPrimaryExpr => |*x| return x.render(tokens, stream), - .ExpectedToken => |*x| return x.render(tokens, stream), - .ExpectedCommaOrEnd => |*x| return x.render(tokens, stream), - .ExpectedParamList => |*x| return x.render(tokens, stream), - .ExpectedPayload => |*x| return x.render(tokens, stream), - .ExpectedBlockOrAssignment => |*x| return x.render(tokens, stream), - .ExpectedBlockOrExpression => |*x| return x.render(tokens, stream), - .ExpectedExprOrAssignment => |*x| return x.render(tokens, stream), - .ExpectedPrefixExpr => |*x| return x.render(tokens, stream), - .ExpectedLoopExpr => |*x| return x.render(tokens, stream), - .ExpectedDerefOrUnwrap => |*x| return x.render(tokens, stream), - .ExpectedSuffixOp => |*x| return x.render(tokens, stream), - .ExpectedBlockOrField => |*x| return x.render(tokens, stream), - .DeclBetweenFields => |*x| return x.render(tokens, stream), - .InvalidAnd => |*x| return x.render(tokens, stream), - .AsteriskAfterPointerDereference => |*x| return x.render(tokens, stream), + pub fn extraData(tree: Tree, index: usize, comptime T: type) T { + const fields = std.meta.fields(T); + var result: T = undefined; + inline for (fields) |field, i| { + comptime assert(field.field_type == Node.Index); + @field(result, field.name) = tree.extra_data[index + i]; } + return result; } - pub fn loc(self: *const Error) TokenIndex { - switch (self.*) { - .InvalidToken => |x| return x.token, - .ExpectedContainerMembers => |x| return x.token, - .ExpectedStringLiteral => |x| return x.token, - .ExpectedIntegerLiteral => |x| return x.token, - .ExpectedPubItem => |x| return x.token, - .ExpectedIdentifier => |x| return x.token, - .ExpectedStatement => |x| return x.token, - .ExpectedVarDeclOrFn => |x| return x.token, - .ExpectedVarDecl => |x| return x.token, - .ExpectedFn => |x| return x.token, - .ExpectedReturnType => |x| return x.token, - .ExpectedAggregateKw => |x| return x.token, - .UnattachedDocComment => |x| return x.token, - .ExpectedEqOrSemi => |x| return x.token, - .ExpectedSemiOrLBrace => |x| return x.token, - .ExpectedSemiOrElse => |x| return x.token, - .ExpectedLabelOrLBrace => |x| return x.token, - .ExpectedLBrace => |x| return x.token, - .ExpectedColonOrRParen => |x| return x.token, - .ExpectedLabelable => |x| return x.token, - .ExpectedInlinable => |x| return x.token, - .ExpectedAsmOutputReturnOrType => |x| return x.token, - .ExpectedCall => |x| return x.node.firstToken(), - .ExpectedCallOrFnProto => |x| return x.node.firstToken(), - .ExpectedSliceOrRBracket => |x| return x.token, - .ExtraAlignQualifier => |x| return x.token, - .ExtraConstQualifier => |x| return x.token, - .ExtraVolatileQualifier => |x| return x.token, - .ExtraAllowZeroQualifier => |x| return x.token, - .ExpectedTypeExpr => |x| return x.token, - .ExpectedPrimaryTypeExpr => |x| return x.token, - .ExpectedParamType => |x| return x.token, - .ExpectedExpr => |x| return x.token, - .ExpectedPrimaryExpr => |x| return x.token, - .ExpectedToken => |x| return x.token, - .ExpectedCommaOrEnd => |x| return x.token, - .ExpectedParamList => |x| return x.token, - .ExpectedPayload => |x| return x.token, - .ExpectedBlockOrAssignment => |x| return x.token, - .ExpectedBlockOrExpression => |x| return x.token, - .ExpectedExprOrAssignment => |x| return x.token, - .ExpectedPrefixExpr => |x| return x.token, - .ExpectedLoopExpr => |x| return x.token, - .ExpectedDerefOrUnwrap => |x| return x.token, - .ExpectedSuffixOp => |x| return x.token, - .ExpectedBlockOrField => |x| return x.token, - .DeclBetweenFields => |x| return x.token, - .InvalidAnd => |x| return x.token, - .AsteriskAfterPointerDereference => |x| return x.token, - } + pub fn rootDecls(tree: Tree) []const Node.Index { + // Root is always index 0. + const nodes_data = tree.nodes.items(.data); + return tree.extra_data[nodes_data[0].lhs..nodes_data[0].rhs]; } - pub const InvalidToken = SingleTokenError("Invalid token '{}'"); - pub const ExpectedContainerMembers = SingleTokenError("Expected test, comptime, var decl, or container field, found '{}'"); - pub const ExpectedStringLiteral = SingleTokenError("Expected string literal, found '{}'"); - pub const ExpectedIntegerLiteral = SingleTokenError("Expected integer literal, found '{}'"); - pub const ExpectedIdentifier = SingleTokenError("Expected identifier, found '{}'"); - pub const ExpectedStatement = SingleTokenError("Expected statement, found '{}'"); - pub const ExpectedVarDeclOrFn = SingleTokenError("Expected variable declaration or function, found '{}'"); - pub const ExpectedVarDecl = SingleTokenError("Expected variable declaration, found '{}'"); - pub const ExpectedFn = SingleTokenError("Expected function, found '{}'"); - pub const ExpectedReturnType = SingleTokenError("Expected 'var' or return type expression, found '{}'"); - pub const ExpectedAggregateKw = SingleTokenError("Expected '" ++ Token.Id.Keyword_struct.symbol() ++ "', '" ++ Token.Id.Keyword_union.symbol() ++ "', '" ++ Token.Id.Keyword_enum.symbol() ++ "', or '" ++ Token.Id.Keyword_opaque.symbol() ++ "', found '{}'"); - pub const ExpectedEqOrSemi = SingleTokenError("Expected '=' or ';', found '{}'"); - pub const ExpectedSemiOrLBrace = SingleTokenError("Expected ';' or '{{', found '{}'"); - pub const ExpectedSemiOrElse = SingleTokenError("Expected ';' or 'else', found '{}'"); - pub const ExpectedLBrace = SingleTokenError("Expected '{{', found '{}'"); - pub const ExpectedLabelOrLBrace = SingleTokenError("Expected label or '{{', found '{}'"); - pub const ExpectedColonOrRParen = SingleTokenError("Expected ':' or ')', found '{}'"); - pub const ExpectedLabelable = SingleTokenError("Expected 'while', 'for', 'inline', 'suspend', or '{{', found '{}'"); - pub const ExpectedInlinable = SingleTokenError("Expected 'while' or 'for', found '{}'"); - pub const ExpectedAsmOutputReturnOrType = SingleTokenError("Expected '->' or '" ++ Token.Id.Identifier.symbol() ++ "', found '{}'"); - pub const ExpectedSliceOrRBracket = SingleTokenError("Expected ']' or '..', found '{}'"); - pub const ExpectedTypeExpr = SingleTokenError("Expected type expression, found '{}'"); - pub const ExpectedPrimaryTypeExpr = SingleTokenError("Expected primary type expression, found '{}'"); - pub const ExpectedExpr = SingleTokenError("Expected expression, found '{}'"); - pub const ExpectedPrimaryExpr = SingleTokenError("Expected primary expression, found '{}'"); - pub const ExpectedParamList = SingleTokenError("Expected parameter list, found '{}'"); - pub const ExpectedPayload = SingleTokenError("Expected loop payload, found '{}'"); - pub const ExpectedBlockOrAssignment = SingleTokenError("Expected block or assignment, found '{}'"); - pub const ExpectedBlockOrExpression = SingleTokenError("Expected block or expression, found '{}'"); - pub const ExpectedExprOrAssignment = SingleTokenError("Expected expression or assignment, found '{}'"); - pub const ExpectedPrefixExpr = SingleTokenError("Expected prefix expression, found '{}'"); - pub const ExpectedLoopExpr = SingleTokenError("Expected loop expression, found '{}'"); - pub const ExpectedDerefOrUnwrap = SingleTokenError("Expected pointer dereference or optional unwrap, found '{}'"); - pub const ExpectedSuffixOp = SingleTokenError("Expected pointer dereference, optional unwrap, or field access, found '{}'"); - pub const ExpectedBlockOrField = SingleTokenError("Expected block or field, found '{}'"); - - pub const ExpectedParamType = SimpleError("Expected parameter type"); - pub const ExpectedPubItem = SimpleError("Expected function or variable declaration after pub"); - pub const UnattachedDocComment = SimpleError("Unattached documentation comment"); - pub const ExtraAlignQualifier = SimpleError("Extra align qualifier"); - pub const ExtraConstQualifier = SimpleError("Extra const qualifier"); - pub const ExtraVolatileQualifier = SimpleError("Extra volatile qualifier"); - pub const ExtraAllowZeroQualifier = SimpleError("Extra allowzero qualifier"); - pub const DeclBetweenFields = SimpleError("Declarations are not allowed between container fields"); - pub const InvalidAnd = SimpleError("`&&` is invalid. Note that `and` is boolean AND."); - pub const AsteriskAfterPointerDereference = SimpleError("`.*` can't be followed by `*`. Are you missing a space?"); - - pub const ExpectedCall = struct { - node: *Node, - - pub fn render(self: *const ExpectedCall, tokens: []const Token.Id, stream: anytype) !void { - return stream.print("expected " ++ @tagName(Node.Tag.Call) ++ ", found {}", .{ - @tagName(self.node.tag), - }); - } - }; - - pub const ExpectedCallOrFnProto = struct { - node: *Node, - - pub fn render(self: *const ExpectedCallOrFnProto, tokens: []const Token.Id, stream: anytype) !void { - return stream.print("expected " ++ @tagName(Node.Tag.Call) ++ " or " ++ - @tagName(Node.Tag.FnProto) ++ ", found {}", .{@tagName(self.node.tag)}); - } - }; - - pub const ExpectedToken = struct { - token: TokenIndex, - expected_id: Token.Id, - - pub fn render(self: *const ExpectedToken, tokens: []const Token.Id, stream: anytype) !void { - const found_token = tokens[self.token]; - switch (found_token) { - .Invalid => { - return stream.print("expected '{}', found invalid bytes", .{self.expected_id.symbol()}); - }, - else => { - const token_name = found_token.symbol(); - return stream.print("expected '{}', found '{}'", .{ self.expected_id.symbol(), token_name }); - }, - } - } - }; - - pub const ExpectedCommaOrEnd = struct { - token: TokenIndex, - end_id: Token.Id, - - pub fn render(self: *const ExpectedCommaOrEnd, tokens: []const Token.Id, stream: anytype) !void { - const actual_token = tokens[self.token]; - return stream.print("expected ',' or '{}', found '{}'", .{ - self.end_id.symbol(), - actual_token.symbol(), - }); + pub fn renderError(tree: Tree, parse_error: Error, stream: anytype) !void { + const token_tags = tree.tokens.items(.tag); + switch (parse_error.tag) { + .asterisk_after_ptr_deref => { + return stream.writeAll("'.*' cannot be followed by '*'. Are you missing a space?"); + }, + .decl_between_fields => { + return stream.writeAll("declarations are not allowed between container fields"); + }, + .expected_block => { + return stream.print("expected block or field, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_block_or_assignment => { + return stream.print("expected block or assignment, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_block_or_expr => { + return stream.print("expected block or expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_block_or_field => { + return stream.print("expected block or field, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_container_members => { + return stream.print("expected test, comptime, var decl, or container field, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_expr => { + return stream.print("expected expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_expr_or_assignment => { + return stream.print("expected expression or assignment, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_fn => { + return stream.print("expected function, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_inlinable => { + return stream.print("expected 'while' or 'for', found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_labelable => { + return stream.print("expected 'while', 'for', 'inline', 'suspend', or '{{', found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_param_list => { + return stream.print("expected parameter list, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_prefix_expr => { + return stream.print("expected prefix expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_primary_type_expr => { + return stream.print("expected primary type expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_pub_item => { + return stream.writeAll("expected function or variable declaration after pub"); + }, + .expected_return_type => { + return stream.print("expected return type expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_semi_or_else => { + return stream.print("expected ';' or 'else', found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_semi_or_lbrace => { + return stream.print("expected ';' or '{{', found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_statement => { + return stream.print("expected statement, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_string_literal => { + return stream.print("expected string literal, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_suffix_op => { + return stream.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_type_expr => { + return stream.print("expected type expression, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_var_decl => { + return stream.print("expected variable declaration, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_var_decl_or_fn => { + return stream.print("expected variable declaration or function, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_loop_payload => { + return stream.print("expected loop payload, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .expected_container => { + return stream.print("expected a struct, enum or union, found '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .extra_align_qualifier => { + return stream.writeAll("extra align qualifier"); + }, + .extra_allowzero_qualifier => { + return stream.writeAll("extra allowzero qualifier"); + }, + .extra_const_qualifier => { + return stream.writeAll("extra const qualifier"); + }, + .extra_volatile_qualifier => { + return stream.writeAll("extra volatile qualifier"); + }, + .invalid_align => { + return stream.writeAll("alignment not allowed on arrays"); + }, + .invalid_and => { + return stream.writeAll("`&&` is invalid; note that `and` is boolean AND"); + }, + .invalid_bit_range => { + return stream.writeAll("bit range not allowed on slices and arrays"); + }, + .invalid_token => { + return stream.print("invalid token '{s}'", .{ + token_tags[parse_error.token].symbol(), + }); + }, + .same_line_doc_comment => { + return stream.writeAll("same line documentation comment"); + }, + .unattached_doc_comment => { + return stream.writeAll("unattached documentation comment"); + }, + + .expected_token => { + const found_tag = token_tags[parse_error.token]; + const expected_symbol = parse_error.extra.expected_tag.symbol(); + switch (found_tag) { + .invalid => return stream.print("expected '{s}', found invalid bytes", .{ + expected_symbol, + }), + else => return stream.print("expected '{s}', found '{s}'", .{ + expected_symbol, found_tag.symbol(), + }), + } + }, } - }; - - fn SingleTokenError(comptime msg: []const u8) type { - return struct { - const ThisError = @This(); + } - token: TokenIndex, + pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { + const tags = tree.nodes.items(.tag); + const datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + var end_offset: TokenIndex = 0; + var n = node; + while (true) switch (tags[n]) { + .root => return 0, + + .test_decl, + .@"errdefer", + .@"defer", + .bool_not, + .negation, + .bit_not, + .negation_wrap, + .address_of, + .@"try", + .@"await", + .optional_type, + .@"switch", + .switch_comma, + .if_simple, + .@"if", + .@"suspend", + .@"resume", + .@"continue", + .@"break", + .@"return", + .anyframe_type, + .identifier, + .anyframe_literal, + .char_literal, + .integer_literal, + .float_literal, + .false_literal, + .true_literal, + .null_literal, + .undefined_literal, + .unreachable_literal, + .string_literal, + .multiline_string_literal, + .grouped_expression, + .builtin_call_two, + .builtin_call_two_comma, + .builtin_call, + .builtin_call_comma, + .error_set_decl, + .@"anytype", + .@"comptime", + .@"nosuspend", + .asm_simple, + .@"asm", + .array_type, + .array_type_sentinel, + .error_value, + => return main_tokens[n] - end_offset, + + .array_init_dot, + .array_init_dot_comma, + .array_init_dot_two, + .array_init_dot_two_comma, + .struct_init_dot, + .struct_init_dot_comma, + .struct_init_dot_two, + .struct_init_dot_two_comma, + .enum_literal, + => return main_tokens[n] - 1 - end_offset, + + .@"catch", + .field_access, + .unwrap_optional, + .equal_equal, + .bang_equal, + .less_than, + .greater_than, + .less_or_equal, + .greater_or_equal, + .assign_mul, + .assign_div, + .assign_mod, + .assign_add, + .assign_sub, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_and, + .assign_bit_xor, + .assign_bit_or, + .assign_mul_wrap, + .assign_add_wrap, + .assign_sub_wrap, + .assign, + .merge_error_sets, + .mul, + .div, + .mod, + .array_mult, + .mul_wrap, + .add, + .sub, + .array_cat, + .add_wrap, + .sub_wrap, + .bit_shift_left, + .bit_shift_right, + .bit_and, + .bit_xor, + .bit_or, + .@"orelse", + .bool_and, + .bool_or, + .slice_open, + .slice, + .slice_sentinel, + .deref, + .array_access, + .array_init_one, + .array_init_one_comma, + .array_init, + .array_init_comma, + .struct_init_one, + .struct_init_one_comma, + .struct_init, + .struct_init_comma, + .call_one, + .call_one_comma, + .call, + .call_comma, + .switch_range, + .error_union, + => n = datas[n].lhs, + + .fn_decl, + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + => { + var i = main_tokens[n]; // fn token + while (i > 0) { + i -= 1; + switch (token_tags[i]) { + .keyword_extern, + .keyword_export, + .keyword_pub, + .keyword_threadlocal, + .string_literal, + => continue, + + else => return i + 1 - end_offset, + } + } + return i - end_offset; + }, - pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: anytype) !void { - const actual_token = tokens[self.token]; - return stream.print(msg, .{actual_token.symbol()}); - } + .@"usingnamespace" => { + const main_token = main_tokens[n]; + if (main_token > 0 and token_tags[main_token - 1] == .keyword_pub) { + end_offset += 1; + } + return main_token - end_offset; + }, + + .async_call_one, + .async_call_one_comma, + .async_call, + .async_call_comma, + => { + end_offset += 1; // async token + n = datas[n].lhs; + }, + + .container_field_init, + .container_field_align, + .container_field, + => { + const name_token = main_tokens[n]; + if (name_token > 0 and token_tags[name_token - 1] == .keyword_comptime) { + end_offset += 1; + } + return name_token - end_offset; + }, + + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + => { + var i = main_tokens[n]; // mut token + while (i > 0) { + i -= 1; + switch (token_tags[i]) { + .keyword_extern, + .keyword_export, + .keyword_comptime, + .keyword_pub, + .keyword_threadlocal, + .string_literal, + => continue, + + else => return i + 1 - end_offset, + } + } + return i - end_offset; + }, + + .block, + .block_semicolon, + .block_two, + .block_two_semicolon, + => { + // Look for a label. + const lbrace = main_tokens[n]; + if (token_tags[lbrace - 1] == .colon) { + end_offset += 2; + } + return lbrace - end_offset; + }, + + .container_decl, + .container_decl_trailing, + .container_decl_two, + .container_decl_two_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => { + const main_token = main_tokens[n]; + switch (token_tags[main_token - 1]) { + .keyword_packed, .keyword_extern => end_offset += 1, + else => {}, + } + return main_token - end_offset; + }, + + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + => { + const main_token = main_tokens[n]; + return switch (token_tags[main_token]) { + .asterisk, + .asterisk_asterisk, + => switch (token_tags[main_token - 1]) { + .l_bracket => main_token - 1, + else => main_token, + }, + .l_bracket => main_token, + else => unreachable, + } - end_offset; + }, + + .switch_case_one => { + if (datas[n].lhs == 0) { + return main_tokens[n] - 1 - end_offset; // else token + } else { + n = datas[n].lhs; + } + }, + .switch_case => { + const extra = tree.extraData(datas[n].lhs, Node.SubRange); + assert(extra.end - extra.start > 0); + n = tree.extra_data[extra.start]; + }, + + .asm_output, .asm_input => { + assert(token_tags[main_tokens[n] - 1] == .l_bracket); + return main_tokens[n] - 1 - end_offset; + }, + + .while_simple, + .while_cont, + .@"while", + .for_simple, + .@"for", + => { + // Look for a label and inline. + const main_token = main_tokens[n]; + var result = main_token; + if (token_tags[result - 1] == .keyword_inline) { + result -= 1; + } + if (token_tags[result - 1] == .colon) { + result -= 2; + } + return result - end_offset; + }, }; } - fn SimpleError(comptime msg: []const u8) type { - return struct { - const ThisError = @This(); - - token: TokenIndex, - - pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: anytype) !void { - return stream.writeAll(msg); - } + pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { + const tags = tree.nodes.items(.tag); + const datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const token_starts = tree.tokens.items(.start); + const token_tags = tree.tokens.items(.tag); + var n = node; + var end_offset: TokenIndex = 0; + while (true) switch (tags[n]) { + .root => return @intCast(TokenIndex, tree.tokens.len - 1), + + .@"usingnamespace", + .bool_not, + .negation, + .bit_not, + .negation_wrap, + .address_of, + .@"try", + .@"await", + .optional_type, + .@"resume", + .@"nosuspend", + .@"comptime", + => n = datas[n].lhs, + + .test_decl, + .@"errdefer", + .@"defer", + .@"catch", + .equal_equal, + .bang_equal, + .less_than, + .greater_than, + .less_or_equal, + .greater_or_equal, + .assign_mul, + .assign_div, + .assign_mod, + .assign_add, + .assign_sub, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_and, + .assign_bit_xor, + .assign_bit_or, + .assign_mul_wrap, + .assign_add_wrap, + .assign_sub_wrap, + .assign, + .merge_error_sets, + .mul, + .div, + .mod, + .array_mult, + .mul_wrap, + .add, + .sub, + .array_cat, + .add_wrap, + .sub_wrap, + .bit_shift_left, + .bit_shift_right, + .bit_and, + .bit_xor, + .bit_or, + .@"orelse", + .bool_and, + .bool_or, + .anyframe_type, + .error_union, + .if_simple, + .while_simple, + .for_simple, + .fn_proto_simple, + .fn_proto_multi, + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + .array_type, + .switch_case_one, + .switch_case, + .switch_range, + => n = datas[n].rhs, + + .field_access, + .unwrap_optional, + .grouped_expression, + .multiline_string_literal, + .error_set_decl, + .asm_simple, + .asm_output, + .asm_input, + .error_value, + => return datas[n].rhs + end_offset, + + .@"anytype", + .anyframe_literal, + .char_literal, + .integer_literal, + .float_literal, + .false_literal, + .true_literal, + .null_literal, + .undefined_literal, + .unreachable_literal, + .identifier, + .deref, + .enum_literal, + .string_literal, + => return main_tokens[n] + end_offset, + + .@"return" => if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + return main_tokens[n] + end_offset; + }, + + .call, .async_call => { + end_offset += 1; // for the rparen + const params = tree.extraData(datas[n].rhs, Node.SubRange); + if (params.end - params.start == 0) { + return main_tokens[n] + end_offset; + } + n = tree.extra_data[params.end - 1]; // last parameter + }, + .tagged_union_enum_tag => { + const members = tree.extraData(datas[n].rhs, Node.SubRange); + if (members.end - members.start == 0) { + end_offset += 4; // for the rparen + rparen + lbrace + rbrace + n = datas[n].lhs; + } else { + end_offset += 1; // for the rbrace + n = tree.extra_data[members.end - 1]; // last parameter + } + }, + .call_comma, + .async_call_comma, + .tagged_union_enum_tag_trailing, + => { + end_offset += 2; // for the comma/semicolon + rparen/rbrace + const params = tree.extraData(datas[n].rhs, Node.SubRange); + assert(params.end > params.start); + n = tree.extra_data[params.end - 1]; // last parameter + }, + .@"switch" => { + const cases = tree.extraData(datas[n].rhs, Node.SubRange); + if (cases.end - cases.start == 0) { + end_offset += 3; // rparen, lbrace, rbrace + n = datas[n].lhs; // condition expression + } else { + end_offset += 1; // for the rbrace + n = tree.extra_data[cases.end - 1]; // last case + } + }, + .container_decl_arg => { + const members = tree.extraData(datas[n].rhs, Node.SubRange); + if (members.end - members.start == 0) { + end_offset += 1; // for the rparen + n = datas[n].lhs; + } else { + end_offset += 1; // for the rbrace + n = tree.extra_data[members.end - 1]; // last parameter + } + }, + .@"asm" => { + const extra = tree.extraData(datas[n].rhs, Node.Asm); + return extra.rparen + end_offset; + }, + .array_init, + .struct_init, + => { + const elements = tree.extraData(datas[n].rhs, Node.SubRange); + assert(elements.end - elements.start > 0); + end_offset += 1; // for the rbrace + n = tree.extra_data[elements.end - 1]; // last element + }, + .array_init_comma, + .struct_init_comma, + .container_decl_arg_trailing, + .switch_comma, + => { + const members = tree.extraData(datas[n].rhs, Node.SubRange); + assert(members.end - members.start > 0); + end_offset += 2; // for the comma + rbrace + n = tree.extra_data[members.end - 1]; // last parameter + }, + .array_init_dot, + .struct_init_dot, + .block, + .container_decl, + .tagged_union, + .builtin_call, + => { + assert(datas[n].rhs - datas[n].lhs > 0); + end_offset += 1; // for the rbrace + n = tree.extra_data[datas[n].rhs - 1]; // last statement + }, + .array_init_dot_comma, + .struct_init_dot_comma, + .block_semicolon, + .container_decl_trailing, + .tagged_union_trailing, + .builtin_call_comma, + => { + assert(datas[n].rhs - datas[n].lhs > 0); + end_offset += 2; // for the comma/semicolon + rbrace/rparen + n = tree.extra_data[datas[n].rhs - 1]; // last member + }, + .call_one, + .async_call_one, + .array_access, + => { + end_offset += 1; // for the rparen/rbracket + if (datas[n].rhs == 0) { + return main_tokens[n] + end_offset; + } + n = datas[n].rhs; + }, + .array_init_dot_two, + .block_two, + .builtin_call_two, + .struct_init_dot_two, + .container_decl_two, + .tagged_union_two, + => { + if (datas[n].rhs != 0) { + end_offset += 1; // for the rparen/rbrace + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + end_offset += 1; // for the rparen/rbrace + n = datas[n].lhs; + } else { + switch (tags[n]) { + .array_init_dot_two, + .block_two, + .struct_init_dot_two, + => end_offset += 1, // rbrace + .builtin_call_two => end_offset += 2, // lparen/lbrace + rparen/rbrace + .container_decl_two => { + var i: u32 = 2; // lbrace + rbrace + while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1; + end_offset += i; + }, + .tagged_union_two => { + var i: u32 = 5; // (enum) {} + while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1; + end_offset += i; + }, + else => unreachable, + } + return main_tokens[n] + end_offset; + } + }, + .array_init_dot_two_comma, + .builtin_call_two_comma, + .block_two_semicolon, + .struct_init_dot_two_comma, + .container_decl_two_trailing, + .tagged_union_two_trailing, + => { + end_offset += 2; // for the comma/semicolon + rbrace/rparen + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + unreachable; + } + }, + .simple_var_decl => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + end_offset += 1; // from mut token to name + return main_tokens[n] + end_offset; + } + }, + .aligned_var_decl => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + end_offset += 1; // for the rparen + n = datas[n].lhs; + } else { + end_offset += 1; // from mut token to name + return main_tokens[n] + end_offset; + } + }, + .global_var_decl => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else { + const extra = tree.extraData(datas[n].lhs, Node.GlobalVarDecl); + if (extra.section_node != 0) { + end_offset += 1; // for the rparen + n = extra.section_node; + } else if (extra.align_node != 0) { + end_offset += 1; // for the rparen + n = extra.align_node; + } else if (extra.type_node != 0) { + n = extra.type_node; + } else { + end_offset += 1; // from mut token to name + return main_tokens[n] + end_offset; + } + } + }, + .local_var_decl => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else { + const extra = tree.extraData(datas[n].lhs, Node.LocalVarDecl); + if (extra.align_node != 0) { + end_offset += 1; // for the rparen + n = extra.align_node; + } else if (extra.type_node != 0) { + n = extra.type_node; + } else { + end_offset += 1; // from mut token to name + return main_tokens[n] + end_offset; + } + } + }, + .container_field_init => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + return main_tokens[n] + end_offset; + } + }, + .container_field_align => { + if (datas[n].rhs != 0) { + end_offset += 1; // for the rparen + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + return main_tokens[n] + end_offset; + } + }, + .container_field => { + const extra = tree.extraData(datas[n].rhs, Node.ContainerField); + if (extra.value_expr != 0) { + n = extra.value_expr; + } else if (extra.align_expr != 0) { + end_offset += 1; // for the rparen + n = extra.align_expr; + } else if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + return main_tokens[n] + end_offset; + } + }, + + .array_init_one, + .struct_init_one, + => { + end_offset += 1; // rbrace + if (datas[n].rhs == 0) { + return main_tokens[n] + end_offset; + } else { + n = datas[n].rhs; + } + }, + .slice_open, + .call_one_comma, + .async_call_one_comma, + .array_init_one_comma, + .struct_init_one_comma, + => { + end_offset += 2; // ellipsis2 + rbracket, or comma + rparen + n = datas[n].rhs; + assert(n != 0); + }, + .slice => { + const extra = tree.extraData(datas[n].rhs, Node.Slice); + assert(extra.end != 0); // should have used SliceOpen + end_offset += 1; // rbracket + n = extra.end; + }, + .slice_sentinel => { + const extra = tree.extraData(datas[n].rhs, Node.SliceSentinel); + assert(extra.sentinel != 0); // should have used Slice + end_offset += 1; // rbracket + n = extra.sentinel; + }, + + .@"continue" => { + if (datas[n].lhs != 0) { + return datas[n].lhs + end_offset; + } else { + return main_tokens[n] + end_offset; + } + }, + .@"break" => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else if (datas[n].lhs != 0) { + return datas[n].lhs + end_offset; + } else { + return main_tokens[n] + end_offset; + } + }, + .fn_decl => { + if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else { + n = datas[n].lhs; + } + }, + .fn_proto_one => { + const extra = tree.extraData(datas[n].lhs, Node.FnProtoOne); + // linksection, callconv, align can appear in any order, so we + // find the last one here. + var max_node: Node.Index = datas[n].rhs; + var max_start = token_starts[main_tokens[max_node]]; + var max_offset: TokenIndex = 0; + if (extra.align_expr != 0) { + const start = token_starts[main_tokens[extra.align_expr]]; + if (start > max_start) { + max_node = extra.align_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + if (extra.section_expr != 0) { + const start = token_starts[main_tokens[extra.section_expr]]; + if (start > max_start) { + max_node = extra.section_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + if (extra.callconv_expr != 0) { + const start = token_starts[main_tokens[extra.callconv_expr]]; + if (start > max_start) { + max_node = extra.callconv_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + n = max_node; + end_offset += max_offset; + }, + .fn_proto => { + const extra = tree.extraData(datas[n].lhs, Node.FnProto); + // linksection, callconv, align can appear in any order, so we + // find the last one here. + var max_node: Node.Index = datas[n].rhs; + var max_start = token_starts[main_tokens[max_node]]; + var max_offset: TokenIndex = 0; + if (extra.align_expr != 0) { + const start = token_starts[main_tokens[extra.align_expr]]; + if (start > max_start) { + max_node = extra.align_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + if (extra.section_expr != 0) { + const start = token_starts[main_tokens[extra.section_expr]]; + if (start > max_start) { + max_node = extra.section_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + if (extra.callconv_expr != 0) { + const start = token_starts[main_tokens[extra.callconv_expr]]; + if (start > max_start) { + max_node = extra.callconv_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } + n = max_node; + end_offset += max_offset; + }, + .while_cont => { + const extra = tree.extraData(datas[n].rhs, Node.WhileCont); + assert(extra.then_expr != 0); + n = extra.then_expr; + }, + .@"while" => { + const extra = tree.extraData(datas[n].rhs, Node.While); + assert(extra.else_expr != 0); + n = extra.else_expr; + }, + .@"if", .@"for" => { + const extra = tree.extraData(datas[n].rhs, Node.If); + assert(extra.else_expr != 0); + n = extra.else_expr; + }, + .@"suspend" => { + if (datas[n].lhs != 0) { + n = datas[n].lhs; + } else { + return main_tokens[n] + end_offset; + } + }, + .array_type_sentinel => { + const extra = tree.extraData(datas[n].rhs, Node.ArrayTypeSentinel); + n = extra.elem_type; + }, }; } -}; -pub const Node = struct { - tag: Tag, + pub fn tokensOnSameLine(tree: Tree, token1: TokenIndex, token2: TokenIndex) bool { + const token_starts = tree.tokens.items(.start); + const source = tree.source[token_starts[token1]..token_starts[token2]]; + return mem.indexOfScalar(u8, source, '\n') == null; + } - pub const Tag = enum { - // Top level - Root, - Use, - TestDecl, - - // Statements - VarDecl, - Defer, - - // Infix operators - Catch, - - // SimpleInfixOp - Add, - AddWrap, - ArrayCat, - ArrayMult, - Assign, - AssignBitAnd, - AssignBitOr, - AssignBitShiftLeft, - AssignBitShiftRight, - AssignBitXor, - AssignDiv, - AssignSub, - AssignSubWrap, - AssignMod, - AssignAdd, - AssignAddWrap, - AssignMul, - AssignMulWrap, - BangEqual, - BitAnd, - BitOr, - BitShiftLeft, - BitShiftRight, - BitXor, - BoolAnd, - BoolOr, - Div, - EqualEqual, - ErrorUnion, - GreaterOrEqual, - GreaterThan, - LessOrEqual, - LessThan, - MergeErrorSets, - Mod, - Mul, - MulWrap, - Period, - Range, - Sub, - SubWrap, - OrElse, - - // SimplePrefixOp - AddressOf, - Await, - BitNot, - BoolNot, - OptionalType, - Negation, - NegationWrap, - Resume, - Try, - - ArrayType, - /// ArrayType but has a sentinel node. - ArrayTypeSentinel, - PtrType, - SliceType, - /// `a[b..c]` - Slice, - /// `a.*` - Deref, - /// `a.?` - UnwrapOptional, - /// `a[b]` - ArrayAccess, - /// `T{a, b}` - ArrayInitializer, - /// ArrayInitializer but with `.` instead of a left-hand-side operand. - ArrayInitializerDot, - /// `T{.a = b}` - StructInitializer, - /// StructInitializer but with `.` instead of a left-hand-side operand. - StructInitializerDot, - /// `foo()` - Call, - - // Control flow - Switch, - While, - For, - If, - Suspend, - Continue, - Break, - Return, - - // Type expressions - AnyType, - ErrorType, - FnProto, - AnyFrameType, - - // Primary expressions - IntegerLiteral, - FloatLiteral, - EnumLiteral, - StringLiteral, - MultilineStringLiteral, - CharLiteral, - BoolLiteral, - NullLiteral, - UndefinedLiteral, - Unreachable, - Identifier, - GroupedExpression, - BuiltinCall, - ErrorSetDecl, - ContainerDecl, - Asm, - Comptime, - Nosuspend, - Block, - LabeledBlock, - - // Misc - DocComment, - SwitchCase, // TODO make this not a child of AST Node - SwitchElse, // TODO make this not a child of AST Node - Else, // TODO make this not a child of AST Node - Payload, // TODO make this not a child of AST Node - PointerPayload, // TODO make this not a child of AST Node - PointerIndexPayload, // TODO make this not a child of AST Node - ContainerField, - ErrorTag, // TODO make this not a child of AST Node - FieldInitializer, // TODO make this not a child of AST Node - - pub fn Type(tag: Tag) type { - return switch (tag) { - .Root => Root, - .Use => Use, - .TestDecl => TestDecl, - .VarDecl => VarDecl, - .Defer => Defer, - .Catch => Catch, - - .Add, - .AddWrap, - .ArrayCat, - .ArrayMult, - .Assign, - .AssignBitAnd, - .AssignBitOr, - .AssignBitShiftLeft, - .AssignBitShiftRight, - .AssignBitXor, - .AssignDiv, - .AssignSub, - .AssignSubWrap, - .AssignMod, - .AssignAdd, - .AssignAddWrap, - .AssignMul, - .AssignMulWrap, - .BangEqual, - .BitAnd, - .BitOr, - .BitShiftLeft, - .BitShiftRight, - .BitXor, - .BoolAnd, - .BoolOr, - .Div, - .EqualEqual, - .ErrorUnion, - .GreaterOrEqual, - .GreaterThan, - .LessOrEqual, - .LessThan, - .MergeErrorSets, - .Mod, - .Mul, - .MulWrap, - .Period, - .Range, - .Sub, - .SubWrap, - .OrElse, - => SimpleInfixOp, - - .AddressOf, - .Await, - .BitNot, - .BoolNot, - .OptionalType, - .Negation, - .NegationWrap, - .Resume, - .Try, - => SimplePrefixOp, - - .Identifier, - .BoolLiteral, - .NullLiteral, - .UndefinedLiteral, - .Unreachable, - .AnyType, - .ErrorType, - .IntegerLiteral, - .FloatLiteral, - .StringLiteral, - .CharLiteral, - => OneToken, - - .Continue, - .Break, - .Return, - => ControlFlowExpression, - - .ArrayType => ArrayType, - .ArrayTypeSentinel => ArrayTypeSentinel, - - .PtrType => PtrType, - .SliceType => SliceType, - .Slice => Slice, - .Deref, .UnwrapOptional => SimpleSuffixOp, - .ArrayAccess => ArrayAccess, - - .ArrayInitializer => ArrayInitializer, - .ArrayInitializerDot => ArrayInitializerDot, - - .StructInitializer => StructInitializer, - .StructInitializerDot => StructInitializerDot, - - .Call => Call, - .Switch => Switch, - .While => While, - .For => For, - .If => If, - .Suspend => Suspend, - .FnProto => FnProto, - .AnyFrameType => AnyFrameType, - .EnumLiteral => EnumLiteral, - .MultilineStringLiteral => MultilineStringLiteral, - .GroupedExpression => GroupedExpression, - .BuiltinCall => BuiltinCall, - .ErrorSetDecl => ErrorSetDecl, - .ContainerDecl => ContainerDecl, - .Asm => Asm, - .Comptime => Comptime, - .Nosuspend => Nosuspend, - .Block => Block, - .LabeledBlock => LabeledBlock, - .DocComment => DocComment, - .SwitchCase => SwitchCase, - .SwitchElse => SwitchElse, - .Else => Else, - .Payload => Payload, - .PointerPayload => PointerPayload, - .PointerIndexPayload => PointerIndexPayload, - .ContainerField => ContainerField, - .ErrorTag => ErrorTag, - .FieldInitializer => FieldInitializer, - }; - } + pub fn getNodeSource(tree: Tree, node: Node.Index) []const u8 { + const token_starts = tree.tokens.items(.start); + const first_token = tree.firstToken(node); + const last_token = tree.lastToken(node); + const start = token_starts[first_token]; + const end = token_starts[last_token] + tree.tokenSlice(last_token).len; + return tree.source[start..end]; + } - pub fn isBlock(tag: Tag) bool { - return switch (tag) { - .Block, .LabeledBlock => true, - else => false, - }; - } - }; + pub fn globalVarDecl(tree: Tree, node: Node.Index) full.VarDecl { + assert(tree.nodes.items(.tag)[node] == .global_var_decl); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.GlobalVarDecl); + return tree.fullVarDecl(.{ + .type_node = extra.type_node, + .align_node = extra.align_node, + .section_node = extra.section_node, + .init_node = data.rhs, + .mut_token = tree.nodes.items(.main_token)[node], + }); + } - /// Prefer `castTag` to this. - pub fn cast(base: *Node, comptime T: type) ?*T { - if (std.meta.fieldInfo(T, "base").default_value) |default_base| { - return base.castTag(default_base.tag); - } - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (base.tag == tag) { - if (T == tag.Type()) { - return @fieldParentPtr(T, "base", base); - } - return null; - } - } - unreachable; + pub fn localVarDecl(tree: Tree, node: Node.Index) full.VarDecl { + assert(tree.nodes.items(.tag)[node] == .local_var_decl); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.LocalVarDecl); + return tree.fullVarDecl(.{ + .type_node = extra.type_node, + .align_node = extra.align_node, + .section_node = 0, + .init_node = data.rhs, + .mut_token = tree.nodes.items(.main_token)[node], + }); } - pub fn castTag(base: *Node, comptime tag: Tag) ?*tag.Type() { - if (base.tag == tag) { - return @fieldParentPtr(tag.Type(), "base", base); - } - return null; + pub fn simpleVarDecl(tree: Tree, node: Node.Index) full.VarDecl { + assert(tree.nodes.items(.tag)[node] == .simple_var_decl); + const data = tree.nodes.items(.data)[node]; + return tree.fullVarDecl(.{ + .type_node = data.lhs, + .align_node = 0, + .section_node = 0, + .init_node = data.rhs, + .mut_token = tree.nodes.items(.main_token)[node], + }); } - pub fn iterate(base: *Node, index: usize) ?*Node { - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (base.tag == tag) { - return @fieldParentPtr(tag.Type(), "base", base).iterate(index); - } - } - unreachable; + pub fn alignedVarDecl(tree: Tree, node: Node.Index) full.VarDecl { + assert(tree.nodes.items(.tag)[node] == .aligned_var_decl); + const data = tree.nodes.items(.data)[node]; + return tree.fullVarDecl(.{ + .type_node = 0, + .align_node = data.lhs, + .section_node = 0, + .init_node = data.rhs, + .mut_token = tree.nodes.items(.main_token)[node], + }); } - pub fn firstToken(base: *const Node) TokenIndex { - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (base.tag == tag) { - return @fieldParentPtr(tag.Type(), "base", base).firstToken(); - } - } - unreachable; + pub fn ifSimple(tree: Tree, node: Node.Index) full.If { + assert(tree.nodes.items(.tag)[node] == .if_simple); + const data = tree.nodes.items(.data)[node]; + return tree.fullIf(.{ + .cond_expr = data.lhs, + .then_expr = data.rhs, + .else_expr = 0, + .if_token = tree.nodes.items(.main_token)[node], + }); } - pub fn lastToken(base: *const Node) TokenIndex { - inline for (@typeInfo(Tag).Enum.fields) |field| { - const tag = @intToEnum(Tag, field.value); - if (base.tag == tag) { - return @fieldParentPtr(tag.Type(), "base", base).lastToken(); - } - } - unreachable; - } - - pub fn requireSemiColon(base: *const Node) bool { - var n = base; - while (true) { - switch (n.tag) { - .Root, - .ContainerField, - .Block, - .LabeledBlock, - .Payload, - .PointerPayload, - .PointerIndexPayload, - .Switch, - .SwitchCase, - .SwitchElse, - .FieldInitializer, - .DocComment, - .TestDecl, - => return false, - - .While => { - const while_node = @fieldParentPtr(While, "base", n); - if (while_node.@"else") |@"else"| { - n = &@"else".base; - continue; - } + pub fn ifFull(tree: Tree, node: Node.Index) full.If { + assert(tree.nodes.items(.tag)[node] == .@"if"); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.If); + return tree.fullIf(.{ + .cond_expr = data.lhs, + .then_expr = extra.then_expr, + .else_expr = extra.else_expr, + .if_token = tree.nodes.items(.main_token)[node], + }); + } - return !while_node.body.tag.isBlock(); - }, - .For => { - const for_node = @fieldParentPtr(For, "base", n); - if (for_node.@"else") |@"else"| { - n = &@"else".base; - continue; - } + pub fn containerField(tree: Tree, node: Node.Index) full.ContainerField { + assert(tree.nodes.items(.tag)[node] == .container_field); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.ContainerField); + return tree.fullContainerField(.{ + .name_token = tree.nodes.items(.main_token)[node], + .type_expr = data.lhs, + .value_expr = extra.value_expr, + .align_expr = extra.align_expr, + }); + } - return !for_node.body.tag.isBlock(); - }, - .If => { - const if_node = @fieldParentPtr(If, "base", n); - if (if_node.@"else") |@"else"| { - n = &@"else".base; - continue; - } + pub fn containerFieldInit(tree: Tree, node: Node.Index) full.ContainerField { + assert(tree.nodes.items(.tag)[node] == .container_field_init); + const data = tree.nodes.items(.data)[node]; + return tree.fullContainerField(.{ + .name_token = tree.nodes.items(.main_token)[node], + .type_expr = data.lhs, + .value_expr = data.rhs, + .align_expr = 0, + }); + } - return !if_node.body.tag.isBlock(); - }, - .Else => { - const else_node = @fieldParentPtr(Else, "base", n); - n = else_node.body; - continue; - }, - .Defer => { - const defer_node = @fieldParentPtr(Defer, "base", n); - return !defer_node.expr.tag.isBlock(); - }, - .Comptime => { - const comptime_node = @fieldParentPtr(Comptime, "base", n); - return !comptime_node.expr.tag.isBlock(); - }, - .Suspend => { - const suspend_node = @fieldParentPtr(Suspend, "base", n); - if (suspend_node.body) |body| { - return !body.tag.isBlock(); - } + pub fn containerFieldAlign(tree: Tree, node: Node.Index) full.ContainerField { + assert(tree.nodes.items(.tag)[node] == .container_field_align); + const data = tree.nodes.items(.data)[node]; + return tree.fullContainerField(.{ + .name_token = tree.nodes.items(.main_token)[node], + .type_expr = data.lhs, + .value_expr = 0, + .align_expr = data.rhs, + }); + } - return true; - }, - .Nosuspend => { - const nosuspend_node = @fieldParentPtr(Nosuspend, "base", n); - return !nosuspend_node.expr.tag.isBlock(); - }, - else => return true, - } - } + pub fn fnProtoSimple(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.FnProto { + assert(tree.nodes.items(.tag)[node] == .fn_proto_simple); + const data = tree.nodes.items(.data)[node]; + buffer[0] = data.lhs; + const params = if (data.lhs == 0) buffer[0..0] else buffer[0..1]; + return tree.fullFnProto(.{ + .fn_token = tree.nodes.items(.main_token)[node], + .return_type = data.rhs, + .params = params, + .align_expr = 0, + .section_expr = 0, + .callconv_expr = 0, + }); } - /// Asserts the node is a Block or LabeledBlock and returns the statements slice. - pub fn blockStatements(base: *Node) []*Node { - if (base.castTag(.Block)) |block| { - return block.statements(); - } else if (base.castTag(.LabeledBlock)) |labeled_block| { - return labeled_block.statements(); - } else { - unreachable; - } + pub fn fnProtoMulti(tree: Tree, node: Node.Index) full.FnProto { + assert(tree.nodes.items(.tag)[node] == .fn_proto_multi); + const data = tree.nodes.items(.data)[node]; + const params_range = tree.extraData(data.lhs, Node.SubRange); + const params = tree.extra_data[params_range.start..params_range.end]; + return tree.fullFnProto(.{ + .fn_token = tree.nodes.items(.main_token)[node], + .return_type = data.rhs, + .params = params, + .align_expr = 0, + .section_expr = 0, + .callconv_expr = 0, + }); } - pub fn findFirstWithId(self: *Node, id: Id) ?*Node { - if (self.id == id) return self; - var child_i: usize = 0; - while (self.iterate(child_i)) |child| : (child_i += 1) { - if (child.findFirstWithId(id)) |result| return result; - } - return null; + pub fn fnProtoOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.FnProto { + assert(tree.nodes.items(.tag)[node] == .fn_proto_one); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.FnProtoOne); + buffer[0] = extra.param; + const params = if (extra.param == 0) buffer[0..0] else buffer[0..1]; + return tree.fullFnProto(.{ + .fn_token = tree.nodes.items(.main_token)[node], + .return_type = data.rhs, + .params = params, + .align_expr = extra.align_expr, + .section_expr = extra.section_expr, + .callconv_expr = extra.callconv_expr, + }); } - pub fn dump(self: *Node, indent: usize) void { - { - var i: usize = 0; - while (i < indent) : (i += 1) { - std.debug.warn(" ", .{}); - } - } - std.debug.warn("{}\n", .{@tagName(self.tag)}); + pub fn fnProto(tree: Tree, node: Node.Index) full.FnProto { + assert(tree.nodes.items(.tag)[node] == .fn_proto); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.FnProto); + const params = tree.extra_data[extra.params_start..extra.params_end]; + return tree.fullFnProto(.{ + .fn_token = tree.nodes.items(.main_token)[node], + .return_type = data.rhs, + .params = params, + .align_expr = extra.align_expr, + .section_expr = extra.section_expr, + .callconv_expr = extra.callconv_expr, + }); + } - var child_i: usize = 0; - while (self.iterate(child_i)) |child| : (child_i += 1) { - child.dump(indent + 2); - } + pub fn structInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.StructInit { + assert(tree.nodes.items(.tag)[node] == .struct_init_one or + tree.nodes.items(.tag)[node] == .struct_init_one_comma); + const data = tree.nodes.items(.data)[node]; + buffer[0] = data.rhs; + const fields = if (data.rhs == 0) buffer[0..0] else buffer[0..1]; + return tree.fullStructInit(.{ + .lbrace = tree.nodes.items(.main_token)[node], + .fields = fields, + .type_expr = data.lhs, + }); } - /// The decls data follows this struct in memory as an array of Node pointers. - pub const Root = struct { - base: Node = Node{ .tag = .Root }, - eof_token: TokenIndex, - decls_len: NodeIndex, + pub fn structInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.StructInit { + assert(tree.nodes.items(.tag)[node] == .struct_init_dot_two or + tree.nodes.items(.tag)[node] == .struct_init_dot_two_comma); + const data = tree.nodes.items(.data)[node]; + buffer.* = .{ data.lhs, data.rhs }; + const fields = if (data.rhs != 0) + buffer[0..2] + else if (data.lhs != 0) + buffer[0..1] + else + buffer[0..0]; + return tree.fullStructInit(.{ + .lbrace = tree.nodes.items(.main_token)[node], + .fields = fields, + .type_expr = 0, + }); + } - /// After this the caller must initialize the decls list. - pub fn create(allocator: *mem.Allocator, decls_len: NodeIndex, eof_token: TokenIndex) !*Root { - const bytes = try allocator.alignedAlloc(u8, @alignOf(Root), sizeInBytes(decls_len)); - const self = @ptrCast(*Root, bytes.ptr); - self.* = .{ - .eof_token = eof_token, - .decls_len = decls_len, - }; - return self; - } + pub fn structInitDot(tree: Tree, node: Node.Index) full.StructInit { + assert(tree.nodes.items(.tag)[node] == .struct_init_dot or + tree.nodes.items(.tag)[node] == .struct_init_dot_comma); + const data = tree.nodes.items(.data)[node]; + return tree.fullStructInit(.{ + .lbrace = tree.nodes.items(.main_token)[node], + .fields = tree.extra_data[data.lhs..data.rhs], + .type_expr = 0, + }); + } - pub fn destroy(self: *Decl, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.decls_len)]; - allocator.free(bytes); - } + pub fn structInit(tree: Tree, node: Node.Index) full.StructInit { + assert(tree.nodes.items(.tag)[node] == .struct_init or + tree.nodes.items(.tag)[node] == .struct_init_comma); + const data = tree.nodes.items(.data)[node]; + const fields_range = tree.extraData(data.rhs, Node.SubRange); + return tree.fullStructInit(.{ + .lbrace = tree.nodes.items(.main_token)[node], + .fields = tree.extra_data[fields_range.start..fields_range.end], + .type_expr = data.lhs, + }); + } - pub fn iterate(self: *const Root, index: usize) ?*Node { - var i = index; + pub fn arrayInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.ArrayInit { + assert(tree.nodes.items(.tag)[node] == .array_init_one or + tree.nodes.items(.tag)[node] == .array_init_one_comma); + const data = tree.nodes.items(.data)[node]; + buffer[0] = data.rhs; + const elements = if (data.rhs == 0) buffer[0..0] else buffer[0..1]; + return .{ + .ast = .{ + .lbrace = tree.nodes.items(.main_token)[node], + .elements = elements, + .type_expr = data.lhs, + }, + }; + } - if (i < self.decls_len) return self.declsConst()[i]; - return null; - } + pub fn arrayInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ArrayInit { + assert(tree.nodes.items(.tag)[node] == .array_init_dot_two or + tree.nodes.items(.tag)[node] == .array_init_dot_two_comma); + const data = tree.nodes.items(.data)[node]; + buffer.* = .{ data.lhs, data.rhs }; + const elements = if (data.rhs != 0) + buffer[0..2] + else if (data.lhs != 0) + buffer[0..1] + else + buffer[0..0]; + return .{ + .ast = .{ + .lbrace = tree.nodes.items(.main_token)[node], + .elements = elements, + .type_expr = 0, + }, + }; + } - pub fn decls(self: *Root) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(Root); - return @ptrCast([*]*Node, decls_start)[0..self.decls_len]; - } + pub fn arrayInitDot(tree: Tree, node: Node.Index) full.ArrayInit { + assert(tree.nodes.items(.tag)[node] == .array_init_dot or + tree.nodes.items(.tag)[node] == .array_init_dot_comma); + const data = tree.nodes.items(.data)[node]; + return .{ + .ast = .{ + .lbrace = tree.nodes.items(.main_token)[node], + .elements = tree.extra_data[data.lhs..data.rhs], + .type_expr = 0, + }, + }; + } - pub fn declsConst(self: *const Root) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(Root); - return @ptrCast([*]const *Node, decls_start)[0..self.decls_len]; - } + pub fn arrayInit(tree: Tree, node: Node.Index) full.ArrayInit { + assert(tree.nodes.items(.tag)[node] == .array_init or + tree.nodes.items(.tag)[node] == .array_init_comma); + const data = tree.nodes.items(.data)[node]; + const elem_range = tree.extraData(data.rhs, Node.SubRange); + return .{ + .ast = .{ + .lbrace = tree.nodes.items(.main_token)[node], + .elements = tree.extra_data[elem_range.start..elem_range.end], + .type_expr = data.lhs, + }, + }; + } - pub fn firstToken(self: *const Root) TokenIndex { - if (self.decls_len == 0) return self.eof_token; - return self.declsConst()[0].firstToken(); - } + pub fn arrayType(tree: Tree, node: Node.Index) full.ArrayType { + assert(tree.nodes.items(.tag)[node] == .array_type); + const data = tree.nodes.items(.data)[node]; + return .{ + .ast = .{ + .lbracket = tree.nodes.items(.main_token)[node], + .elem_count = data.lhs, + .sentinel = null, + .elem_type = data.rhs, + }, + }; + } - pub fn lastToken(self: *const Root) TokenIndex { - if (self.decls_len == 0) return self.eof_token; - return self.declsConst()[self.decls_len - 1].lastToken(); - } + pub fn arrayTypeSentinel(tree: Tree, node: Node.Index) full.ArrayType { + assert(tree.nodes.items(.tag)[node] == .array_type_sentinel); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.ArrayTypeSentinel); + return .{ + .ast = .{ + .lbracket = tree.nodes.items(.main_token)[node], + .elem_count = data.lhs, + .sentinel = extra.sentinel, + .elem_type = extra.elem_type, + }, + }; + } - fn sizeInBytes(decls_len: NodeIndex) usize { - return @sizeOf(Root) + @sizeOf(*Node) * @as(usize, decls_len); - } - }; + pub fn ptrTypeAligned(tree: Tree, node: Node.Index) full.PtrType { + assert(tree.nodes.items(.tag)[node] == .ptr_type_aligned); + const data = tree.nodes.items(.data)[node]; + return tree.fullPtrType(.{ + .main_token = tree.nodes.items(.main_token)[node], + .align_node = data.lhs, + .sentinel = 0, + .bit_range_start = 0, + .bit_range_end = 0, + .child_type = data.rhs, + }); + } - /// Trailed in memory by possibly many things, with each optional thing - /// determined by a bit in `trailer_flags`. - pub const VarDecl = struct { - base: Node = Node{ .tag = .VarDecl }, - trailer_flags: TrailerFlags, - mut_token: TokenIndex, - name_token: TokenIndex, - semicolon_token: TokenIndex, - - pub const TrailerFlags = std.meta.TrailerFlags(struct { - doc_comments: *DocComment, - visib_token: TokenIndex, - thread_local_token: TokenIndex, - eq_token: TokenIndex, - comptime_token: TokenIndex, - extern_export_token: TokenIndex, - lib_name: *Node, - type_node: *Node, - align_node: *Node, - section_node: *Node, - init_node: *Node, + pub fn ptrTypeSentinel(tree: Tree, node: Node.Index) full.PtrType { + assert(tree.nodes.items(.tag)[node] == .ptr_type_sentinel); + const data = tree.nodes.items(.data)[node]; + return tree.fullPtrType(.{ + .main_token = tree.nodes.items(.main_token)[node], + .align_node = 0, + .sentinel = data.lhs, + .bit_range_start = 0, + .bit_range_end = 0, + .child_type = data.rhs, }); + } - pub fn getDocComments(self: *const VarDecl) ?*DocComment { - return self.getTrailer(.doc_comments); - } + pub fn ptrType(tree: Tree, node: Node.Index) full.PtrType { + assert(tree.nodes.items(.tag)[node] == .ptr_type); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.PtrType); + return tree.fullPtrType(.{ + .main_token = tree.nodes.items(.main_token)[node], + .align_node = extra.align_node, + .sentinel = extra.sentinel, + .bit_range_start = 0, + .bit_range_end = 0, + .child_type = data.rhs, + }); + } - pub fn setDocComments(self: *VarDecl, value: *DocComment) void { - self.setTrailer(.doc_comments, value); - } + pub fn ptrTypeBitRange(tree: Tree, node: Node.Index) full.PtrType { + assert(tree.nodes.items(.tag)[node] == .ptr_type_bit_range); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.PtrTypeBitRange); + return tree.fullPtrType(.{ + .main_token = tree.nodes.items(.main_token)[node], + .align_node = extra.align_node, + .sentinel = extra.sentinel, + .bit_range_start = extra.bit_range_start, + .bit_range_end = extra.bit_range_end, + .child_type = data.rhs, + }); + } - pub fn getVisibToken(self: *const VarDecl) ?TokenIndex { - return self.getTrailer(.visib_token); - } + pub fn sliceOpen(tree: Tree, node: Node.Index) full.Slice { + assert(tree.nodes.items(.tag)[node] == .slice_open); + const data = tree.nodes.items(.data)[node]; + return .{ + .ast = .{ + .sliced = data.lhs, + .lbracket = tree.nodes.items(.main_token)[node], + .start = data.rhs, + .end = 0, + .sentinel = 0, + }, + }; + } - pub fn setVisibToken(self: *VarDecl, value: TokenIndex) void { - self.setTrailer(.visib_token, value); - } + pub fn slice(tree: Tree, node: Node.Index) full.Slice { + assert(tree.nodes.items(.tag)[node] == .slice); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.Slice); + return .{ + .ast = .{ + .sliced = data.lhs, + .lbracket = tree.nodes.items(.main_token)[node], + .start = extra.start, + .end = extra.end, + .sentinel = 0, + }, + }; + } - pub fn getThreadLocalToken(self: *const VarDecl) ?TokenIndex { - return self.getTrailer(.thread_local_token); - } + pub fn sliceSentinel(tree: Tree, node: Node.Index) full.Slice { + assert(tree.nodes.items(.tag)[node] == .slice_sentinel); + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.SliceSentinel); + return .{ + .ast = .{ + .sliced = data.lhs, + .lbracket = tree.nodes.items(.main_token)[node], + .start = extra.start, + .end = extra.end, + .sentinel = extra.sentinel, + }, + }; + } - pub fn setThreadLocalToken(self: *VarDecl, value: TokenIndex) void { - self.setTrailer(.thread_local_token, value); - } + pub fn containerDeclTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .container_decl_two or + tree.nodes.items(.tag)[node] == .container_decl_two_trailing); + const data = tree.nodes.items(.data)[node]; + buffer.* = .{ data.lhs, data.rhs }; + const members = if (data.rhs != 0) + buffer[0..2] + else if (data.lhs != 0) + buffer[0..1] + else + buffer[0..0]; + return tree.fullContainerDecl(.{ + .main_token = tree.nodes.items(.main_token)[node], + .enum_token = null, + .members = members, + .arg = 0, + }); + } - pub fn getEqToken(self: *const VarDecl) ?TokenIndex { - return self.getTrailer(.eq_token); - } + pub fn containerDecl(tree: Tree, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .container_decl or + tree.nodes.items(.tag)[node] == .container_decl_trailing); + const data = tree.nodes.items(.data)[node]; + return tree.fullContainerDecl(.{ + .main_token = tree.nodes.items(.main_token)[node], + .enum_token = null, + .members = tree.extra_data[data.lhs..data.rhs], + .arg = 0, + }); + } - pub fn setEqToken(self: *VarDecl, value: TokenIndex) void { - self.setTrailer(.eq_token, value); - } + pub fn containerDeclArg(tree: Tree, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .container_decl_arg or + tree.nodes.items(.tag)[node] == .container_decl_arg_trailing); + const data = tree.nodes.items(.data)[node]; + const members_range = tree.extraData(data.rhs, Node.SubRange); + return tree.fullContainerDecl(.{ + .main_token = tree.nodes.items(.main_token)[node], + .enum_token = null, + .members = tree.extra_data[members_range.start..members_range.end], + .arg = data.lhs, + }); + } - pub fn getComptimeToken(self: *const VarDecl) ?TokenIndex { - return self.getTrailer(.comptime_token); - } + pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .tagged_union_two or + tree.nodes.items(.tag)[node] == .tagged_union_two_trailing); + const data = tree.nodes.items(.data)[node]; + buffer.* = .{ data.lhs, data.rhs }; + const members = if (data.rhs != 0) + buffer[0..2] + else if (data.lhs != 0) + buffer[0..1] + else + buffer[0..0]; + const main_token = tree.nodes.items(.main_token)[node]; + return tree.fullContainerDecl(.{ + .main_token = main_token, + .enum_token = main_token + 2, // union lparen enum + .members = members, + .arg = 0, + }); + } - pub fn setComptimeToken(self: *VarDecl, value: TokenIndex) void { - self.setTrailer(.comptime_token, value); - } + pub fn taggedUnion(tree: Tree, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .tagged_union or + tree.nodes.items(.tag)[node] == .tagged_union_trailing); + const data = tree.nodes.items(.data)[node]; + const main_token = tree.nodes.items(.main_token)[node]; + return tree.fullContainerDecl(.{ + .main_token = main_token, + .enum_token = main_token + 2, // union lparen enum + .members = tree.extra_data[data.lhs..data.rhs], + .arg = 0, + }); + } - pub fn getExternExportToken(self: *const VarDecl) ?TokenIndex { - return self.getTrailer(.extern_export_token); - } + pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) full.ContainerDecl { + assert(tree.nodes.items(.tag)[node] == .tagged_union_enum_tag or + tree.nodes.items(.tag)[node] == .tagged_union_enum_tag_trailing); + const data = tree.nodes.items(.data)[node]; + const members_range = tree.extraData(data.rhs, Node.SubRange); + const main_token = tree.nodes.items(.main_token)[node]; + return tree.fullContainerDecl(.{ + .main_token = main_token, + .enum_token = main_token + 2, // union lparen enum + .members = tree.extra_data[members_range.start..members_range.end], + .arg = data.lhs, + }); + } - pub fn setExternExportToken(self: *VarDecl, value: TokenIndex) void { - self.setTrailer(.extern_export_token, value); - } + pub fn switchCaseOne(tree: Tree, node: Node.Index) full.SwitchCase { + const data = &tree.nodes.items(.data)[node]; + const values: *[1]Node.Index = &data.lhs; + return tree.fullSwitchCase(.{ + .values = if (data.lhs == 0) values[0..0] else values[0..1], + .arrow_token = tree.nodes.items(.main_token)[node], + .target_expr = data.rhs, + }); + } - pub fn getLibName(self: *const VarDecl) ?*Node { - return self.getTrailer(.lib_name); - } + pub fn switchCase(tree: Tree, node: Node.Index) full.SwitchCase { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.lhs, Node.SubRange); + return tree.fullSwitchCase(.{ + .values = tree.extra_data[extra.start..extra.end], + .arrow_token = tree.nodes.items(.main_token)[node], + .target_expr = data.rhs, + }); + } - pub fn setLibName(self: *VarDecl, value: *Node) void { - self.setTrailer(.lib_name, value); - } + pub fn asmSimple(tree: Tree, node: Node.Index) full.Asm { + const data = tree.nodes.items(.data)[node]; + return tree.fullAsm(.{ + .asm_token = tree.nodes.items(.main_token)[node], + .template = data.lhs, + .items = &.{}, + .rparen = data.rhs, + }); + } - pub fn getTypeNode(self: *const VarDecl) ?*Node { - return self.getTrailer(.type_node); - } + pub fn asmFull(tree: Tree, node: Node.Index) full.Asm { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.Asm); + return tree.fullAsm(.{ + .asm_token = tree.nodes.items(.main_token)[node], + .template = data.lhs, + .items = tree.extra_data[extra.items_start..extra.items_end], + .rparen = extra.rparen, + }); + } - pub fn setTypeNode(self: *VarDecl, value: *Node) void { - self.setTrailer(.type_node, value); - } + pub fn whileSimple(tree: Tree, node: Node.Index) full.While { + const data = tree.nodes.items(.data)[node]; + return tree.fullWhile(.{ + .while_token = tree.nodes.items(.main_token)[node], + .cond_expr = data.lhs, + .cont_expr = 0, + .then_expr = data.rhs, + .else_expr = 0, + }); + } - pub fn getAlignNode(self: *const VarDecl) ?*Node { - return self.getTrailer(.align_node); - } + pub fn whileCont(tree: Tree, node: Node.Index) full.While { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.WhileCont); + return tree.fullWhile(.{ + .while_token = tree.nodes.items(.main_token)[node], + .cond_expr = data.lhs, + .cont_expr = extra.cont_expr, + .then_expr = extra.then_expr, + .else_expr = 0, + }); + } - pub fn setAlignNode(self: *VarDecl, value: *Node) void { - self.setTrailer(.align_node, value); - } + pub fn whileFull(tree: Tree, node: Node.Index) full.While { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.While); + return tree.fullWhile(.{ + .while_token = tree.nodes.items(.main_token)[node], + .cond_expr = data.lhs, + .cont_expr = extra.cont_expr, + .then_expr = extra.then_expr, + .else_expr = extra.else_expr, + }); + } - pub fn getSectionNode(self: *const VarDecl) ?*Node { - return self.getTrailer(.section_node); - } + pub fn forSimple(tree: Tree, node: Node.Index) full.While { + const data = tree.nodes.items(.data)[node]; + return tree.fullWhile(.{ + .while_token = tree.nodes.items(.main_token)[node], + .cond_expr = data.lhs, + .cont_expr = 0, + .then_expr = data.rhs, + .else_expr = 0, + }); + } - pub fn setSectionNode(self: *VarDecl, value: *Node) void { - self.setTrailer(.section_node, value); - } + pub fn forFull(tree: Tree, node: Node.Index) full.While { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.If); + return tree.fullWhile(.{ + .while_token = tree.nodes.items(.main_token)[node], + .cond_expr = data.lhs, + .cont_expr = 0, + .then_expr = extra.then_expr, + .else_expr = extra.else_expr, + }); + } - pub fn getInitNode(self: *const VarDecl) ?*Node { - return self.getTrailer(.init_node); - } + pub fn callOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.Call { + const data = tree.nodes.items(.data)[node]; + buffer.* = .{data.rhs}; + const params = if (data.rhs != 0) buffer[0..1] else buffer[0..0]; + return tree.fullCall(.{ + .lparen = tree.nodes.items(.main_token)[node], + .fn_expr = data.lhs, + .params = params, + }); + } - pub fn setInitNode(self: *VarDecl, value: *Node) void { - self.setTrailer(.init_node, value); - } + pub fn callFull(tree: Tree, node: Node.Index) full.Call { + const data = tree.nodes.items(.data)[node]; + const extra = tree.extraData(data.rhs, Node.SubRange); + return tree.fullCall(.{ + .lparen = tree.nodes.items(.main_token)[node], + .fn_expr = data.lhs, + .params = tree.extra_data[extra.start..extra.end], + }); + } - pub const RequiredFields = struct { - mut_token: TokenIndex, - name_token: TokenIndex, - semicolon_token: TokenIndex, + fn fullVarDecl(tree: Tree, info: full.VarDecl.Ast) full.VarDecl { + const token_tags = tree.tokens.items(.tag); + var result: full.VarDecl = .{ + .ast = info, + .visib_token = null, + .extern_export_token = null, + .lib_name = null, + .threadlocal_token = null, + .comptime_token = null, }; - - fn getTrailer(self: *const VarDecl, comptime field: TrailerFlags.FieldEnum) ?TrailerFlags.Field(field) { - const trailers_start = @ptrCast([*]const u8, self) + @sizeOf(VarDecl); - return self.trailer_flags.get(trailers_start, field); - } - - fn setTrailer(self: *VarDecl, comptime field: TrailerFlags.FieldEnum, value: TrailerFlags.Field(field)) void { - const trailers_start = @ptrCast([*]u8, self) + @sizeOf(VarDecl); - self.trailer_flags.set(trailers_start, field, value); - } - - pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: TrailerFlags.InitStruct) !*VarDecl { - const trailer_flags = TrailerFlags.init(trailers); - const bytes = try allocator.alignedAlloc(u8, @alignOf(VarDecl), sizeInBytes(trailer_flags)); - const var_decl = @ptrCast(*VarDecl, bytes.ptr); - var_decl.* = .{ - .trailer_flags = trailer_flags, - .mut_token = required.mut_token, - .name_token = required.name_token, - .semicolon_token = required.semicolon_token, - }; - const trailers_start = bytes.ptr + @sizeOf(VarDecl); - trailer_flags.setMany(trailers_start, trailers); - return var_decl; - } - - pub fn destroy(self: *VarDecl, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.trailer_flags)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const VarDecl, index: usize) ?*Node { - var i = index; - - if (self.getTypeNode()) |type_node| { - if (i < 1) return type_node; - i -= 1; - } - - if (self.getAlignNode()) |align_node| { - if (i < 1) return align_node; - i -= 1; - } - - if (self.getSectionNode()) |section_node| { - if (i < 1) return section_node; - i -= 1; - } - - if (self.getInitNode()) |init_node| { - if (i < 1) return init_node; - i -= 1; + var i = info.mut_token; + while (i > 0) { + i -= 1; + switch (token_tags[i]) { + .keyword_extern, .keyword_export => result.extern_export_token = i, + .keyword_comptime => result.comptime_token = i, + .keyword_pub => result.visib_token = i, + .keyword_threadlocal => result.threadlocal_token = i, + .string_literal => result.lib_name = i, + else => break, } - - return null; } + return result; + } - pub fn firstToken(self: *const VarDecl) TokenIndex { - if (self.getVisibToken()) |visib_token| return visib_token; - if (self.getThreadLocalToken()) |thread_local_token| return thread_local_token; - if (self.getComptimeToken()) |comptime_token| return comptime_token; - if (self.getExternExportToken()) |extern_export_token| return extern_export_token; - assert(self.getLibName() == null); - return self.mut_token; + fn fullIf(tree: Tree, info: full.If.Ast) full.If { + const token_tags = tree.tokens.items(.tag); + var result: full.If = .{ + .ast = info, + .payload_token = null, + .error_token = null, + .else_token = undefined, + }; + // if (cond_expr) |x| + // ^ ^ + const payload_pipe = tree.lastToken(info.cond_expr) + 2; + if (token_tags[payload_pipe] == .pipe) { + result.payload_token = payload_pipe + 1; } - - pub fn lastToken(self: *const VarDecl) TokenIndex { - return self.semicolon_token; + if (info.else_expr != 0) { + // then_expr else |x| + // ^ ^ + result.else_token = tree.lastToken(info.then_expr) + 1; + if (token_tags[result.else_token + 1] == .pipe) { + result.error_token = result.else_token + 2; + } } + return result; + } - fn sizeInBytes(trailer_flags: TrailerFlags) usize { - return @sizeOf(VarDecl) + trailer_flags.sizeInBytes(); + fn fullContainerField(tree: Tree, info: full.ContainerField.Ast) full.ContainerField { + const token_tags = tree.tokens.items(.tag); + var result: full.ContainerField = .{ + .ast = info, + .comptime_token = null, + }; + // comptime name: type = init, + // ^ + if (info.name_token > 0 and token_tags[info.name_token - 1] == .keyword_comptime) { + result.comptime_token = info.name_token - 1; } - }; - - pub const Use = struct { - base: Node = Node{ .tag = .Use }, - doc_comments: ?*DocComment, - visib_token: ?TokenIndex, - use_token: TokenIndex, - expr: *Node, - semicolon_token: TokenIndex, - - pub fn iterate(self: *const Use, index: usize) ?*Node { - var i = index; + return result; + } - if (i < 1) return self.expr; + fn fullFnProto(tree: Tree, info: full.FnProto.Ast) full.FnProto { + const token_tags = tree.tokens.items(.tag); + var result: full.FnProto = .{ + .ast = info, + .visib_token = null, + .extern_export_token = null, + .lib_name = null, + .name_token = null, + .lparen = undefined, + }; + var i = info.fn_token; + while (i > 0) { i -= 1; - - return null; - } - - pub fn firstToken(self: *const Use) TokenIndex { - if (self.visib_token) |visib_token| return visib_token; - return self.use_token; - } - - pub fn lastToken(self: *const Use) TokenIndex { - return self.semicolon_token; - } - }; - - pub const ErrorSetDecl = struct { - base: Node = Node{ .tag = .ErrorSetDecl }, - error_token: TokenIndex, - rbrace_token: TokenIndex, - decls_len: NodeIndex, - - /// After this the caller must initialize the decls list. - pub fn alloc(allocator: *mem.Allocator, decls_len: NodeIndex) !*ErrorSetDecl { - const bytes = try allocator.alignedAlloc(u8, @alignOf(ErrorSetDecl), sizeInBytes(decls_len)); - return @ptrCast(*ErrorSetDecl, bytes.ptr); - } - - pub fn free(self: *ErrorSetDecl, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.decls_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const ErrorSetDecl, index: usize) ?*Node { - var i = index; - - if (i < self.decls_len) return self.declsConst()[i]; - i -= self.decls_len; - - return null; - } - - pub fn firstToken(self: *const ErrorSetDecl) TokenIndex { - return self.error_token; + switch (token_tags[i]) { + .keyword_extern, .keyword_export => result.extern_export_token = i, + .keyword_pub => result.visib_token = i, + .string_literal => result.lib_name = i, + else => break, + } } - - pub fn lastToken(self: *const ErrorSetDecl) TokenIndex { - return self.rbrace_token; + const after_fn_token = info.fn_token + 1; + if (token_tags[after_fn_token] == .identifier) { + result.name_token = after_fn_token; + result.lparen = after_fn_token + 1; + } else { + result.lparen = after_fn_token; } + assert(token_tags[result.lparen] == .l_paren); - pub fn decls(self: *ErrorSetDecl) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(ErrorSetDecl); - return @ptrCast([*]*Node, decls_start)[0..self.decls_len]; - } + return result; + } - pub fn declsConst(self: *const ErrorSetDecl) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(ErrorSetDecl); - return @ptrCast([*]const *Node, decls_start)[0..self.decls_len]; - } + fn fullStructInit(tree: Tree, info: full.StructInit.Ast) full.StructInit { + const token_tags = tree.tokens.items(.tag); + var result: full.StructInit = .{ + .ast = info, + }; + return result; + } - fn sizeInBytes(decls_len: NodeIndex) usize { - return @sizeOf(ErrorSetDecl) + @sizeOf(*Node) * @as(usize, decls_len); + fn fullPtrType(tree: Tree, info: full.PtrType.Ast) full.PtrType { + const token_tags = tree.tokens.items(.tag); + // TODO: looks like stage1 isn't quite smart enough to handle enum + // literals in some places here + const Size = std.builtin.TypeInfo.Pointer.Size; + const size: Size = switch (token_tags[info.main_token]) { + .asterisk, + .asterisk_asterisk, + => switch (token_tags[info.main_token + 1]) { + .r_bracket, .colon => .Many, + .identifier => if (token_tags[info.main_token - 1] == .l_bracket) Size.C else .One, + else => .One, + }, + .l_bracket => Size.Slice, + else => unreachable, + }; + var result: full.PtrType = .{ + .size = size, + .allowzero_token = null, + .const_token = null, + .volatile_token = null, + .ast = info, + }; + // We need to be careful that we don't iterate over any sub-expressions + // here while looking for modifiers as that could result in false + // positives. Therefore, start after a sentinel if there is one and + // skip over any align node and bit range nodes. + var i = if (info.sentinel != 0) tree.lastToken(info.sentinel) + 1 else info.main_token; + const end = tree.firstToken(info.child_type); + while (i < end) : (i += 1) { + switch (token_tags[i]) { + .keyword_allowzero => result.allowzero_token = i, + .keyword_const => result.const_token = i, + .keyword_volatile => result.volatile_token = i, + .keyword_align => { + assert(info.align_node != 0); + if (info.bit_range_end != 0) { + assert(info.bit_range_start != 0); + i = tree.lastToken(info.bit_range_end) + 1; + } else { + i = tree.lastToken(info.align_node) + 1; + } + }, + else => {}, + } } - }; + return result; + } - /// The fields and decls Node pointers directly follow this struct in memory. - pub const ContainerDecl = struct { - base: Node = Node{ .tag = .ContainerDecl }, - kind_token: TokenIndex, - layout_token: ?TokenIndex, - lbrace_token: TokenIndex, - rbrace_token: TokenIndex, - fields_and_decls_len: NodeIndex, - init_arg_expr: InitArg, - - pub const InitArg = union(enum) { - None, - Enum: ?*Node, - Type: *Node, + fn fullContainerDecl(tree: Tree, info: full.ContainerDecl.Ast) full.ContainerDecl { + const token_tags = tree.tokens.items(.tag); + var result: full.ContainerDecl = .{ + .ast = info, + .layout_token = null, }; - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, fields_and_decls_len: NodeIndex) !*ContainerDecl { - const bytes = try allocator.alignedAlloc(u8, @alignOf(ContainerDecl), sizeInBytes(fields_and_decls_len)); - return @ptrCast(*ContainerDecl, bytes.ptr); + switch (token_tags[info.main_token - 1]) { + .keyword_extern, .keyword_packed => result.layout_token = info.main_token - 1, + else => {}, } + return result; + } - pub fn free(self: *ContainerDecl, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.fields_and_decls_len)]; - allocator.free(bytes); + fn fullSwitchCase(tree: Tree, info: full.SwitchCase.Ast) full.SwitchCase { + const token_tags = tree.tokens.items(.tag); + var result: full.SwitchCase = .{ + .ast = info, + .payload_token = null, + }; + if (token_tags[info.arrow_token + 1] == .pipe) { + result.payload_token = info.arrow_token + 2; } + return result; + } - pub fn iterate(self: *const ContainerDecl, index: usize) ?*Node { - var i = index; - - switch (self.init_arg_expr) { - .Type => |t| { - if (i < 1) return t; - i -= 1; - }, - .None, .Enum => {}, + fn fullAsm(tree: Tree, info: full.Asm.Ast) full.Asm { + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); + var result: full.Asm = .{ + .ast = info, + .volatile_token = null, + .inputs = &.{}, + .outputs = &.{}, + .first_clobber = null, + }; + if (token_tags[info.asm_token + 1] == .keyword_volatile) { + result.volatile_token = info.asm_token + 1; + } + const outputs_end: usize = for (info.items) |item, i| { + switch (node_tags[item]) { + .asm_output => continue, + else => break i, + } + } else info.items.len; + + result.outputs = info.items[0..outputs_end]; + result.inputs = info.items[outputs_end..]; + + if (info.items.len == 0) { + // asm ("foo" ::: "a", "b"); + const template_token = tree.lastToken(info.template); + if (token_tags[template_token + 1] == .colon and + token_tags[template_token + 2] == .colon and + token_tags[template_token + 3] == .colon and + token_tags[template_token + 4] == .string_literal) + { + result.first_clobber = template_token + 4; + } + } else if (result.inputs.len != 0) { + // asm ("foo" :: [_] "" (y) : "a", "b"); + const last_input = result.inputs[result.inputs.len - 1]; + const rparen = tree.lastToken(last_input); + if (token_tags[rparen + 1] == .colon and + token_tags[rparen + 2] == .string_literal) + { + result.first_clobber = rparen + 2; } - - if (i < self.fields_and_decls_len) return self.fieldsAndDeclsConst()[i]; - i -= self.fields_and_decls_len; - - return null; - } - - pub fn firstToken(self: *const ContainerDecl) TokenIndex { - if (self.layout_token) |layout_token| { - return layout_token; + } else { + // asm ("foo" : [_] "" (x) :: "a", "b"); + const last_output = result.outputs[result.outputs.len - 1]; + const rparen = tree.lastToken(last_output); + if (token_tags[rparen + 1] == .colon and + token_tags[rparen + 2] == .colon and + token_tags[rparen + 3] == .string_literal) + { + result.first_clobber = rparen + 3; } - return self.kind_token; } - pub fn lastToken(self: *const ContainerDecl) TokenIndex { - return self.rbrace_token; - } + return result; + } - pub fn fieldsAndDecls(self: *ContainerDecl) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(ContainerDecl); - return @ptrCast([*]*Node, decls_start)[0..self.fields_and_decls_len]; + fn fullWhile(tree: Tree, info: full.While.Ast) full.While { + const token_tags = tree.tokens.items(.tag); + var result: full.While = .{ + .ast = info, + .inline_token = null, + .label_token = null, + .payload_token = null, + .else_token = undefined, + .error_token = null, + }; + var tok_i = info.while_token - 1; + if (token_tags[tok_i] == .keyword_inline) { + result.inline_token = tok_i; + tok_i -= 1; } - - pub fn fieldsAndDeclsConst(self: *const ContainerDecl) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(ContainerDecl); - return @ptrCast([*]const *Node, decls_start)[0..self.fields_and_decls_len]; + if (token_tags[tok_i] == .colon and + token_tags[tok_i - 1] == .identifier) + { + result.label_token = tok_i - 1; } - - fn sizeInBytes(fields_and_decls_len: NodeIndex) usize { - return @sizeOf(ContainerDecl) + @sizeOf(*Node) * @as(usize, fields_and_decls_len); + const last_cond_token = tree.lastToken(info.cond_expr); + if (token_tags[last_cond_token + 2] == .pipe) { + result.payload_token = last_cond_token + 3; } - }; - - pub const ContainerField = struct { - base: Node = Node{ .tag = .ContainerField }, - doc_comments: ?*DocComment, - comptime_token: ?TokenIndex, - name_token: TokenIndex, - type_expr: ?*Node, - value_expr: ?*Node, - align_expr: ?*Node, - - pub fn iterate(self: *const ContainerField, index: usize) ?*Node { - var i = index; - - if (self.type_expr) |type_expr| { - if (i < 1) return type_expr; - i -= 1; + if (info.else_expr != 0) { + // then_expr else |x| + // ^ ^ + result.else_token = tree.lastToken(info.then_expr) + 1; + if (token_tags[result.else_token + 1] == .pipe) { + result.error_token = result.else_token + 2; } - - if (self.align_expr) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - - if (self.value_expr) |value_expr| { - if (i < 1) return value_expr; - i -= 1; - } - - return null; } + return result; + } - pub fn firstToken(self: *const ContainerField) TokenIndex { - return self.comptime_token orelse self.name_token; + fn fullCall(tree: Tree, info: full.Call.Ast) full.Call { + const token_tags = tree.tokens.items(.tag); + var result: full.Call = .{ + .ast = info, + .async_token = null, + }; + const maybe_async_token = tree.firstToken(info.fn_expr) - 1; + if (token_tags[maybe_async_token] == .keyword_async) { + result.async_token = maybe_async_token; } + return result; + } +}; - pub fn lastToken(self: *const ContainerField) TokenIndex { - if (self.value_expr) |value_expr| { - return value_expr.lastToken(); - } - if (self.align_expr) |align_expr| { - // The expression refers to what's inside the parenthesis, the - // last token is the closing one - return align_expr.lastToken() + 1; - } - if (self.type_expr) |type_expr| { - return type_expr.lastToken(); - } +/// Fully assembled AST node information. +pub const full = struct { + pub const VarDecl = struct { + visib_token: ?TokenIndex, + extern_export_token: ?TokenIndex, + lib_name: ?TokenIndex, + threadlocal_token: ?TokenIndex, + comptime_token: ?TokenIndex, + ast: Ast, - return self.name_token; - } + pub const Ast = struct { + mut_token: TokenIndex, + type_node: Node.Index, + align_node: Node.Index, + section_node: Node.Index, + init_node: Node.Index, + }; }; - pub const ErrorTag = struct { - base: Node = Node{ .tag = .ErrorTag }, - doc_comments: ?*DocComment, - name_token: TokenIndex, - - pub fn iterate(self: *const ErrorTag, index: usize) ?*Node { - var i = index; - - if (self.doc_comments) |comments| { - if (i < 1) return &comments.base; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const ErrorTag) TokenIndex { - return self.name_token; - } + pub const If = struct { + /// Points to the first token after the `|`. Will either be an identifier or + /// a `*` (with an identifier immediately after it). + payload_token: ?TokenIndex, + /// Points to the identifier after the `|`. + error_token: ?TokenIndex, + /// Populated only if else_expr != 0. + else_token: TokenIndex, + ast: Ast, - pub fn lastToken(self: *const ErrorTag) TokenIndex { - return self.name_token; - } + pub const Ast = struct { + if_token: TokenIndex, + cond_expr: Node.Index, + then_expr: Node.Index, + else_expr: Node.Index, + }; }; - pub const OneToken = struct { - base: Node, - token: TokenIndex, + pub const While = struct { + ast: Ast, + inline_token: ?TokenIndex, + label_token: ?TokenIndex, + payload_token: ?TokenIndex, + error_token: ?TokenIndex, + /// Populated only if else_expr != 0. + else_token: TokenIndex, - pub fn iterate(self: *const OneToken, index: usize) ?*Node { - return null; - } + pub const Ast = struct { + while_token: TokenIndex, + cond_expr: Node.Index, + cont_expr: Node.Index, + then_expr: Node.Index, + else_expr: Node.Index, + }; + }; - pub fn firstToken(self: *const OneToken) TokenIndex { - return self.token; - } + pub const ContainerField = struct { + comptime_token: ?TokenIndex, + ast: Ast, - pub fn lastToken(self: *const OneToken) TokenIndex { - return self.token; - } + pub const Ast = struct { + name_token: TokenIndex, + type_expr: Node.Index, + value_expr: Node.Index, + align_expr: Node.Index, + }; }; - /// The params are directly after the FnProto in memory. - /// Next, each optional thing determined by a bit in `trailer_flags`. pub const FnProto = struct { - base: Node = Node{ .tag = .FnProto }, - trailer_flags: TrailerFlags, - fn_token: TokenIndex, - params_len: NodeIndex, - return_type: ReturnType, - - pub const TrailerFlags = std.meta.TrailerFlags(struct { - doc_comments: *DocComment, - body_node: *Node, - lib_name: *Node, // populated if this is an extern declaration - align_expr: *Node, // populated if align(A) is present - section_expr: *Node, // populated if linksection(A) is present - callconv_expr: *Node, // populated if callconv(A) is present - visib_token: TokenIndex, - name_token: TokenIndex, - var_args_token: TokenIndex, - extern_export_inline_token: TokenIndex, - is_extern_prototype: void, // TODO: Remove once extern fn rewriting is - is_async: void, // TODO: remove once async fn rewriting is - }); + visib_token: ?TokenIndex, + extern_export_token: ?TokenIndex, + lib_name: ?TokenIndex, + name_token: ?TokenIndex, + lparen: TokenIndex, + ast: Ast, - pub const RequiredFields = struct { + pub const Ast = struct { fn_token: TokenIndex, - params_len: NodeIndex, - return_type: ReturnType, - }; - - pub const ReturnType = union(enum) { - Explicit: *Node, - InferErrorSet: *Node, - Invalid: TokenIndex, + return_type: Node.Index, + params: []const Node.Index, + align_expr: Node.Index, + section_expr: Node.Index, + callconv_expr: Node.Index, }; - pub const ParamDecl = struct { - doc_comments: ?*DocComment, - comptime_token: ?TokenIndex, - noalias_token: ?TokenIndex, + pub const Param = struct { + first_doc_comment: ?TokenIndex, name_token: ?TokenIndex, - param_type: ParamType, - - pub const ParamType = union(enum) { - any_type: *Node, - type_expr: *Node, - }; - - pub fn iterate(self: *const ParamDecl, index: usize) ?*Node { - var i = index; + comptime_noalias: ?TokenIndex, + anytype_ellipsis3: ?TokenIndex, + type_expr: Node.Index, + }; - if (i < 1) { - switch (self.param_type) { - .any_type, .type_expr => |node| return node, + /// Abstracts over the fact that anytype and ... are not included + /// in the params slice, since they are simple identifiers and + /// not sub-expressions. + pub const Iterator = struct { + tree: *const Tree, + fn_proto: *const FnProto, + param_i: usize, + tok_i: TokenIndex, + tok_flag: bool, + + pub fn next(it: *Iterator) ?Param { + const token_tags = it.tree.tokens.items(.tag); + while (true) { + var first_doc_comment: ?TokenIndex = null; + var comptime_noalias: ?TokenIndex = null; + var name_token: ?TokenIndex = null; + if (!it.tok_flag) { + if (it.param_i >= it.fn_proto.ast.params.len) { + return null; + } + const param_type = it.fn_proto.ast.params[it.param_i]; + var tok_i = it.tree.firstToken(param_type) - 1; + while (true) : (tok_i -= 1) switch (token_tags[tok_i]) { + .colon => continue, + .identifier => name_token = tok_i, + .doc_comment => first_doc_comment = tok_i, + .keyword_comptime, .keyword_noalias => comptime_noalias = tok_i, + else => break, + }; + it.param_i += 1; + it.tok_i = it.tree.lastToken(param_type) + 1; + it.tok_flag = true; + return Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = comptime_noalias, + .name_token = name_token, + .anytype_ellipsis3 = null, + .type_expr = param_type, + }; } - } - i -= 1; - - return null; - } - - pub fn firstToken(self: *const ParamDecl) TokenIndex { - if (self.comptime_token) |comptime_token| return comptime_token; - if (self.noalias_token) |noalias_token| return noalias_token; - if (self.name_token) |name_token| return name_token; - switch (self.param_type) { - .any_type, .type_expr => |node| return node.firstToken(), - } - } - - pub fn lastToken(self: *const ParamDecl) TokenIndex { - switch (self.param_type) { - .any_type, .type_expr => |node| return node.lastToken(), + // Look for anytype and ... params afterwards. + if (token_tags[it.tok_i] == .comma) { + it.tok_i += 1; + } else { + return null; + } + if (token_tags[it.tok_i] == .doc_comment) { + first_doc_comment = it.tok_i; + while (token_tags[it.tok_i] == .doc_comment) { + it.tok_i += 1; + } + } + switch (token_tags[it.tok_i]) { + .ellipsis3 => { + it.tok_flag = false; // Next iteration should return null. + return Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = null, + .name_token = null, + .anytype_ellipsis3 = it.tok_i, + .type_expr = 0, + }; + }, + .keyword_noalias, .keyword_comptime => { + comptime_noalias = it.tok_i; + it.tok_i += 1; + }, + else => {}, + } + if (token_tags[it.tok_i] == .identifier and + token_tags[it.tok_i + 1] == .colon) + { + name_token = it.tok_i; + it.tok_i += 2; + } + if (token_tags[it.tok_i] == .keyword_anytype) { + it.tok_i += 1; + return Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = comptime_noalias, + .name_token = name_token, + .anytype_ellipsis3 = it.tok_i - 1, + .type_expr = 0, + }; + } + it.tok_flag = false; } } }; - /// For debugging purposes. - pub fn dump(self: *const FnProto) void { - const trailers_start = @alignCast( - @alignOf(ParamDecl), - @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, - ); - std.debug.print("{*} flags: {b} name_token: {} {*} params_len: {}\n", .{ - self, - self.trailer_flags.bits, - self.getNameToken(), - self.trailer_flags.ptrConst(trailers_start, .name_token), - self.params_len, - }); - } - - pub fn getDocComments(self: *const FnProto) ?*DocComment { - return self.getTrailer(.doc_comments); - } - - pub fn setDocComments(self: *FnProto, value: *DocComment) void { - self.setTrailer(.doc_comments, value); - } - - pub fn getBodyNode(self: *const FnProto) ?*Node { - return self.getTrailer(.body_node); - } - - pub fn setBodyNode(self: *FnProto, value: *Node) void { - self.setTrailer(.body_node, value); - } - - pub fn getLibName(self: *const FnProto) ?*Node { - return self.getTrailer(.lib_name); - } - - pub fn setLibName(self: *FnProto, value: *Node) void { - self.setTrailer(.lib_name, value); - } - - pub fn getAlignExpr(self: *const FnProto) ?*Node { - return self.getTrailer(.align_expr); - } - - pub fn setAlignExpr(self: *FnProto, value: *Node) void { - self.setTrailer(.align_expr, value); - } - - pub fn getSectionExpr(self: *const FnProto) ?*Node { - return self.getTrailer(.section_expr); - } - - pub fn setSectionExpr(self: *FnProto, value: *Node) void { - self.setTrailer(.section_expr, value); - } - - pub fn getCallconvExpr(self: *const FnProto) ?*Node { - return self.getTrailer(.callconv_expr); - } - - pub fn setCallconvExpr(self: *FnProto, value: *Node) void { - self.setTrailer(.callconv_expr, value); - } - - pub fn getVisibToken(self: *const FnProto) ?TokenIndex { - return self.getTrailer(.visib_token); - } - - pub fn setVisibToken(self: *FnProto, value: TokenIndex) void { - self.setTrailer(.visib_token, value); - } - - pub fn getNameToken(self: *const FnProto) ?TokenIndex { - return self.getTrailer(.name_token); - } - - pub fn setNameToken(self: *FnProto, value: TokenIndex) void { - self.setTrailer(.name_token, value); - } - - pub fn getVarArgsToken(self: *const FnProto) ?TokenIndex { - return self.getTrailer(.var_args_token); - } - - pub fn setVarArgsToken(self: *FnProto, value: TokenIndex) void { - self.setTrailer(.var_args_token, value); - } - - pub fn getExternExportInlineToken(self: *const FnProto) ?TokenIndex { - return self.getTrailer(.extern_export_inline_token); - } - - pub fn setExternExportInlineToken(self: *FnProto, value: TokenIndex) void { - self.setTrailer(.extern_export_inline_token, value); - } - - pub fn getIsExternPrototype(self: *const FnProto) ?void { - return self.getTrailer(.is_extern_prototype); - } - - pub fn setIsExternPrototype(self: *FnProto, value: void) void { - self.setTrailer(.is_extern_prototype, value); - } - - pub fn getIsAsync(self: *const FnProto) ?void { - return self.getTrailer(.is_async); - } - - pub fn setIsAsync(self: *FnProto, value: void) void { - self.setTrailer(.is_async, value); - } - - fn getTrailer(self: *const FnProto, comptime field: TrailerFlags.FieldEnum) ?TrailerFlags.Field(field) { - const trailers_start = @alignCast( - @alignOf(ParamDecl), - @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, - ); - return self.trailer_flags.get(trailers_start, field); - } - - fn setTrailer(self: *FnProto, comptime field: TrailerFlags.FieldEnum, value: TrailerFlags.Field(field)) void { - const trailers_start = @alignCast( - @alignOf(ParamDecl), - @ptrCast([*]u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, - ); - self.trailer_flags.set(trailers_start, field, value); - } - - /// After this the caller must initialize the params list. - pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: TrailerFlags.InitStruct) !*FnProto { - const trailer_flags = TrailerFlags.init(trailers); - const bytes = try allocator.alignedAlloc(u8, @alignOf(FnProto), sizeInBytes( - required.params_len, - trailer_flags, - )); - const fn_proto = @ptrCast(*FnProto, bytes.ptr); - fn_proto.* = .{ - .trailer_flags = trailer_flags, - .fn_token = required.fn_token, - .params_len = required.params_len, - .return_type = required.return_type, - }; - const trailers_start = @alignCast( - @alignOf(ParamDecl), - bytes.ptr + @sizeOf(FnProto) + @sizeOf(ParamDecl) * required.params_len, - ); - trailer_flags.setMany(trailers_start, trailers); - return fn_proto; - } - - pub fn destroy(self: *FnProto, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len, self.trailer_flags)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const FnProto, index: usize) ?*Node { - var i = index; - - if (self.getLibName()) |lib_name| { - if (i < 1) return lib_name; - i -= 1; - } - - const params_len: usize = if (self.params_len == 0) - 0 - else switch (self.paramsConst()[self.params_len - 1].param_type) { - .any_type, .type_expr => self.params_len, + pub fn iterate(fn_proto: FnProto, tree: Tree) Iterator { + return .{ + .tree = &tree, + .fn_proto = &fn_proto, + .param_i = 0, + .tok_i = undefined, + .tok_flag = false, }; - if (i < params_len) { - switch (self.paramsConst()[i].param_type) { - .any_type => |n| return n, - .type_expr => |n| return n, - } - } - i -= params_len; - - if (self.getAlignExpr()) |align_expr| { - if (i < 1) return align_expr; - i -= 1; - } - - if (self.getSectionExpr()) |section_expr| { - if (i < 1) return section_expr; - i -= 1; - } - - switch (self.return_type) { - .Explicit, .InferErrorSet => |node| { - if (i < 1) return node; - i -= 1; - }, - .Invalid => {}, - } - - if (self.getBodyNode()) |body_node| { - if (i < 1) return body_node; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const FnProto) TokenIndex { - if (self.getVisibToken()) |visib_token| return visib_token; - if (self.getExternExportInlineToken()) |extern_export_inline_token| return extern_export_inline_token; - assert(self.getLibName() == null); - return self.fn_token; - } - - pub fn lastToken(self: *const FnProto) TokenIndex { - if (self.getBodyNode()) |body_node| return body_node.lastToken(); - switch (self.return_type) { - .Explicit, .InferErrorSet => |node| return node.lastToken(), - .Invalid => |tok| return tok, - } - } - - pub fn params(self: *FnProto) []ParamDecl { - const params_start = @ptrCast([*]u8, self) + @sizeOf(FnProto); - return @ptrCast([*]ParamDecl, params_start)[0..self.params_len]; - } - - pub fn paramsConst(self: *const FnProto) []const ParamDecl { - const params_start = @ptrCast([*]const u8, self) + @sizeOf(FnProto); - return @ptrCast([*]const ParamDecl, params_start)[0..self.params_len]; - } - - fn sizeInBytes(params_len: NodeIndex, trailer_flags: TrailerFlags) usize { - return @sizeOf(FnProto) + @sizeOf(ParamDecl) * @as(usize, params_len) + trailer_flags.sizeInBytes(); } }; - pub const AnyFrameType = struct { - base: Node = Node{ .tag = .AnyFrameType }, - anyframe_token: TokenIndex, - result: ?Result, + pub const StructInit = struct { + ast: Ast, - pub const Result = struct { - arrow_token: TokenIndex, - return_type: *Node, + pub const Ast = struct { + lbrace: TokenIndex, + fields: []const Node.Index, + type_expr: Node.Index, }; - - pub fn iterate(self: *const AnyFrameType, index: usize) ?*Node { - var i = index; - - if (self.result) |result| { - if (i < 1) return result.return_type; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const AnyFrameType) TokenIndex { - return self.anyframe_token; - } - - pub fn lastToken(self: *const AnyFrameType) TokenIndex { - if (self.result) |result| return result.return_type.lastToken(); - return self.anyframe_token; - } }; - /// The statements of the block follow Block directly in memory. - pub const Block = struct { - base: Node = Node{ .tag = .Block }, - statements_len: NodeIndex, - lbrace: TokenIndex, - rbrace: TokenIndex, - - /// After this the caller must initialize the statements list. - pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*Block { - const bytes = try allocator.alignedAlloc(u8, @alignOf(Block), sizeInBytes(statements_len)); - return @ptrCast(*Block, bytes.ptr); - } - - pub fn free(self: *Block, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.statements_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const Block, index: usize) ?*Node { - var i = index; - - if (i < self.statements_len) return self.statementsConst()[i]; - i -= self.statements_len; - - return null; - } - - pub fn firstToken(self: *const Block) TokenIndex { - return self.lbrace; - } - - pub fn lastToken(self: *const Block) TokenIndex { - return self.rbrace; - } - - pub fn statements(self: *Block) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(Block); - return @ptrCast([*]*Node, decls_start)[0..self.statements_len]; - } - - pub fn statementsConst(self: *const Block) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(Block); - return @ptrCast([*]const *Node, decls_start)[0..self.statements_len]; - } - - fn sizeInBytes(statements_len: NodeIndex) usize { - return @sizeOf(Block) + @sizeOf(*Node) * @as(usize, statements_len); - } - }; - - /// The statements of the block follow LabeledBlock directly in memory. - pub const LabeledBlock = struct { - base: Node = Node{ .tag = .LabeledBlock }, - statements_len: NodeIndex, - lbrace: TokenIndex, - rbrace: TokenIndex, - label: TokenIndex, - - /// After this the caller must initialize the statements list. - pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*LabeledBlock { - const bytes = try allocator.alignedAlloc(u8, @alignOf(LabeledBlock), sizeInBytes(statements_len)); - return @ptrCast(*LabeledBlock, bytes.ptr); - } - - pub fn free(self: *LabeledBlock, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.statements_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const LabeledBlock, index: usize) ?*Node { - var i = index; - - if (i < self.statements_len) return self.statementsConst()[i]; - i -= self.statements_len; - - return null; - } - - pub fn firstToken(self: *const LabeledBlock) TokenIndex { - return self.label; - } - - pub fn lastToken(self: *const LabeledBlock) TokenIndex { - return self.rbrace; - } - - pub fn statements(self: *LabeledBlock) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(LabeledBlock); - return @ptrCast([*]*Node, decls_start)[0..self.statements_len]; - } - - pub fn statementsConst(self: *const LabeledBlock) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(LabeledBlock); - return @ptrCast([*]const *Node, decls_start)[0..self.statements_len]; - } - - fn sizeInBytes(statements_len: NodeIndex) usize { - return @sizeOf(LabeledBlock) + @sizeOf(*Node) * @as(usize, statements_len); - } - }; - - pub const Defer = struct { - base: Node = Node{ .tag = .Defer }, - defer_token: TokenIndex, - payload: ?*Node, - expr: *Node, - - pub fn iterate(self: *const Defer, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Defer) TokenIndex { - return self.defer_token; - } - - pub fn lastToken(self: *const Defer) TokenIndex { - return self.expr.lastToken(); - } - }; - - pub const Comptime = struct { - base: Node = Node{ .tag = .Comptime }, - doc_comments: ?*DocComment, - comptime_token: TokenIndex, - expr: *Node, - - pub fn iterate(self: *const Comptime, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Comptime) TokenIndex { - return self.comptime_token; - } - - pub fn lastToken(self: *const Comptime) TokenIndex { - return self.expr.lastToken(); - } - }; - - pub const Nosuspend = struct { - base: Node = Node{ .tag = .Nosuspend }, - nosuspend_token: TokenIndex, - expr: *Node, - - pub fn iterate(self: *const Nosuspend, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } + pub const ArrayInit = struct { + ast: Ast, - pub fn firstToken(self: *const Nosuspend) TokenIndex { - return self.nosuspend_token; - } - - pub fn lastToken(self: *const Nosuspend) TokenIndex { - return self.expr.lastToken(); - } - }; - - pub const Payload = struct { - base: Node = Node{ .tag = .Payload }, - lpipe: TokenIndex, - error_symbol: *Node, - rpipe: TokenIndex, - - pub fn iterate(self: *const Payload, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.error_symbol; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Payload) TokenIndex { - return self.lpipe; - } - - pub fn lastToken(self: *const Payload) TokenIndex { - return self.rpipe; - } + pub const Ast = struct { + lbrace: TokenIndex, + elements: []const Node.Index, + type_expr: Node.Index, + }; }; - pub const PointerPayload = struct { - base: Node = Node{ .tag = .PointerPayload }, - lpipe: TokenIndex, - ptr_token: ?TokenIndex, - value_symbol: *Node, - rpipe: TokenIndex, - - pub fn iterate(self: *const PointerPayload, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.value_symbol; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const PointerPayload) TokenIndex { - return self.lpipe; - } + pub const ArrayType = struct { + ast: Ast, - pub fn lastToken(self: *const PointerPayload) TokenIndex { - return self.rpipe; - } + pub const Ast = struct { + lbracket: TokenIndex, + elem_count: Node.Index, + sentinel: ?Node.Index, + elem_type: Node.Index, + }; }; - pub const PointerIndexPayload = struct { - base: Node = Node{ .tag = .PointerIndexPayload }, - lpipe: TokenIndex, - ptr_token: ?TokenIndex, - value_symbol: *Node, - index_symbol: ?*Node, - rpipe: TokenIndex, - - pub fn iterate(self: *const PointerIndexPayload, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.value_symbol; - i -= 1; - - if (self.index_symbol) |index_symbol| { - if (i < 1) return index_symbol; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const PointerIndexPayload) TokenIndex { - return self.lpipe; - } - - pub fn lastToken(self: *const PointerIndexPayload) TokenIndex { - return self.rpipe; - } + pub const PtrType = struct { + size: std.builtin.TypeInfo.Pointer.Size, + allowzero_token: ?TokenIndex, + const_token: ?TokenIndex, + volatile_token: ?TokenIndex, + ast: Ast, + + pub const Ast = struct { + main_token: TokenIndex, + align_node: Node.Index, + sentinel: Node.Index, + bit_range_start: Node.Index, + bit_range_end: Node.Index, + child_type: Node.Index, + }; }; - pub const Else = struct { - base: Node = Node{ .tag = .Else }, - else_token: TokenIndex, - payload: ?*Node, - body: *Node, - - pub fn iterate(self: *const Else, index: usize) ?*Node { - var i = index; - - if (self.payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - - if (i < 1) return self.body; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Else) TokenIndex { - return self.else_token; - } + pub const Slice = struct { + ast: Ast, - pub fn lastToken(self: *const Else) TokenIndex { - return self.body.lastToken(); - } + pub const Ast = struct { + sliced: Node.Index, + lbracket: TokenIndex, + start: Node.Index, + end: Node.Index, + sentinel: Node.Index, + }; }; - /// The cases node pointers are found in memory after Switch. - /// They must be SwitchCase or SwitchElse nodes. - pub const Switch = struct { - base: Node = Node{ .tag = .Switch }, - switch_token: TokenIndex, - rbrace: TokenIndex, - cases_len: NodeIndex, - expr: *Node, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, cases_len: NodeIndex) !*Switch { - const bytes = try allocator.alignedAlloc(u8, @alignOf(Switch), sizeInBytes(cases_len)); - return @ptrCast(*Switch, bytes.ptr); - } - - pub fn free(self: *Switch, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.cases_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const Switch, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - if (i < self.cases_len) return self.casesConst()[i]; - i -= self.cases_len; - - return null; - } - - pub fn firstToken(self: *const Switch) TokenIndex { - return self.switch_token; - } - - pub fn lastToken(self: *const Switch) TokenIndex { - return self.rbrace; - } - - pub fn cases(self: *Switch) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(Switch); - return @ptrCast([*]*Node, decls_start)[0..self.cases_len]; - } - - pub fn casesConst(self: *const Switch) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(Switch); - return @ptrCast([*]const *Node, decls_start)[0..self.cases_len]; - } - - fn sizeInBytes(cases_len: NodeIndex) usize { - return @sizeOf(Switch) + @sizeOf(*Node) * @as(usize, cases_len); - } + pub const ContainerDecl = struct { + layout_token: ?TokenIndex, + ast: Ast, + + pub const Ast = struct { + main_token: TokenIndex, + /// Populated when main_token is Keyword_union. + enum_token: ?TokenIndex, + members: []const Node.Index, + arg: Node.Index, + }; }; - /// Items sub-nodes appear in memory directly following SwitchCase. pub const SwitchCase = struct { - base: Node = Node{ .tag = .SwitchCase }, - arrow_token: TokenIndex, - payload: ?*Node, - expr: *Node, - items_len: NodeIndex, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, items_len: NodeIndex) !*SwitchCase { - const bytes = try allocator.alignedAlloc(u8, @alignOf(SwitchCase), sizeInBytes(items_len)); - return @ptrCast(*SwitchCase, bytes.ptr); - } - - pub fn free(self: *SwitchCase, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.items_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const SwitchCase, index: usize) ?*Node { - var i = index; - - if (i < self.items_len) return self.itemsConst()[i]; - i -= self.items_len; - - if (self.payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const SwitchCase) TokenIndex { - return self.itemsConst()[0].firstToken(); - } - - pub fn lastToken(self: *const SwitchCase) TokenIndex { - return self.expr.lastToken(); - } - - pub fn items(self: *SwitchCase) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(SwitchCase); - return @ptrCast([*]*Node, decls_start)[0..self.items_len]; - } - - pub fn itemsConst(self: *const SwitchCase) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(SwitchCase); - return @ptrCast([*]const *Node, decls_start)[0..self.items_len]; - } - - fn sizeInBytes(items_len: NodeIndex) usize { - return @sizeOf(SwitchCase) + @sizeOf(*Node) * @as(usize, items_len); - } - }; - - pub const SwitchElse = struct { - base: Node = Node{ .tag = .SwitchElse }, - token: TokenIndex, - - pub fn iterate(self: *const SwitchElse, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const SwitchElse) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const SwitchElse) TokenIndex { - return self.token; - } - }; - - pub const While = struct { - base: Node = Node{ .tag = .While }, - label: ?TokenIndex, - inline_token: ?TokenIndex, - while_token: TokenIndex, - condition: *Node, - payload: ?*Node, - continue_expr: ?*Node, - body: *Node, - @"else": ?*Else, - - pub fn iterate(self: *const While, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.condition; - i -= 1; - - if (self.payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - - if (self.continue_expr) |continue_expr| { - if (i < 1) return continue_expr; - i -= 1; - } - - if (i < 1) return self.body; - i -= 1; - - if (self.@"else") |@"else"| { - if (i < 1) return &@"else".base; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const While) TokenIndex { - if (self.label) |label| { - return label; - } - - if (self.inline_token) |inline_token| { - return inline_token; - } - - return self.while_token; - } - - pub fn lastToken(self: *const While) TokenIndex { - if (self.@"else") |@"else"| { - return @"else".body.lastToken(); - } - - return self.body.lastToken(); - } + /// Points to the first token after the `|`. Will either be an identifier or + /// a `*` (with an identifier immediately after it). + payload_token: ?TokenIndex, + ast: Ast, + + pub const Ast = struct { + /// If empty, this is an else case + values: []const Node.Index, + arrow_token: TokenIndex, + target_expr: Node.Index, + }; }; - pub const For = struct { - base: Node = Node{ .tag = .For }, - label: ?TokenIndex, - inline_token: ?TokenIndex, - for_token: TokenIndex, - array_expr: *Node, - payload: *Node, - body: *Node, - @"else": ?*Else, - - pub fn iterate(self: *const For, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.array_expr; - i -= 1; - - if (i < 1) return self.payload; - i -= 1; - - if (i < 1) return self.body; - i -= 1; - - if (self.@"else") |@"else"| { - if (i < 1) return &@"else".base; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const For) TokenIndex { - if (self.label) |label| { - return label; - } - - if (self.inline_token) |inline_token| { - return inline_token; - } - - return self.for_token; - } - - pub fn lastToken(self: *const For) TokenIndex { - if (self.@"else") |@"else"| { - return @"else".body.lastToken(); - } - - return self.body.lastToken(); - } + pub const Asm = struct { + ast: Ast, + volatile_token: ?TokenIndex, + first_clobber: ?TokenIndex, + outputs: []const Node.Index, + inputs: []const Node.Index, + + pub const Ast = struct { + asm_token: TokenIndex, + template: Node.Index, + items: []const Node.Index, + rparen: TokenIndex, + }; }; - pub const If = struct { - base: Node = Node{ .tag = .If }, - if_token: TokenIndex, - condition: *Node, - payload: ?*Node, - body: *Node, - @"else": ?*Else, - - pub fn iterate(self: *const If, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.condition; - i -= 1; - - if (self.payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - - if (i < 1) return self.body; - i -= 1; - - if (self.@"else") |@"else"| { - if (i < 1) return &@"else".base; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const If) TokenIndex { - return self.if_token; - } - - pub fn lastToken(self: *const If) TokenIndex { - if (self.@"else") |@"else"| { - return @"else".body.lastToken(); - } + pub const Call = struct { + ast: Ast, + async_token: ?TokenIndex, - return self.body.lastToken(); - } + pub const Ast = struct { + lparen: TokenIndex, + fn_expr: Node.Index, + params: []const Node.Index, + }; }; +}; - pub const Catch = struct { - base: Node = Node{ .tag = .Catch }, - op_token: TokenIndex, - lhs: *Node, - rhs: *Node, - payload: ?*Node, - - pub fn iterate(self: *const Catch, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (self.payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - - if (i < 1) return self.rhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Catch) TokenIndex { - return self.lhs.firstToken(); - } +pub const Error = struct { + tag: Tag, + token: TokenIndex, + extra: union { + none: void, + expected_tag: Token.Tag, + } = .{ .none = {} }, - pub fn lastToken(self: *const Catch) TokenIndex { - return self.rhs.lastToken(); - } + pub const Tag = enum { + asterisk_after_ptr_deref, + decl_between_fields, + expected_block, + expected_block_or_assignment, + expected_block_or_expr, + expected_block_or_field, + expected_container_members, + expected_expr, + expected_expr_or_assignment, + expected_fn, + expected_inlinable, + expected_labelable, + expected_param_list, + expected_prefix_expr, + expected_primary_type_expr, + expected_pub_item, + expected_return_type, + expected_semi_or_else, + expected_semi_or_lbrace, + expected_statement, + expected_string_literal, + expected_suffix_op, + expected_type_expr, + expected_var_decl, + expected_var_decl_or_fn, + expected_loop_payload, + expected_container, + extra_align_qualifier, + extra_allowzero_qualifier, + extra_const_qualifier, + extra_volatile_qualifier, + invalid_align, + invalid_and, + invalid_bit_range, + invalid_token, + same_line_doc_comment, + unattached_doc_comment, + + /// `expected_tag` is populated. + expected_token, }; +}; - pub const SimpleInfixOp = struct { - base: Node, - op_token: TokenIndex, - lhs: *Node, - rhs: *Node, - - pub fn iterate(self: *const SimpleInfixOp, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; +pub const Node = struct { + tag: Tag, + main_token: TokenIndex, + data: Data, - if (i < 1) return self.rhs; - i -= 1; + pub const Index = u32; - return null; - } + comptime { + // Goal is to keep this under one byte for efficiency. + assert(@sizeOf(Tag) == 1); + } - pub fn firstToken(self: *const SimpleInfixOp) TokenIndex { - return self.lhs.firstToken(); - } + /// Note: The FooComma/FooSemicolon variants exist to ease the implementation of + /// Tree.lastToken() + pub const Tag = enum { + /// sub_list[lhs...rhs] + root, + /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`. + @"usingnamespace", + /// lhs is test name token (must be string literal), if any. + /// rhs is the body node. + test_decl, + /// lhs is the index into extra_data. + /// rhs is the initialization expression, if any. + /// main_token is `var` or `const`. + global_var_decl, + /// `var a: x align(y) = rhs` + /// lhs is the index into extra_data. + /// main_token is `var` or `const`. + local_var_decl, + /// `var a: lhs = rhs`. lhs and rhs may be unused. + /// Can be local or global. + /// main_token is `var` or `const`. + simple_var_decl, + /// `var a align(lhs) = rhs`. lhs and rhs may be unused. + /// Can be local or global. + /// main_token is `var` or `const`. + aligned_var_decl, + /// lhs is the identifier token payload if any, + /// rhs is the deferred expression. + @"errdefer", + /// lhs is unused. + /// rhs is the deferred expression. + @"defer", + /// lhs catch rhs + /// lhs catch |err| rhs + /// main_token is the `catch` keyword. + /// payload is determined by looking at the next token after the `catch` keyword. + @"catch", + /// `lhs.a`. main_token is the dot. rhs is the identifier token index. + field_access, + /// `lhs.?`. main_token is the dot. rhs is the `?` token index. + unwrap_optional, + /// `lhs == rhs`. main_token is op. + equal_equal, + /// `lhs != rhs`. main_token is op. + bang_equal, + /// `lhs < rhs`. main_token is op. + less_than, + /// `lhs > rhs`. main_token is op. + greater_than, + /// `lhs <= rhs`. main_token is op. + less_or_equal, + /// `lhs >= rhs`. main_token is op. + greater_or_equal, + /// `lhs *= rhs`. main_token is op. + assign_mul, + /// `lhs /= rhs`. main_token is op. + assign_div, + /// `lhs *= rhs`. main_token is op. + assign_mod, + /// `lhs += rhs`. main_token is op. + assign_add, + /// `lhs -= rhs`. main_token is op. + assign_sub, + /// `lhs <<= rhs`. main_token is op. + assign_bit_shift_left, + /// `lhs >>= rhs`. main_token is op. + assign_bit_shift_right, + /// `lhs &= rhs`. main_token is op. + assign_bit_and, + /// `lhs ^= rhs`. main_token is op. + assign_bit_xor, + /// `lhs |= rhs`. main_token is op. + assign_bit_or, + /// `lhs *%= rhs`. main_token is op. + assign_mul_wrap, + /// `lhs +%= rhs`. main_token is op. + assign_add_wrap, + /// `lhs -%= rhs`. main_token is op. + assign_sub_wrap, + /// `lhs = rhs`. main_token is op. + assign, + /// `lhs || rhs`. main_token is the `||`. + merge_error_sets, + /// `lhs * rhs`. main_token is the `*`. + mul, + /// `lhs / rhs`. main_token is the `/`. + div, + /// `lhs % rhs`. main_token is the `%`. + mod, + /// `lhs ** rhs`. main_token is the `**`. + array_mult, + /// `lhs *% rhs`. main_token is the `*%`. + mul_wrap, + /// `lhs + rhs`. main_token is the `+`. + add, + /// `lhs - rhs`. main_token is the `-`. + sub, + /// `lhs ++ rhs`. main_token is the `++`. + array_cat, + /// `lhs +% rhs`. main_token is the `+%`. + add_wrap, + /// `lhs -% rhs`. main_token is the `-%`. + sub_wrap, + /// `lhs << rhs`. main_token is the `<<`. + bit_shift_left, + /// `lhs >> rhs`. main_token is the `>>`. + bit_shift_right, + /// `lhs & rhs`. main_token is the `&`. + bit_and, + /// `lhs ^ rhs`. main_token is the `^`. + bit_xor, + /// `lhs | rhs`. main_token is the `|`. + bit_or, + /// `lhs orelse rhs`. main_token is the `orelse`. + @"orelse", + /// `lhs and rhs`. main_token is the `and`. + bool_and, + /// `lhs or rhs`. main_token is the `or`. + bool_or, + /// `op lhs`. rhs unused. main_token is op. + bool_not, + /// `op lhs`. rhs unused. main_token is op. + negation, + /// `op lhs`. rhs unused. main_token is op. + bit_not, + /// `op lhs`. rhs unused. main_token is op. + negation_wrap, + /// `op lhs`. rhs unused. main_token is op. + address_of, + /// `op lhs`. rhs unused. main_token is op. + @"try", + /// `op lhs`. rhs unused. main_token is op. + @"await", + /// `?lhs`. rhs unused. main_token is the `?`. + optional_type, + /// `[lhs]rhs`. lhs can be omitted to make it a slice. + array_type, + /// `[lhs:a]b`. `array_type_sentinel[rhs]`. + array_type_sentinel, + /// `[*]align(lhs) rhs`. lhs can be omitted. + /// `*align(lhs) rhs`. lhs can be omitted. + /// `[]rhs`. + /// main_token is the asterisk if a pointer or the lbracket if a slice + /// main_token might be a ** token, which is shared with a parent/child + /// pointer type and may require special handling. + ptr_type_aligned, + /// `[*:lhs]rhs`. lhs can be omitted. + /// `*rhs`. + /// `[:lhs]rhs`. + /// main_token is the asterisk if a pointer or the lbracket if a slice + /// main_token might be a ** token, which is shared with a parent/child + /// pointer type and may require special handling. + ptr_type_sentinel, + /// lhs is index into ptr_type. rhs is the element type expression. + /// main_token is the asterisk if a pointer or the lbracket if a slice + /// main_token might be a ** token, which is shared with a parent/child + /// pointer type and may require special handling. + ptr_type, + /// lhs is index into ptr_type_bit_range. rhs is the element type expression. + /// main_token is the asterisk if a pointer or the lbracket if a slice + /// main_token might be a ** token, which is shared with a parent/child + /// pointer type and may require special handling. + ptr_type_bit_range, + /// `lhs[rhs..]` + /// main_token is the lbracket. + slice_open, + /// `lhs[b..c]`. rhs is index into Slice + /// main_token is the lbracket. + slice, + /// `lhs[b..c :d]`. rhs is index into SliceSentinel + /// main_token is the lbracket. + slice_sentinel, + /// `lhs.*`. rhs is unused. + deref, + /// `lhs[rhs]`. + array_access, + /// `lhs{rhs}`. rhs can be omitted. + array_init_one, + /// `lhs{rhs,}`. rhs can *not* be omitted + array_init_one_comma, + /// `.{lhs, rhs}`. lhs and rhs can be omitted. + array_init_dot_two, + /// Same as `array_init_dot_two` except there is known to be a trailing comma + /// before the final rbrace. + array_init_dot_two_comma, + /// `.{a, b}`. `sub_list[lhs..rhs]`. + array_init_dot, + /// Same as `array_init_dot` except there is known to be a trailing comma + /// before the final rbrace. + array_init_dot_comma, + /// `lhs{a, b}`. `sub_range_list[rhs]`. lhs can be omitted which means `.{a, b}`. + array_init, + /// Same as `array_init` except there is known to be a trailing comma + /// before the final rbrace. + array_init_comma, + /// `lhs{.a = rhs}`. rhs can be omitted making it empty. + /// main_token is the lbrace. + struct_init_one, + /// `lhs{.a = rhs,}`. rhs can *not* be omitted. + /// main_token is the lbrace. + struct_init_one_comma, + /// `.{.a = lhs, .b = rhs}`. lhs and rhs can be omitted. + /// main_token is the lbrace. + /// No trailing comma before the rbrace. + struct_init_dot_two, + /// Same as `struct_init_dot_two` except there is known to be a trailing comma + /// before the final rbrace. + struct_init_dot_two_comma, + /// `.{.a = b, .c = d}`. `sub_list[lhs..rhs]`. + /// main_token is the lbrace. + struct_init_dot, + /// Same as `struct_init_dot` except there is known to be a trailing comma + /// before the final rbrace. + struct_init_dot_comma, + /// `lhs{.a = b, .c = d}`. `sub_range_list[rhs]`. + /// lhs can be omitted which means `.{.a = b, .c = d}`. + /// main_token is the lbrace. + struct_init, + /// Same as `struct_init` except there is known to be a trailing comma + /// before the final rbrace. + struct_init_comma, + /// `lhs(rhs)`. rhs can be omitted. + /// main_token is the lparen. + call_one, + /// `lhs(rhs,)`. rhs can be omitted. + /// main_token is the lparen. + call_one_comma, + /// `async lhs(rhs)`. rhs can be omitted. + async_call_one, + /// `async lhs(rhs,)`. + async_call_one_comma, + /// `lhs(a, b, c)`. `SubRange[rhs]`. + /// main_token is the `(`. + call, + /// `lhs(a, b, c,)`. `SubRange[rhs]`. + /// main_token is the `(`. + call_comma, + /// `async lhs(a, b, c)`. `SubRange[rhs]`. + /// main_token is the `(`. + async_call, + /// `async lhs(a, b, c,)`. `SubRange[rhs]`. + /// main_token is the `(`. + async_call_comma, + /// `switch(lhs) {}`. `SubRange[rhs]`. + @"switch", + /// Same as switch except there is known to be a trailing comma + /// before the final rbrace + switch_comma, + /// `lhs => rhs`. If lhs is omitted it means `else`. + /// main_token is the `=>` + switch_case_one, + /// `a, b, c => rhs`. `SubRange[lhs]`. + /// main_token is the `=>` + switch_case, + /// `lhs...rhs`. + switch_range, + /// `while (lhs) rhs`. + /// `while (lhs) |x| rhs`. + while_simple, + /// `while (lhs) : (a) b`. `WhileCont[rhs]`. + /// `while (lhs) : (a) b`. `WhileCont[rhs]`. + while_cont, + /// `while (lhs) : (a) b else c`. `While[rhs]`. + /// `while (lhs) |x| : (a) b else c`. `While[rhs]`. + /// `while (lhs) |x| : (a) b else |y| c`. `While[rhs]`. + @"while", + /// `for (lhs) rhs`. + for_simple, + /// `for (lhs) a else b`. `if_list[rhs]`. + @"for", + /// `if (lhs) rhs`. + /// `if (lhs) |a| rhs`. + if_simple, + /// `if (lhs) a else b`. `If[rhs]`. + /// `if (lhs) |x| a else b`. `If[rhs]`. + /// `if (lhs) |x| a else |y| b`. `If[rhs]`. + @"if", + /// `suspend lhs`. lhs can be omitted. rhs is unused. + @"suspend", + /// `resume lhs`. rhs is unused. + @"resume", + /// `continue`. lhs is token index of label if any. rhs is unused. + @"continue", + /// `break :lhs rhs` + /// both lhs and rhs may be omitted. + @"break", + /// `return lhs`. lhs can be omitted. rhs is unused. + @"return", + /// `fn(a: lhs) rhs`. lhs can be omitted. + /// anytype and ... parameters are omitted from the AST tree. + /// main_token is the `fn` keyword. + /// extern function declarations use this tag. + fn_proto_simple, + /// `fn(a: b, c: d) rhs`. `sub_range_list[lhs]`. + /// anytype and ... parameters are omitted from the AST tree. + /// main_token is the `fn` keyword. + /// extern function declarations use this tag. + fn_proto_multi, + /// `fn(a: b) rhs linksection(e) callconv(f)`. `FnProtoOne[lhs]`. + /// zero or one parameters. + /// anytype and ... parameters are omitted from the AST tree. + /// main_token is the `fn` keyword. + /// extern function declarations use this tag. + fn_proto_one, + /// `fn(a: b, c: d) rhs linksection(e) callconv(f)`. `FnProto[lhs]`. + /// anytype and ... parameters are omitted from the AST tree. + /// main_token is the `fn` keyword. + /// extern function declarations use this tag. + fn_proto, + /// lhs is the fn_proto. + /// rhs is the function body block. + /// Note that extern function declarations use the fn_proto tags rather + /// than this one. + fn_decl, + /// `anyframe->rhs`. main_token is `anyframe`. `lhs` is arrow token index. + anyframe_type, + /// Both lhs and rhs unused. + anyframe_literal, + /// Both lhs and rhs unused. + char_literal, + /// Both lhs and rhs unused. + integer_literal, + /// Both lhs and rhs unused. + float_literal, + /// Both lhs and rhs unused. + false_literal, + /// Both lhs and rhs unused. + true_literal, + /// Both lhs and rhs unused. + null_literal, + /// Both lhs and rhs unused. + undefined_literal, + /// Both lhs and rhs unused. + unreachable_literal, + /// Both lhs and rhs unused. + /// Most identifiers will not have explicit AST nodes, however for expressions + /// which could be one of many different kinds of AST nodes, there will be an + /// identifier AST node for it. + identifier, + /// lhs is the dot token index, rhs unused, main_token is the identifier. + enum_literal, + /// main_token is the string literal token + /// Both lhs and rhs unused. + string_literal, + /// main_token is the first token index (redundant with lhs) + /// lhs is the first token index; rhs is the last token index. + /// Could be a series of multiline_string_literal_line tokens, or a single + /// string_literal token. + multiline_string_literal, + /// `(lhs)`. main_token is the `(`; rhs is the token index of the `)`. + grouped_expression, + /// `@a(lhs, rhs)`. lhs and rhs may be omitted. + /// main_token is the builtin token. + builtin_call_two, + /// Same as builtin_call_two but there is known to be a trailing comma before the rparen. + builtin_call_two_comma, + /// `@a(b, c)`. `sub_list[lhs..rhs]`. + /// main_token is the builtin token. + builtin_call, + /// Same as builtin_call but there is known to be a trailing comma before the rparen. + builtin_call_comma, + /// `error{a, b}`. + /// rhs is the rbrace, lhs is unused. + error_set_decl, + /// `struct {}`, `union {}`, `opaque {}`, `enum {}`. `extra_data[lhs..rhs]`. + /// main_token is `struct`, `union`, `opaque`, `enum` keyword. + container_decl, + /// Same as ContainerDecl but there is known to be a trailing comma + /// or semicolon before the rbrace. + container_decl_trailing, + /// `struct {lhs, rhs}`, `union {lhs, rhs}`, `opaque {lhs, rhs}`, `enum {lhs, rhs}`. + /// lhs or rhs can be omitted. + /// main_token is `struct`, `union`, `opaque`, `enum` keyword. + container_decl_two, + /// Same as ContainerDeclTwo except there is known to be a trailing comma + /// or semicolon before the rbrace. + container_decl_two_trailing, + /// `union(lhs)` / `enum(lhs)`. `SubRange[rhs]`. + container_decl_arg, + /// Same as container_decl_arg but there is known to be a trailing + /// comma or semicolon before the rbrace. + container_decl_arg_trailing, + /// `union(enum) {}`. `sub_list[lhs..rhs]`. + /// Note that tagged unions with explicitly provided enums are represented + /// by `container_decl_arg`. + tagged_union, + /// Same as tagged_union but there is known to be a trailing comma + /// or semicolon before the rbrace. + tagged_union_trailing, + /// `union(enum) {lhs, rhs}`. lhs or rhs may be omitted. + /// Note that tagged unions with explicitly provided enums are represented + /// by `container_decl_arg`. + tagged_union_two, + /// Same as tagged_union_two but there is known to be a trailing comma + /// or semicolon before the rbrace. + tagged_union_two_trailing, + /// `union(enum(lhs)) {}`. `SubRange[rhs]`. + tagged_union_enum_tag, + /// Same as tagged_union_enum_tag but there is known to be a trailing comma + /// or semicolon before the rbrace. + tagged_union_enum_tag_trailing, + /// `a: lhs = rhs,`. lhs and rhs can be omitted. + /// main_token is the field name identifier. + /// lastToken() does not include the possible trailing comma. + container_field_init, + /// `a: lhs align(rhs),`. rhs can be omitted. + /// main_token is the field name identifier. + /// lastToken() does not include the possible trailing comma. + container_field_align, + /// `a: lhs align(c) = d,`. `container_field_list[rhs]`. + /// main_token is the field name identifier. + /// lastToken() does not include the possible trailing comma. + container_field, + /// `anytype`. both lhs and rhs unused. + /// Used by `ContainerField`. + @"anytype", + /// `comptime lhs`. rhs unused. + @"comptime", + /// `nosuspend lhs`. rhs unused. + @"nosuspend", + /// `{lhs rhs}`. rhs or lhs can be omitted. + /// main_token points at the lbrace. + block_two, + /// Same as block_two but there is known to be a semicolon before the rbrace. + block_two_semicolon, + /// `{}`. `sub_list[lhs..rhs]`. + /// main_token points at the lbrace. + block, + /// Same as block but there is known to be a semicolon before the rbrace. + block_semicolon, + /// `asm(lhs)`. rhs is the token index of the rparen. + asm_simple, + /// `asm(lhs, a)`. `Asm[rhs]`. + @"asm", + /// `[a] "b" (c)`. lhs is 0, rhs is token index of the rparen. + /// `[a] "b" (-> lhs)`. rhs is token index of the rparen. + /// main_token is `a`. + asm_output, + /// `[a] "b" (lhs)`. rhs is token index of the rparen. + /// main_token is `a`. + asm_input, + /// `error.a`. lhs is token index of `.`. rhs is token index of `a`. + error_value, + /// `lhs!rhs`. main_token is the `!`. + error_union, + + pub fn isContainerField(tag: Tag) bool { + return switch (tag) { + .container_field_init, + .container_field_align, + .container_field, + => true, - pub fn lastToken(self: *const SimpleInfixOp) TokenIndex { - return self.rhs.lastToken(); + else => false, + }; } }; - pub const SimplePrefixOp = struct { - base: Node, - op_token: TokenIndex, - rhs: *Node, - - const Self = @This(); - - pub fn iterate(self: *const Self, index: usize) ?*Node { - if (index == 0) return self.rhs; - return null; - } - - pub fn firstToken(self: *const Self) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const Self) TokenIndex { - return self.rhs.lastToken(); - } + pub const Data = struct { + lhs: Index, + rhs: Index, }; - pub const ArrayType = struct { - base: Node = Node{ .tag = .ArrayType }, - op_token: TokenIndex, - rhs: *Node, - len_expr: *Node, - - pub fn iterate(self: *const ArrayType, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.len_expr; - i -= 1; - - if (i < 1) return self.rhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const ArrayType) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const ArrayType) TokenIndex { - return self.rhs.lastToken(); - } + pub const LocalVarDecl = struct { + type_node: Index, + align_node: Index, }; pub const ArrayTypeSentinel = struct { - base: Node = Node{ .tag = .ArrayTypeSentinel }, - op_token: TokenIndex, - rhs: *Node, - len_expr: *Node, - sentinel: *Node, - - pub fn iterate(self: *const ArrayTypeSentinel, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.len_expr; - i -= 1; - - if (i < 1) return self.sentinel; - i -= 1; - - if (i < 1) return self.rhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const ArrayTypeSentinel) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const ArrayTypeSentinel) TokenIndex { - return self.rhs.lastToken(); - } + elem_type: Index, + sentinel: Index, }; pub const PtrType = struct { - base: Node = Node{ .tag = .PtrType }, - op_token: TokenIndex, - rhs: *Node, - /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents - /// one of these possibly-null things. Then we have them directly follow the PtrType in memory. - ptr_info: PtrInfo = .{}, - - pub fn iterate(self: *const PtrType, index: usize) ?*Node { - var i = index; - - if (self.ptr_info.sentinel) |sentinel| { - if (i < 1) return sentinel; - i -= 1; - } - - if (self.ptr_info.align_info) |align_info| { - if (i < 1) return align_info.node; - i -= 1; - } - - if (i < 1) return self.rhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const PtrType) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const PtrType) TokenIndex { - return self.rhs.lastToken(); - } - }; - - pub const SliceType = struct { - base: Node = Node{ .tag = .SliceType }, - op_token: TokenIndex, - rhs: *Node, - /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents - /// one of these possibly-null things. Then we have them directly follow the SliceType in memory. - ptr_info: PtrInfo = .{}, - - pub fn iterate(self: *const SliceType, index: usize) ?*Node { - var i = index; - - if (self.ptr_info.sentinel) |sentinel| { - if (i < 1) return sentinel; - i -= 1; - } - - if (self.ptr_info.align_info) |align_info| { - if (i < 1) return align_info.node; - i -= 1; - } - - if (i < 1) return self.rhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const SliceType) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const SliceType) TokenIndex { - return self.rhs.lastToken(); - } - }; - - pub const FieldInitializer = struct { - base: Node = Node{ .tag = .FieldInitializer }, - period_token: TokenIndex, - name_token: TokenIndex, - expr: *Node, - - pub fn iterate(self: *const FieldInitializer, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const FieldInitializer) TokenIndex { - return self.period_token; - } - - pub fn lastToken(self: *const FieldInitializer) TokenIndex { - return self.expr.lastToken(); - } - }; - - /// Elements occur directly in memory after ArrayInitializer. - pub const ArrayInitializer = struct { - base: Node = Node{ .tag = .ArrayInitializer }, - rtoken: TokenIndex, - list_len: NodeIndex, - lhs: *Node, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, list_len: NodeIndex) !*ArrayInitializer { - const bytes = try allocator.alignedAlloc(u8, @alignOf(ArrayInitializer), sizeInBytes(list_len)); - return @ptrCast(*ArrayInitializer, bytes.ptr); - } - - pub fn free(self: *ArrayInitializer, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.list_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const ArrayInitializer, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (i < self.list_len) return self.listConst()[i]; - i -= self.list_len; - - return null; - } - - pub fn firstToken(self: *const ArrayInitializer) TokenIndex { - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const ArrayInitializer) TokenIndex { - return self.rtoken; - } - - pub fn list(self: *ArrayInitializer) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(ArrayInitializer); - return @ptrCast([*]*Node, decls_start)[0..self.list_len]; - } - - pub fn listConst(self: *const ArrayInitializer) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(ArrayInitializer); - return @ptrCast([*]const *Node, decls_start)[0..self.list_len]; - } - - fn sizeInBytes(list_len: NodeIndex) usize { - return @sizeOf(ArrayInitializer) + @sizeOf(*Node) * @as(usize, list_len); - } + sentinel: Index, + align_node: Index, }; - /// Elements occur directly in memory after ArrayInitializerDot. - pub const ArrayInitializerDot = struct { - base: Node = Node{ .tag = .ArrayInitializerDot }, - dot: TokenIndex, - rtoken: TokenIndex, - list_len: NodeIndex, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, list_len: NodeIndex) !*ArrayInitializerDot { - const bytes = try allocator.alignedAlloc(u8, @alignOf(ArrayInitializerDot), sizeInBytes(list_len)); - return @ptrCast(*ArrayInitializerDot, bytes.ptr); - } - - pub fn free(self: *ArrayInitializerDot, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.list_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const ArrayInitializerDot, index: usize) ?*Node { - var i = index; - - if (i < self.list_len) return self.listConst()[i]; - i -= self.list_len; - - return null; - } - - pub fn firstToken(self: *const ArrayInitializerDot) TokenIndex { - return self.dot; - } - - pub fn lastToken(self: *const ArrayInitializerDot) TokenIndex { - return self.rtoken; - } - - pub fn list(self: *ArrayInitializerDot) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(ArrayInitializerDot); - return @ptrCast([*]*Node, decls_start)[0..self.list_len]; - } - - pub fn listConst(self: *const ArrayInitializerDot) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(ArrayInitializerDot); - return @ptrCast([*]const *Node, decls_start)[0..self.list_len]; - } - - fn sizeInBytes(list_len: NodeIndex) usize { - return @sizeOf(ArrayInitializerDot) + @sizeOf(*Node) * @as(usize, list_len); - } + pub const PtrTypeBitRange = struct { + sentinel: Index, + align_node: Index, + bit_range_start: Index, + bit_range_end: Index, }; - /// Elements occur directly in memory after StructInitializer. - pub const StructInitializer = struct { - base: Node = Node{ .tag = .StructInitializer }, - rtoken: TokenIndex, - list_len: NodeIndex, - lhs: *Node, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, list_len: NodeIndex) !*StructInitializer { - const bytes = try allocator.alignedAlloc(u8, @alignOf(StructInitializer), sizeInBytes(list_len)); - return @ptrCast(*StructInitializer, bytes.ptr); - } - - pub fn free(self: *StructInitializer, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.list_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const StructInitializer, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (i < self.list_len) return self.listConst()[i]; - i -= self.list_len; - - return null; - } - - pub fn firstToken(self: *const StructInitializer) TokenIndex { - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const StructInitializer) TokenIndex { - return self.rtoken; - } - - pub fn list(self: *StructInitializer) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(StructInitializer); - return @ptrCast([*]*Node, decls_start)[0..self.list_len]; - } - - pub fn listConst(self: *const StructInitializer) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(StructInitializer); - return @ptrCast([*]const *Node, decls_start)[0..self.list_len]; - } - - fn sizeInBytes(list_len: NodeIndex) usize { - return @sizeOf(StructInitializer) + @sizeOf(*Node) * @as(usize, list_len); - } - }; - - /// Elements occur directly in memory after StructInitializerDot. - pub const StructInitializerDot = struct { - base: Node = Node{ .tag = .StructInitializerDot }, - dot: TokenIndex, - rtoken: TokenIndex, - list_len: NodeIndex, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, list_len: NodeIndex) !*StructInitializerDot { - const bytes = try allocator.alignedAlloc(u8, @alignOf(StructInitializerDot), sizeInBytes(list_len)); - return @ptrCast(*StructInitializerDot, bytes.ptr); - } - - pub fn free(self: *StructInitializerDot, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.list_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const StructInitializerDot, index: usize) ?*Node { - var i = index; - - if (i < self.list_len) return self.listConst()[i]; - i -= self.list_len; - - return null; - } - - pub fn firstToken(self: *const StructInitializerDot) TokenIndex { - return self.dot; - } - - pub fn lastToken(self: *const StructInitializerDot) TokenIndex { - return self.rtoken; - } - - pub fn list(self: *StructInitializerDot) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(StructInitializerDot); - return @ptrCast([*]*Node, decls_start)[0..self.list_len]; - } - - pub fn listConst(self: *const StructInitializerDot) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(StructInitializerDot); - return @ptrCast([*]const *Node, decls_start)[0..self.list_len]; - } - - fn sizeInBytes(list_len: NodeIndex) usize { - return @sizeOf(StructInitializerDot) + @sizeOf(*Node) * @as(usize, list_len); - } + pub const SubRange = struct { + /// Index into sub_list. + start: Index, + /// Index into sub_list. + end: Index, }; - /// Parameter nodes directly follow Call in memory. - pub const Call = struct { - base: Node = Node{ .tag = .Call }, - rtoken: TokenIndex, - lhs: *Node, - params_len: NodeIndex, - async_token: ?TokenIndex, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, params_len: NodeIndex) !*Call { - const bytes = try allocator.alignedAlloc(u8, @alignOf(Call), sizeInBytes(params_len)); - return @ptrCast(*Call, bytes.ptr); - } - - pub fn free(self: *Call, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const Call, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (i < self.params_len) return self.paramsConst()[i]; - i -= self.params_len; - - return null; - } - - pub fn firstToken(self: *const Call) TokenIndex { - if (self.async_token) |async_token| return async_token; - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const Call) TokenIndex { - return self.rtoken; - } - - pub fn params(self: *Call) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(Call); - return @ptrCast([*]*Node, decls_start)[0..self.params_len]; - } - - pub fn paramsConst(self: *const Call) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(Call); - return @ptrCast([*]const *Node, decls_start)[0..self.params_len]; - } - - fn sizeInBytes(params_len: NodeIndex) usize { - return @sizeOf(Call) + @sizeOf(*Node) * @as(usize, params_len); - } + pub const If = struct { + then_expr: Index, + else_expr: Index, }; - pub const ArrayAccess = struct { - base: Node = Node{ .tag = .ArrayAccess }, - rtoken: TokenIndex, - lhs: *Node, - index_expr: *Node, - - pub fn iterate(self: *const ArrayAccess, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (i < 1) return self.index_expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const ArrayAccess) TokenIndex { - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const ArrayAccess) TokenIndex { - return self.rtoken; - } + pub const ContainerField = struct { + value_expr: Index, + align_expr: Index, }; - pub const SimpleSuffixOp = struct { - base: Node, - rtoken: TokenIndex, - lhs: *Node, - - pub fn iterate(self: *const SimpleSuffixOp, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const SimpleSuffixOp) TokenIndex { - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const SimpleSuffixOp) TokenIndex { - return self.rtoken; - } + pub const GlobalVarDecl = struct { + type_node: Index, + align_node: Index, + section_node: Index, }; pub const Slice = struct { - base: Node = Node{ .tag = .Slice }, - rtoken: TokenIndex, - lhs: *Node, - start: *Node, - end: ?*Node, - sentinel: ?*Node, - - pub fn iterate(self: *const Slice, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.lhs; - i -= 1; - - if (i < 1) return self.start; - i -= 1; - - if (self.end) |end| { - if (i < 1) return end; - i -= 1; - } - if (self.sentinel) |sentinel| { - if (i < 1) return sentinel; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const Slice) TokenIndex { - return self.lhs.firstToken(); - } - - pub fn lastToken(self: *const Slice) TokenIndex { - return self.rtoken; - } - }; - - pub const GroupedExpression = struct { - base: Node = Node{ .tag = .GroupedExpression }, - lparen: TokenIndex, - expr: *Node, - rparen: TokenIndex, - - pub fn iterate(self: *const GroupedExpression, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const GroupedExpression) TokenIndex { - return self.lparen; - } - - pub fn lastToken(self: *const GroupedExpression) TokenIndex { - return self.rparen; - } + start: Index, + end: Index, }; - /// Trailed in memory by possibly many things, with each optional thing - /// determined by a bit in `trailer_flags`. - /// Can be: return, break, continue - pub const ControlFlowExpression = struct { - base: Node, - trailer_flags: TrailerFlags, - ltoken: TokenIndex, - - pub const TrailerFlags = std.meta.TrailerFlags(struct { - rhs: *Node, - label: TokenIndex, - }); - - pub const RequiredFields = struct { - tag: Tag, - ltoken: TokenIndex, - }; - - pub fn getRHS(self: *const ControlFlowExpression) ?*Node { - return self.getTrailer(.rhs); - } - - pub fn setRHS(self: *ControlFlowExpression, value: *Node) void { - self.setTrailer(.rhs, value); - } - - pub fn getLabel(self: *const ControlFlowExpression) ?TokenIndex { - return self.getTrailer(.label); - } - - pub fn setLabel(self: *ControlFlowExpression, value: TokenIndex) void { - self.setTrailer(.label, value); - } - - fn getTrailer(self: *const ControlFlowExpression, comptime field: TrailerFlags.FieldEnum) ?TrailerFlags.Field(field) { - const trailers_start = @ptrCast([*]const u8, self) + @sizeOf(ControlFlowExpression); - return self.trailer_flags.get(trailers_start, field); - } - - fn setTrailer(self: *ControlFlowExpression, comptime field: TrailerFlags.FieldEnum, value: TrailerFlags.Field(field)) void { - const trailers_start = @ptrCast([*]u8, self) + @sizeOf(ControlFlowExpression); - self.trailer_flags.set(trailers_start, field, value); - } - - pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: TrailerFlags.InitStruct) !*ControlFlowExpression { - const trailer_flags = TrailerFlags.init(trailers); - const bytes = try allocator.alignedAlloc(u8, @alignOf(ControlFlowExpression), sizeInBytes(trailer_flags)); - const ctrl_flow_expr = @ptrCast(*ControlFlowExpression, bytes.ptr); - ctrl_flow_expr.* = .{ - .base = .{ .tag = required.tag }, - .trailer_flags = trailer_flags, - .ltoken = required.ltoken, - }; - const trailers_start = bytes.ptr + @sizeOf(ControlFlowExpression); - trailer_flags.setMany(trailers_start, trailers); - return ctrl_flow_expr; - } - - pub fn destroy(self: *ControlFlowExpression, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.trailer_flags)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const ControlFlowExpression, index: usize) ?*Node { - var i = index; - - if (self.getRHS()) |rhs| { - if (i < 1) return rhs; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const ControlFlowExpression) TokenIndex { - return self.ltoken; - } - - pub fn lastToken(self: *const ControlFlowExpression) TokenIndex { - if (self.getRHS()) |rhs| { - return rhs.lastToken(); - } - - if (self.getLabel()) |label| { - return label; - } - - return self.ltoken; - } - - fn sizeInBytes(trailer_flags: TrailerFlags) usize { - return @sizeOf(ControlFlowExpression) + trailer_flags.sizeInBytes(); - } + pub const SliceSentinel = struct { + start: Index, + end: Index, + sentinel: Index, }; - pub const Suspend = struct { - base: Node = Node{ .tag = .Suspend }, - suspend_token: TokenIndex, - body: ?*Node, - - pub fn iterate(self: *const Suspend, index: usize) ?*Node { - var i = index; - - if (self.body) |body| { - if (i < 1) return body; - i -= 1; - } - - return null; - } - - pub fn firstToken(self: *const Suspend) TokenIndex { - return self.suspend_token; - } - - pub fn lastToken(self: *const Suspend) TokenIndex { - if (self.body) |body| { - return body.lastToken(); - } - - return self.suspend_token; - } + pub const While = struct { + cont_expr: Index, + then_expr: Index, + else_expr: Index, }; - pub const EnumLiteral = struct { - base: Node = Node{ .tag = .EnumLiteral }, - dot: TokenIndex, - name: TokenIndex, - - pub fn iterate(self: *const EnumLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const EnumLiteral) TokenIndex { - return self.dot; - } - - pub fn lastToken(self: *const EnumLiteral) TokenIndex { - return self.name; - } + pub const WhileCont = struct { + cont_expr: Index, + then_expr: Index, }; - /// Parameters are in memory following BuiltinCall. - pub const BuiltinCall = struct { - base: Node = Node{ .tag = .BuiltinCall }, - params_len: NodeIndex, - builtin_token: TokenIndex, - rparen_token: TokenIndex, - - /// After this the caller must initialize the fields_and_decls list. - pub fn alloc(allocator: *mem.Allocator, params_len: NodeIndex) !*BuiltinCall { - const bytes = try allocator.alignedAlloc(u8, @alignOf(BuiltinCall), sizeInBytes(params_len)); - return @ptrCast(*BuiltinCall, bytes.ptr); - } - - pub fn free(self: *BuiltinCall, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const BuiltinCall, index: usize) ?*Node { - var i = index; - - if (i < self.params_len) return self.paramsConst()[i]; - i -= self.params_len; - - return null; - } - - pub fn firstToken(self: *const BuiltinCall) TokenIndex { - return self.builtin_token; - } - - pub fn lastToken(self: *const BuiltinCall) TokenIndex { - return self.rparen_token; - } - - pub fn params(self: *BuiltinCall) []*Node { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(BuiltinCall); - return @ptrCast([*]*Node, decls_start)[0..self.params_len]; - } - - pub fn paramsConst(self: *const BuiltinCall) []const *Node { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(BuiltinCall); - return @ptrCast([*]const *Node, decls_start)[0..self.params_len]; - } - - fn sizeInBytes(params_len: NodeIndex) usize { - return @sizeOf(BuiltinCall) + @sizeOf(*Node) * @as(usize, params_len); - } + pub const FnProtoOne = struct { + /// Populated if there is exactly 1 parameter. Otherwise there are 0 parameters. + param: Index, + /// Populated if align(A) is present. + align_expr: Index, + /// Populated if linksection(A) is present. + section_expr: Index, + /// Populated if callconv(A) is present. + callconv_expr: Index, }; - /// The string literal tokens appear directly in memory after MultilineStringLiteral. - pub const MultilineStringLiteral = struct { - base: Node = Node{ .tag = .MultilineStringLiteral }, - lines_len: TokenIndex, - - /// After this the caller must initialize the lines list. - pub fn alloc(allocator: *mem.Allocator, lines_len: NodeIndex) !*MultilineStringLiteral { - const bytes = try allocator.alignedAlloc(u8, @alignOf(MultilineStringLiteral), sizeInBytes(lines_len)); - return @ptrCast(*MultilineStringLiteral, bytes.ptr); - } - - pub fn free(self: *MultilineStringLiteral, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.lines_len)]; - allocator.free(bytes); - } - - pub fn iterate(self: *const MultilineStringLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const MultilineStringLiteral) TokenIndex { - return self.linesConst()[0]; - } - - pub fn lastToken(self: *const MultilineStringLiteral) TokenIndex { - return self.linesConst()[self.lines_len - 1]; - } - - pub fn lines(self: *MultilineStringLiteral) []TokenIndex { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(MultilineStringLiteral); - return @ptrCast([*]TokenIndex, decls_start)[0..self.lines_len]; - } - - pub fn linesConst(self: *const MultilineStringLiteral) []const TokenIndex { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(MultilineStringLiteral); - return @ptrCast([*]const TokenIndex, decls_start)[0..self.lines_len]; - } - - fn sizeInBytes(lines_len: NodeIndex) usize { - return @sizeOf(MultilineStringLiteral) + @sizeOf(TokenIndex) * @as(usize, lines_len); - } + pub const FnProto = struct { + params_start: Index, + params_end: Index, + /// Populated if align(A) is present. + align_expr: Index, + /// Populated if linksection(A) is present. + section_expr: Index, + /// Populated if callconv(A) is present. + callconv_expr: Index, }; pub const Asm = struct { - base: Node = Node{ .tag = .Asm }, - asm_token: TokenIndex, + items_start: Index, + items_end: Index, + /// Needed to make lastToken() work. rparen: TokenIndex, - volatile_token: ?TokenIndex, - template: *Node, - outputs: []Output, - inputs: []Input, - /// A clobber node must be a StringLiteral or MultilineStringLiteral. - clobbers: []*Node, - - pub const Output = struct { - lbracket: TokenIndex, - symbolic_name: *Node, - constraint: *Node, - kind: Kind, - rparen: TokenIndex, - - pub const Kind = union(enum) { - Variable: *OneToken, - Return: *Node, - }; - - pub fn iterate(self: *const Output, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.symbolic_name; - i -= 1; - - if (i < 1) return self.constraint; - i -= 1; - - switch (self.kind) { - .Variable => |variable_name| { - if (i < 1) return &variable_name.base; - i -= 1; - }, - .Return => |return_type| { - if (i < 1) return return_type; - i -= 1; - }, - } - - return null; - } - - pub fn firstToken(self: *const Output) TokenIndex { - return self.lbracket; - } - - pub fn lastToken(self: *const Output) TokenIndex { - return self.rparen; - } - }; - - pub const Input = struct { - lbracket: TokenIndex, - symbolic_name: *Node, - constraint: *Node, - expr: *Node, - rparen: TokenIndex, - - pub fn iterate(self: *const Input, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.symbolic_name; - i -= 1; - - if (i < 1) return self.constraint; - i -= 1; - - if (i < 1) return self.expr; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const Input) TokenIndex { - return self.lbracket; - } - - pub fn lastToken(self: *const Input) TokenIndex { - return self.rparen; - } - }; - - pub fn iterate(self: *const Asm, index: usize) ?*Node { - var i = index; - - if (i < self.outputs.len * 3) switch (i % 3) { - 0 => return self.outputs[i / 3].symbolic_name, - 1 => return self.outputs[i / 3].constraint, - 2 => switch (self.outputs[i / 3].kind) { - .Variable => |variable_name| return &variable_name.base, - .Return => |return_type| return return_type, - }, - else => unreachable, - }; - i -= self.outputs.len * 3; - - if (i < self.inputs.len * 3) switch (i % 3) { - 0 => return self.inputs[i / 3].symbolic_name, - 1 => return self.inputs[i / 3].constraint, - 2 => return self.inputs[i / 3].expr, - else => unreachable, - }; - i -= self.inputs.len * 3; - - return null; - } - - pub fn firstToken(self: *const Asm) TokenIndex { - return self.asm_token; - } - - pub fn lastToken(self: *const Asm) TokenIndex { - return self.rparen; - } - }; - - /// TODO remove from the Node base struct - /// TODO actually maybe remove entirely in favor of iterating backward from Node.firstToken() - /// and forwards to find same-line doc comments. - pub const DocComment = struct { - base: Node = Node{ .tag = .DocComment }, - /// Points to the first doc comment token. API users are expected to iterate over the - /// tokens array, looking for more doc comments, ignoring line comments, and stopping - /// at the first other token. - first_line: TokenIndex, - - pub fn iterate(self: *const DocComment, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const DocComment) TokenIndex { - return self.first_line; - } - - /// Returns the first doc comment line. Be careful, this may not be the desired behavior, - /// which would require the tokens array. - pub fn lastToken(self: *const DocComment) TokenIndex { - return self.first_line; - } - }; - - pub const TestDecl = struct { - base: Node = Node{ .tag = .TestDecl }, - doc_comments: ?*DocComment, - test_token: TokenIndex, - name: *Node, - body_node: *Node, - - pub fn iterate(self: *const TestDecl, index: usize) ?*Node { - var i = index; - - if (i < 1) return self.body_node; - i -= 1; - - return null; - } - - pub fn firstToken(self: *const TestDecl) TokenIndex { - return self.test_token; - } - - pub fn lastToken(self: *const TestDecl) TokenIndex { - return self.body_node.lastToken(); - } }; }; - -pub const PtrInfo = struct { - allowzero_token: ?TokenIndex = null, - align_info: ?Align = null, - const_token: ?TokenIndex = null, - volatile_token: ?TokenIndex = null, - sentinel: ?*Node = null, - - pub const Align = struct { - node: *Node, - bit_range: ?BitRange = null, - - pub const BitRange = struct { - start: *Node, - end: *Node, - }; - }; -}; - -test "iterate" { - var root = Node.Root{ - .base = Node{ .tag = Node.Tag.Root }, - .decls_len = 0, - .eof_token = 0, - }; - var base = &root.base; - testing.expect(base.iterate(0) == null); -} diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 5c68fb3798..8d6f63f5e3 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -130,6 +130,9 @@ pub const CrossTarget = struct { .wasi, .emscripten, .uefi, + .opencl, + .glsl450, + .vulkan, .other, => { self.os_version_min = .{ .none = {} }; @@ -519,29 +522,29 @@ pub const CrossTarget = struct { var result = std.ArrayList(u8).init(allocator); defer result.deinit(); - try result.outStream().print("{}-{}", .{ arch_name, os_name }); + try result.writer().print("{s}-{s}", .{ arch_name, os_name }); // The zig target syntax does not allow specifying a max os version with no min, so // if either are present, we need the min. if (self.os_version_min != null or self.os_version_max != null) { switch (self.getOsVersionMin()) { .none => {}, - .semver => |v| try result.outStream().print(".{}", .{v}), - .windows => |v| try result.outStream().print("{s}", .{v}), + .semver => |v| try result.writer().print(".{}", .{v}), + .windows => |v| try result.writer().print("{s}", .{v}), } } if (self.os_version_max) |max| { switch (max) { .none => {}, - .semver => |v| try result.outStream().print("...{}", .{v}), - .windows => |v| try result.outStream().print("..{s}", .{v}), + .semver => |v| try result.writer().print("...{}", .{v}), + .windows => |v| try result.writer().print("..{s}", .{v}), } } if (self.glibc_version) |v| { - try result.outStream().print("-{}.{}", .{ @tagName(self.getAbi()), v }); + try result.writer().print("-{s}.{}", .{ @tagName(self.getAbi()), v }); } else if (self.abi) |abi| { - try result.outStream().print("-{}", .{@tagName(abi)}); + try result.writer().print("-{s}", .{@tagName(abi)}); } return result.toOwnedSlice(); @@ -595,7 +598,7 @@ pub const CrossTarget = struct { .Dynamic => "", }; - return std.fmt.allocPrint(allocator, "{}-{}{}", .{ arch, os, static_suffix }); + return std.fmt.allocPrint(allocator, "{s}-{s}{s}", .{ arch, os, static_suffix }); } pub const Executor = union(enum) { @@ -730,6 +733,9 @@ pub const CrossTarget = struct { .wasi, .emscripten, .uefi, + .opencl, + .glsl450, + .vulkan, .other, => return error.InvalidOperatingSystemVersion, @@ -790,7 +796,7 @@ test "CrossTarget.parse" { var buf: [256]u8 = undefined; const triple = std.fmt.bufPrint( buf[0..], - "native-native-{}.2.1.1", + "native-native-{s}.2.1.1", .{@tagName(std.Target.current.abi)}, ) catch unreachable; diff --git a/lib/std/zig/fmt.zig b/lib/std/zig/fmt.zig new file mode 100644 index 0000000000..2ca5279263 --- /dev/null +++ b/lib/std/zig/fmt.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const mem = std.mem; + +/// Print the string as a Zig identifier escaping it with @"" syntax if needed. +pub fn formatId( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + if (isValidId(bytes)) { + return writer.writeAll(bytes); + } + try writer.writeAll("@\""); + try formatEscapes(bytes, fmt, options, writer); + try writer.writeByte('"'); +} + +/// Return a Formatter for a Zig identifier +pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { + return .{ .data = bytes }; +} + +pub fn isValidId(bytes: []const u8) bool { + for (bytes) |c, i| { + switch (c) { + '_', 'a'...'z', 'A'...'Z' => {}, + '0'...'9' => if (i == 0) return false, + else => return false, + } + } + return std.zig.Token.getKeyword(bytes) == null; +} + +pub fn formatEscapes( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + for (bytes) |byte| switch (byte) { + '\n' => try writer.writeAll("\\n"), + '\r' => try writer.writeAll("\\r"), + '\t' => try writer.writeAll("\\t"), + '\\' => try writer.writeAll("\\\\"), + '"' => try writer.writeAll("\\\""), + '\'' => try writer.writeAll("\\'"), + ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), + // Use hex escapes for rest any unprintable characters. + else => { + try writer.writeAll("\\x"); + try std.fmt.formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); + }, + }; +} + +/// Return a Formatter for Zig Escapes +pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(formatEscapes) { + return .{ .data = bytes }; +} + +test "escape invalid identifiers" { + const expectFmt = std.testing.expectFmt; + try expectFmt("@\"while\"", "{}", .{fmtId("while")}); + try expectFmt("hello", "{}", .{fmtId("hello")}); + try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); + try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); + try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); + try expectFmt( + \\" \\ hi \x07 \x11 \" derp \'" + , "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); +} diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 411273b149..7a6404fbb2 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -11,85 +11,181 @@ const Node = ast.Node; const Tree = ast.Tree; const AstError = ast.Error; const TokenIndex = ast.TokenIndex; -const NodeIndex = ast.NodeIndex; const Token = std.zig.Token; pub const Error = error{ParseError} || Allocator.Error; /// Result should be freed with tree.deinit() when there are /// no more references to any of the tokens or nodes. -pub fn parse(gpa: *Allocator, source: []const u8) Allocator.Error!*Tree { - var token_ids = std.ArrayList(Token.Id).init(gpa); - defer token_ids.deinit(); - var token_locs = std.ArrayList(Token.Loc).init(gpa); - defer token_locs.deinit(); +pub fn parse(gpa: *Allocator, source: []const u8) Allocator.Error!Tree { + var tokens = ast.TokenList{}; + defer tokens.deinit(gpa); // Empirically, the zig std lib has an 8:1 ratio of source bytes to token count. const estimated_token_count = source.len / 8; - try token_ids.ensureCapacity(estimated_token_count); - try token_locs.ensureCapacity(estimated_token_count); + try tokens.ensureCapacity(gpa, estimated_token_count); var tokenizer = std.zig.Tokenizer.init(source); while (true) { const token = tokenizer.next(); - try token_ids.append(token.id); - try token_locs.append(token.loc); - if (token.id == .Eof) break; + try tokens.append(gpa, .{ + .tag = token.tag, + .start = @intCast(u32, token.loc.start), + }); + if (token.tag == .eof) break; } var parser: Parser = .{ .source = source, - .arena = std.heap.ArenaAllocator.init(gpa), .gpa = gpa, - .token_ids = token_ids.items, - .token_locs = token_locs.items, + .token_tags = tokens.items(.tag), + .token_starts = tokens.items(.start), .errors = .{}, + .nodes = .{}, + .extra_data = .{}, .tok_i = 0, }; defer parser.errors.deinit(gpa); - errdefer parser.arena.deinit(); - - while (token_ids.items[parser.tok_i] == .LineComment) parser.tok_i += 1; - - const root_node = try parser.parseRoot(); + defer parser.nodes.deinit(gpa); + defer parser.extra_data.deinit(gpa); + + // Empirically, Zig source code has a 2:1 ratio of tokens to AST nodes. + // Make sure at least 1 so we can use appendAssumeCapacity on the root node below. + const estimated_node_count = (tokens.len + 2) / 2; + try parser.nodes.ensureCapacity(gpa, estimated_node_count); + + // Root node must be index 0. + // Root <- skip ContainerMembers eof + parser.nodes.appendAssumeCapacity(.{ + .tag = .root, + .main_token = 0, + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }); + const root_members = try parser.parseContainerMembers(); + const root_decls = try root_members.toSpan(&parser); + if (parser.token_tags[parser.tok_i] != .eof) { + try parser.warnExpected(.eof); + } + parser.nodes.items(.data)[0] = .{ + .lhs = root_decls.start, + .rhs = root_decls.end, + }; - const tree = try parser.arena.allocator.create(Tree); - tree.* = .{ - .gpa = gpa, + // TODO experiment with compacting the MultiArrayList slices here + return Tree{ .source = source, - .token_ids = token_ids.toOwnedSlice(), - .token_locs = token_locs.toOwnedSlice(), + .tokens = tokens.toOwnedSlice(), + .nodes = parser.nodes.toOwnedSlice(), + .extra_data = parser.extra_data.toOwnedSlice(gpa), .errors = parser.errors.toOwnedSlice(gpa), - .root_node = root_node, - .arena = parser.arena.state, }; - return tree; } +const null_node: Node.Index = 0; + /// Represents in-progress parsing, will be converted to an ast.Tree after completion. const Parser = struct { - arena: std.heap.ArenaAllocator, gpa: *Allocator, source: []const u8, - token_ids: []const Token.Id, - token_locs: []const Token.Loc, + token_tags: []const Token.Tag, + token_starts: []const ast.ByteOffset, tok_i: TokenIndex, errors: std.ArrayListUnmanaged(AstError), + nodes: ast.NodeList, + extra_data: std.ArrayListUnmanaged(Node.Index), - /// Root <- skip ContainerMembers eof - fn parseRoot(p: *Parser) Allocator.Error!*Node.Root { - const decls = try parseContainerMembers(p, true); - defer p.gpa.free(decls); + const SmallSpan = union(enum) { + zero_or_one: Node.Index, + multi: []Node.Index, - // parseContainerMembers will try to skip as much - // invalid tokens as it can so this can only be the EOF - const eof_token = p.eatToken(.Eof).?; + fn deinit(self: SmallSpan, gpa: *Allocator) void { + switch (self) { + .zero_or_one => {}, + .multi => |list| gpa.free(list), + } + } + }; - const decls_len = @intCast(NodeIndex, decls.len); - const node = try Node.Root.create(&p.arena.allocator, decls_len, eof_token); - std.mem.copy(*Node, node.decls(), decls); + const Members = struct { + len: usize, + lhs: Node.Index, + rhs: Node.Index, + trailing: bool, - return node; + fn toSpan(self: Members, p: *Parser) !Node.SubRange { + if (self.len <= 2) { + const nodes = [2]Node.Index{ self.lhs, self.rhs }; + return p.listToSpan(nodes[0..self.len]); + } else { + return Node.SubRange{ .start = self.lhs, .end = self.rhs }; + } + } + }; + + fn listToSpan(p: *Parser, list: []const Node.Index) !Node.SubRange { + try p.extra_data.appendSlice(p.gpa, list); + return Node.SubRange{ + .start = @intCast(Node.Index, p.extra_data.items.len - list.len), + .end = @intCast(Node.Index, p.extra_data.items.len), + }; + } + + fn addNode(p: *Parser, elem: ast.NodeList.Elem) Allocator.Error!Node.Index { + const result = @intCast(Node.Index, p.nodes.len); + try p.nodes.append(p.gpa, elem); + return result; + } + + fn addExtra(p: *Parser, extra: anytype) Allocator.Error!Node.Index { + const fields = std.meta.fields(@TypeOf(extra)); + try p.extra_data.ensureCapacity(p.gpa, p.extra_data.items.len + fields.len); + const result = @intCast(u32, p.extra_data.items.len); + inline for (fields) |field| { + comptime assert(field.field_type == Node.Index); + p.extra_data.appendAssumeCapacity(@field(extra, field.name)); + } + return result; + } + + fn warn(p: *Parser, tag: ast.Error.Tag) error{OutOfMemory}!void { + @setCold(true); + try p.warnMsg(.{ .tag = tag, .token = p.tok_i }); + } + + fn warnExpected(p: *Parser, expected_token: Token.Tag) error{OutOfMemory}!void { + @setCold(true); + try p.warnMsg(.{ + .tag = .expected_token, + .token = p.tok_i, + .extra = .{ .expected_tag = expected_token }, + }); + } + fn warnMsg(p: *Parser, msg: ast.Error) error{OutOfMemory}!void { + @setCold(true); + try p.errors.append(p.gpa, msg); + } + + fn fail(p: *Parser, tag: ast.Error.Tag) error{ ParseError, OutOfMemory } { + @setCold(true); + return p.failMsg(.{ .tag = tag, .token = p.tok_i }); + } + + fn failExpected(p: *Parser, expected_token: Token.Tag) error{ ParseError, OutOfMemory } { + @setCold(true); + return p.failMsg(.{ + .tag = .expected_token, + .token = p.tok_i, + .extra = .{ .expected_tag = expected_token }, + }); + } + + fn failMsg(p: *Parser, msg: ast.Error) error{ ParseError, OutOfMemory } { + @setCold(true); + try p.warnMsg(msg); + return error.ParseError; } /// ContainerMembers @@ -99,176 +195,226 @@ const Parser = struct { /// / ContainerField COMMA ContainerMembers /// / ContainerField /// / - fn parseContainerMembers(p: *Parser, top_level: bool) ![]*Node { - var list = std.ArrayList(*Node).init(p.gpa); + /// TopLevelComptime <- KEYWORD_comptime BlockExpr + fn parseContainerMembers(p: *Parser) !Members { + var list = std.ArrayList(Node.Index).init(p.gpa); defer list.deinit(); var field_state: union(enum) { - /// no fields have been seen + /// No fields have been seen. none, - /// currently parsing fields + /// Currently parsing fields. seen, - /// saw fields and then a declaration after them. - /// payload is first token of previous declaration. - end: TokenIndex, - /// ther was a declaration between fields, don't report more errors + /// Saw fields and then a declaration after them. + /// Payload is first token of previous declaration. + end: Node.Index, + /// There was a declaration between fields, don't report more errors. err, } = .none; - while (true) { - if (try p.parseContainerDocComments()) |node| { - try list.append(node); - continue; - } - - const doc_comments = try p.parseDocComment(); + // Skip container doc comments. + while (p.eatToken(.container_doc_comment)) |_| {} - if (p.parseTestDecl() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseError => { - p.findNextContainerMember(); - continue; + var trailing = false; + while (true) { + const doc_comment = try p.eatDocComments(); + + switch (p.token_tags[p.tok_i]) { + .keyword_test => { + const test_decl_node = try p.expectTestDeclRecoverable(); + if (test_decl_node != 0) { + if (field_state == .seen) { + field_state = .{ .end = test_decl_node }; + } + try list.append(test_decl_node); + } + trailing = false; }, - }) |node| { - if (field_state == .seen) { - field_state = .{ .end = node.firstToken() }; - } - node.cast(Node.TestDecl).?.doc_comments = doc_comments; - try list.append(node); - continue; - } - - if (p.parseTopLevelComptime() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseError => { - p.findNextContainerMember(); - continue; + .keyword_comptime => switch (p.token_tags[p.tok_i + 1]) { + .identifier => { + p.tok_i += 1; + const container_field = try p.expectContainerFieldRecoverable(); + if (container_field != 0) { + switch (field_state) { + .none => field_state = .seen, + .err, .seen => {}, + .end => |node| { + try p.warnMsg(.{ + .tag = .decl_between_fields, + .token = p.nodes.items(.main_token)[node], + }); + // Continue parsing; error will be reported later. + field_state = .err; + }, + } + try list.append(container_field); + switch (p.token_tags[p.tok_i]) { + .comma => { + p.tok_i += 1; + trailing = true; + continue; + }, + .r_brace, .eof => { + trailing = false; + break; + }, + else => {}, + } + // There is not allowed to be a decl after a field with no comma. + // Report error but recover parser. + try p.warnExpected(.comma); + p.findNextContainerMember(); + } + }, + .l_brace => { + const comptime_token = p.nextToken(); + const block = p.parseBlock() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => blk: { + p.findNextContainerMember(); + break :blk null_node; + }, + }; + if (block != 0) { + const comptime_node = try p.addNode(.{ + .tag = .@"comptime", + .main_token = comptime_token, + .data = .{ + .lhs = block, + .rhs = undefined, + }, + }); + if (field_state == .seen) { + field_state = .{ .end = comptime_node }; + } + try list.append(comptime_node); + } + trailing = false; + }, + else => { + p.tok_i += 1; + try p.warn(.expected_block_or_field); + }, }, - }) |node| { - if (field_state == .seen) { - field_state = .{ .end = node.firstToken() }; - } - node.cast(Node.Comptime).?.doc_comments = doc_comments; - try list.append(node); - continue; - } - - const visib_token = p.eatToken(.Keyword_pub); - - if (p.parseTopLevelDecl(doc_comments, visib_token) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseError => { - p.findNextContainerMember(); - continue; + .keyword_pub => { + p.tok_i += 1; + const top_level_decl = try p.expectTopLevelDeclRecoverable(); + if (top_level_decl != 0) { + if (field_state == .seen) { + field_state = .{ .end = top_level_decl }; + } + try list.append(top_level_decl); + } + trailing = p.token_tags[p.tok_i - 1] == .semicolon; }, - }) |node| { - if (field_state == .seen) { - field_state = .{ .end = visib_token orelse node.firstToken() }; - } - try list.append(node); - continue; - } - - if (visib_token != null) { - try p.errors.append(p.gpa, .{ - .ExpectedPubItem = .{ .token = p.tok_i }, - }); - // ignore this pub - continue; - } - - if (p.parseContainerField() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseError => { - // attempt to recover - p.findNextContainerMember(); - continue; + .keyword_usingnamespace => { + const node = try p.expectUsingNamespaceRecoverable(); + if (node != 0) { + if (field_state == .seen) { + field_state = .{ .end = node }; + } + try list.append(node); + } + trailing = p.token_tags[p.tok_i - 1] == .semicolon; + }, + .keyword_const, + .keyword_var, + .keyword_threadlocal, + .keyword_export, + .keyword_extern, + .keyword_inline, + .keyword_noinline, + .keyword_fn, + => { + const top_level_decl = try p.expectTopLevelDeclRecoverable(); + if (top_level_decl != 0) { + if (field_state == .seen) { + field_state = .{ .end = top_level_decl }; + } + try list.append(top_level_decl); + } + trailing = p.token_tags[p.tok_i - 1] == .semicolon; }, - }) |node| { - switch (field_state) { - .none => field_state = .seen, - .err, .seen => {}, - .end => |tok| { - try p.errors.append(p.gpa, .{ - .DeclBetweenFields = .{ .token = tok }, + .identifier => { + const container_field = try p.expectContainerFieldRecoverable(); + if (container_field != 0) { + switch (field_state) { + .none => field_state = .seen, + .err, .seen => {}, + .end => |node| { + try p.warnMsg(.{ + .tag = .decl_between_fields, + .token = p.nodes.items(.main_token)[node], + }); + // Continue parsing; error will be reported later. + field_state = .err; + }, + } + try list.append(container_field); + switch (p.token_tags[p.tok_i]) { + .comma => { + p.tok_i += 1; + trailing = true; + continue; + }, + .r_brace, .eof => { + trailing = false; + break; + }, + else => {}, + } + // There is not allowed to be a decl after a field with no comma. + // Report error but recover parser. + try p.warnExpected(.comma); + p.findNextContainerMember(); + } + }, + .eof, .r_brace => { + if (doc_comment) |tok| { + try p.warnMsg(.{ + .tag = .unattached_doc_comment, + .token = tok, }); - // continue parsing, error will be reported later - field_state = .err; - }, - } - - const field = node.cast(Node.ContainerField).?; - field.doc_comments = doc_comments; - try list.append(node); - const comma = p.eatToken(.Comma) orelse { - // try to continue parsing - const index = p.tok_i; - p.findNextContainerMember(); - const next = p.token_ids[p.tok_i]; - switch (next) { - .Eof => { - // no invalid tokens were found - if (index == p.tok_i) break; - - // Invalid tokens, add error and exit - try p.errors.append(p.gpa, .{ - .ExpectedToken = .{ .token = index, .expected_id = .Comma }, - }); - break; - }, - else => { - if (next == .RBrace) { - if (!top_level) break; - _ = p.nextToken(); - } - - // add error and continue - try p.errors.append(p.gpa, .{ - .ExpectedToken = .{ .token = index, .expected_id = .Comma }, - }); - continue; - }, } - }; - if (try p.parseAppendedDocComment(comma)) |appended_comment| - field.doc_comments = appended_comment; - continue; - } - - // Dangling doc comment - if (doc_comments != null) { - try p.errors.append(p.gpa, .{ - .UnattachedDocComment = .{ .token = doc_comments.?.firstToken() }, - }); - } - - const next = p.token_ids[p.tok_i]; - switch (next) { - .Eof => break, - .Keyword_comptime => { - _ = p.nextToken(); - try p.errors.append(p.gpa, .{ - .ExpectedBlockOrField = .{ .token = p.tok_i }, - }); + break; }, else => { - const index = p.tok_i; - if (next == .RBrace) { - if (!top_level) break; - _ = p.nextToken(); - } - - // this was likely not supposed to end yet, - // try to find the next declaration + try p.warn(.expected_container_members); + // This was likely not supposed to end yet; try to find the next declaration. p.findNextContainerMember(); - try p.errors.append(p.gpa, .{ - .ExpectedContainerMembers = .{ .token = index }, - }); }, } } - return list.toOwnedSlice(); + switch (list.items.len) { + 0 => return Members{ + .len = 0, + .lhs = 0, + .rhs = 0, + .trailing = trailing, + }, + 1 => return Members{ + .len = 1, + .lhs = list.items[0], + .rhs = 0, + .trailing = trailing, + }, + 2 => return Members{ + .len = 2, + .lhs = list.items[0], + .rhs = list.items[1], + .trailing = trailing, + }, + else => { + const span = try p.listToSpan(list.items); + return Members{ + .len = list.items.len, + .lhs = span.start, + .rhs = span.end, + .trailing = trailing, + }; + }, + } } /// Attempts to find next container member by searching for certain tokens @@ -276,47 +422,52 @@ const Parser = struct { var level: u32 = 0; while (true) { const tok = p.nextToken(); - switch (p.token_ids[tok]) { - // any of these can start a new top level declaration - .Keyword_test, - .Keyword_comptime, - .Keyword_pub, - .Keyword_export, - .Keyword_extern, - .Keyword_inline, - .Keyword_noinline, - .Keyword_usingnamespace, - .Keyword_threadlocal, - .Keyword_const, - .Keyword_var, - .Keyword_fn, - .Identifier, + switch (p.token_tags[tok]) { + // Any of these can start a new top level declaration. + .keyword_test, + .keyword_comptime, + .keyword_pub, + .keyword_export, + .keyword_extern, + .keyword_inline, + .keyword_noinline, + .keyword_usingnamespace, + .keyword_threadlocal, + .keyword_const, + .keyword_var, + .keyword_fn, => { if (level == 0) { - p.putBackToken(tok); + p.tok_i -= 1; return; } }, - .Comma, .Semicolon => { + .identifier => { + if (p.token_tags[tok + 1] == .comma and level == 0) { + p.tok_i -= 1; + return; + } + }, + .comma, .semicolon => { // this decl was likely meant to end here if (level == 0) { return; } }, - .LParen, .LBracket, .LBrace => level += 1, - .RParen, .RBracket => { + .l_paren, .l_bracket, .l_brace => level += 1, + .r_paren, .r_bracket => { if (level != 0) level -= 1; }, - .RBrace => { + .r_brace => { if (level == 0) { // end of container, exit - p.putBackToken(tok); + p.tok_i -= 1; return; } level -= 1; }, - .Eof => { - p.putBackToken(tok); + .eof => { + p.tok_i -= 1; return; }, else => {}, @@ -329,22 +480,22 @@ const Parser = struct { var level: u32 = 0; while (true) { const tok = p.nextToken(); - switch (p.token_ids[tok]) { - .LBrace => level += 1, - .RBrace => { + switch (p.token_tags[tok]) { + .l_brace => level += 1, + .r_brace => { if (level == 0) { - p.putBackToken(tok); + p.tok_i -= 1; return; } level -= 1; }, - .Semicolon => { + .semicolon => { if (level == 0) { return; } }, - .Eof => { - p.putBackToken(tok); + .eof => { + p.tok_i -= 1; return; }, else => {}, @@ -352,330 +503,337 @@ const Parser = struct { } } - /// Eat a multiline container doc comment - fn parseContainerDocComments(p: *Parser) !?*Node { - if (p.eatToken(.ContainerDocComment)) |first_line| { - while (p.eatToken(.ContainerDocComment)) |_| {} - const node = try p.arena.allocator.create(Node.DocComment); - node.* = .{ .first_line = first_line }; - return &node.base; - } - return null; - } - - /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE Block - fn parseTestDecl(p: *Parser) !?*Node { - const test_token = p.eatToken(.Keyword_test) orelse return null; - const name_node = try p.expectNode(parseStringLiteralSingle, .{ - .ExpectedStringLiteral = .{ .token = p.tok_i }, + /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block + fn expectTestDecl(p: *Parser) !Node.Index { + const test_token = p.assertToken(.keyword_test); + const name_token = p.eatToken(.string_literal); + const block_node = try p.parseBlock(); + if (block_node == 0) return p.fail(.expected_block); + return p.addNode(.{ + .tag = .test_decl, + .main_token = test_token, + .data = .{ + .lhs = name_token orelse 0, + .rhs = block_node, + }, }); - const block_node = (try p.parseBlock(null)) orelse { - try p.errors.append(p.gpa, .{ .ExpectedLBrace = .{ .token = p.tok_i } }); - return error.ParseError; - }; - - const test_node = try p.arena.allocator.create(Node.TestDecl); - test_node.* = .{ - .doc_comments = null, - .test_token = test_token, - .name = name_node, - .body_node = block_node, - }; - return &test_node.base; } - /// TopLevelComptime <- KEYWORD_comptime BlockExpr - fn parseTopLevelComptime(p: *Parser) !?*Node { - const tok = p.eatToken(.Keyword_comptime) orelse return null; - const lbrace = p.eatToken(.LBrace) orelse { - p.putBackToken(tok); - return null; - }; - p.putBackToken(lbrace); - const block_node = try p.expectNode(parseBlockExpr, .{ - .ExpectedLabelOrLBrace = .{ .token = p.tok_i }, - }); - - const comptime_node = try p.arena.allocator.create(Node.Comptime); - comptime_node.* = .{ - .doc_comments = null, - .comptime_token = tok, - .expr = block_node, + fn expectTestDeclRecoverable(p: *Parser) error{OutOfMemory}!Node.Index { + return p.expectTestDecl() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => { + p.findNextContainerMember(); + return null_node; + }, }; - return &comptime_node.base; } /// TopLevelDecl /// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block) /// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl /// / KEYWORD_usingnamespace Expr SEMICOLON - fn parseTopLevelDecl(p: *Parser, doc_comments: ?*Node.DocComment, visib_token: ?TokenIndex) !?*Node { - var lib_name: ?*Node = null; - const extern_export_inline_token = blk: { - if (p.eatToken(.Keyword_export)) |token| break :blk token; - if (p.eatToken(.Keyword_extern)) |token| { - lib_name = try p.parseStringLiteralSingle(); - break :blk token; + fn expectTopLevelDecl(p: *Parser) !Node.Index { + const extern_export_inline_token = p.nextToken(); + var expect_fn: bool = false; + var expect_var_or_fn: bool = false; + switch (p.token_tags[extern_export_inline_token]) { + .keyword_extern => { + _ = p.eatToken(.string_literal); + expect_var_or_fn = true; + }, + .keyword_export => expect_var_or_fn = true, + .keyword_inline, .keyword_noinline => expect_fn = true, + else => p.tok_i -= 1, + } + const fn_proto = try p.parseFnProto(); + if (fn_proto != 0) { + switch (p.token_tags[p.tok_i]) { + .semicolon => { + p.tok_i += 1; + return fn_proto; + }, + .l_brace => { + const body_block = try p.parseBlock(); + assert(body_block != 0); + return p.addNode(.{ + .tag = .fn_decl, + .main_token = p.nodes.items(.main_token)[fn_proto], + .data = .{ + .lhs = fn_proto, + .rhs = body_block, + }, + }); + }, + else => { + // Since parseBlock only return error.ParseError on + // a missing '}' we can assume this function was + // supposed to end here. + try p.warn(.expected_semi_or_lbrace); + return null_node; + }, } - if (p.eatToken(.Keyword_inline)) |token| break :blk token; - if (p.eatToken(.Keyword_noinline)) |token| break :blk token; - break :blk null; - }; - - if (try p.parseFnProto(.top_level, .{ - .doc_comments = doc_comments, - .visib_token = visib_token, - .extern_export_inline_token = extern_export_inline_token, - .lib_name = lib_name, - })) |node| { - return node; } - - if (extern_export_inline_token) |token| { - if (p.token_ids[token] == .Keyword_inline or - p.token_ids[token] == .Keyword_noinline) - { - try p.errors.append(p.gpa, .{ - .ExpectedFn = .{ .token = p.tok_i }, - }); - return error.ParseError; - } + if (expect_fn) { + try p.warn(.expected_fn); + return error.ParseError; } - const thread_local_token = p.eatToken(.Keyword_threadlocal); - - if (try p.parseVarDecl(.{ - .doc_comments = doc_comments, - .visib_token = visib_token, - .thread_local_token = thread_local_token, - .extern_export_token = extern_export_inline_token, - .lib_name = lib_name, - })) |node| { - return node; + const thread_local_token = p.eatToken(.keyword_threadlocal); + const var_decl = try p.parseVarDecl(); + if (var_decl != 0) { + const semicolon_token = try p.expectToken(.semicolon); + return var_decl; } - if (thread_local_token != null) { - try p.errors.append(p.gpa, .{ - .ExpectedVarDecl = .{ .token = p.tok_i }, - }); - // ignore this and try again; - return error.ParseError; + return p.fail(.expected_var_decl); } - - if (extern_export_inline_token) |token| { - try p.errors.append(p.gpa, .{ - .ExpectedVarDeclOrFn = .{ .token = p.tok_i }, - }); - // ignore this and try again; - return error.ParseError; + if (expect_var_or_fn) { + return p.fail(.expected_var_decl_or_fn); + } + if (p.token_tags[p.tok_i] != .keyword_usingnamespace) { + return p.fail(.expected_pub_item); } + return p.expectUsingNamespace(); + } - const use_token = p.eatToken(.Keyword_usingnamespace) orelse return null; - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - const semicolon_token = try p.expectToken(.Semicolon); - - const node = try p.arena.allocator.create(Node.Use); - node.* = .{ - .doc_comments = doc_comments orelse try p.parseAppendedDocComment(semicolon_token), - .visib_token = visib_token, - .use_token = use_token, - .expr = expr, - .semicolon_token = semicolon_token, + fn expectTopLevelDeclRecoverable(p: *Parser) error{OutOfMemory}!Node.Index { + return p.expectTopLevelDecl() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => { + p.findNextContainerMember(); + return null_node; + }, }; + } - return &node.base; + fn expectUsingNamespace(p: *Parser) !Node.Index { + const usingnamespace_token = p.assertToken(.keyword_usingnamespace); + const expr = try p.expectExpr(); + const semicolon_token = try p.expectToken(.semicolon); + return p.addNode(.{ + .tag = .@"usingnamespace", + .main_token = usingnamespace_token, + .data = .{ + .lhs = expr, + .rhs = undefined, + }, + }); } - /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? CallConv? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr) - fn parseFnProto(p: *Parser, level: enum { top_level, as_type }, fields: struct { - doc_comments: ?*Node.DocComment = null, - visib_token: ?TokenIndex = null, - extern_export_inline_token: ?TokenIndex = null, - lib_name: ?*Node = null, - }) !?*Node { - // TODO: Remove once extern/async fn rewriting is - var is_async: ?void = null; - var is_extern_prototype: ?void = null; - const cc_token: ?TokenIndex = blk: { - if (p.eatToken(.Keyword_extern)) |token| { - is_extern_prototype = {}; - break :blk token; - } - if (p.eatToken(.Keyword_async)) |token| { - is_async = {}; - break :blk token; - } - break :blk null; - }; - const fn_token = p.eatToken(.Keyword_fn) orelse { - if (cc_token) |token| - p.putBackToken(token); - return null; + fn expectUsingNamespaceRecoverable(p: *Parser) error{OutOfMemory}!Node.Index { + return p.expectUsingNamespace() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => { + p.findNextContainerMember(); + return null_node; + }, }; - const name_token = p.eatToken(.Identifier); - const lparen = try p.expectToken(.LParen); + } + + /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? CallConv? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr) + fn parseFnProto(p: *Parser) !Node.Index { + const fn_token = p.eatToken(.keyword_fn) orelse return null_node; + _ = p.eatToken(.identifier); const params = try p.parseParamDeclList(); - defer p.gpa.free(params); - const var_args_token = p.eatToken(.Ellipsis3); - const rparen = try p.expectToken(.RParen); + defer params.deinit(p.gpa); const align_expr = try p.parseByteAlign(); const section_expr = try p.parseLinkSection(); const callconv_expr = try p.parseCallconv(); - const exclamation_token = p.eatToken(.Bang); + const bang_token = p.eatToken(.bang); - const return_type_expr = (try p.parseAnyType()) orelse - try p.expectNodeRecoverable(parseTypeExpr, .{ + const return_type_expr = try p.parseTypeExpr(); + if (return_type_expr == 0) { // most likely the user forgot to specify the return type. // Mark return type as invalid and try to continue. - .ExpectedReturnType = .{ .token = p.tok_i }, - }); + try p.warn(.expected_return_type); + } - // TODO https://github.com/ziglang/zig/issues/3750 - const R = Node.FnProto.ReturnType; - const return_type = if (return_type_expr == null) - R{ .Invalid = rparen } - else if (exclamation_token != null) - R{ .InferErrorSet = return_type_expr.? } - else - R{ .Explicit = return_type_expr.? }; - - const body_node: ?*Node = switch (level) { - .top_level => blk: { - if (p.eatToken(.Semicolon)) |_| { - break :blk null; - } - const body_block = (try p.parseBlock(null)) orelse { - // Since parseBlock only return error.ParseError on - // a missing '}' we can assume this function was - // supposed to end here. - try p.errors.append(p.gpa, .{ .ExpectedSemiOrLBrace = .{ .token = p.tok_i } }); - break :blk null; - }; - break :blk body_block; + if (align_expr == 0 and section_expr == 0 and callconv_expr == 0) { + switch (params) { + .zero_or_one => |param| return p.addNode(.{ + .tag = .fn_proto_simple, + .main_token = fn_token, + .data = .{ + .lhs = param, + .rhs = return_type_expr, + }, + }), + .multi => |list| { + const span = try p.listToSpan(list); + return p.addNode(.{ + .tag = .fn_proto_multi, + .main_token = fn_token, + .data = .{ + .lhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + .rhs = return_type_expr, + }, + }); + }, + } + } + switch (params) { + .zero_or_one => |param| return p.addNode(.{ + .tag = .fn_proto_one, + .main_token = fn_token, + .data = .{ + .lhs = try p.addExtra(Node.FnProtoOne{ + .param = param, + .align_expr = align_expr, + .section_expr = section_expr, + .callconv_expr = callconv_expr, + }), + .rhs = return_type_expr, + }, + }), + .multi => |list| { + const span = try p.listToSpan(list); + return p.addNode(.{ + .tag = .fn_proto, + .main_token = fn_token, + .data = .{ + .lhs = try p.addExtra(Node.FnProto{ + .params_start = span.start, + .params_end = span.end, + .align_expr = align_expr, + .section_expr = section_expr, + .callconv_expr = callconv_expr, + }), + .rhs = return_type_expr, + }, + }); }, - .as_type => null, - }; - - const fn_proto_node = try Node.FnProto.create(&p.arena.allocator, .{ - .params_len = params.len, - .fn_token = fn_token, - .return_type = return_type, - }, .{ - .doc_comments = fields.doc_comments, - .visib_token = fields.visib_token, - .name_token = name_token, - .var_args_token = var_args_token, - .extern_export_inline_token = fields.extern_export_inline_token, - .body_node = body_node, - .lib_name = fields.lib_name, - .align_expr = align_expr, - .section_expr = section_expr, - .callconv_expr = callconv_expr, - .is_extern_prototype = is_extern_prototype, - .is_async = is_async, - }); - std.mem.copy(Node.FnProto.ParamDecl, fn_proto_node.params(), params); - - return &fn_proto_node.base; + } } /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON - fn parseVarDecl(p: *Parser, fields: struct { - doc_comments: ?*Node.DocComment = null, - visib_token: ?TokenIndex = null, - thread_local_token: ?TokenIndex = null, - extern_export_token: ?TokenIndex = null, - lib_name: ?*Node = null, - comptime_token: ?TokenIndex = null, - }) !?*Node { - const mut_token = p.eatToken(.Keyword_const) orelse - p.eatToken(.Keyword_var) orelse - return null; + fn parseVarDecl(p: *Parser) !Node.Index { + const mut_token = p.eatToken(.keyword_const) orelse + p.eatToken(.keyword_var) orelse + return null_node; - const name_token = try p.expectToken(.Identifier); - const type_node = if (p.eatToken(.Colon) != null) - try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, - }) - else - null; + _ = try p.expectToken(.identifier); + const type_node: Node.Index = if (p.eatToken(.colon) == null) 0 else try p.expectTypeExpr(); const align_node = try p.parseByteAlign(); const section_node = try p.parseLinkSection(); - const eq_token = p.eatToken(.Equal); - const init_node = if (eq_token != null) blk: { - break :blk try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + const init_node: Node.Index = if (p.eatToken(.equal) == null) 0 else try p.expectExpr(); + if (section_node == 0) { + if (align_node == 0) { + return p.addNode(.{ + .tag = .simple_var_decl, + .main_token = mut_token, + .data = .{ + .lhs = type_node, + .rhs = init_node, + }, + }); + } else if (type_node == 0) { + return p.addNode(.{ + .tag = .aligned_var_decl, + .main_token = mut_token, + .data = .{ + .lhs = align_node, + .rhs = init_node, + }, + }); + } else { + return p.addNode(.{ + .tag = .local_var_decl, + .main_token = mut_token, + .data = .{ + .lhs = try p.addExtra(Node.LocalVarDecl{ + .type_node = type_node, + .align_node = align_node, + }), + .rhs = init_node, + }, + }); + } + } else { + return p.addNode(.{ + .tag = .global_var_decl, + .main_token = mut_token, + .data = .{ + .lhs = try p.addExtra(Node.GlobalVarDecl{ + .type_node = type_node, + .align_node = align_node, + .section_node = section_node, + }), + .rhs = init_node, + }, }); - } else null; - const semicolon_token = try p.expectToken(.Semicolon); - - const doc_comments = fields.doc_comments orelse try p.parseAppendedDocComment(semicolon_token); - - const node = try Node.VarDecl.create(&p.arena.allocator, .{ - .mut_token = mut_token, - .name_token = name_token, - .semicolon_token = semicolon_token, - }, .{ - .doc_comments = doc_comments, - .visib_token = fields.visib_token, - .thread_local_token = fields.thread_local_token, - .eq_token = eq_token, - .comptime_token = fields.comptime_token, - .extern_export_token = fields.extern_export_token, - .lib_name = fields.lib_name, - .type_node = type_node, - .align_node = align_node, - .section_node = section_node, - .init_node = init_node, - }); - return &node.base; + } } /// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)? - fn parseContainerField(p: *Parser) !?*Node { - const comptime_token = p.eatToken(.Keyword_comptime); - const name_token = p.eatToken(.Identifier) orelse { - if (comptime_token) |t| p.putBackToken(t); - return null; - }; - - var align_expr: ?*Node = null; - var type_expr: ?*Node = null; - if (p.eatToken(.Colon)) |_| { - if (p.eatToken(.Keyword_anytype) orelse p.eatToken(.Keyword_var)) |anytype_tok| { - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .AnyType }, - .token = anytype_tok, - }; - type_expr = &node.base; - } else { - type_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, + fn expectContainerField(p: *Parser) !Node.Index { + const comptime_token = p.eatToken(.keyword_comptime); + const name_token = p.assertToken(.identifier); + + var align_expr: Node.Index = 0; + var type_expr: Node.Index = 0; + if (p.eatToken(.colon)) |_| { + if (p.eatToken(.keyword_anytype)) |anytype_tok| { + type_expr = try p.addNode(.{ + .tag = .@"anytype", + .main_token = anytype_tok, + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, }); + } else { + type_expr = try p.expectTypeExpr(); align_expr = try p.parseByteAlign(); } } - const value_expr = if (p.eatToken(.Equal)) |_| - try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }) - else - null; - - const node = try p.arena.allocator.create(Node.ContainerField); - node.* = .{ - .doc_comments = null, - .comptime_token = comptime_token, - .name_token = name_token, - .type_expr = type_expr, - .value_expr = value_expr, - .align_expr = align_expr, + const value_expr: Node.Index = if (p.eatToken(.equal) == null) 0 else try p.expectExpr(); + + if (align_expr == 0) { + return p.addNode(.{ + .tag = .container_field_init, + .main_token = name_token, + .data = .{ + .lhs = type_expr, + .rhs = value_expr, + }, + }); + } else if (value_expr == 0) { + return p.addNode(.{ + .tag = .container_field_align, + .main_token = name_token, + .data = .{ + .lhs = type_expr, + .rhs = align_expr, + }, + }); + } else { + return p.addNode(.{ + .tag = .container_field, + .main_token = name_token, + .data = .{ + .lhs = type_expr, + .rhs = try p.addExtra(Node.ContainerField{ + .value_expr = value_expr, + .align_expr = align_expr, + }), + }, + }); + } + } + + fn expectContainerFieldRecoverable(p: *Parser) error{OutOfMemory}!Node.Index { + return p.expectContainerField() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => { + p.findNextContainerMember(); + return null_node; + }, }; - return &node.base; } /// Statement @@ -689,391 +847,1023 @@ const Parser = struct { /// / LabeledStatement /// / SwitchExpr /// / AssignExpr SEMICOLON - fn parseStatement(p: *Parser) Error!?*Node { - const comptime_token = p.eatToken(.Keyword_comptime); + fn parseStatement(p: *Parser) Error!Node.Index { + const comptime_token = p.eatToken(.keyword_comptime); - if (try p.parseVarDecl(.{ - .comptime_token = comptime_token, - })) |node| { - return node; + const var_decl = try p.parseVarDecl(); + if (var_decl != 0) { + _ = try p.expectTokenRecoverable(.semicolon); + return var_decl; } if (comptime_token) |token| { - const block_expr = try p.expectNode(parseBlockExprStatement, .{ - .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, + return p.addNode(.{ + .tag = .@"comptime", + .main_token = token, + .data = .{ + .lhs = try p.expectBlockExprStatement(), + .rhs = undefined, + }, }); + } - const node = try p.arena.allocator.create(Node.Comptime); - node.* = .{ - .doc_comments = null, - .comptime_token = token, - .expr = block_expr, - }; - return &node.base; + switch (p.token_tags[p.tok_i]) { + .keyword_nosuspend => { + return p.addNode(.{ + .tag = .@"nosuspend", + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.expectBlockExprStatement(), + .rhs = undefined, + }, + }); + }, + .keyword_suspend => { + const token = p.nextToken(); + const block_expr: Node.Index = if (p.eatToken(.semicolon) != null) + 0 + else + try p.expectBlockExprStatement(); + return p.addNode(.{ + .tag = .@"suspend", + .main_token = token, + .data = .{ + .lhs = block_expr, + .rhs = undefined, + }, + }); + }, + .keyword_defer => return p.addNode(.{ + .tag = .@"defer", + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = try p.expectBlockExprStatement(), + }, + }), + .keyword_errdefer => return p.addNode(.{ + .tag = .@"errdefer", + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.parsePayload(), + .rhs = try p.expectBlockExprStatement(), + }, + }), + .keyword_switch => return p.expectSwitchExpr(), + .keyword_if => return p.expectIfStatement(), + else => {}, } - if (p.eatToken(.Keyword_nosuspend)) |nosuspend_token| { - const block_expr = try p.expectNode(parseBlockExprStatement, .{ - .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, - }); + const labeled_statement = try p.parseLabeledStatement(); + if (labeled_statement != 0) return labeled_statement; - const node = try p.arena.allocator.create(Node.Nosuspend); - node.* = .{ - .nosuspend_token = nosuspend_token, - .expr = block_expr, - }; - return &node.base; + const assign_expr = try p.parseAssignExpr(); + if (assign_expr != 0) { + _ = try p.expectTokenRecoverable(.semicolon); + return assign_expr; } - if (p.eatToken(.Keyword_suspend)) |suspend_token| { - const semicolon = p.eatToken(.Semicolon); - - const body_node = if (semicolon == null) blk: { - break :blk try p.expectNode(parseBlockExprStatement, .{ - .ExpectedBlockOrExpression = .{ .token = p.tok_i }, - }); - } else null; + return null_node; + } - const node = try p.arena.allocator.create(Node.Suspend); - node.* = .{ - .suspend_token = suspend_token, - .body = body_node, - }; - return &node.base; + fn expectStatement(p: *Parser) !Node.Index { + const statement = try p.parseStatement(); + if (statement == 0) { + return p.fail(.expected_statement); } + return statement; + } - const defer_token = p.eatToken(.Keyword_defer) orelse p.eatToken(.Keyword_errdefer); - if (defer_token) |token| { - const payload = if (p.token_ids[token] == .Keyword_errdefer) - try p.parsePayload() - else - null; - const expr_node = try p.expectNode(parseBlockExprStatement, .{ - .ExpectedBlockOrExpression = .{ .token = p.tok_i }, - }); - const node = try p.arena.allocator.create(Node.Defer); - node.* = .{ - .defer_token = token, - .expr = expr_node, - .payload = payload, + /// If a parse error occurs, reports an error, but then finds the next statement + /// and returns that one instead. If a parse error occurs but there is no following + /// statement, returns 0. + fn expectStatementRecoverable(p: *Parser) error{OutOfMemory}!Node.Index { + while (true) { + return p.expectStatement() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseError => { + p.findNextStmt(); // Try to skip to the next statement. + if (p.token_tags[p.tok_i] == .r_brace) return null_node; + continue; + }, }; - return &node.base; - } - - if (try p.parseIfStatement()) |node| return node; - if (try p.parseLabeledStatement()) |node| return node; - if (try p.parseSwitchExpr()) |node| return node; - if (try p.parseAssignExpr()) |node| { - _ = try p.expectTokenRecoverable(.Semicolon); - return node; } - - return null; } /// IfStatement /// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )? /// / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement ) - fn parseIfStatement(p: *Parser) !?*Node { - const if_node = (try p.parseIfPrefix()) orelse return null; - const if_prefix = if_node.cast(Node.If).?; - - const block_expr = (try p.parseBlockExpr()); - const assign_expr = if (block_expr == null) - try p.expectNode(parseAssignExpr, .{ - .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, - }) - else - null; - - const semicolon = if (assign_expr != null) p.eatToken(.Semicolon) else null; - - const else_node = if (semicolon == null) blk: { - const else_token = p.eatToken(.Keyword_else) orelse break :blk null; - const payload = try p.parsePayload(); - const else_body = try p.expectNode(parseStatement, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - - const node = try p.arena.allocator.create(Node.Else); - node.* = .{ - .else_token = else_token, - .payload = payload, - .body = else_body, - }; - - break :blk node; - } else null; - - if (block_expr) |body| { - if_prefix.body = body; - if_prefix.@"else" = else_node; - return if_node; - } - - if (assign_expr) |body| { - if_prefix.body = body; - if (semicolon != null) return if_node; - if (else_node != null) { - if_prefix.@"else" = else_node; - return if_node; + fn expectIfStatement(p: *Parser) !Node.Index { + const if_token = p.assertToken(.keyword_if); + _ = try p.expectToken(.l_paren); + const condition = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const then_payload = try p.parsePtrPayload(); + + // TODO propose to change the syntax so that semicolons are always required + // inside if statements, even if there is an `else`. + var else_required = false; + const then_expr = blk: { + const block_expr = try p.parseBlockExpr(); + if (block_expr != 0) break :blk block_expr; + const assign_expr = try p.parseAssignExpr(); + if (assign_expr == 0) { + return p.fail(.expected_block_or_assignment); } - try p.errors.append(p.gpa, .{ - .ExpectedSemiOrElse = .{ .token = p.tok_i }, + if (p.eatToken(.semicolon)) |_| { + return p.addNode(.{ + .tag = .if_simple, + .main_token = if_token, + .data = .{ + .lhs = condition, + .rhs = assign_expr, + }, + }); + } + else_required = true; + break :blk assign_expr; + }; + const else_token = p.eatToken(.keyword_else) orelse { + if (else_required) { + try p.warn(.expected_semi_or_else); + } + return p.addNode(.{ + .tag = .if_simple, + .main_token = if_token, + .data = .{ + .lhs = condition, + .rhs = then_expr, + }, }); - } - - return if_node; + }; + const else_payload = try p.parsePayload(); + const else_expr = try p.expectStatement(); + return p.addNode(.{ + .tag = .@"if", + .main_token = if_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.If{ + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, + }); } /// LabeledStatement <- BlockLabel? (Block / LoopStatement) - fn parseLabeledStatement(p: *Parser) !?*Node { - var colon: TokenIndex = undefined; - const label_token = p.parseBlockLabel(&colon); - - if (try p.parseBlock(label_token)) |node| return node; - - if (try p.parseLoopStatement()) |node| { - if (node.cast(Node.For)) |for_node| { - for_node.label = label_token; - } else if (node.cast(Node.While)) |while_node| { - while_node.label = label_token; - } else unreachable; - return node; - } + fn parseLabeledStatement(p: *Parser) !Node.Index { + const label_token = p.parseBlockLabel(); + const block = try p.parseBlock(); + if (block != 0) return block; - if (label_token != null) { - try p.errors.append(p.gpa, .{ - .ExpectedLabelable = .{ .token = p.tok_i }, - }); - return error.ParseError; + const loop_stmt = try p.parseLoopStatement(); + if (loop_stmt != 0) return loop_stmt; + + if (label_token != 0) { + return p.fail(.expected_labelable); } - return null; + return null_node; } /// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement) - fn parseLoopStatement(p: *Parser) !?*Node { - const inline_token = p.eatToken(.Keyword_inline); + fn parseLoopStatement(p: *Parser) !Node.Index { + const inline_token = p.eatToken(.keyword_inline); - if (try p.parseForStatement()) |node| { - node.cast(Node.For).?.inline_token = inline_token; - return node; - } + const for_statement = try p.parseForStatement(); + if (for_statement != 0) return for_statement; - if (try p.parseWhileStatement()) |node| { - node.cast(Node.While).?.inline_token = inline_token; - return node; - } - if (inline_token == null) return null; + const while_statement = try p.parseWhileStatement(); + if (while_statement != 0) return while_statement; + + if (inline_token == null) return null_node; // If we've seen "inline", there should have been a "for" or "while" - try p.errors.append(p.gpa, .{ - .ExpectedInlinable = .{ .token = p.tok_i }, - }); - return error.ParseError; + return p.fail(.expected_inlinable); } + /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload /// ForStatement /// <- ForPrefix BlockExpr ( KEYWORD_else Statement )? /// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement ) - fn parseForStatement(p: *Parser) !?*Node { - const node = (try p.parseForPrefix()) orelse return null; - const for_prefix = node.cast(Node.For).?; - - if (try p.parseBlockExpr()) |block_expr_node| { - for_prefix.body = block_expr_node; - - if (p.eatToken(.Keyword_else)) |else_token| { - const statement_node = try p.expectNode(parseStatement, .{ - .InvalidToken = .{ .token = p.tok_i }, + fn parseForStatement(p: *Parser) !Node.Index { + const for_token = p.eatToken(.keyword_for) orelse return null_node; + _ = try p.expectToken(.l_paren); + const array_expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const found_payload = try p.parsePtrIndexPayload(); + if (found_payload == 0) try p.warn(.expected_loop_payload); + + // TODO propose to change the syntax so that semicolons are always required + // inside while statements, even if there is an `else`. + var else_required = false; + const then_expr = blk: { + const block_expr = try p.parseBlockExpr(); + if (block_expr != 0) break :blk block_expr; + const assign_expr = try p.parseAssignExpr(); + if (assign_expr == 0) { + return p.fail(.expected_block_or_assignment); + } + if (p.eatToken(.semicolon)) |_| { + return p.addNode(.{ + .tag = .for_simple, + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = assign_expr, + }, }); - - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = null, - .body = statement_node, - }; - for_prefix.@"else" = else_node; - - return node; } + else_required = true; + break :blk assign_expr; + }; + const else_token = p.eatToken(.keyword_else) orelse { + if (else_required) { + try p.warn(.expected_semi_or_else); + } + return p.addNode(.{ + .tag = .for_simple, + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = then_expr, + }, + }); + }; + return p.addNode(.{ + .tag = .@"for", + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = try p.addExtra(Node.If{ + .then_expr = then_expr, + .else_expr = try p.expectStatement(), + }), + }, + }); + } - return node; - } - - for_prefix.body = try p.expectNode(parseAssignExpr, .{ - .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, + /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? + /// WhileStatement + /// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )? + /// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement ) + fn parseWhileStatement(p: *Parser) !Node.Index { + const while_token = p.eatToken(.keyword_while) orelse return null_node; + _ = try p.expectToken(.l_paren); + const condition = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const then_payload = try p.parsePtrPayload(); + const cont_expr = try p.parseWhileContinueExpr(); + + // TODO propose to change the syntax so that semicolons are always required + // inside while statements, even if there is an `else`. + var else_required = false; + const then_expr = blk: { + const block_expr = try p.parseBlockExpr(); + if (block_expr != 0) break :blk block_expr; + const assign_expr = try p.parseAssignExpr(); + if (assign_expr == 0) { + return p.fail(.expected_block_or_assignment); + } + if (p.eatToken(.semicolon)) |_| { + if (cont_expr == 0) { + return p.addNode(.{ + .tag = .while_simple, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = assign_expr, + }, + }); + } else { + return p.addNode(.{ + .tag = .while_cont, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.WhileCont{ + .cont_expr = cont_expr, + .then_expr = assign_expr, + }), + }, + }); + } + } + else_required = true; + break :blk assign_expr; + }; + const else_token = p.eatToken(.keyword_else) orelse { + if (else_required) { + try p.warn(.expected_semi_or_else); + } + if (cont_expr == 0) { + return p.addNode(.{ + .tag = .while_simple, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = then_expr, + }, + }); + } else { + return p.addNode(.{ + .tag = .while_cont, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.WhileCont{ + .cont_expr = cont_expr, + .then_expr = then_expr, + }), + }, + }); + } + }; + const else_payload = try p.parsePayload(); + const else_expr = try p.expectStatement(); + return p.addNode(.{ + .tag = .@"while", + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.While{ + .cont_expr = cont_expr, + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, }); + } - if (p.eatToken(.Semicolon) != null) return node; + /// BlockExprStatement + /// <- BlockExpr + /// / AssignExpr SEMICOLON + fn parseBlockExprStatement(p: *Parser) !Node.Index { + const block_expr = try p.parseBlockExpr(); + if (block_expr != 0) { + return block_expr; + } + const assign_expr = try p.parseAssignExpr(); + if (assign_expr != 0) { + _ = try p.expectTokenRecoverable(.semicolon); + return assign_expr; + } + return null_node; + } - if (p.eatToken(.Keyword_else)) |else_token| { - const statement_node = try p.expectNode(parseStatement, .{ - .ExpectedStatement = .{ .token = p.tok_i }, - }); + fn expectBlockExprStatement(p: *Parser) !Node.Index { + const node = try p.parseBlockExprStatement(); + if (node == 0) { + return p.fail(.expected_block_or_expr); + } + return node; + } - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = null, - .body = statement_node, - }; - for_prefix.@"else" = else_node; - return node; + /// BlockExpr <- BlockLabel? Block + fn parseBlockExpr(p: *Parser) Error!Node.Index { + switch (p.token_tags[p.tok_i]) { + .identifier => { + if (p.token_tags[p.tok_i + 1] == .colon and + p.token_tags[p.tok_i + 2] == .l_brace) + { + p.tok_i += 2; + return p.parseBlock(); + } else { + return null_node; + } + }, + .l_brace => return p.parseBlock(), + else => return null_node, } + } - try p.errors.append(p.gpa, .{ - .ExpectedSemiOrElse = .{ .token = p.tok_i }, + /// AssignExpr <- Expr (AssignOp Expr)? + /// AssignOp + /// <- ASTERISKEQUAL + /// / SLASHEQUAL + /// / PERCENTEQUAL + /// / PLUSEQUAL + /// / MINUSEQUAL + /// / LARROW2EQUAL + /// / RARROW2EQUAL + /// / AMPERSANDEQUAL + /// / CARETEQUAL + /// / PIPEEQUAL + /// / ASTERISKPERCENTEQUAL + /// / PLUSPERCENTEQUAL + /// / MINUSPERCENTEQUAL + /// / EQUAL + fn parseAssignExpr(p: *Parser) !Node.Index { + const expr = try p.parseExpr(); + if (expr == 0) return null_node; + + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .asterisk_equal => .assign_mul, + .slash_equal => .assign_div, + .percent_equal => .assign_mod, + .plus_equal => .assign_add, + .minus_equal => .assign_sub, + .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left, + .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right, + .ampersand_equal => .assign_bit_and, + .caret_equal => .assign_bit_xor, + .pipe_equal => .assign_bit_or, + .asterisk_percent_equal => .assign_mul_wrap, + .plus_percent_equal => .assign_add_wrap, + .minus_percent_equal => .assign_sub_wrap, + .equal => .assign, + else => return expr, + }; + return p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = expr, + .rhs = try p.expectExpr(), + }, }); + } - return node; + fn expectAssignExpr(p: *Parser) !Node.Index { + const expr = try p.parseAssignExpr(); + if (expr == 0) { + return p.fail(.expected_expr_or_assignment); + } + return expr; } - /// WhileStatement - /// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )? - /// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement ) - fn parseWhileStatement(p: *Parser) !?*Node { - const node = (try p.parseWhilePrefix()) orelse return null; - const while_prefix = node.cast(Node.While).?; + /// Expr <- BoolOrExpr + fn parseExpr(p: *Parser) Error!Node.Index { + return p.parseBoolOrExpr(); + } - if (try p.parseBlockExpr()) |block_expr_node| { - while_prefix.body = block_expr_node; + fn expectExpr(p: *Parser) Error!Node.Index { + const node = try p.parseExpr(); + if (node == 0) { + return p.fail(.expected_expr); + } else { + return node; + } + } - if (p.eatToken(.Keyword_else)) |else_token| { - const payload = try p.parsePayload(); + /// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)* + fn parseBoolOrExpr(p: *Parser) Error!Node.Index { + var res = try p.parseBoolAndExpr(); + if (res == 0) return null_node; - const statement_node = try p.expectNode(parseStatement, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); + while (true) { + switch (p.token_tags[p.tok_i]) { + .keyword_or => { + const or_token = p.nextToken(); + const rhs = try p.parseBoolAndExpr(); + if (rhs == 0) { + return p.fail(.invalid_token); + } + res = try p.addNode(.{ + .tag = .bool_or, + .main_token = or_token, + .data = .{ + .lhs = res, + .rhs = rhs, + }, + }); + }, + else => return res, + } + } + } - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = payload, - .body = statement_node, - }; - while_prefix.@"else" = else_node; + /// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)* + fn parseBoolAndExpr(p: *Parser) !Node.Index { + var res = try p.parseCompareExpr(); + if (res == 0) return null_node; - return node; + while (true) { + switch (p.token_tags[p.tok_i]) { + .keyword_and => { + const and_token = p.nextToken(); + const rhs = try p.parseCompareExpr(); + if (rhs == 0) { + return p.fail(.invalid_token); + } + res = try p.addNode(.{ + .tag = .bool_and, + .main_token = and_token, + .data = .{ + .lhs = res, + .rhs = rhs, + }, + }); + }, + .invalid_ampersands => { + try p.warn(.invalid_and); + p.tok_i += 1; + return p.parseCompareExpr(); + }, + else => return res, } - - return node; } + } - while_prefix.body = try p.expectNode(parseAssignExpr, .{ - .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, + /// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)? + /// CompareOp + /// <- EQUALEQUAL + /// / EXCLAMATIONMARKEQUAL + /// / LARROW + /// / RARROW + /// / LARROWEQUAL + /// / RARROWEQUAL + fn parseCompareExpr(p: *Parser) !Node.Index { + const expr = try p.parseBitwiseExpr(); + if (expr == 0) return null_node; + + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .equal_equal => .equal_equal, + .bang_equal => .bang_equal, + .angle_bracket_left => .less_than, + .angle_bracket_right => .greater_than, + .angle_bracket_left_equal => .less_or_equal, + .angle_bracket_right_equal => .greater_or_equal, + else => return expr, + }; + return p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = expr, + .rhs = try p.expectBitwiseExpr(), + }, }); + } - if (p.eatToken(.Semicolon) != null) return node; - - if (p.eatToken(.Keyword_else)) |else_token| { - const payload = try p.parsePayload(); + /// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)* + /// BitwiseOp + /// <- AMPERSAND + /// / CARET + /// / PIPE + /// / KEYWORD_orelse + /// / KEYWORD_catch Payload? + fn parseBitwiseExpr(p: *Parser) !Node.Index { + var res = try p.parseBitShiftExpr(); + if (res == 0) return null_node; - const statement_node = try p.expectNode(parseStatement, .{ - .ExpectedStatement = .{ .token = p.tok_i }, + while (true) { + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .ampersand => .bit_and, + .caret => .bit_xor, + .pipe => .bit_or, + .keyword_orelse => .@"orelse", + .keyword_catch => { + const catch_token = p.nextToken(); + _ = try p.parsePayload(); + const rhs = try p.parseBitShiftExpr(); + if (rhs == 0) { + return p.fail(.invalid_token); + } + res = try p.addNode(.{ + .tag = .@"catch", + .main_token = catch_token, + .data = .{ + .lhs = res, + .rhs = rhs, + }, + }); + continue; + }, + else => return res, + }; + res = try p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = res, + .rhs = try p.expectBitShiftExpr(), + }, }); + } + } - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = payload, - .body = statement_node, - }; - while_prefix.@"else" = else_node; + fn expectBitwiseExpr(p: *Parser) Error!Node.Index { + const node = try p.parseBitwiseExpr(); + if (node == 0) { + return p.fail(.invalid_token); + } else { return node; } + } - try p.errors.append(p.gpa, .{ - .ExpectedSemiOrElse = .{ .token = p.tok_i }, - }); + /// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)* + /// BitShiftOp + /// <- LARROW2 + /// / RARROW2 + fn parseBitShiftExpr(p: *Parser) Error!Node.Index { + var res = try p.parseAdditionExpr(); + if (res == 0) return null_node; - return node; + while (true) { + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .angle_bracket_angle_bracket_left => .bit_shift_left, + .angle_bracket_angle_bracket_right => .bit_shift_right, + else => return res, + }; + res = try p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = res, + .rhs = try p.expectAdditionExpr(), + }, + }); + } } - /// BlockExprStatement - /// <- BlockExpr - /// / AssignExpr SEMICOLON - fn parseBlockExprStatement(p: *Parser) !?*Node { - if (try p.parseBlockExpr()) |node| return node; - if (try p.parseAssignExpr()) |node| { - _ = try p.expectTokenRecoverable(.Semicolon); + fn expectBitShiftExpr(p: *Parser) Error!Node.Index { + const node = try p.parseBitShiftExpr(); + if (node == 0) { + return p.fail(.invalid_token); + } else { return node; } - return null; } - /// BlockExpr <- BlockLabel? Block - fn parseBlockExpr(p: *Parser) Error!?*Node { - var colon: TokenIndex = undefined; - const label_token = p.parseBlockLabel(&colon); - const block_node = (try p.parseBlock(label_token)) orelse { - if (label_token) |label| { - p.putBackToken(label + 1); // ":" - p.putBackToken(label); // IDENTIFIER - } - return null; - }; - return block_node; - } - - /// AssignExpr <- Expr (AssignOp Expr)? - fn parseAssignExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseAssignOp, parseExpr, .Once); - } + /// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)* + /// AdditionOp + /// <- PLUS + /// / MINUS + /// / PLUS2 + /// / PLUSPERCENT + /// / MINUSPERCENT + fn parseAdditionExpr(p: *Parser) Error!Node.Index { + var res = try p.parseMultiplyExpr(); + if (res == 0) return null_node; - /// Expr <- BoolOrExpr - fn parseExpr(p: *Parser) Error!?*Node { - return p.parsePrefixOpExpr(parseTry, parseBoolOrExpr); + while (true) { + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .plus => .add, + .minus => .sub, + .plus_plus => .array_cat, + .plus_percent => .add_wrap, + .minus_percent => .sub_wrap, + else => return res, + }; + res = try p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = res, + .rhs = try p.expectMultiplyExpr(), + }, + }); + } } - /// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)* - fn parseBoolOrExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr( - SimpleBinOpParseFn(.Keyword_or, .BoolOr), - parseBoolAndExpr, - .Infinitely, - ); + fn expectAdditionExpr(p: *Parser) Error!Node.Index { + const node = try p.parseAdditionExpr(); + if (node == 0) { + return p.fail(.invalid_token); + } + return node; } - /// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)* - fn parseBoolAndExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr( - SimpleBinOpParseFn(.Keyword_and, .BoolAnd), - parseCompareExpr, - .Infinitely, - ); - } + /// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)* + /// MultiplyOp + /// <- PIPE2 + /// / ASTERISK + /// / SLASH + /// / PERCENT + /// / ASTERISK2 + /// / ASTERISKPERCENT + fn parseMultiplyExpr(p: *Parser) Error!Node.Index { + var res = try p.parsePrefixExpr(); + if (res == 0) return null_node; - /// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)? - fn parseCompareExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseCompareOp, parseBitwiseExpr, .Once); + while (true) { + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .pipe_pipe => .merge_error_sets, + .asterisk => .mul, + .slash => .div, + .percent => .mod, + .asterisk_asterisk => .array_mult, + .asterisk_percent => .mul_wrap, + else => return res, + }; + res = try p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = res, + .rhs = try p.expectPrefixExpr(), + }, + }); + } } - /// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)* - fn parseBitwiseExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseBitwiseOp, parseBitShiftExpr, .Infinitely); + fn expectMultiplyExpr(p: *Parser) Error!Node.Index { + const node = try p.parseMultiplyExpr(); + if (node == 0) { + return p.fail(.invalid_token); + } + return node; } - /// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)* - fn parseBitShiftExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseBitShiftOp, parseAdditionExpr, .Infinitely); + /// PrefixExpr <- PrefixOp* PrimaryExpr + /// PrefixOp + /// <- EXCLAMATIONMARK + /// / MINUS + /// / TILDE + /// / MINUSPERCENT + /// / AMPERSAND + /// / KEYWORD_try + /// / KEYWORD_await + fn parsePrefixExpr(p: *Parser) Error!Node.Index { + const tag: Node.Tag = switch (p.token_tags[p.tok_i]) { + .bang => .bool_not, + .minus => .negation, + .tilde => .bit_not, + .minus_percent => .negation_wrap, + .ampersand => .address_of, + .keyword_try => .@"try", + .keyword_await => .@"await", + else => return p.parsePrimaryExpr(), + }; + return p.addNode(.{ + .tag = tag, + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.expectPrefixExpr(), + .rhs = undefined, + }, + }); } - /// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)* - fn parseAdditionExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseAdditionOp, parseMultiplyExpr, .Infinitely); + fn expectPrefixExpr(p: *Parser) Error!Node.Index { + const node = try p.parsePrefixExpr(); + if (node == 0) { + return p.fail(.expected_prefix_expr); + } + return node; } - /// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)* - fn parseMultiplyExpr(p: *Parser) !?*Node { - return p.parseBinOpExpr(parseMultiplyOp, parsePrefixExpr, .Infinitely); + /// TypeExpr <- PrefixTypeOp* ErrorUnionExpr + /// PrefixTypeOp + /// <- QUESTIONMARK + /// / KEYWORD_anyframe MINUSRARROW + /// / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* + /// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* + /// PtrTypeStart + /// <- ASTERISK + /// / ASTERISK2 + /// / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET + /// ArrayTypeStart <- LBRACKET Expr? (COLON Expr)? RBRACKET + fn parseTypeExpr(p: *Parser) Error!Node.Index { + switch (p.token_tags[p.tok_i]) { + .question_mark => return p.addNode(.{ + .tag = .optional_type, + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.expectTypeExpr(), + .rhs = undefined, + }, + }), + .keyword_anyframe => switch (p.token_tags[p.tok_i + 1]) { + .arrow => return p.addNode(.{ + .tag = .anyframe_type, + .main_token = p.nextToken(), + .data = .{ + .lhs = p.nextToken(), + .rhs = try p.expectTypeExpr(), + }, + }), + else => return p.parseErrorUnionExpr(), + }, + .asterisk => { + const asterisk = p.nextToken(); + const mods = try p.parsePtrModifiers(); + const elem_type = try p.expectTypeExpr(); + if (mods.bit_range_start == 0) { + return p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); + } else { + return p.addNode(.{ + .tag = .ptr_type_bit_range, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrTypeBitRange{ + .sentinel = 0, + .align_node = mods.align_node, + .bit_range_start = mods.bit_range_start, + .bit_range_end = mods.bit_range_end, + }), + .rhs = elem_type, + }, + }); + } + }, + .asterisk_asterisk => { + const asterisk = p.nextToken(); + const mods = try p.parsePtrModifiers(); + const elem_type = try p.expectTypeExpr(); + const inner: Node.Index = inner: { + if (mods.bit_range_start == 0) { + break :inner try p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); + } else { + break :inner try p.addNode(.{ + .tag = .ptr_type_bit_range, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrTypeBitRange{ + .sentinel = 0, + .align_node = mods.align_node, + .bit_range_start = mods.bit_range_start, + .bit_range_end = mods.bit_range_end, + }), + .rhs = elem_type, + }, + }); + } + }; + return p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = 0, + .rhs = inner, + }, + }); + }, + .l_bracket => switch (p.token_tags[p.tok_i + 1]) { + .asterisk => { + const lbracket = p.nextToken(); + const asterisk = p.nextToken(); + var sentinel: Node.Index = 0; + prefix: { + if (p.eatToken(.identifier)) |ident| { + const token_slice = p.source[p.token_starts[ident]..][0..2]; + if (!std.mem.eql(u8, token_slice, "c]")) { + p.tok_i -= 1; + } else { + break :prefix; + } + } + if (p.eatToken(.colon)) |_| { + sentinel = try p.expectExpr(); + } + } + _ = try p.expectToken(.r_bracket); + const mods = try p.parsePtrModifiers(); + const elem_type = try p.expectTypeExpr(); + if (mods.bit_range_start == 0) { + if (sentinel == 0) { + return p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); + } else if (mods.align_node == 0) { + return p.addNode(.{ + .tag = .ptr_type_sentinel, + .main_token = asterisk, + .data = .{ + .lhs = sentinel, + .rhs = elem_type, + }, + }); + } else { + return p.addNode(.{ + .tag = .ptr_type, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrType{ + .sentinel = sentinel, + .align_node = mods.align_node, + }), + .rhs = elem_type, + }, + }); + } + } else { + return p.addNode(.{ + .tag = .ptr_type_bit_range, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrTypeBitRange{ + .sentinel = sentinel, + .align_node = mods.align_node, + .bit_range_start = mods.bit_range_start, + .bit_range_end = mods.bit_range_end, + }), + .rhs = elem_type, + }, + }); + } + }, + else => { + const lbracket = p.nextToken(); + const len_expr = try p.parseExpr(); + const sentinel: Node.Index = if (p.eatToken(.colon)) |_| + try p.expectExpr() + else + 0; + _ = try p.expectToken(.r_bracket); + const mods = try p.parsePtrModifiers(); + const elem_type = try p.expectTypeExpr(); + if (mods.bit_range_start != 0) { + try p.warnMsg(.{ + .tag = .invalid_bit_range, + .token = p.nodes.items(.main_token)[mods.bit_range_start], + }); + } + if (len_expr == 0) { + if (sentinel == 0) { + return p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = lbracket, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); + } else if (mods.align_node == 0) { + return p.addNode(.{ + .tag = .ptr_type_sentinel, + .main_token = lbracket, + .data = .{ + .lhs = sentinel, + .rhs = elem_type, + }, + }); + } else { + return p.addNode(.{ + .tag = .ptr_type, + .main_token = lbracket, + .data = .{ + .lhs = try p.addExtra(Node.PtrType{ + .sentinel = sentinel, + .align_node = mods.align_node, + }), + .rhs = elem_type, + }, + }); + } + } else { + if (mods.align_node != 0) { + try p.warnMsg(.{ + .tag = .invalid_align, + .token = p.nodes.items(.main_token)[mods.align_node], + }); + } + if (sentinel == 0) { + return p.addNode(.{ + .tag = .array_type, + .main_token = lbracket, + .data = .{ + .lhs = len_expr, + .rhs = elem_type, + }, + }); + } else { + return p.addNode(.{ + .tag = .array_type_sentinel, + .main_token = lbracket, + .data = .{ + .lhs = len_expr, + .rhs = try p.addExtra(.{ + .elem_type = elem_type, + .sentinel = sentinel, + }), + }, + }); + } + } + }, + }, + else => return p.parseErrorUnionExpr(), + } } - /// PrefixExpr <- PrefixOp* PrimaryExpr - fn parsePrefixExpr(p: *Parser) !?*Node { - return p.parsePrefixOpExpr(parsePrefixOp, parsePrimaryExpr); + fn expectTypeExpr(p: *Parser) Error!Node.Index { + const node = try p.parseTypeExpr(); + if (node == 0) { + return p.fail(.expected_type_expr); + } + return node; } /// PrimaryExpr @@ -1088,434 +1878,601 @@ const Parser = struct { /// / BlockLabel? LoopExpr /// / Block /// / CurlySuffixExpr - fn parsePrimaryExpr(p: *Parser) !?*Node { - if (try p.parseAsmExpr()) |node| return node; - if (try p.parseIfExpr()) |node| return node; - - if (p.eatToken(.Keyword_break)) |token| { - const label = try p.parseBreakLabel(); - const expr_node = try p.parseExpr(); - const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ - .tag = .Break, - .ltoken = token, - }, .{ - .label = label, - .rhs = expr_node, - }); - return &node.base; - } - - if (p.eatToken(.Keyword_comptime)) |token| { - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - const node = try p.arena.allocator.create(Node.Comptime); - node.* = .{ - .doc_comments = null, - .comptime_token = token, - .expr = expr_node, - }; - return &node.base; - } - - if (p.eatToken(.Keyword_nosuspend)) |token| { - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - const node = try p.arena.allocator.create(Node.Nosuspend); - node.* = .{ - .nosuspend_token = token, - .expr = expr_node, - }; - return &node.base; - } - - if (p.eatToken(.Keyword_continue)) |token| { - const label = try p.parseBreakLabel(); - const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ - .tag = .Continue, - .ltoken = token, - }, .{ - .label = label, - .rhs = null, - }); - return &node.base; - } - - if (p.eatToken(.Keyword_resume)) |token| { - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - const node = try p.arena.allocator.create(Node.SimplePrefixOp); - node.* = .{ - .base = .{ .tag = .Resume }, - .op_token = token, - .rhs = expr_node, - }; - return &node.base; - } - - if (p.eatToken(.Keyword_return)) |token| { - const expr_node = try p.parseExpr(); - const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ - .tag = .Return, - .ltoken = token, - }, .{ - .rhs = expr_node, - }); - return &node.base; - } - - var colon: TokenIndex = undefined; - const label = p.parseBlockLabel(&colon); - if (try p.parseLoopExpr()) |node| { - if (node.cast(Node.For)) |for_node| { - for_node.label = label; - } else if (node.cast(Node.While)) |while_node| { - while_node.label = label; - } else unreachable; - return node; - } - if (label) |token| { - p.putBackToken(token + 1); // ":" - p.putBackToken(token); // IDENTIFIER + fn parsePrimaryExpr(p: *Parser) !Node.Index { + switch (p.token_tags[p.tok_i]) { + .keyword_asm => return p.expectAsmExpr(), + .keyword_if => return p.parseIfExpr(), + .keyword_break => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"break", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.parseBreakLabel(), + .rhs = try p.parseExpr(), + }, + }); + }, + .keyword_continue => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"continue", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.parseBreakLabel(), + .rhs = undefined, + }, + }); + }, + .keyword_comptime => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"comptime", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.expectExpr(), + .rhs = undefined, + }, + }); + }, + .keyword_nosuspend => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"nosuspend", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.expectExpr(), + .rhs = undefined, + }, + }); + }, + .keyword_resume => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"resume", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.expectExpr(), + .rhs = undefined, + }, + }); + }, + .keyword_return => { + p.tok_i += 1; + return p.addNode(.{ + .tag = .@"return", + .main_token = p.tok_i - 1, + .data = .{ + .lhs = try p.parseExpr(), + .rhs = undefined, + }, + }); + }, + .identifier => { + if (p.token_tags[p.tok_i + 1] == .colon) { + switch (p.token_tags[p.tok_i + 2]) { + .keyword_inline => { + p.tok_i += 3; + switch (p.token_tags[p.tok_i]) { + .keyword_for => return p.parseForExpr(), + .keyword_while => return p.parseWhileExpr(), + else => return p.fail(.expected_inlinable), + } + }, + .keyword_for => { + p.tok_i += 2; + return p.parseForExpr(); + }, + .keyword_while => { + p.tok_i += 2; + return p.parseWhileExpr(); + }, + .l_brace => { + p.tok_i += 2; + return p.parseBlock(); + }, + else => return p.parseCurlySuffixExpr(), + } + } else { + return p.parseCurlySuffixExpr(); + } + }, + .keyword_inline => { + p.tok_i += 2; + switch (p.token_tags[p.tok_i]) { + .keyword_for => return p.parseForExpr(), + .keyword_while => return p.parseWhileExpr(), + else => return p.fail(.expected_inlinable), + } + }, + .keyword_for => return p.parseForExpr(), + .keyword_while => return p.parseWhileExpr(), + .l_brace => return p.parseBlock(), + else => return p.parseCurlySuffixExpr(), } - - if (try p.parseBlock(null)) |node| return node; - if (try p.parseCurlySuffixExpr()) |node| return node; - - return null; } /// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? - fn parseIfExpr(p: *Parser) !?*Node { + fn parseIfExpr(p: *Parser) !Node.Index { return p.parseIf(parseExpr); } /// Block <- LBRACE Statement* RBRACE - fn parseBlock(p: *Parser, label_token: ?TokenIndex) !?*Node { - const lbrace = p.eatToken(.LBrace) orelse return null; - - var statements = std.ArrayList(*Node).init(p.gpa); - defer statements.deinit(); - - while (true) { - const statement = (p.parseStatement() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseError => { - // try to skip to the next statement - p.findNextStmt(); - continue; + fn parseBlock(p: *Parser) !Node.Index { + const lbrace = p.eatToken(.l_brace) orelse return null_node; + + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = .block_two, + .main_token = lbrace, + .data = .{ + .lhs = 0, + .rhs = 0, }, - }) orelse break; - try statements.append(statement); + }); } - const rbrace = try p.expectToken(.RBrace); - - const statements_len = @intCast(NodeIndex, statements.items.len); - - if (label_token) |label| { - const block_node = try Node.LabeledBlock.alloc(&p.arena.allocator, statements_len); - block_node.* = .{ - .label = label, - .lbrace = lbrace, - .statements_len = statements_len, - .rbrace = rbrace, - }; - std.mem.copy(*Node, block_node.statements(), statements.items); - return &block_node.base; - } else { - const block_node = try Node.Block.alloc(&p.arena.allocator, statements_len); - block_node.* = .{ - .lbrace = lbrace, - .statements_len = statements_len, - .rbrace = rbrace, - }; - std.mem.copy(*Node, block_node.statements(), statements.items); - return &block_node.base; + const stmt_one = try p.expectStatementRecoverable(); + if (p.eatToken(.r_brace)) |_| { + const semicolon = p.token_tags[p.tok_i - 2] == .semicolon; + return p.addNode(.{ + .tag = if (semicolon) .block_two_semicolon else .block_two, + .main_token = lbrace, + .data = .{ + .lhs = stmt_one, + .rhs = 0, + }, + }); } - } - - /// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr) - fn parseLoopExpr(p: *Parser) !?*Node { - const inline_token = p.eatToken(.Keyword_inline); - - if (try p.parseForExpr()) |node| { - node.cast(Node.For).?.inline_token = inline_token; - return node; + const stmt_two = try p.expectStatementRecoverable(); + if (p.eatToken(.r_brace)) |_| { + const semicolon = p.token_tags[p.tok_i - 2] == .semicolon; + return p.addNode(.{ + .tag = if (semicolon) .block_two_semicolon else .block_two, + .main_token = lbrace, + .data = .{ + .lhs = stmt_one, + .rhs = stmt_two, + }, + }); } - if (try p.parseWhileExpr()) |node| { - node.cast(Node.While).?.inline_token = inline_token; - return node; - } + var statements = std.ArrayList(Node.Index).init(p.gpa); + defer statements.deinit(); - if (inline_token == null) return null; + try statements.appendSlice(&.{ stmt_one, stmt_two }); - // If we've seen "inline", there should have been a "for" or "while" - try p.errors.append(p.gpa, .{ - .ExpectedInlinable = .{ .token = p.tok_i }, + while (true) { + const statement = try p.expectStatementRecoverable(); + if (statement == 0) break; + try statements.append(statement); + if (p.token_tags[p.tok_i] == .r_brace) break; + } + _ = try p.expectToken(.r_brace); + const semicolon = p.token_tags[p.tok_i - 2] == .semicolon; + const statements_span = try p.listToSpan(statements.items); + return p.addNode(.{ + .tag = if (semicolon) .block_semicolon else .block, + .main_token = lbrace, + .data = .{ + .lhs = statements_span.start, + .rhs = statements_span.end, + }, }); - return error.ParseError; } + /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload /// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)? - fn parseForExpr(p: *Parser) !?*Node { - const node = (try p.parseForPrefix()) orelse return null; - const for_prefix = node.cast(Node.For).?; - - const body_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - for_prefix.body = body_node; - - if (p.eatToken(.Keyword_else)) |else_token| { - const body = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + fn parseForExpr(p: *Parser) !Node.Index { + const for_token = p.eatToken(.keyword_for) orelse return null_node; + _ = try p.expectToken(.l_paren); + const array_expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const found_payload = try p.parsePtrIndexPayload(); + if (found_payload == 0) try p.warn(.expected_loop_payload); + + const then_expr = try p.expectExpr(); + const else_token = p.eatToken(.keyword_else) orelse { + return p.addNode(.{ + .tag = .for_simple, + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = then_expr, + }, }); - - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = null, - .body = body, - }; - - for_prefix.@"else" = else_node; - } - - return node; + }; + const else_expr = try p.expectExpr(); + return p.addNode(.{ + .tag = .@"for", + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = try p.addExtra(Node.If{ + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, + }); } + /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? /// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? - fn parseWhileExpr(p: *Parser) !?*Node { - const node = (try p.parseWhilePrefix()) orelse return null; - const while_prefix = node.cast(Node.While).?; - - const body_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + fn parseWhileExpr(p: *Parser) !Node.Index { + const while_token = p.eatToken(.keyword_while) orelse return null_node; + _ = try p.expectToken(.l_paren); + const condition = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const then_payload = try p.parsePtrPayload(); + const cont_expr = try p.parseWhileContinueExpr(); + + const then_expr = try p.expectExpr(); + const else_token = p.eatToken(.keyword_else) orelse { + if (cont_expr == 0) { + return p.addNode(.{ + .tag = .while_simple, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = then_expr, + }, + }); + } else { + return p.addNode(.{ + .tag = .while_cont, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.WhileCont{ + .cont_expr = cont_expr, + .then_expr = then_expr, + }), + }, + }); + } + }; + const else_payload = try p.parsePayload(); + const else_expr = try p.expectExpr(); + return p.addNode(.{ + .tag = .@"while", + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.While{ + .cont_expr = cont_expr, + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, }); - while_prefix.body = body_node; - - if (p.eatToken(.Keyword_else)) |else_token| { - const payload = try p.parsePayload(); - const body = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = payload, - .body = body, - }; - - while_prefix.@"else" = else_node; - } - - return node; } /// CurlySuffixExpr <- TypeExpr InitList? - fn parseCurlySuffixExpr(p: *Parser) !?*Node { - const lhs = (try p.parseTypeExpr()) orelse return null; - const suffix_op = (try p.parseInitList(lhs)) orelse return lhs; - return suffix_op; - } - /// InitList /// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE /// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE /// / LBRACE RBRACE - fn parseInitList(p: *Parser, lhs: *Node) !?*Node { - const lbrace = p.eatToken(.LBrace) orelse return null; - var init_list = std.ArrayList(*Node).init(p.gpa); - defer init_list.deinit(); + fn parseCurlySuffixExpr(p: *Parser) !Node.Index { + const lhs = try p.parseTypeExpr(); + if (lhs == 0) return null_node; + const lbrace = p.eatToken(.l_brace) orelse return lhs; + + // If there are 0 or 1 items, we can use ArrayInitOne/StructInitOne; + // otherwise we use the full ArrayInit/StructInit. + + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = .struct_init_one, + .main_token = lbrace, + .data = .{ + .lhs = lhs, + .rhs = 0, + }, + }); + } + const field_init = try p.parseFieldInit(); + if (field_init != 0) { + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_one != null) .struct_init_one_comma else .struct_init_one, + .main_token = lbrace, + .data = .{ + .lhs = lhs, + .rhs = field_init, + }, + }); + } + + var init_list = std.ArrayList(Node.Index).init(p.gpa); + defer init_list.deinit(); - if (try p.parseFieldInit()) |field_init| { try init_list.append(field_init); - while (p.eatToken(.Comma)) |_| { - const next = (try p.parseFieldInit()) orelse break; - try init_list.append(next); - } - const node = try Node.StructInitializer.alloc(&p.arena.allocator, init_list.items.len); - node.* = .{ - .lhs = lhs, - .rtoken = try p.expectToken(.RBrace), - .list_len = init_list.items.len, - }; - std.mem.copy(*Node, node.list(), init_list.items); - return &node.base; - } - if (try p.parseExpr()) |expr| { - try init_list.append(expr); - while (p.eatToken(.Comma)) |_| { - const next = (try p.parseExpr()) orelse break; + while (true) { + const next = try p.expectFieldInit(); try init_list.append(next); + + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_brace)) |_| break; + continue; + }, + .r_brace => break, + .colon, .r_paren, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_brace); + }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } } - const node = try Node.ArrayInitializer.alloc(&p.arena.allocator, init_list.items.len); - node.* = .{ - .lhs = lhs, - .rtoken = try p.expectToken(.RBrace), - .list_len = init_list.items.len, - }; - std.mem.copy(*Node, node.list(), init_list.items); - return &node.base; + const span = try p.listToSpan(init_list.items); + return p.addNode(.{ + .tag = if (p.token_tags[p.tok_i - 2] == .comma) .struct_init_comma else .struct_init, + .main_token = lbrace, + .data = .{ + .lhs = lhs, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); } - const node = try p.arena.allocator.create(Node.StructInitializer); - node.* = .{ - .lhs = lhs, - .rtoken = try p.expectToken(.RBrace), - .list_len = 0, - }; - return &node.base; - } + const elem_init = try p.expectExpr(); + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_one != null) .array_init_one_comma else .array_init_one, + .main_token = lbrace, + .data = .{ + .lhs = lhs, + .rhs = elem_init, + }, + }); + } + if (comma_one == null) { + try p.warnExpected(.comma); + } - /// InitList - /// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE - /// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE - /// / LBRACE RBRACE - fn parseAnonInitList(p: *Parser, dot: TokenIndex) !?*Node { - const lbrace = p.eatToken(.LBrace) orelse return null; - var init_list = std.ArrayList(*Node).init(p.gpa); + var init_list = std.ArrayList(Node.Index).init(p.gpa); defer init_list.deinit(); - if (try p.parseFieldInit()) |field_init| { - try init_list.append(field_init); - while (p.eatToken(.Comma)) |_| { - const next = (try p.parseFieldInit()) orelse break; - try init_list.append(next); - } - const node = try Node.StructInitializerDot.alloc(&p.arena.allocator, init_list.items.len); - node.* = .{ - .dot = dot, - .rtoken = try p.expectToken(.RBrace), - .list_len = init_list.items.len, - }; - std.mem.copy(*Node, node.list(), init_list.items); - return &node.base; - } + try init_list.append(elem_init); - if (try p.parseExpr()) |expr| { - try init_list.append(expr); - while (p.eatToken(.Comma)) |_| { - const next = (try p.parseExpr()) orelse break; - try init_list.append(next); + var trailing_comma = true; + var next = try p.parseExpr(); + while (next != 0) : (next = try p.parseExpr()) { + try init_list.append(next); + if (p.eatToken(.comma) == null) { + trailing_comma = false; + break; } - const node = try Node.ArrayInitializerDot.alloc(&p.arena.allocator, init_list.items.len); - node.* = .{ - .dot = dot, - .rtoken = try p.expectToken(.RBrace), - .list_len = init_list.items.len, - }; - std.mem.copy(*Node, node.list(), init_list.items); - return &node.base; } - - const node = try p.arena.allocator.create(Node.StructInitializerDot); - node.* = .{ - .dot = dot, - .rtoken = try p.expectToken(.RBrace), - .list_len = 0, - }; - return &node.base; - } - - /// TypeExpr <- PrefixTypeOp* ErrorUnionExpr - fn parseTypeExpr(p: *Parser) Error!?*Node { - return p.parsePrefixOpExpr(parsePrefixTypeOp, parseErrorUnionExpr); + _ = try p.expectToken(.r_brace); + const span = try p.listToSpan(init_list.items); + return p.addNode(.{ + .tag = if (trailing_comma) .array_init_comma else .array_init, + .main_token = lbrace, + .data = .{ + .lhs = lhs, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); } /// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)? - fn parseErrorUnionExpr(p: *Parser) !?*Node { - const suffix_expr = (try p.parseSuffixExpr()) orelse return null; - - if (try SimpleBinOpParseFn(.Bang, .ErrorUnion)(p)) |node| { - const error_union = node.castTag(.ErrorUnion).?; - const type_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, - }); - error_union.lhs = suffix_expr; - error_union.rhs = type_expr; - return node; - } - - return suffix_expr; + fn parseErrorUnionExpr(p: *Parser) !Node.Index { + const suffix_expr = try p.parseSuffixExpr(); + if (suffix_expr == 0) return null_node; + const bang = p.eatToken(.bang) orelse return suffix_expr; + return p.addNode(.{ + .tag = .error_union, + .main_token = bang, + .data = .{ + .lhs = suffix_expr, + .rhs = try p.expectTypeExpr(), + }, + }); } /// SuffixExpr /// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments /// / PrimaryTypeExpr (SuffixOp / FnCallArguments)* - fn parseSuffixExpr(p: *Parser) !?*Node { - const maybe_async = p.eatToken(.Keyword_async); - if (maybe_async) |async_token| { - const token_fn = p.eatToken(.Keyword_fn); - if (token_fn != null) { - // TODO: remove this hack when async fn rewriting is - // HACK: If we see the keyword `fn`, then we assume that - // we are parsing an async fn proto, and not a call. - // We therefore put back all tokens consumed by the async - // prefix... - p.putBackToken(token_fn.?); - p.putBackToken(async_token); - return p.parsePrimaryTypeExpr(); - } - var res = try p.expectNode(parsePrimaryTypeExpr, .{ - .ExpectedPrimaryTypeExpr = .{ .token = p.tok_i }, - }); + /// FnCallArguments <- LPAREN ExprList RPAREN + /// ExprList <- (Expr COMMA)* Expr? + fn parseSuffixExpr(p: *Parser) !Node.Index { + if (p.eatToken(.keyword_async)) |async_token| { + var res = try p.expectPrimaryTypeExpr(); - while (try p.parseSuffixOp(res)) |node| { + while (true) { + const node = try p.parseSuffixOp(res); + if (node == 0) break; res = node; } - - const params = (try p.parseFnCallArguments()) orelse { - try p.errors.append(p.gpa, .{ - .ExpectedParamList = .{ .token = p.tok_i }, - }); - // ignore this, continue parsing + const lparen = p.nextToken(); + if (p.token_tags[lparen] != .l_paren) { + p.tok_i -= 1; + try p.warn(.expected_param_list); return res; - }; - defer p.gpa.free(params.list); - const node = try Node.Call.alloc(&p.arena.allocator, params.list.len); - node.* = .{ - .lhs = res, - .params_len = params.list.len, - .async_token = async_token, - .rtoken = params.rparen, - }; - std.mem.copy(*Node, node.params(), params.list); - return &node.base; - } - if (try p.parsePrimaryTypeExpr()) |expr| { - var res = expr; + } + if (p.eatToken(.r_paren)) |_| { + return p.addNode(.{ + .tag = .async_call_one, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = 0, + }, + }); + } + const param_one = try p.expectExpr(); + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_paren)) |_| { + return p.addNode(.{ + .tag = if (comma_one == null) .async_call_one else .async_call_one_comma, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = param_one, + }, + }); + } + if (comma_one == null) { + try p.warnExpected(.comma); + } + + var param_list = std.ArrayList(Node.Index).init(p.gpa); + defer param_list.deinit(); + + try param_list.append(param_one); while (true) { - if (try p.parseSuffixOp(res)) |node| { - res = node; - continue; - } - if (try p.parseFnCallArguments()) |params| { - defer p.gpa.free(params.list); - const call = try Node.Call.alloc(&p.arena.allocator, params.list.len); - call.* = .{ - .lhs = res, - .params_len = params.list.len, - .async_token = null, - .rtoken = params.rparen, - }; - std.mem.copy(*Node, call.params(), params.list); - res = &call.base; - continue; + const next = try p.expectExpr(); + try param_list.append(next); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + const span = try p.listToSpan(param_list.items); + return p.addNode(.{ + .tag = .async_call_comma, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); + } else { + continue; + } + }, + .r_paren => { + const span = try p.listToSpan(param_list.items); + return p.addNode(.{ + .tag = .async_call, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); + }, + .colon, .r_brace, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_paren); + }, + else => { + p.tok_i -= 1; + try p.warnExpected(.comma); + }, } - break; } - return res; } + var res = try p.parsePrimaryTypeExpr(); + if (res == 0) return res; - return null; + while (true) { + const suffix_op = try p.parseSuffixOp(res); + if (suffix_op != 0) { + res = suffix_op; + continue; + } + res = res: { + const lparen = p.eatToken(.l_paren) orelse return res; + if (p.eatToken(.r_paren)) |_| { + break :res try p.addNode(.{ + .tag = .call_one, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = 0, + }, + }); + } + const param_one = try p.expectExpr(); + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_paren)) |_| { + break :res try p.addNode(.{ + .tag = if (comma_one == null) .call_one else .call_one_comma, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = param_one, + }, + }); + } + if (comma_one == null) { + try p.warnExpected(.comma); + } + + var param_list = std.ArrayList(Node.Index).init(p.gpa); + defer param_list.deinit(); + + try param_list.append(param_one); + + while (true) { + const next = try p.expectExpr(); + try param_list.append(next); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + const span = try p.listToSpan(param_list.items); + break :res try p.addNode(.{ + .tag = .call_comma, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); + } else { + continue; + } + }, + .r_paren => { + const span = try p.listToSpan(param_list.items); + break :res try p.addNode(.{ + .tag = .call, + .main_token = lparen, + .data = .{ + .lhs = res, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); + }, + .colon, .r_brace, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_paren); + }, + else => { + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } + }; + } } /// PrimaryTypeExpr @@ -1523,6 +2480,7 @@ const Parser = struct { /// / CHAR_LITERAL /// / ContainerDecl /// / DOT IDENTIFIER + /// / DOT InitList /// / ErrorSetDecl /// / FLOAT /// / FnProto @@ -1541,260 +2499,546 @@ const Parser = struct { /// / KEYWORD_unreachable /// / STRINGLITERAL /// / SwitchExpr - fn parsePrimaryTypeExpr(p: *Parser) !?*Node { - if (try p.parseBuiltinCall()) |node| return node; - if (p.eatToken(.CharLiteral)) |token| { - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .CharLiteral }, - .token = token, - }; - return &node.base; - } - if (try p.parseContainerDecl()) |node| return node; - if (try p.parseAnonLiteral()) |node| return node; - if (try p.parseErrorSetDecl()) |node| return node; - if (try p.parseFloatLiteral()) |node| return node; - if (try p.parseFnProto(.as_type, .{})) |node| return node; - if (try p.parseGroupedExpr()) |node| return node; - if (try p.parseLabeledTypeExpr()) |node| return node; - if (try p.parseIdentifier()) |node| return node; - if (try p.parseIfTypeExpr()) |node| return node; - if (try p.parseIntegerLiteral()) |node| return node; - if (p.eatToken(.Keyword_comptime)) |token| { - const expr = (try p.parseTypeExpr()) orelse return null; - const node = try p.arena.allocator.create(Node.Comptime); - node.* = .{ - .doc_comments = null, - .comptime_token = token, - .expr = expr, - }; - return &node.base; - } - if (p.eatToken(.Keyword_error)) |token| { - const period = try p.expectTokenRecoverable(.Period); - const identifier = try p.expectNodeRecoverable(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - const global_error_set = try p.createLiteral(.ErrorType, token); - if (period == null or identifier == null) return global_error_set; - - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = Node{ .tag = .Period }, - .op_token = period.?, - .lhs = global_error_set, - .rhs = identifier.?, - }; - return &node.base; - } - if (p.eatToken(.Keyword_false)) |token| return p.createLiteral(.BoolLiteral, token); - if (p.eatToken(.Keyword_null)) |token| return p.createLiteral(.NullLiteral, token); - if (p.eatToken(.Keyword_anyframe)) |token| { - const node = try p.arena.allocator.create(Node.AnyFrameType); - node.* = .{ - .anyframe_token = token, - .result = null, - }; - return &node.base; - } - if (p.eatToken(.Keyword_true)) |token| return p.createLiteral(.BoolLiteral, token); - if (p.eatToken(.Keyword_undefined)) |token| return p.createLiteral(.UndefinedLiteral, token); - if (p.eatToken(.Keyword_unreachable)) |token| return p.createLiteral(.Unreachable, token); - if (try p.parseStringLiteral()) |node| return node; - if (try p.parseSwitchExpr()) |node| return node; - - return null; - } - /// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto - fn parseContainerDecl(p: *Parser) !?*Node { - const layout_token = p.eatToken(.Keyword_extern) orelse - p.eatToken(.Keyword_packed); - - const node = (try p.parseContainerDeclAuto()) orelse { - if (layout_token) |token| - p.putBackToken(token); - return null; - }; - node.cast(Node.ContainerDecl).?.*.layout_token = layout_token; - return node; - } - + /// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE + /// InitList + /// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE + /// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE + /// / LBRACE RBRACE /// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE - fn parseErrorSetDecl(p: *Parser) !?*Node { - const error_token = p.eatToken(.Keyword_error) orelse return null; - if (p.eatToken(.LBrace) == null) { - // Might parse as `KEYWORD_error DOT IDENTIFIER` later in PrimaryTypeExpr, so don't error - p.putBackToken(error_token); - return null; - } - const decls = try p.parseErrorTagList(); - defer p.gpa.free(decls); - const rbrace = try p.expectToken(.RBrace); - - const node = try Node.ErrorSetDecl.alloc(&p.arena.allocator, decls.len); - node.* = .{ - .error_token = error_token, - .decls_len = decls.len, - .rbrace_token = rbrace, - }; - std.mem.copy(*Node, node.decls(), decls); - return &node.base; - } - /// GroupedExpr <- LPAREN Expr RPAREN - fn parseGroupedExpr(p: *Parser) !?*Node { - const lparen = p.eatToken(.LParen) orelse return null; - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - const rparen = try p.expectToken(.RParen); - - const node = try p.arena.allocator.create(Node.GroupedExpression); - node.* = .{ - .lparen = lparen, - .expr = expr, - .rparen = rparen, - }; - return &node.base; - } - /// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? - fn parseIfTypeExpr(p: *Parser) !?*Node { - return p.parseIf(parseTypeExpr); - } - /// LabeledTypeExpr /// <- BlockLabel Block /// / BlockLabel? LoopTypeExpr - fn parseLabeledTypeExpr(p: *Parser) !?*Node { - var colon: TokenIndex = undefined; - const label = p.parseBlockLabel(&colon); + /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) + fn parsePrimaryTypeExpr(p: *Parser) !Node.Index { + switch (p.token_tags[p.tok_i]) { + .char_literal => return p.addNode(.{ + .tag = .char_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .integer_literal => return p.addNode(.{ + .tag = .integer_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .float_literal => return p.addNode(.{ + .tag = .float_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_false => return p.addNode(.{ + .tag = .false_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_true => return p.addNode(.{ + .tag = .true_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_null => return p.addNode(.{ + .tag = .null_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_undefined => return p.addNode(.{ + .tag = .undefined_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_unreachable => return p.addNode(.{ + .tag = .unreachable_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .keyword_anyframe => return p.addNode(.{ + .tag = .anyframe_literal, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + .string_literal => { + const main_token = p.nextToken(); + return p.addNode(.{ + .tag = .string_literal, + .main_token = main_token, + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }); + }, - if (label) |label_token| { - if (try p.parseBlock(label_token)) |node| return node; - } + .builtin => return p.parseBuiltinCall(), + .keyword_fn => return p.parseFnProto(), + .keyword_if => return p.parseIf(parseTypeExpr), + .keyword_switch => return p.expectSwitchExpr(), - if (try p.parseLoopTypeExpr()) |node| { - switch (node.tag) { - .For => node.cast(Node.For).?.label = label, - .While => node.cast(Node.While).?.label = label, - else => unreachable, - } - return node; - } + .keyword_extern, + .keyword_packed, + => { + p.tok_i += 1; + return p.parseContainerDeclAuto(); + }, - if (label) |token| { - p.putBackToken(colon); - p.putBackToken(token); - } - return null; - } + .keyword_struct, + .keyword_opaque, + .keyword_enum, + .keyword_union, + => return p.parseContainerDeclAuto(), + + .keyword_comptime => return p.addNode(.{ + .tag = .@"comptime", + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.expectTypeExpr(), + .rhs = undefined, + }, + }), + .multiline_string_literal_line => { + const first_line = p.nextToken(); + while (p.token_tags[p.tok_i] == .multiline_string_literal_line) { + p.tok_i += 1; + } + return p.addNode(.{ + .tag = .multiline_string_literal, + .main_token = first_line, + .data = .{ + .lhs = first_line, + .rhs = p.tok_i - 1, + }, + }); + }, + .identifier => switch (p.token_tags[p.tok_i + 1]) { + .colon => switch (p.token_tags[p.tok_i + 2]) { + .keyword_inline => { + p.tok_i += 3; + switch (p.token_tags[p.tok_i]) { + .keyword_for => return p.parseForTypeExpr(), + .keyword_while => return p.parseWhileTypeExpr(), + else => return p.fail(.expected_inlinable), + } + }, + .keyword_for => { + p.tok_i += 2; + return p.parseForTypeExpr(); + }, + .keyword_while => { + p.tok_i += 2; + return p.parseWhileTypeExpr(); + }, + .l_brace => { + p.tok_i += 2; + return p.parseBlock(); + }, + else => return p.addNode(.{ + .tag = .identifier, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + }, + else => return p.addNode(.{ + .tag = .identifier, + .main_token = p.nextToken(), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }), + }, + .keyword_inline => { + p.tok_i += 1; + switch (p.token_tags[p.tok_i]) { + .keyword_for => return p.parseForTypeExpr(), + .keyword_while => return p.parseWhileTypeExpr(), + else => return p.fail(.expected_inlinable), + } + }, + .keyword_for => return p.parseForTypeExpr(), + .keyword_while => return p.parseWhileTypeExpr(), + .period => switch (p.token_tags[p.tok_i + 1]) { + .identifier => return p.addNode(.{ + .tag = .enum_literal, + .data = .{ + .lhs = p.nextToken(), // dot + .rhs = undefined, + }, + .main_token = p.nextToken(), // identifier + }), + .l_brace => { + const lbrace = p.tok_i + 1; + p.tok_i = lbrace + 1; + + // If there are 0, 1, or 2 items, we can use ArrayInitDotTwo/StructInitDotTwo; + // otherwise we use the full ArrayInitDot/StructInitDot. + + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = .struct_init_dot_two, + .main_token = lbrace, + .data = .{ + .lhs = 0, + .rhs = 0, + }, + }); + } + const field_init_one = try p.parseFieldInit(); + if (field_init_one != 0) { + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_one != null) .struct_init_dot_two_comma else .struct_init_dot_two, + .main_token = lbrace, + .data = .{ + .lhs = field_init_one, + .rhs = 0, + }, + }); + } + if (comma_one == null) { + try p.warnExpected(.comma); + } + const field_init_two = try p.expectFieldInit(); + const comma_two = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_two != null) .struct_init_dot_two_comma else .struct_init_dot_two, + .main_token = lbrace, + .data = .{ + .lhs = field_init_one, + .rhs = field_init_two, + }, + }); + } + if (comma_two == null) { + try p.warnExpected(.comma); + } + var init_list = std.ArrayList(Node.Index).init(p.gpa); + defer init_list.deinit(); + + try init_list.appendSlice(&.{ field_init_one, field_init_two }); + + while (true) { + const next = try p.expectFieldInit(); + assert(next != 0); + try init_list.append(next); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_brace)) |_| break; + continue; + }, + .r_brace => break, + .colon, .r_paren, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_brace); + }, + else => { + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } + const span = try p.listToSpan(init_list.items); + const trailing_comma = p.token_tags[p.tok_i - 2] == .comma; + return p.addNode(.{ + .tag = if (trailing_comma) .struct_init_dot_comma else .struct_init_dot, + .main_token = lbrace, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + } - /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) - fn parseLoopTypeExpr(p: *Parser) !?*Node { - const inline_token = p.eatToken(.Keyword_inline); + const elem_init_one = try p.expectExpr(); + const comma_one = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_one != null) .array_init_dot_two_comma else .array_init_dot_two, + .main_token = lbrace, + .data = .{ + .lhs = elem_init_one, + .rhs = 0, + }, + }); + } + if (comma_one == null) { + try p.warnExpected(.comma); + } + const elem_init_two = try p.expectExpr(); + const comma_two = p.eatToken(.comma); + if (p.eatToken(.r_brace)) |_| { + return p.addNode(.{ + .tag = if (comma_two != null) .array_init_dot_two_comma else .array_init_dot_two, + .main_token = lbrace, + .data = .{ + .lhs = elem_init_one, + .rhs = elem_init_two, + }, + }); + } + if (comma_two == null) { + try p.warnExpected(.comma); + } + var init_list = std.ArrayList(Node.Index).init(p.gpa); + defer init_list.deinit(); + + try init_list.appendSlice(&.{ elem_init_one, elem_init_two }); + + while (true) { + const next = try p.expectExpr(); + if (next == 0) break; + try init_list.append(next); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_brace)) |_| break; + continue; + }, + .r_brace => break, + .colon, .r_paren, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_brace); + }, + else => { + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } + const span = try p.listToSpan(init_list.items); + return p.addNode(.{ + .tag = if (p.token_tags[p.tok_i - 2] == .comma) .array_init_dot_comma else .array_init_dot, + .main_token = lbrace, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + }, + else => return null_node, + }, + .keyword_error => switch (p.token_tags[p.tok_i + 1]) { + .l_brace => { + const error_token = p.tok_i; + p.tok_i += 2; + + if (p.eatToken(.r_brace)) |rbrace| { + return p.addNode(.{ + .tag = .error_set_decl, + .main_token = error_token, + .data = .{ + .lhs = undefined, + .rhs = rbrace, + }, + }); + } - if (try p.parseForTypeExpr()) |node| { - node.cast(Node.For).?.inline_token = inline_token; - return node; + while (true) { + const doc_comment = try p.eatDocComments(); + const identifier = try p.expectToken(.identifier); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_brace)) |_| break; + continue; + }, + .r_brace => break, + .colon, .r_paren, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_brace); + }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } + return p.addNode(.{ + .tag = .error_set_decl, + .main_token = error_token, + .data = .{ + .lhs = undefined, + .rhs = p.tok_i - 1, // rbrace + }, + }); + }, + else => { + const main_token = p.nextToken(); + const period = p.eatToken(.period); + if (period == null) try p.warnExpected(.period); + const identifier = p.eatToken(.identifier); + if (identifier == null) try p.warnExpected(.identifier); + return p.addNode(.{ + .tag = .error_value, + .main_token = main_token, + .data = .{ + .lhs = period orelse 0, + .rhs = identifier orelse 0, + }, + }); + }, + }, + .l_paren => return p.addNode(.{ + .tag = .grouped_expression, + .main_token = p.nextToken(), + .data = .{ + .lhs = try p.expectExpr(), + .rhs = try p.expectToken(.r_paren), + }, + }), + else => return null_node, } + } - if (try p.parseWhileTypeExpr()) |node| { - node.cast(Node.While).?.inline_token = inline_token; - return node; + fn expectPrimaryTypeExpr(p: *Parser) !Node.Index { + const node = try p.parsePrimaryTypeExpr(); + if (node == 0) { + return p.fail(.expected_primary_type_expr); } - - if (inline_token == null) return null; - - // If we've seen "inline", there should have been a "for" or "while" - try p.errors.append(p.gpa, .{ - .ExpectedInlinable = .{ .token = p.tok_i }, - }); - return error.ParseError; + return node; } + /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload /// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)? - fn parseForTypeExpr(p: *Parser) !?*Node { - const node = (try p.parseForPrefix()) orelse return null; - const for_prefix = node.cast(Node.For).?; - - const type_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, - }); - for_prefix.body = type_expr; - - if (p.eatToken(.Keyword_else)) |else_token| { - const else_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, + fn parseForTypeExpr(p: *Parser) !Node.Index { + const for_token = p.eatToken(.keyword_for) orelse return null_node; + _ = try p.expectToken(.l_paren); + const array_expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const found_payload = try p.parsePtrIndexPayload(); + if (found_payload == 0) try p.warn(.expected_loop_payload); + + const then_expr = try p.expectExpr(); + const else_token = p.eatToken(.keyword_else) orelse { + return p.addNode(.{ + .tag = .for_simple, + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = then_expr, + }, }); - - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = null, - .body = else_expr, - }; - - for_prefix.@"else" = else_node; - } - - return node; + }; + const else_expr = try p.expectTypeExpr(); + return p.addNode(.{ + .tag = .@"for", + .main_token = for_token, + .data = .{ + .lhs = array_expr, + .rhs = try p.addExtra(Node.If{ + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, + }); } + /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? /// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? - fn parseWhileTypeExpr(p: *Parser) !?*Node { - const node = (try p.parseWhilePrefix()) orelse return null; - const while_prefix = node.cast(Node.While).?; - - const type_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, + fn parseWhileTypeExpr(p: *Parser) !Node.Index { + const while_token = p.eatToken(.keyword_while) orelse return null_node; + _ = try p.expectToken(.l_paren); + const condition = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const then_payload = try p.parsePtrPayload(); + const cont_expr = try p.parseWhileContinueExpr(); + + const then_expr = try p.expectTypeExpr(); + const else_token = p.eatToken(.keyword_else) orelse { + if (cont_expr == 0) { + return p.addNode(.{ + .tag = .while_simple, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = then_expr, + }, + }); + } else { + return p.addNode(.{ + .tag = .while_cont, + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.WhileCont{ + .cont_expr = cont_expr, + .then_expr = then_expr, + }), + }, + }); + } + }; + const else_payload = try p.parsePayload(); + const else_expr = try p.expectTypeExpr(); + return p.addNode(.{ + .tag = .@"while", + .main_token = while_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.While{ + .cont_expr = cont_expr, + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, }); - while_prefix.body = type_expr; - - if (p.eatToken(.Keyword_else)) |else_token| { - const payload = try p.parsePayload(); - - const else_expr = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, - }); - - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = null, - .body = else_expr, - }; - - while_prefix.@"else" = else_node; - } - - return node; } /// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE - fn parseSwitchExpr(p: *Parser) !?*Node { - const switch_token = p.eatToken(.Keyword_switch) orelse return null; - _ = try p.expectToken(.LParen); - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - _ = try p.expectToken(.LBrace); + fn expectSwitchExpr(p: *Parser) !Node.Index { + const switch_token = p.assertToken(.keyword_switch); + _ = try p.expectToken(.l_paren); + const expr_node = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + _ = try p.expectToken(.l_brace); const cases = try p.parseSwitchProngList(); - defer p.gpa.free(cases); - const rbrace = try p.expectToken(.RBrace); - - const node = try Node.Switch.alloc(&p.arena.allocator, cases.len); - node.* = .{ - .switch_token = switch_token, - .expr = expr_node, - .cases_len = cases.len, - .rbrace = rbrace, - }; - std.mem.copy(*Node, node.cases(), cases); - return &node.base; + const trailing_comma = p.token_tags[p.tok_i - 1] == .comma; + _ = try p.expectToken(.r_brace); + + return p.addNode(.{ + .tag = if (trailing_comma) .switch_comma else .@"switch", + .main_token = switch_token, + .data = .{ + .lhs = expr_node, + .rhs = try p.addExtra(Node.SubRange{ + .start = cases.start, + .end = cases.end, + }), + }, + }); } /// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN @@ -1802,854 +3046,395 @@ const Parser = struct { /// AsmInput <- COLON AsmInputList AsmClobbers? /// AsmClobbers <- COLON StringList /// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL? - fn parseAsmExpr(p: *Parser) !?*Node { - const asm_token = p.eatToken(.Keyword_asm) orelse return null; - const volatile_token = p.eatToken(.Keyword_volatile); - _ = try p.expectToken(.LParen); - const template = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - - var arena_outputs: []Node.Asm.Output = &[0]Node.Asm.Output{}; - var arena_inputs: []Node.Asm.Input = &[0]Node.Asm.Input{}; - var arena_clobbers: []*Node = &[0]*Node{}; - - if (p.eatToken(.Colon) != null) { - const outputs = try p.parseAsmOutputList(); - defer p.gpa.free(outputs); - arena_outputs = try p.arena.allocator.dupe(Node.Asm.Output, outputs); - - if (p.eatToken(.Colon) != null) { - const inputs = try p.parseAsmInputList(); - defer p.gpa.free(inputs); - arena_inputs = try p.arena.allocator.dupe(Node.Asm.Input, inputs); - - if (p.eatToken(.Colon) != null) { - const clobbers = try ListParseFn(*Node, parseStringLiteral)(p); - defer p.gpa.free(clobbers); - arena_clobbers = try p.arena.allocator.dupe(*Node, clobbers); - } - } + /// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem? + /// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem? + fn expectAsmExpr(p: *Parser) !Node.Index { + const asm_token = p.assertToken(.keyword_asm); + _ = p.eatToken(.keyword_volatile); + _ = try p.expectToken(.l_paren); + const template = try p.expectExpr(); + + if (p.eatToken(.r_paren)) |rparen| { + return p.addNode(.{ + .tag = .asm_simple, + .main_token = asm_token, + .data = .{ + .lhs = template, + .rhs = rparen, + }, + }); } - const node = try p.arena.allocator.create(Node.Asm); - node.* = .{ - .asm_token = asm_token, - .volatile_token = volatile_token, - .template = template, - .outputs = arena_outputs, - .inputs = arena_inputs, - .clobbers = arena_clobbers, - .rparen = try p.expectToken(.RParen), - }; - - return &node.base; - } + _ = try p.expectToken(.colon); - /// DOT IDENTIFIER - fn parseAnonLiteral(p: *Parser) !?*Node { - const dot = p.eatToken(.Period) orelse return null; + var list = std.ArrayList(Node.Index).init(p.gpa); + defer list.deinit(); - // anon enum literal - if (p.eatToken(.Identifier)) |name| { - const node = try p.arena.allocator.create(Node.EnumLiteral); - node.* = .{ - .dot = dot, - .name = name, - }; - return &node.base; + while (true) { + const output_item = try p.parseAsmOutputItem(); + if (output_item == 0) break; + try list.append(output_item); + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + .colon, .r_paren, .r_brace, .r_bracket => break, // All possible delimiters. + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + try p.warnExpected(.comma); + }, + } } - - if (try p.parseAnonInitList(dot)) |node| { - return node; + if (p.eatToken(.colon)) |_| { + while (true) { + const input_item = try p.parseAsmInputItem(); + if (input_item == 0) break; + try list.append(input_item); + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + .colon, .r_paren, .r_brace, .r_bracket => break, // All possible delimiters. + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + try p.warnExpected(.comma); + }, + } + } + if (p.eatToken(.colon)) |_| { + while (p.eatToken(.string_literal)) |_| { + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + .colon, .r_paren, .r_brace, .r_bracket => break, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + try p.warnExpected(.comma); + }, + } + } + } } - - p.putBackToken(dot); - return null; + const rparen = try p.expectToken(.r_paren); + const span = try p.listToSpan(list.items); + return p.addNode(.{ + .tag = .@"asm", + .main_token = asm_token, + .data = .{ + .lhs = template, + .rhs = try p.addExtra(Node.Asm{ + .items_start = span.start, + .items_end = span.end, + .rparen = rparen, + }), + }, + }); } /// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN - fn parseAsmOutputItem(p: *Parser) !?Node.Asm.Output { - const lbracket = p.eatToken(.LBracket) orelse return null; - const name = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RBracket); - - const constraint = try p.expectNode(parseStringLiteral, .{ - .ExpectedStringLiteral = .{ .token = p.tok_i }, - }); - - _ = try p.expectToken(.LParen); - const kind: Node.Asm.Output.Kind = blk: { - if (p.eatToken(.Arrow) != null) { - const return_ident = try p.expectNode(parseTypeExpr, .{ - .ExpectedTypeExpr = .{ .token = p.tok_i }, - }); - break :blk .{ .Return = return_ident }; + fn parseAsmOutputItem(p: *Parser) !Node.Index { + _ = p.eatToken(.l_bracket) orelse return null_node; + const identifier = try p.expectToken(.identifier); + _ = try p.expectToken(.r_bracket); + _ = try p.expectToken(.string_literal); + _ = try p.expectToken(.l_paren); + const type_expr: Node.Index = blk: { + if (p.eatToken(.arrow)) |_| { + break :blk try p.expectTypeExpr(); + } else { + _ = try p.expectToken(.identifier); + break :blk null_node; } - const variable = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - break :blk .{ .Variable = variable.castTag(.Identifier).? }; - }; - const rparen = try p.expectToken(.RParen); - - return Node.Asm.Output{ - .lbracket = lbracket, - .symbolic_name = name, - .constraint = constraint, - .kind = kind, - .rparen = rparen, }; + const rparen = try p.expectToken(.r_paren); + return p.addNode(.{ + .tag = .asm_output, + .main_token = identifier, + .data = .{ + .lhs = type_expr, + .rhs = rparen, + }, + }); } /// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN - fn parseAsmInputItem(p: *Parser) !?Node.Asm.Input { - const lbracket = p.eatToken(.LBracket) orelse return null; - const name = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RBracket); - - const constraint = try p.expectNode(parseStringLiteral, .{ - .ExpectedStringLiteral = .{ .token = p.tok_i }, - }); - - _ = try p.expectToken(.LParen); - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + fn parseAsmInputItem(p: *Parser) !Node.Index { + _ = p.eatToken(.l_bracket) orelse return null_node; + const identifier = try p.expectToken(.identifier); + _ = try p.expectToken(.r_bracket); + _ = try p.expectToken(.string_literal); + _ = try p.expectToken(.l_paren); + const expr = try p.expectExpr(); + const rparen = try p.expectToken(.r_paren); + return p.addNode(.{ + .tag = .asm_input, + .main_token = identifier, + .data = .{ + .lhs = expr, + .rhs = rparen, + }, }); - const rparen = try p.expectToken(.RParen); - - return Node.Asm.Input{ - .lbracket = lbracket, - .symbolic_name = name, - .constraint = constraint, - .expr = expr, - .rparen = rparen, - }; } /// BreakLabel <- COLON IDENTIFIER - fn parseBreakLabel(p: *Parser) !?TokenIndex { - _ = p.eatToken(.Colon) orelse return null; - const ident = try p.expectToken(.Identifier); - return ident; + fn parseBreakLabel(p: *Parser) !TokenIndex { + _ = p.eatToken(.colon) orelse return @as(TokenIndex, 0); + return p.expectToken(.identifier); } /// BlockLabel <- IDENTIFIER COLON - fn parseBlockLabel(p: *Parser, colon_token: *TokenIndex) ?TokenIndex { - const identifier = p.eatToken(.Identifier) orelse return null; - if (p.eatToken(.Colon)) |colon| { - colon_token.* = colon; + fn parseBlockLabel(p: *Parser) TokenIndex { + if (p.token_tags[p.tok_i] == .identifier and + p.token_tags[p.tok_i + 1] == .colon) + { + const identifier = p.tok_i; + p.tok_i += 2; return identifier; } - p.putBackToken(identifier); - return null; + return 0; } /// FieldInit <- DOT IDENTIFIER EQUAL Expr - fn parseFieldInit(p: *Parser) !?*Node { - const period_token = p.eatToken(.Period) orelse return null; - const name_token = p.eatToken(.Identifier) orelse { - // Because of anon literals `.{` is also valid. - p.putBackToken(period_token); - return null; - }; - const eq_token = p.eatToken(.Equal) orelse { - // `.Name` may also be an enum literal, which is a later rule. - p.putBackToken(name_token); - p.putBackToken(period_token); - return null; - }; - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); + fn parseFieldInit(p: *Parser) !Node.Index { + if (p.token_tags[p.tok_i + 0] == .period and + p.token_tags[p.tok_i + 1] == .identifier and + p.token_tags[p.tok_i + 2] == .equal) + { + p.tok_i += 3; + return p.expectExpr(); + } else { + return null_node; + } + } - const node = try p.arena.allocator.create(Node.FieldInitializer); - node.* = .{ - .period_token = period_token, - .name_token = name_token, - .expr = expr_node, - }; - return &node.base; + fn expectFieldInit(p: *Parser) !Node.Index { + _ = try p.expectToken(.period); + _ = try p.expectToken(.identifier); + _ = try p.expectToken(.equal); + return p.expectExpr(); } /// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN - fn parseWhileContinueExpr(p: *Parser) !?*Node { - _ = p.eatToken(.Colon) orelse return null; - _ = try p.expectToken(.LParen); - const node = try p.expectNode(parseAssignExpr, .{ - .ExpectedExprOrAssignment = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); + fn parseWhileContinueExpr(p: *Parser) !Node.Index { + _ = p.eatToken(.colon) orelse return null_node; + _ = try p.expectToken(.l_paren); + const node = try p.parseAssignExpr(); + if (node == 0) return p.fail(.expected_expr_or_assignment); + _ = try p.expectToken(.r_paren); return node; } /// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN - fn parseLinkSection(p: *Parser) !?*Node { - _ = p.eatToken(.Keyword_linksection) orelse return null; - _ = try p.expectToken(.LParen); - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); + fn parseLinkSection(p: *Parser) !Node.Index { + _ = p.eatToken(.keyword_linksection) orelse return null_node; + _ = try p.expectToken(.l_paren); + const expr_node = try p.expectExpr(); + _ = try p.expectToken(.r_paren); return expr_node; } /// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN - fn parseCallconv(p: *Parser) !?*Node { - _ = p.eatToken(.Keyword_callconv) orelse return null; - _ = try p.expectToken(.LParen); - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); + fn parseCallconv(p: *Parser) !Node.Index { + _ = p.eatToken(.keyword_callconv) orelse return null_node; + _ = try p.expectToken(.l_paren); + const expr_node = try p.expectExpr(); + _ = try p.expectToken(.r_paren); return expr_node; } - /// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType - fn parseParamDecl(p: *Parser) !?Node.FnProto.ParamDecl { - const doc_comments = try p.parseDocComment(); - const noalias_token = p.eatToken(.Keyword_noalias); - const comptime_token = if (noalias_token == null) p.eatToken(.Keyword_comptime) else null; - const name_token = blk: { - const identifier = p.eatToken(.Identifier) orelse break :blk null; - if (p.eatToken(.Colon) != null) break :blk identifier; - p.putBackToken(identifier); // ParamType may also be an identifier - break :blk null; - }; - const param_type = (try p.parseParamType()) orelse { - // Only return cleanly if no keyword, identifier, or doc comment was found - if (noalias_token == null and - comptime_token == null and - name_token == null and - doc_comments == null) - { - return null; - } - try p.errors.append(p.gpa, .{ - .ExpectedParamType = .{ .token = p.tok_i }, - }); - return error.ParseError; - }; - - return Node.FnProto.ParamDecl{ - .doc_comments = doc_comments, - .comptime_token = comptime_token, - .noalias_token = noalias_token, - .name_token = name_token, - .param_type = param_type, - }; - } - + /// ParamDecl + /// <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType + /// / DOT3 /// ParamType /// <- Keyword_anytype - /// / DOT3 /// / TypeExpr - fn parseParamType(p: *Parser) !?Node.FnProto.ParamDecl.ParamType { - // TODO cast from tuple to error union is broken - const P = Node.FnProto.ParamDecl.ParamType; - if (try p.parseAnyType()) |node| return P{ .any_type = node }; - if (try p.parseTypeExpr()) |node| return P{ .type_expr = node }; - return null; - } - - /// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload? - fn parseIfPrefix(p: *Parser) !?*Node { - const if_token = p.eatToken(.Keyword_if) orelse return null; - _ = try p.expectToken(.LParen); - const condition = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - const payload = try p.parsePtrPayload(); - - const node = try p.arena.allocator.create(Node.If); - node.* = .{ - .if_token = if_token, - .condition = condition, - .payload = payload, - .body = undefined, // set by caller - .@"else" = null, - }; - return &node.base; - } - - /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? - fn parseWhilePrefix(p: *Parser) !?*Node { - const while_token = p.eatToken(.Keyword_while) orelse return null; - - _ = try p.expectToken(.LParen); - const condition = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - - const payload = try p.parsePtrPayload(); - const continue_expr = try p.parseWhileContinueExpr(); - - const node = try p.arena.allocator.create(Node.While); - node.* = .{ - .label = null, - .inline_token = null, - .while_token = while_token, - .condition = condition, - .payload = payload, - .continue_expr = continue_expr, - .body = undefined, // set by caller - .@"else" = null, - }; - return &node.base; - } - - /// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload - fn parseForPrefix(p: *Parser) !?*Node { - const for_token = p.eatToken(.Keyword_for) orelse return null; - - _ = try p.expectToken(.LParen); - const array_expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - - const payload = try p.expectNode(parsePtrIndexPayload, .{ - .ExpectedPayload = .{ .token = p.tok_i }, - }); - - const node = try p.arena.allocator.create(Node.For); - node.* = .{ - .label = null, - .inline_token = null, - .for_token = for_token, - .array_expr = array_expr, - .payload = payload, - .body = undefined, // set by caller - .@"else" = null, - }; - return &node.base; + /// This function can return null nodes and then still return nodes afterwards, + /// such as in the case of anytype and `...`. Caller must look for rparen to find + /// out when there are no more param decls left. + fn expectParamDecl(p: *Parser) !Node.Index { + _ = try p.eatDocComments(); + switch (p.token_tags[p.tok_i]) { + .keyword_noalias, .keyword_comptime => p.tok_i += 1, + .ellipsis3 => { + p.tok_i += 1; + return null_node; + }, + else => {}, + } + if (p.token_tags[p.tok_i] == .identifier and + p.token_tags[p.tok_i + 1] == .colon) + { + p.tok_i += 2; + } + switch (p.token_tags[p.tok_i]) { + .keyword_anytype => { + p.tok_i += 1; + return null_node; + }, + else => return p.expectTypeExpr(), + } } /// Payload <- PIPE IDENTIFIER PIPE - fn parsePayload(p: *Parser) !?*Node { - const lpipe = p.eatToken(.Pipe) orelse return null; - const identifier = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - const rpipe = try p.expectToken(.Pipe); - - const node = try p.arena.allocator.create(Node.Payload); - node.* = .{ - .lpipe = lpipe, - .error_symbol = identifier, - .rpipe = rpipe, - }; - return &node.base; + fn parsePayload(p: *Parser) !TokenIndex { + _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0); + const identifier = try p.expectToken(.identifier); + _ = try p.expectToken(.pipe); + return identifier; } /// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE - fn parsePtrPayload(p: *Parser) !?*Node { - const lpipe = p.eatToken(.Pipe) orelse return null; - const asterisk = p.eatToken(.Asterisk); - const identifier = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - const rpipe = try p.expectToken(.Pipe); - - const node = try p.arena.allocator.create(Node.PointerPayload); - node.* = .{ - .lpipe = lpipe, - .ptr_token = asterisk, - .value_symbol = identifier, - .rpipe = rpipe, - }; - return &node.base; + fn parsePtrPayload(p: *Parser) !TokenIndex { + _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0); + _ = p.eatToken(.asterisk); + const identifier = try p.expectToken(.identifier); + _ = try p.expectToken(.pipe); + return identifier; } /// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE - fn parsePtrIndexPayload(p: *Parser) !?*Node { - const lpipe = p.eatToken(.Pipe) orelse return null; - const asterisk = p.eatToken(.Asterisk); - const identifier = try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - - const index = if (p.eatToken(.Comma) == null) - null - else - try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); - - const rpipe = try p.expectToken(.Pipe); - - const node = try p.arena.allocator.create(Node.PointerIndexPayload); - node.* = .{ - .lpipe = lpipe, - .ptr_token = asterisk, - .value_symbol = identifier, - .index_symbol = index, - .rpipe = rpipe, - }; - return &node.base; + /// Returns the first identifier token, if any. + fn parsePtrIndexPayload(p: *Parser) !TokenIndex { + _ = p.eatToken(.pipe) orelse return @as(TokenIndex, 0); + _ = p.eatToken(.asterisk); + const identifier = try p.expectToken(.identifier); + if (p.eatToken(.comma) != null) { + _ = try p.expectToken(.identifier); + } + _ = try p.expectToken(.pipe); + return identifier; } /// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr - fn parseSwitchProng(p: *Parser) !?*Node { - const node = (try p.parseSwitchCase()) orelse return null; - const arrow = try p.expectToken(.EqualAngleBracketRight); - const payload = try p.parsePtrPayload(); - const expr = try p.expectNode(parseAssignExpr, .{ - .ExpectedExprOrAssignment = .{ .token = p.tok_i }, - }); - - const switch_case = node.cast(Node.SwitchCase).?; - switch_case.arrow_token = arrow; - switch_case.payload = payload; - switch_case.expr = expr; - - return node; - } - /// SwitchCase /// <- SwitchItem (COMMA SwitchItem)* COMMA? /// / KEYWORD_else - fn parseSwitchCase(p: *Parser) !?*Node { - var list = std.ArrayList(*Node).init(p.gpa); + fn parseSwitchProng(p: *Parser) !Node.Index { + if (p.eatToken(.keyword_else)) |_| { + const arrow_token = try p.expectToken(.equal_angle_bracket_right); + _ = try p.parsePtrPayload(); + return p.addNode(.{ + .tag = .switch_case_one, + .main_token = arrow_token, + .data = .{ + .lhs = 0, + .rhs = try p.expectAssignExpr(), + }, + }); + } + const first_item = try p.parseSwitchItem(); + if (first_item == 0) return null_node; + + if (p.eatToken(.equal_angle_bracket_right)) |arrow_token| { + _ = try p.parsePtrPayload(); + return p.addNode(.{ + .tag = .switch_case_one, + .main_token = arrow_token, + .data = .{ + .lhs = first_item, + .rhs = try p.expectAssignExpr(), + }, + }); + } + + var list = std.ArrayList(Node.Index).init(p.gpa); defer list.deinit(); - if (try p.parseSwitchItem()) |first_item| { - try list.append(first_item); - while (p.eatToken(.Comma) != null) { - const next_item = (try p.parseSwitchItem()) orelse break; - try list.append(next_item); - } - } else if (p.eatToken(.Keyword_else)) |else_token| { - const else_node = try p.arena.allocator.create(Node.SwitchElse); - else_node.* = .{ - .token = else_token, - }; - try list.append(&else_node.base); - } else return null; - - const node = try Node.SwitchCase.alloc(&p.arena.allocator, list.items.len); - node.* = .{ - .items_len = list.items.len, - .arrow_token = undefined, // set by caller - .payload = null, - .expr = undefined, // set by caller - }; - std.mem.copy(*Node, node.items(), list.items); - return &node.base; + try list.append(first_item); + while (p.eatToken(.comma)) |_| { + const next_item = try p.parseSwitchItem(); + if (next_item == 0) break; + try list.append(next_item); + } + const span = try p.listToSpan(list.items); + const arrow_token = try p.expectToken(.equal_angle_bracket_right); + _ = try p.parsePtrPayload(); + return p.addNode(.{ + .tag = .switch_case, + .main_token = arrow_token, + .data = .{ + .lhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + .rhs = try p.expectAssignExpr(), + }, + }); } /// SwitchItem <- Expr (DOT3 Expr)? - fn parseSwitchItem(p: *Parser) !?*Node { - const expr = (try p.parseExpr()) orelse return null; - if (p.eatToken(.Ellipsis3)) |token| { - const range_end = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + fn parseSwitchItem(p: *Parser) !Node.Index { + const expr = try p.parseExpr(); + if (expr == 0) return null_node; + + if (p.eatToken(.ellipsis3)) |token| { + return p.addNode(.{ + .tag = .switch_range, + .main_token = token, + .data = .{ + .lhs = expr, + .rhs = try p.expectExpr(), + }, }); - - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = Node{ .tag = .Range }, - .op_token = token, - .lhs = expr, - .rhs = range_end, - }; - return &node.base; } return expr; } - /// AssignOp - /// <- ASTERISKEQUAL - /// / SLASHEQUAL - /// / PERCENTEQUAL - /// / PLUSEQUAL - /// / MINUSEQUAL - /// / LARROW2EQUAL - /// / RARROW2EQUAL - /// / AMPERSANDEQUAL - /// / CARETEQUAL - /// / PIPEEQUAL - /// / ASTERISKPERCENTEQUAL - /// / PLUSPERCENTEQUAL - /// / MINUSPERCENTEQUAL - /// / EQUAL - fn parseAssignOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .AsteriskEqual => .AssignMul, - .SlashEqual => .AssignDiv, - .PercentEqual => .AssignMod, - .PlusEqual => .AssignAdd, - .MinusEqual => .AssignSub, - .AngleBracketAngleBracketLeftEqual => .AssignBitShiftLeft, - .AngleBracketAngleBracketRightEqual => .AssignBitShiftRight, - .AmpersandEqual => .AssignBitAnd, - .CaretEqual => .AssignBitXor, - .PipeEqual => .AssignBitOr, - .AsteriskPercentEqual => .AssignMulWrap, - .PlusPercentEqual => .AssignAddWrap, - .MinusPercentEqual => .AssignSubWrap, - .Equal => .Assign, - else => { - p.putBackToken(token); - return null; - }, - }; - - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = .{ .tag = op }, - .op_token = token, - .lhs = undefined, // set by caller - .rhs = undefined, // set by caller - }; - return &node.base; - } - - /// CompareOp - /// <- EQUALEQUAL - /// / EXCLAMATIONMARKEQUAL - /// / LARROW - /// / RARROW - /// / LARROWEQUAL - /// / RARROWEQUAL - fn parseCompareOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .EqualEqual => .EqualEqual, - .BangEqual => .BangEqual, - .AngleBracketLeft => .LessThan, - .AngleBracketRight => .GreaterThan, - .AngleBracketLeftEqual => .LessOrEqual, - .AngleBracketRightEqual => .GreaterOrEqual, - else => { - p.putBackToken(token); - return null; - }, - }; - - return p.createInfixOp(token, op); - } - - /// BitwiseOp - /// <- AMPERSAND - /// / CARET - /// / PIPE - /// / KEYWORD_orelse - /// / KEYWORD_catch Payload? - fn parseBitwiseOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .Ampersand => .BitAnd, - .Caret => .BitXor, - .Pipe => .BitOr, - .Keyword_orelse => .OrElse, - .Keyword_catch => { - const payload = try p.parsePayload(); - const node = try p.arena.allocator.create(Node.Catch); - node.* = .{ - .op_token = token, - .lhs = undefined, // set by caller - .rhs = undefined, // set by caller - .payload = payload, - }; - return &node.base; - }, - else => { - p.putBackToken(token); - return null; - }, - }; - - return p.createInfixOp(token, op); - } - - /// BitShiftOp - /// <- LARROW2 - /// / RARROW2 - fn parseBitShiftOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .AngleBracketAngleBracketLeft => .BitShiftLeft, - .AngleBracketAngleBracketRight => .BitShiftRight, - else => { - p.putBackToken(token); - return null; - }, - }; - - return p.createInfixOp(token, op); - } - - /// AdditionOp - /// <- PLUS - /// / MINUS - /// / PLUS2 - /// / PLUSPERCENT - /// / MINUSPERCENT - fn parseAdditionOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .Plus => .Add, - .Minus => .Sub, - .PlusPlus => .ArrayCat, - .PlusPercent => .AddWrap, - .MinusPercent => .SubWrap, - else => { - p.putBackToken(token); - return null; - }, - }; - - return p.createInfixOp(token, op); - } - - /// MultiplyOp - /// <- PIPE2 - /// / ASTERISK - /// / SLASH - /// / PERCENT - /// / ASTERISK2 - /// / ASTERISKPERCENT - fn parseMultiplyOp(p: *Parser) !?*Node { - const token = p.nextToken(); - const op: Node.Tag = switch (p.token_ids[token]) { - .PipePipe => .MergeErrorSets, - .Asterisk => .Mul, - .Slash => .Div, - .Percent => .Mod, - .AsteriskAsterisk => .ArrayMult, - .AsteriskPercent => .MulWrap, - else => { - p.putBackToken(token); - return null; - }, - }; - - return p.createInfixOp(token, op); - } - - /// PrefixOp - /// <- EXCLAMATIONMARK - /// / MINUS - /// / TILDE - /// / MINUSPERCENT - /// / AMPERSAND - /// / KEYWORD_try - /// / KEYWORD_await - fn parsePrefixOp(p: *Parser) !?*Node { - const token = p.nextToken(); - switch (p.token_ids[token]) { - .Bang => return p.allocSimplePrefixOp(.BoolNot, token), - .Minus => return p.allocSimplePrefixOp(.Negation, token), - .Tilde => return p.allocSimplePrefixOp(.BitNot, token), - .MinusPercent => return p.allocSimplePrefixOp(.NegationWrap, token), - .Ampersand => return p.allocSimplePrefixOp(.AddressOf, token), - .Keyword_try => return p.allocSimplePrefixOp(.Try, token), - .Keyword_await => return p.allocSimplePrefixOp(.Await, token), - else => { - p.putBackToken(token); - return null; - }, - } - } + const PtrModifiers = struct { + align_node: Node.Index, + bit_range_start: Node.Index, + bit_range_end: Node.Index, + }; - fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Tag, token: TokenIndex) !?*Node { - const node = try p.arena.allocator.create(Node.SimplePrefixOp); - node.* = .{ - .base = .{ .tag = tag }, - .op_token = token, - .rhs = undefined, // set by caller + fn parsePtrModifiers(p: *Parser) !PtrModifiers { + var result: PtrModifiers = .{ + .align_node = 0, + .bit_range_start = 0, + .bit_range_end = 0, }; - return &node.base; - } - - // TODO: ArrayTypeStart is either an array or a slice, but const/allowzero only work on - // pointers. Consider updating this rule: - // ... - // / ArrayTypeStart - // / SliceTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* - // / PtrTypeStart ... - - /// PrefixTypeOp - /// <- QUESTIONMARK - /// / KEYWORD_anyframe MINUSRARROW - /// / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* - /// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* - fn parsePrefixTypeOp(p: *Parser) !?*Node { - if (p.eatToken(.QuestionMark)) |token| { - const node = try p.arena.allocator.create(Node.SimplePrefixOp); - node.* = .{ - .base = .{ .tag = .OptionalType }, - .op_token = token, - .rhs = undefined, // set by caller - }; - return &node.base; - } - - if (p.eatToken(.Keyword_anyframe)) |token| { - const arrow = p.eatToken(.Arrow) orelse { - p.putBackToken(token); - return null; - }; - const node = try p.arena.allocator.create(Node.AnyFrameType); - node.* = .{ - .anyframe_token = token, - .result = .{ - .arrow_token = arrow, - .return_type = undefined, // set by caller - }, - }; - return &node.base; - } - - if (try p.parsePtrTypeStart()) |node| { - // If the token encountered was **, there will be two nodes instead of one. - // The attributes should be applied to the rightmost operator. - var ptr_info = if (node.cast(Node.PtrType)) |ptr_type| - if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) - &ptr_type.rhs.cast(Node.PtrType).?.ptr_info - else - &ptr_type.ptr_info - else if (node.cast(Node.SliceType)) |slice_type| - &slice_type.ptr_info - else - unreachable; - - while (true) { - if (p.eatToken(.Keyword_align)) |align_token| { - const lparen = try p.expectToken(.LParen); - const expr_node = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - - // Optional bit range - const bit_range = if (p.eatToken(.Colon)) |_| bit_range_value: { - const range_start = try p.expectNode(parseIntegerLiteral, .{ - .ExpectedIntegerLiteral = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.Colon); - const range_end = try p.expectNode(parseIntegerLiteral, .{ - .ExpectedIntegerLiteral = .{ .token = p.tok_i }, - }); - - break :bit_range_value ast.PtrInfo.Align.BitRange{ - .start = range_start, - .end = range_end, - }; - } else null; - _ = try p.expectToken(.RParen); - - if (ptr_info.align_info != null) { - try p.errors.append(p.gpa, .{ - .ExtraAlignQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - - ptr_info.align_info = ast.PtrInfo.Align{ - .node = expr_node, - .bit_range = bit_range, - }; - - continue; - } - if (p.eatToken(.Keyword_const)) |const_token| { - if (ptr_info.const_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraConstQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - ptr_info.const_token = const_token; - continue; - } - if (p.eatToken(.Keyword_volatile)) |volatile_token| { - if (ptr_info.volatile_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; + var saw_const = false; + var saw_volatile = false; + var saw_allowzero = false; + while (true) { + switch (p.token_tags[p.tok_i]) { + .keyword_align => { + if (result.align_node != 0) { + try p.warn(.extra_align_qualifier); } - ptr_info.volatile_token = volatile_token; - continue; - } - if (p.eatToken(.Keyword_allowzero)) |allowzero_token| { - if (ptr_info.allowzero_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; + p.tok_i += 1; + _ = try p.expectToken(.l_paren); + result.align_node = try p.expectExpr(); + + if (p.eatToken(.colon)) |_| { + result.bit_range_start = try p.expectExpr(); + _ = try p.expectToken(.colon); + result.bit_range_end = try p.expectExpr(); } - ptr_info.allowzero_token = allowzero_token; - continue; - } - break; - } - return node; - } - - if (try p.parseArrayTypeStart()) |node| { - if (node.cast(Node.SliceType)) |slice_type| { - // Collect pointer qualifiers in any order, but disallow duplicates - while (true) { - if (try p.parseByteAlign()) |align_expr| { - if (slice_type.ptr_info.align_info != null) { - try p.errors.append(p.gpa, .{ - .ExtraAlignQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.ptr_info.align_info = ast.PtrInfo.Align{ - .node = align_expr, - .bit_range = null, - }; - continue; - } - if (p.eatToken(.Keyword_const)) |const_token| { - if (slice_type.ptr_info.const_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraConstQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.ptr_info.const_token = const_token; - continue; + _ = try p.expectToken(.r_paren); + }, + .keyword_const => { + if (saw_const) { + try p.warn(.extra_const_qualifier); } - if (p.eatToken(.Keyword_volatile)) |volatile_token| { - if (slice_type.ptr_info.volatile_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.ptr_info.volatile_token = volatile_token; - continue; + p.tok_i += 1; + saw_const = true; + }, + .keyword_volatile => { + if (saw_volatile) { + try p.warn(.extra_volatile_qualifier); } - if (p.eatToken(.Keyword_allowzero)) |allowzero_token| { - if (slice_type.ptr_info.allowzero_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.ptr_info.allowzero_token = allowzero_token; - continue; + p.tok_i += 1; + saw_volatile = true; + }, + .keyword_allowzero => { + if (saw_allowzero) { + try p.warn(.extra_allowzero_qualifier); } - break; - } + p.tok_i += 1; + saw_allowzero = true; + }, + else => return result, } - return node; } - - return null; } /// SuffixOp @@ -2657,841 +3442,654 @@ const Parser = struct { /// / DOT IDENTIFIER /// / DOTASTERISK /// / DOTQUESTIONMARK - fn parseSuffixOp(p: *Parser, lhs: *Node) !?*Node { - if (p.eatToken(.LBracket)) |_| { - const index_expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - - if (p.eatToken(.Ellipsis2) != null) { - const end_expr = try p.parseExpr(); - const sentinel: ?*Node = if (p.eatToken(.Colon) != null) - try p.parseExpr() - else - null; - const rtoken = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.Slice); - node.* = .{ - .lhs = lhs, - .rtoken = rtoken, - .start = index_expr, - .end = end_expr, - .sentinel = sentinel, - }; - return &node.base; - } - - const rtoken = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.ArrayAccess); - node.* = .{ - .lhs = lhs, - .rtoken = rtoken, - .index_expr = index_expr, - }; - return &node.base; - } - - if (p.eatToken(.PeriodAsterisk)) |period_asterisk| { - const node = try p.arena.allocator.create(Node.SimpleSuffixOp); - node.* = .{ - .base = .{ .tag = .Deref }, - .lhs = lhs, - .rtoken = period_asterisk, - }; - return &node.base; - } - - if (p.eatToken(.Invalid_periodasterisks)) |period_asterisk| { - try p.errors.append(p.gpa, .{ - .AsteriskAfterPointerDereference = .{ .token = period_asterisk }, - }); - const node = try p.arena.allocator.create(Node.SimpleSuffixOp); - node.* = .{ - .base = .{ .tag = .Deref }, - .lhs = lhs, - .rtoken = period_asterisk, - }; - return &node.base; - } - - if (p.eatToken(.Period)) |period| { - if (try p.parseIdentifier()) |identifier| { - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = Node{ .tag = .Period }, - .op_token = period, - .lhs = lhs, - .rhs = identifier, - }; - return &node.base; - } - if (p.eatToken(.QuestionMark)) |question_mark| { - const node = try p.arena.allocator.create(Node.SimpleSuffixOp); - node.* = .{ - .base = .{ .tag = .UnwrapOptional }, - .lhs = lhs, - .rtoken = question_mark, - }; - return &node.base; - } - try p.errors.append(p.gpa, .{ - .ExpectedSuffixOp = .{ .token = p.tok_i }, - }); - return null; - } - - return null; - } - - /// FnCallArguments <- LPAREN ExprList RPAREN - /// ExprList <- (Expr COMMA)* Expr? - fn parseFnCallArguments(p: *Parser) !?AnnotatedParamList { - if (p.eatToken(.LParen) == null) return null; - const list = try ListParseFn(*Node, parseExpr)(p); - errdefer p.gpa.free(list); - const rparen = try p.expectToken(.RParen); - return AnnotatedParamList{ .list = list, .rparen = rparen }; - } - - const AnnotatedParamList = struct { - list: []*Node, - rparen: TokenIndex, - }; - - /// ArrayTypeStart <- LBRACKET Expr? (COLON Expr)? RBRACKET - fn parseArrayTypeStart(p: *Parser) !?*Node { - const lbracket = p.eatToken(.LBracket) orelse return null; - const expr = try p.parseExpr(); - const sentinel = if (p.eatToken(.Colon)) |_| - try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }) - else - null; - const rbracket = try p.expectToken(.RBracket); - - if (expr) |len_expr| { - if (sentinel) |s| { - const node = try p.arena.allocator.create(Node.ArrayTypeSentinel); - node.* = .{ - .op_token = lbracket, - .rhs = undefined, // set by caller - .len_expr = len_expr, - .sentinel = s, - }; - return &node.base; - } else { - const node = try p.arena.allocator.create(Node.ArrayType); - node.* = .{ - .op_token = lbracket, - .rhs = undefined, // set by caller - .len_expr = len_expr, - }; - return &node.base; - } - } - - const node = try p.arena.allocator.create(Node.SliceType); - node.* = .{ - .op_token = lbracket, - .rhs = undefined, // set by caller - .ptr_info = .{ .sentinel = sentinel }, - }; - return &node.base; - } - - /// PtrTypeStart - /// <- ASTERISK - /// / ASTERISK2 - /// / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET - fn parsePtrTypeStart(p: *Parser) !?*Node { - if (p.eatToken(.Asterisk)) |asterisk| { - const sentinel = if (p.eatToken(.Colon)) |_| - try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }) - else - null; - const node = try p.arena.allocator.create(Node.PtrType); - node.* = .{ - .op_token = asterisk, - .rhs = undefined, // set by caller - .ptr_info = .{ .sentinel = sentinel }, - }; - return &node.base; - } - - if (p.eatToken(.AsteriskAsterisk)) |double_asterisk| { - const node = try p.arena.allocator.create(Node.PtrType); - node.* = .{ - .op_token = double_asterisk, - .rhs = undefined, // set by caller - }; - - // Special case for **, which is its own token - const child = try p.arena.allocator.create(Node.PtrType); - child.* = .{ - .op_token = double_asterisk, - .rhs = undefined, // set by caller - }; - node.rhs = &child.base; - - return &node.base; - } - if (p.eatToken(.LBracket)) |lbracket| { - const asterisk = p.eatToken(.Asterisk) orelse { - p.putBackToken(lbracket); - return null; - }; - if (p.eatToken(.Identifier)) |ident| { - const token_loc = p.token_locs[ident]; - const token_slice = p.source[token_loc.start..token_loc.end]; - if (!std.mem.eql(u8, token_slice, "c")) { - p.putBackToken(ident); - } else { - _ = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.PtrType); - node.* = .{ - .op_token = lbracket, - .rhs = undefined, // set by caller - }; - return &node.base; + fn parseSuffixOp(p: *Parser, lhs: Node.Index) !Node.Index { + switch (p.token_tags[p.tok_i]) { + .l_bracket => { + const lbracket = p.nextToken(); + const index_expr = try p.expectExpr(); + + if (p.eatToken(.ellipsis2)) |_| { + const end_expr = try p.parseExpr(); + if (end_expr == 0) { + _ = try p.expectToken(.r_bracket); + return p.addNode(.{ + .tag = .slice_open, + .main_token = lbracket, + .data = .{ + .lhs = lhs, + .rhs = index_expr, + }, + }); + } + if (p.eatToken(.colon)) |_| { + const sentinel = try p.parseExpr(); + _ = try p.expectToken(.r_bracket); + return p.addNode(.{ + .tag = .slice_sentinel, + .main_token = lbracket, + .data = .{ + .lhs = lhs, + .rhs = try p.addExtra(Node.SliceSentinel{ + .start = index_expr, + .end = end_expr, + .sentinel = sentinel, + }), + }, + }); + } else { + _ = try p.expectToken(.r_bracket); + return p.addNode(.{ + .tag = .slice, + .main_token = lbracket, + .data = .{ + .lhs = lhs, + .rhs = try p.addExtra(Node.Slice{ + .start = index_expr, + .end = end_expr, + }), + }, + }); + } } - } - const sentinel = if (p.eatToken(.Colon)) |_| - try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }) - else - null; - _ = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.PtrType); - node.* = .{ - .op_token = lbracket, - .rhs = undefined, // set by caller - .ptr_info = .{ .sentinel = sentinel }, - }; - return &node.base; + _ = try p.expectToken(.r_bracket); + return p.addNode(.{ + .tag = .array_access, + .main_token = lbracket, + .data = .{ + .lhs = lhs, + .rhs = index_expr, + }, + }); + }, + .period_asterisk => return p.addNode(.{ + .tag = .deref, + .main_token = p.nextToken(), + .data = .{ + .lhs = lhs, + .rhs = undefined, + }, + }), + .invalid_periodasterisks => { + try p.warn(.asterisk_after_ptr_deref); + return p.addNode(.{ + .tag = .deref, + .main_token = p.nextToken(), + .data = .{ + .lhs = lhs, + .rhs = undefined, + }, + }); + }, + .period => switch (p.token_tags[p.tok_i + 1]) { + .identifier => return p.addNode(.{ + .tag = .field_access, + .main_token = p.nextToken(), + .data = .{ + .lhs = lhs, + .rhs = p.nextToken(), + }, + }), + .question_mark => return p.addNode(.{ + .tag = .unwrap_optional, + .main_token = p.nextToken(), + .data = .{ + .lhs = lhs, + .rhs = p.nextToken(), + }, + }), + else => { + p.tok_i += 1; + try p.warn(.expected_suffix_op); + return null_node; + }, + }, + else => return null_node, } - return null; } - /// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE - fn parseContainerDeclAuto(p: *Parser) !?*Node { - const container_decl_type = (try p.parseContainerDeclType()) orelse return null; - const lbrace = try p.expectToken(.LBrace); - const members = try p.parseContainerMembers(false); - defer p.gpa.free(members); - const rbrace = try p.expectToken(.RBrace); - - const members_len = @intCast(NodeIndex, members.len); - const node = try Node.ContainerDecl.alloc(&p.arena.allocator, members_len); - node.* = .{ - .layout_token = null, - .kind_token = container_decl_type.kind_token, - .init_arg_expr = container_decl_type.init_arg_expr, - .fields_and_decls_len = members_len, - .lbrace_token = lbrace, - .rbrace_token = rbrace, - }; - std.mem.copy(*Node, node.fieldsAndDecls(), members); - return &node.base; - } - - /// Holds temporary data until we are ready to construct the full ContainerDecl AST node. - const ContainerDeclType = struct { - kind_token: TokenIndex, - init_arg_expr: Node.ContainerDecl.InitArg, - }; - + /// Caller must have already verified the first token. /// ContainerDeclType /// <- KEYWORD_struct /// / KEYWORD_enum (LPAREN Expr RPAREN)? /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? /// / KEYWORD_opaque - fn parseContainerDeclType(p: *Parser) !?ContainerDeclType { - const kind_token = p.nextToken(); - - const init_arg_expr = switch (p.token_ids[kind_token]) { - .Keyword_struct, .Keyword_opaque => Node.ContainerDecl.InitArg{ .None = {} }, - .Keyword_enum => blk: { - if (p.eatToken(.LParen) != null) { - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - break :blk Node.ContainerDecl.InitArg{ .Type = expr }; + fn parseContainerDeclAuto(p: *Parser) !Node.Index { + const main_token = p.nextToken(); + const arg_expr = switch (p.token_tags[main_token]) { + .keyword_struct, .keyword_opaque => null_node, + .keyword_enum => blk: { + if (p.eatToken(.l_paren)) |_| { + const expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + break :blk expr; + } else { + break :blk null_node; } - break :blk Node.ContainerDecl.InitArg{ .None = {} }; - }, - .Keyword_union => blk: { - if (p.eatToken(.LParen) != null) { - if (p.eatToken(.Keyword_enum) != null) { - if (p.eatToken(.LParen) != null) { - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, + }, + .keyword_union => blk: { + if (p.eatToken(.l_paren)) |_| { + if (p.eatToken(.keyword_enum)) |_| { + if (p.eatToken(.l_paren)) |_| { + const enum_tag_expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + _ = try p.expectToken(.r_paren); + + _ = try p.expectToken(.l_brace); + const members = try p.parseContainerMembers(); + const members_span = try members.toSpan(p); + _ = try p.expectToken(.r_brace); + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .tagged_union_enum_tag_trailing, + false => .tagged_union_enum_tag, + }, + .main_token = main_token, + .data = .{ + .lhs = enum_tag_expr, + .rhs = try p.addExtra(members_span), + }, }); - _ = try p.expectToken(.RParen); - _ = try p.expectToken(.RParen); - break :blk Node.ContainerDecl.InitArg{ .Enum = expr }; + } else { + _ = try p.expectToken(.r_paren); + + _ = try p.expectToken(.l_brace); + const members = try p.parseContainerMembers(); + _ = try p.expectToken(.r_brace); + if (members.len <= 2) { + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .tagged_union_two_trailing, + false => .tagged_union_two, + }, + .main_token = main_token, + .data = .{ + .lhs = members.lhs, + .rhs = members.rhs, + }, + }); + } else { + const span = try members.toSpan(p); + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .tagged_union_trailing, + false => .tagged_union, + }, + .main_token = main_token, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + } } - _ = try p.expectToken(.RParen); - break :blk Node.ContainerDecl.InitArg{ .Enum = null }; + } else { + const expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + break :blk expr; } - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); - break :blk Node.ContainerDecl.InitArg{ .Type = expr }; + } else { + break :blk null_node; } - break :blk Node.ContainerDecl.InitArg{ .None = {} }; }, else => { - p.putBackToken(kind_token); - return null; + p.tok_i -= 1; + return p.fail(.expected_container); }, }; - - return ContainerDeclType{ - .kind_token = kind_token, - .init_arg_expr = init_arg_expr, - }; + _ = try p.expectToken(.l_brace); + const members = try p.parseContainerMembers(); + _ = try p.expectToken(.r_brace); + if (arg_expr == 0) { + if (members.len <= 2) { + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .container_decl_two_trailing, + false => .container_decl_two, + }, + .main_token = main_token, + .data = .{ + .lhs = members.lhs, + .rhs = members.rhs, + }, + }); + } else { + const span = try members.toSpan(p); + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .container_decl_trailing, + false => .container_decl, + }, + .main_token = main_token, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + } + } else { + const span = try members.toSpan(p); + return p.addNode(.{ + .tag = switch (members.trailing) { + true => .container_decl_arg_trailing, + false => .container_decl_arg, + }, + .main_token = main_token, + .data = .{ + .lhs = arg_expr, + .rhs = try p.addExtra(Node.SubRange{ + .start = span.start, + .end = span.end, + }), + }, + }); + } } + /// Holds temporary data until we are ready to construct the full ContainerDecl AST node. /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN - fn parseByteAlign(p: *Parser) !?*Node { - _ = p.eatToken(.Keyword_align) orelse return null; - _ = try p.expectToken(.LParen); - const expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }); - _ = try p.expectToken(.RParen); + fn parseByteAlign(p: *Parser) !Node.Index { + _ = p.eatToken(.keyword_align) orelse return null_node; + _ = try p.expectToken(.l_paren); + const expr = try p.expectExpr(); + _ = try p.expectToken(.r_paren); return expr; } - /// IdentifierList <- (IDENTIFIER COMMA)* IDENTIFIER? - /// Only ErrorSetDecl parses an IdentifierList - fn parseErrorTagList(p: *Parser) ![]*Node { - return ListParseFn(*Node, parseErrorTag)(p); - } - /// SwitchProngList <- (SwitchProng COMMA)* SwitchProng? - fn parseSwitchProngList(p: *Parser) ![]*Node { - return ListParseFn(*Node, parseSwitchProng)(p); + fn parseSwitchProngList(p: *Parser) !Node.SubRange { + return ListParseFn(parseSwitchProng)(p); } - /// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem? - fn parseAsmOutputList(p: *Parser) Error![]Node.Asm.Output { - return ListParseFn(Node.Asm.Output, parseAsmOutputItem)(p); - } + /// ParamDeclList <- (ParamDecl COMMA)* ParamDecl? + fn parseParamDeclList(p: *Parser) !SmallSpan { + _ = try p.expectToken(.l_paren); + if (p.eatToken(.r_paren)) |_| { + return SmallSpan{ .zero_or_one = 0 }; + } + const param_one = while (true) { + const param = try p.expectParamDecl(); + if (param != 0) break param; + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + return SmallSpan{ .zero_or_one = 0 }; + } + continue; + }, + .r_paren => return SmallSpan{ .zero_or_one = 0 }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } else unreachable; - /// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem? - fn parseAsmInputList(p: *Parser) Error![]Node.Asm.Input { - return ListParseFn(Node.Asm.Input, parseAsmInputItem)(p); - } + const param_two = while (true) { + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + return SmallSpan{ .zero_or_one = param_one }; + } + const param = try p.expectParamDecl(); + if (param != 0) break param; + continue; + }, + .r_paren => return SmallSpan{ .zero_or_one = param_one }, + .colon, .r_brace, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_paren); + }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } else unreachable; - /// ParamDeclList <- (ParamDecl COMMA)* ParamDecl? - fn parseParamDeclList(p: *Parser) ![]Node.FnProto.ParamDecl { - return ListParseFn(Node.FnProto.ParamDecl, parseParamDecl)(p); + var list = std.ArrayList(Node.Index).init(p.gpa); + defer list.deinit(); + + try list.appendSlice(&.{ param_one, param_two }); + + while (true) { + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.token_tags[p.tok_i] == .r_paren) { + p.tok_i += 1; + return SmallSpan{ .multi = list.toOwnedSlice() }; + } + const param = try p.expectParamDecl(); + if (param != 0) { + try list.append(param); + } + continue; + }, + .r_paren => return SmallSpan{ .multi = list.toOwnedSlice() }, + .colon, .r_brace, .r_bracket => { + p.tok_i -= 1; + return p.failExpected(.r_paren); + }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + } } - const NodeParseFn = fn (p: *Parser) Error!?*Node; + const NodeParseFn = fn (p: *Parser) Error!Node.Index; - fn ListParseFn(comptime E: type, comptime nodeParseFn: anytype) ParseFn([]E) { + fn ListParseFn(comptime nodeParseFn: anytype) (fn (p: *Parser) Error!Node.SubRange) { return struct { - pub fn parse(p: *Parser) ![]E { - var list = std.ArrayList(E).init(p.gpa); + pub fn parse(p: *Parser) Error!Node.SubRange { + var list = std.ArrayList(Node.Index).init(p.gpa); defer list.deinit(); - while (try nodeParseFn(p)) |item| { + while (true) { + const item = try nodeParseFn(p); + if (item == 0) break; + try list.append(item); - switch (p.token_ids[p.tok_i]) { - .Comma => _ = p.nextToken(), + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, // all possible delimiters - .Colon, .RParen, .RBrace, .RBracket => break, + .colon, .r_paren, .r_brace, .r_bracket => break, else => { - // this is likely just a missing comma, - // continue parsing this list and give an error - try p.errors.append(p.gpa, .{ - .ExpectedToken = .{ .token = p.tok_i, .expected_id = .Comma }, - }); + // This is likely just a missing comma; + // give an error but continue parsing this list. + try p.warnExpected(.comma); }, } } - return list.toOwnedSlice(); + return p.listToSpan(list.items); } }.parse; } - fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.Tag) NodeParseFn { - return struct { - pub fn parse(p: *Parser) Error!?*Node { - const op_token = if (token == .Keyword_and) switch (p.token_ids[p.tok_i]) { - .Keyword_and => p.nextToken(), - .Invalid_ampersands => blk: { - try p.errors.append(p.gpa, .{ - .InvalidAnd = .{ .token = p.tok_i }, - }); - break :blk p.nextToken(); - }, - else => return null, - } else p.eatToken(token) orelse return null; - - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = .{ .tag = op }, - .op_token = op_token, - .lhs = undefined, // set by caller - .rhs = undefined, // set by caller - }; - return &node.base; - } - }.parse; - } - - // Helper parsers not included in the grammar - - fn parseBuiltinCall(p: *Parser) !?*Node { - const token = p.eatToken(.Builtin) orelse return null; - const params = (try p.parseFnCallArguments()) orelse { - try p.errors.append(p.gpa, .{ - .ExpectedParamList = .{ .token = p.tok_i }, + /// FnCallArguments <- LPAREN ExprList RPAREN + /// ExprList <- (Expr COMMA)* Expr? + fn parseBuiltinCall(p: *Parser) !Node.Index { + const builtin_token = p.assertToken(.builtin); + if (p.token_tags[p.nextToken()] != .l_paren) { + p.tok_i -= 1; + try p.warn(.expected_param_list); + // Pretend this was an identifier so we can continue parsing. + return p.addNode(.{ + .tag = .identifier, + .main_token = builtin_token, + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, }); + } + if (p.eatToken(.r_paren)) |_| { + return p.addNode(.{ + .tag = .builtin_call_two, + .main_token = builtin_token, + .data = .{ + .lhs = 0, + .rhs = 0, + }, + }); + } + const param_one = try p.expectExpr(); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + return p.addNode(.{ + .tag = .builtin_call_two_comma, + .main_token = builtin_token, + .data = .{ + .lhs = param_one, + .rhs = 0, + }, + }); + } + }, + .r_paren => return p.addNode(.{ + .tag = .builtin_call_two, + .main_token = builtin_token, + .data = .{ + .lhs = param_one, + .rhs = 0, + }, + }), + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } + const param_two = try p.expectExpr(); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + return p.addNode(.{ + .tag = .builtin_call_two_comma, + .main_token = builtin_token, + .data = .{ + .lhs = param_one, + .rhs = param_two, + }, + }); + } + }, + .r_paren => return p.addNode(.{ + .tag = .builtin_call_two, + .main_token = builtin_token, + .data = .{ + .lhs = param_one, + .rhs = param_two, + }, + }), + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } - // lets pretend this was an identifier so we can continue parsing - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .Identifier }, - .token = token, - }; - return &node.base; - }; - defer p.gpa.free(params.list); - - const node = try Node.BuiltinCall.alloc(&p.arena.allocator, params.list.len); - node.* = .{ - .builtin_token = token, - .params_len = params.list.len, - .rparen_token = params.rparen, - }; - std.mem.copy(*Node, node.params(), params.list); - return &node.base; - } - - fn parseErrorTag(p: *Parser) !?*Node { - const doc_comments = try p.parseDocComment(); // no need to rewind on failure - const token = p.eatToken(.Identifier) orelse return null; - - const node = try p.arena.allocator.create(Node.ErrorTag); - node.* = .{ - .doc_comments = doc_comments, - .name_token = token, - }; - return &node.base; - } - - fn parseIdentifier(p: *Parser) !?*Node { - const token = p.eatToken(.Identifier) orelse return null; - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .Identifier }, - .token = token, - }; - return &node.base; - } - - fn parseAnyType(p: *Parser) !?*Node { - const token = p.eatToken(.Keyword_anytype) orelse - p.eatToken(.Keyword_var) orelse return null; // TODO remove in next release cycle - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .AnyType }, - .token = token, - }; - return &node.base; - } + var list = std.ArrayList(Node.Index).init(p.gpa); + defer list.deinit(); - fn createLiteral(p: *Parser, tag: ast.Node.Tag, token: TokenIndex) !*Node { - const result = try p.arena.allocator.create(Node.OneToken); - result.* = .{ - .base = .{ .tag = tag }, - .token = token, - }; - return &result.base; - } + try list.appendSlice(&.{ param_one, param_two }); - fn parseStringLiteralSingle(p: *Parser) !?*Node { - if (p.eatToken(.StringLiteral)) |token| { - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .StringLiteral }, - .token = token, - }; - return &node.base; + while (true) { + const param = try p.expectExpr(); + try list.append(param); + switch (p.token_tags[p.nextToken()]) { + .comma => { + if (p.eatToken(.r_paren)) |_| { + const params = try p.listToSpan(list.items); + return p.addNode(.{ + .tag = .builtin_call_comma, + .main_token = builtin_token, + .data = .{ + .lhs = params.start, + .rhs = params.end, + }, + }); + } + continue; + }, + .r_paren => { + const params = try p.listToSpan(list.items); + return p.addNode(.{ + .tag = .builtin_call, + .main_token = builtin_token, + .data = .{ + .lhs = params.start, + .rhs = params.end, + }, + }); + }, + else => { + // This is likely just a missing comma; + // give an error but continue parsing this list. + p.tok_i -= 1; + try p.warnExpected(.comma); + }, + } } - return null; } // string literal or multiline string literal - fn parseStringLiteral(p: *Parser) !?*Node { - if (try p.parseStringLiteralSingle()) |node| return node; - - if (p.eatToken(.MultilineStringLiteralLine)) |first_line| { - const start_tok_i = p.tok_i; - var tok_i = start_tok_i; - var count: usize = 1; // including first_line - while (true) : (tok_i += 1) { - switch (p.token_ids[tok_i]) { - .LineComment => continue, - .MultilineStringLiteralLine => count += 1, - else => break, - } - } - - const node = try Node.MultilineStringLiteral.alloc(&p.arena.allocator, count); - node.* = .{ .lines_len = count }; - const lines = node.lines(); - tok_i = start_tok_i; - lines[0] = first_line; - count = 1; - while (true) : (tok_i += 1) { - switch (p.token_ids[tok_i]) { - .LineComment => continue, - .MultilineStringLiteralLine => { - lines[count] = tok_i; - count += 1; + fn parseStringLiteral(p: *Parser) !Node.Index { + switch (p.token_tags[p.tok_i]) { + .string_literal => { + const main_token = p.nextToken(); + return p.addNode(.{ + .tag = .string_literal, + .main_token = main_token, + .data = .{ + .lhs = undefined, + .rhs = undefined, }, - else => break, + }); + }, + .multiline_string_literal_line => { + const first_line = p.nextToken(); + while (p.token_tags[p.tok_i] == .multiline_string_literal_line) { + p.tok_i += 1; } - } - p.tok_i = tok_i; - return &node.base; + return p.addNode(.{ + .tag = .multiline_string_literal, + .main_token = first_line, + .data = .{ + .lhs = first_line, + .rhs = p.tok_i - 1, + }, + }); + }, + else => return null_node, } - - return null; } - fn parseIntegerLiteral(p: *Parser) !?*Node { - const token = p.eatToken(.IntegerLiteral) orelse return null; - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .IntegerLiteral }, - .token = token, - }; - return &node.base; + fn expectStringLiteral(p: *Parser) !Node.Index { + const node = try p.parseStringLiteral(); + if (node == 0) { + return p.fail(.expected_string_literal); + } + return node; } - fn parseFloatLiteral(p: *Parser) !?*Node { - const token = p.eatToken(.FloatLiteral) orelse return null; - const node = try p.arena.allocator.create(Node.OneToken); - node.* = .{ - .base = .{ .tag = .FloatLiteral }, - .token = token, - }; - return &node.base; + fn expectIntegerLiteral(p: *Parser) !Node.Index { + return p.addNode(.{ + .tag = .integer_literal, + .main_token = try p.expectToken(.integer_literal), + .data = .{ + .lhs = undefined, + .rhs = undefined, + }, + }); } - fn parseTry(p: *Parser) !?*Node { - const token = p.eatToken(.Keyword_try) orelse return null; - const node = try p.arena.allocator.create(Node.SimplePrefixOp); - node.* = .{ - .base = .{ .tag = .Try }, - .op_token = token, - .rhs = undefined, // set by caller - }; - return &node.base; - } + /// KEYWORD_if LPAREN Expr RPAREN PtrPayload? Body (KEYWORD_else Payload? Body)? + fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !Node.Index { + const if_token = p.eatToken(.keyword_if) orelse return null_node; + _ = try p.expectToken(.l_paren); + const condition = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + const then_payload = try p.parsePtrPayload(); - /// IfPrefix Body (KEYWORD_else Payload? Body)? - fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !?*Node { - const node = (try p.parseIfPrefix()) orelse return null; - const if_prefix = node.cast(Node.If).?; + const then_expr = try bodyParseFn(p); + if (then_expr == 0) return p.fail(.invalid_token); - if_prefix.body = try p.expectNode(bodyParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, + const else_token = p.eatToken(.keyword_else) orelse return p.addNode(.{ + .tag = .if_simple, + .main_token = if_token, + .data = .{ + .lhs = condition, + .rhs = then_expr, + }, }); - - const else_token = p.eatToken(.Keyword_else) orelse return node; - const payload = try p.parsePayload(); - const else_expr = try p.expectNode(bodyParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, + const else_payload = try p.parsePayload(); + const else_expr = try bodyParseFn(p); + if (else_expr == 0) return p.fail(.invalid_token); + + return p.addNode(.{ + .tag = .@"if", + .main_token = if_token, + .data = .{ + .lhs = condition, + .rhs = try p.addExtra(Node.If{ + .then_expr = then_expr, + .else_expr = else_expr, + }), + }, }); - const else_node = try p.arena.allocator.create(Node.Else); - else_node.* = .{ - .else_token = else_token, - .payload = payload, - .body = else_expr, - }; - if_prefix.@"else" = else_node; - - return node; } - /// Eat a multiline doc comment - fn parseDocComment(p: *Parser) !?*Node.DocComment { - if (p.eatToken(.DocComment)) |first_line| { - while (p.eatToken(.DocComment)) |_| {} - const node = try p.arena.allocator.create(Node.DocComment); - node.* = .{ .first_line = first_line }; - return node; + /// Skips over doc comment tokens. Returns the first one, if any. + fn eatDocComments(p: *Parser) !?TokenIndex { + if (p.eatToken(.doc_comment)) |tok| { + var first_line = tok; + if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) { + try p.warnMsg(.{ + .tag = .same_line_doc_comment, + .token = tok, + }); + first_line = p.eatToken(.doc_comment) orelse return null; + } + while (p.eatToken(.doc_comment)) |_| {} + return first_line; } return null; } fn tokensOnSameLine(p: *Parser, token1: TokenIndex, token2: TokenIndex) bool { - return std.mem.indexOfScalar(u8, p.source[p.token_locs[token1].end..p.token_locs[token2].start], '\n') == null; + return std.mem.indexOfScalar(u8, p.source[p.token_starts[token1]..p.token_starts[token2]], '\n') == null; } - /// Eat a single-line doc comment on the same line as another node - fn parseAppendedDocComment(p: *Parser, after_token: TokenIndex) !?*Node.DocComment { - const comment_token = p.eatToken(.DocComment) orelse return null; - if (p.tokensOnSameLine(after_token, comment_token)) { - const node = try p.arena.allocator.create(Node.DocComment); - node.* = .{ .first_line = comment_token }; - return node; - } - p.putBackToken(comment_token); - return null; + fn eatToken(p: *Parser, tag: Token.Tag) ?TokenIndex { + return if (p.token_tags[p.tok_i] == tag) p.nextToken() else null; } - /// Op* Child - fn parsePrefixOpExpr(p: *Parser, comptime opParseFn: NodeParseFn, comptime childParseFn: NodeParseFn) Error!?*Node { - if (try opParseFn(p)) |first_op| { - var rightmost_op = first_op; - while (true) { - switch (rightmost_op.tag) { - .AddressOf, - .Await, - .BitNot, - .BoolNot, - .OptionalType, - .Negation, - .NegationWrap, - .Resume, - .Try, - => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.SimplePrefixOp).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .ArrayType => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.ArrayType).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .ArrayTypeSentinel => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.ArrayTypeSentinel).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .SliceType => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.SliceType).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .PtrType => { - var ptr_type = rightmost_op.cast(Node.PtrType).?; - // If the token encountered was **, there will be two nodes - if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) { - rightmost_op = ptr_type.rhs; - ptr_type = rightmost_op.cast(Node.PtrType).?; - } - if (try opParseFn(p)) |rhs| { - ptr_type.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .AnyFrameType => { - const prom = rightmost_op.cast(Node.AnyFrameType).?; - if (try opParseFn(p)) |rhs| { - prom.result.?.return_type = rhs; - rightmost_op = rhs; - } else break; - }, - else => unreachable, - } - } - - // If any prefix op existed, a child node on the RHS is required - switch (rightmost_op.tag) { - .AddressOf, - .Await, - .BitNot, - .BoolNot, - .OptionalType, - .Negation, - .NegationWrap, - .Resume, - .Try, - => { - const prefix_op = rightmost_op.cast(Node.SimplePrefixOp).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .ArrayType => { - const prefix_op = rightmost_op.cast(Node.ArrayType).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .ArrayTypeSentinel => { - const prefix_op = rightmost_op.cast(Node.ArrayTypeSentinel).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .PtrType => { - const prefix_op = rightmost_op.cast(Node.PtrType).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .SliceType => { - const prefix_op = rightmost_op.cast(Node.SliceType).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .AnyFrameType => { - const prom = rightmost_op.cast(Node.AnyFrameType).?; - prom.result.?.return_type = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - else => unreachable, - } - - return first_op; - } - - // Otherwise, the child node is optional - return childParseFn(p); + fn assertToken(p: *Parser, tag: Token.Tag) TokenIndex { + const token = p.nextToken(); + assert(p.token_tags[token] == tag); + return token; } - /// Child (Op Child)* - /// Child (Op Child)? - fn parseBinOpExpr( - p: *Parser, - opParseFn: NodeParseFn, - childParseFn: NodeParseFn, - chain: enum { - Once, - Infinitely, - }, - ) Error!?*Node { - var res = (try childParseFn(p)) orelse return null; - - while (try opParseFn(p)) |node| { - const right = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, + fn expectToken(p: *Parser, tag: Token.Tag) Error!TokenIndex { + const token = p.nextToken(); + if (p.token_tags[token] != tag) { + p.tok_i -= 1; // Go back so that we can recover properly. + return p.failMsg(.{ + .tag = .expected_token, + .token = token, + .extra = .{ .expected_tag = tag }, }); - const left = res; - res = node; - - if (node.castTag(.Catch)) |op| { - op.lhs = left; - op.rhs = right; - } else if (node.cast(Node.SimpleInfixOp)) |op| { - op.lhs = left; - op.rhs = right; - } - - switch (chain) { - .Once => break, - .Infinitely => continue, - } } - - return res; - } - - fn createInfixOp(p: *Parser, op_token: TokenIndex, tag: Node.Tag) !*Node { - const node = try p.arena.allocator.create(Node.SimpleInfixOp); - node.* = .{ - .base = Node{ .tag = tag }, - .op_token = op_token, - .lhs = undefined, // set by caller - .rhs = undefined, // set by caller - }; - return &node.base; - } - - fn eatToken(p: *Parser, id: Token.Id) ?TokenIndex { - return if (p.token_ids[p.tok_i] == id) p.nextToken() else null; - } - - fn expectToken(p: *Parser, id: Token.Id) Error!TokenIndex { - return (try p.expectTokenRecoverable(id)) orelse error.ParseError; + return token; } - fn expectTokenRecoverable(p: *Parser, id: Token.Id) !?TokenIndex { - const token = p.nextToken(); - if (p.token_ids[token] != id) { - try p.errors.append(p.gpa, .{ - .ExpectedToken = .{ .token = token, .expected_id = id }, - }); - // go back so that we can recover properly - p.putBackToken(token); + fn expectTokenRecoverable(p: *Parser, tag: Token.Tag) !?TokenIndex { + if (p.token_tags[p.tok_i] != tag) { + try p.warnExpected(tag); return null; + } else { + return p.nextToken(); } - return token; } fn nextToken(p: *Parser) TokenIndex { const result = p.tok_i; p.tok_i += 1; - assert(p.token_ids[result] != .LineComment); - if (p.tok_i >= p.token_ids.len) return result; - - while (true) { - if (p.token_ids[p.tok_i] != .LineComment) return result; - p.tok_i += 1; - } - } - - fn putBackToken(p: *Parser, putting_back: TokenIndex) void { - while (p.tok_i > 0) { - p.tok_i -= 1; - if (p.token_ids[p.tok_i] == .LineComment) continue; - assert(putting_back == p.tok_i); - return; - } - } - - /// TODO Delete this function. I don't like the inversion of control. - fn expectNode( - p: *Parser, - parseFn: NodeParseFn, - /// if parsing fails - err: AstError, - ) Error!*Node { - return (try p.expectNodeRecoverable(parseFn, err)) orelse return error.ParseError; - } - - /// TODO Delete this function. I don't like the inversion of control. - fn expectNodeRecoverable( - p: *Parser, - parseFn: NodeParseFn, - /// if parsing fails - err: AstError, - ) !?*Node { - return (try parseFn(p)) orelse { - try p.errors.append(p.gpa, err); - return null; - }; + return result; } }; -fn ParseFn(comptime T: type) type { - return fn (p: *Parser) Error!T; -} - -test "std.zig.parser" { +test { _ = @import("parser_test.zig"); } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 1205dbadab..2b9e3fb03c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,277 +1,36 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -test "zig fmt: convert var to anytype" { - // TODO remove in next release cycle + +// TODO Remove this after zig 0.9.0 is released. +test "zig fmt: rewrite inline functions as callconv(.Inline)" { try testTransform( - \\pub fn main( - \\ a: var, - \\ bar: var, - \\) void {} + \\inline fn foo() void {} + \\ , - \\pub fn main( - \\ a: anytype, - \\ bar: anytype, - \\) void {} + \\fn foo() callconv(.Inline) void {} \\ ); } -test "zig fmt: noasync to nosuspend" { - // TODO: remove this - try testTransform( - \\pub fn main() void { - \\ noasync call(); - \\} - , - \\pub fn main() void { - \\ nosuspend call(); - \\} +test "zig fmt: simple top level comptime block" { + try testCanonical( + \\// line comment + \\comptime {} \\ ); } -test "recovery: top level" { - try testError( - \\test "" {inline} - \\test "" {inline} - , &[_]Error{ - .ExpectedInlinable, - .ExpectedInlinable, - }); -} - -test "recovery: block statements" { - try testError( - \\test "" { - \\ foo + +; - \\ inline; - \\} - , &[_]Error{ - .InvalidToken, - .ExpectedInlinable, - }); -} - -test "recovery: missing comma" { - try testError( - \\test "" { - \\ switch (foo) { - \\ 2 => {} - \\ 3 => {} - \\ else => { - \\ foo && bar +; - \\ } - \\ } - \\} - , &[_]Error{ - .ExpectedToken, - .ExpectedToken, - .InvalidAnd, - .InvalidToken, - }); -} - -test "recovery: extra qualifier" { - try testError( - \\const a: *const const u8; - \\test "" - , &[_]Error{ - .ExtraConstQualifier, - .ExpectedLBrace, - }); -} - -test "recovery: missing return type" { - try testError( - \\fn foo() { - \\ a && b; - \\} - \\test "" - , &[_]Error{ - .ExpectedReturnType, - .InvalidAnd, - .ExpectedLBrace, - }); -} - -test "recovery: continue after invalid decl" { - try testError( - \\fn foo { - \\ inline; - \\} - \\pub test "" { - \\ async a && b; - \\} - , &[_]Error{ - .ExpectedToken, - .ExpectedPubItem, - .ExpectedParamList, - .InvalidAnd, - }); - try testError( - \\threadlocal test "" { - \\ @a && b; - \\} - , &[_]Error{ - .ExpectedVarDecl, - .ExpectedParamList, - .InvalidAnd, - }); -} - -test "recovery: invalid extern/inline" { - try testError( - \\inline test "" { a && b; } - , &[_]Error{ - .ExpectedFn, - .InvalidAnd, - }); - try testError( - \\extern "" test "" { a && b; } - , &[_]Error{ - .ExpectedVarDeclOrFn, - .InvalidAnd, - }); -} - -test "recovery: missing semicolon" { - try testError( - \\test "" { - \\ comptime a && b - \\ c && d - \\ @foo - \\} - , &[_]Error{ - .InvalidAnd, - .ExpectedToken, - .InvalidAnd, - .ExpectedToken, - .ExpectedParamList, - .ExpectedToken, - }); -} - -test "recovery: invalid container members" { - try testError( - \\usingnamespace; - \\foo+ - \\bar@, - \\while (a == 2) { test "" {}} - \\test "" { - \\ a && b - \\} - , &[_]Error{ - .ExpectedExpr, - .ExpectedToken, - .ExpectedToken, - .ExpectedContainerMembers, - .InvalidAnd, - .ExpectedToken, - }); -} - -test "recovery: invalid parameter" { - try testError( - \\fn main() void { - \\ a(comptime T: type) - \\} - , &[_]Error{ - .ExpectedToken, - }); -} - -test "recovery: extra '}' at top level" { - try testError( - \\}}} - \\test "" { - \\ a && b; - \\} - , &[_]Error{ - .ExpectedContainerMembers, - .ExpectedContainerMembers, - .ExpectedContainerMembers, - .InvalidAnd, - }); -} - -test "recovery: mismatched bracket at top level" { - try testError( - \\const S = struct { - \\ arr: 128]?G - \\}; - , &[_]Error{ - .ExpectedToken, - }); -} - -test "recovery: invalid global error set access" { - try testError( - \\test "" { - \\ error && foo; - \\} - , &[_]Error{ - .ExpectedToken, - .ExpectedIdentifier, - .InvalidAnd, - }); -} - -test "recovery: invalid asterisk after pointer dereference" { - try testError( - \\test "" { - \\ var sequence = "repeat".*** 10; - \\} - , &[_]Error{ - .AsteriskAfterPointerDereference, - }); - try testError( - \\test "" { - \\ var sequence = "repeat".** 10&&a; - \\} - , &[_]Error{ - .AsteriskAfterPointerDereference, - .InvalidAnd, - }); -} - -test "recovery: missing semicolon after if, for, while stmt" { - try testError( - \\test "" { - \\ if (foo) bar - \\ for (foo) |a| bar - \\ while (foo) bar - \\ a && b; - \\} - , &[_]Error{ - .ExpectedSemiOrElse, - .ExpectedSemiOrElse, - .ExpectedSemiOrElse, - .InvalidAnd, - }); -} - -test "recovery: invalid comptime" { - try testError( - \\comptime - , &[_]Error{ - .ExpectedBlockOrField, - }); -} - -test "recovery: missing block after for/while loops" { - try testError( - \\test "" { while (foo) } - , &[_]Error{ - .ExpectedBlockOrAssignment, - }); - try testError( - \\test "" { for (foo) |bar| } - , &[_]Error{ - .ExpectedBlockOrAssignment, - }); +test "zig fmt: two spaced line comments before decl" { + try testCanonical( + \\// line comment + \\ + \\// another + \\comptime {} + \\ + ); } test "zig fmt: respect line breaks after var declarations" { @@ -325,6 +84,35 @@ test "zig fmt: empty file" { ); } +test "zig fmt: file ends in comment" { + try testTransform( + \\ //foobar + , + \\//foobar + \\ + ); +} + +test "zig fmt: file ends in comment after var decl" { + try testTransform( + \\const x = 42; + \\ //foobar + , + \\const x = 42; + \\//foobar + \\ + ); +} + +test "zig fmt: doc comments on test" { + try testCanonical( + \\/// hello + \\/// world + \\test "" {} + \\ + ); +} + test "zig fmt: if statment" { try testCanonical( \\test "" { @@ -357,7 +145,7 @@ test "zig fmt: decl between fields" { \\ b: usize, \\}; , &[_]Error{ - .DeclBetweenFields, + .decl_between_fields, }); } @@ -365,7 +153,7 @@ test "zig fmt: eof after missing comma" { try testError( \\foo() , &[_]Error{ - .ExpectedToken, + .expected_token, }); } @@ -402,7 +190,7 @@ test "zig fmt: nosuspend await" { ); } -test "zig fmt: trailing comma in container declaration" { +test "zig fmt: container declaration, single line" { try testCanonical( \\const X = struct { foo: i32 }; \\const X = struct { foo: i32, bar: i32 }; @@ -411,27 +199,126 @@ test "zig fmt: trailing comma in container declaration" { \\const X = struct { foo: i32 align(4) = 1, bar: i32 align(4) = 2 }; \\ ); +} + +test "zig fmt: container declaration, one item, multi line trailing comma" { try testCanonical( \\test "" { \\ comptime { \\ const X = struct { + \\ x: i32, + \\ }; + \\ } + \\} + \\ + ); +} + +test "zig fmt: container declaration, no trailing comma on separate line" { + try testTransform( + \\test "" { + \\ comptime { + \\ const X = struct { \\ x: i32 \\ }; \\ } \\} \\ + , + \\test "" { + \\ comptime { + \\ const X = struct { x: i32 }; + \\ } + \\} + \\ ); +} + +test "zig fmt: container declaration, line break, no trailing comma" { try testTransform( \\const X = struct { \\ foo: i32, bar: i8 }; , + \\const X = struct { foo: i32, bar: i8 }; + \\ + ); +} + +test "zig fmt: container declaration, transform trailing comma" { + try testTransform( + \\const X = struct { + \\ foo: i32, bar: i8, }; + , + \\const X = struct { + \\ foo: i32, + \\ bar: i8, + \\}; + \\ + ); +} + +test "zig fmt: remove empty lines at start/end of container decl" { + try testTransform( + \\const X = struct { + \\ + \\ foo: i32, + \\ + \\ bar: i8, + \\ + \\}; + \\ + , \\const X = struct { - \\ foo: i32, bar: i8 + \\ foo: i32, + \\ + \\ bar: i8, \\}; \\ ); } +test "zig fmt: remove empty lines at start/end of block" { + try testTransform( + \\test { + \\ + \\ if (foo) { + \\ foo(); + \\ } + \\ + \\} + \\ + , + \\test { + \\ if (foo) { + \\ foo(); + \\ } + \\} + \\ + ); +} + +test "zig fmt: allow empty line before commment at start of block" { + try testCanonical( + \\test { + \\ + \\ // foo + \\ const x = 42; + \\} + \\ + ); +} + +test "zig fmt: allow empty line before commment at start of block" { + try testCanonical( + \\test { + \\ + \\ // foo + \\ const x = 42; + \\} + \\ + ); +} + test "zig fmt: trailing comma in fn parameter list" { try testCanonical( \\pub fn f( @@ -480,6 +367,31 @@ test "zig fmt: comptime struct field" { ); } +test "zig fmt: break from block" { + try testCanonical( + \\const a = blk: { + \\ break :blk 42; + \\}; + \\const b = blk: { + \\ break :blk; + \\}; + \\const c = { + \\ break 42; + \\}; + \\const d = { + \\ break; + \\}; + \\ + ); +} + +test "zig fmt: grouped expressions (parentheses)" { + try testCanonical( + \\const r = (x + y) * (a + b); + \\ + ); +} + test "zig fmt: c pointer type" { try testCanonical( \\pub extern fn repro() [*c]const u8; @@ -535,6 +447,19 @@ test "zig fmt: anytype struct field" { ); } +test "zig fmt: array types last token" { + try testCanonical( + \\test { + \\ const x = [40]u32; + \\} + \\ + \\test { + \\ const x = [40:0]u32; + \\} + \\ + ); +} + test "zig fmt: sentinel-terminated array type" { try testCanonical( \\pub fn cStrToPrefixedFileW(s: [*:0]const u8) ![PATH_MAX_WIDE:0]u16 { @@ -553,6 +478,58 @@ test "zig fmt: sentinel-terminated slice type" { ); } +test "zig fmt: pointer-to-one with modifiers" { + try testCanonical( + \\const x: *u32 = undefined; + \\const y: *allowzero align(8) const volatile u32 = undefined; + \\const z: *allowzero align(8:4:2) const volatile u32 = undefined; + \\ + ); +} + +test "zig fmt: pointer-to-many with modifiers" { + try testCanonical( + \\const x: [*]u32 = undefined; + \\const y: [*]allowzero align(8) const volatile u32 = undefined; + \\const z: [*]allowzero align(8:4:2) const volatile u32 = undefined; + \\ + ); +} + +test "zig fmt: sentinel pointer with modifiers" { + try testCanonical( + \\const x: [*:42]u32 = undefined; + \\const y: [*:42]allowzero align(8) const volatile u32 = undefined; + \\const y: [*:42]allowzero align(8:4:2) const volatile u32 = undefined; + \\ + ); +} + +test "zig fmt: c pointer with modifiers" { + try testCanonical( + \\const x: [*c]u32 = undefined; + \\const y: [*c]allowzero align(8) const volatile u32 = undefined; + \\const z: [*c]allowzero align(8:4:2) const volatile u32 = undefined; + \\ + ); +} + +test "zig fmt: slice with modifiers" { + try testCanonical( + \\const x: []u32 = undefined; + \\const y: []allowzero align(8) const volatile u32 = undefined; + \\ + ); +} + +test "zig fmt: sentinel slice with modifiers" { + try testCanonical( + \\const x: [:42]u32 = undefined; + \\const y: [:42]allowzero align(8) const volatile u32 = undefined; + \\ + ); +} + test "zig fmt: anon literal in array" { try testCanonical( \\var arr: [2]Foo = .{ @@ -581,19 +558,328 @@ test "zig fmt: alignment in anonymous literal" { ); } -test "zig fmt: anon struct literal syntax" { +test "zig fmt: anon struct literal 0 element" { try testCanonical( - \\const x = .{ - \\ .a = b, - \\ .c = d, - \\}; + \\test { + \\ const x = .{}; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 1 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 1 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 2 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b, .c = d }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 2 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ .c = d, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 3 element" { + try testCanonical( + \\test { + \\ const x = .{ .a = b, .c = d, .e = f }; + \\} + \\ + ); +} + +test "zig fmt: anon struct literal 3 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ .a = b, + \\ .c = d, + \\ .e = f, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 0 element" { + try testCanonical( + \\test { + \\ const x = X{}; + \\} + \\ + ); +} + +test "zig fmt: struct literal 1 element" { + try testCanonical( + \\test { + \\ const x = X{ .a = b }; + \\} + \\ + ); +} + +test "zig fmt: Unicode code point literal larger than u8" { + try testCanonical( + \\test { + \\ const x = X{ + \\ .a = b, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 2 element" { + try testCanonical( + \\test { + \\ const x = X{ .a = b, .c = d }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 2 element comma" { + try testCanonical( + \\test { + \\ const x = X{ + \\ .a = b, + \\ .c = d, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 3 element" { + try testCanonical( + \\test { + \\ const x = X{ .a = b, .c = d, .e = f }; + \\} + \\ + ); +} + +test "zig fmt: struct literal 3 element comma" { + try testCanonical( + \\test { + \\ const x = X{ + \\ .a = b, + \\ .c = d, + \\ .e = f, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 1 element" { + try testCanonical( + \\test { + \\ const x = .{a}; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 1 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ a, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 2 element" { + try testCanonical( + \\test { + \\ const x = .{ a, b }; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 2 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ a, + \\ b, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 3 element" { + try testCanonical( + \\test { + \\ const x = .{ a, b, c }; + \\} + \\ + ); +} + +test "zig fmt: anon list literal 3 element comma" { + try testCanonical( + \\test { + \\ const x = .{ + \\ a, + \\ // foo + \\ b, + \\ + \\ c, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: array literal 0 element" { + try testCanonical( + \\test { + \\ const x = [_]u32{}; + \\} + \\ + ); +} + +test "zig fmt: array literal 1 element" { + try testCanonical( + \\test { + \\ const x = [_]u32{a}; + \\} + \\ + ); +} + +test "zig fmt: array literal 1 element comma" { + try testCanonical( + \\test { + \\ const x = [1]u32{ + \\ a, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: array literal 2 element" { + try testCanonical( + \\test { + \\ const x = [_]u32{ a, b }; + \\} \\ ); } -test "zig fmt: anon list literal syntax" { +test "zig fmt: array literal 2 element comma" { try testCanonical( - \\const x = .{ a, b, c }; + \\test { + \\ const x = [2]u32{ + \\ a, + \\ b, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: array literal 3 element" { + try testCanonical( + \\test { + \\ const x = [_]u32{ a, b, c }; + \\} + \\ + ); +} + +test "zig fmt: array literal 3 element comma" { + try testCanonical( + \\test { + \\ const x = [3]u32{ + \\ a, + \\ b, + \\ c, + \\ }; + \\} + \\ + ); +} + +test "zig fmt: sentinel array literal 1 element" { + try testCanonical( + \\test { + \\ const x = [_:9000]u32{a}; + \\} + \\ + ); +} + +test "zig fmt: slices" { + try testCanonical( + \\const a = b[0..]; + \\const c = d[0..1]; + \\const e = f[0..1 :0]; + \\ + ); +} + +test "zig fmt: slices with spaces in bounds" { + try testCanonical( + \\const a = b[0 + 0 ..]; + \\const c = d[0 + 0 .. 1]; + \\const e = f[0 .. 1 + 1 :0]; + \\ + ); +} + +test "zig fmt: block in slice expression" { + try testCanonical( + \\const a = b[{ + \\ _ = x; + \\}..]; + \\const c = d[0..{ + \\ _ = x; + \\ _ = y; + \\}]; + \\const e = f[0..1 :{ + \\ _ = x; + \\ _ = y; + \\ _ = z; + \\}]; \\ ); } @@ -651,6 +937,25 @@ test "zig fmt: tagged union with enum values" { ); } +test "zig fmt: tagged union enum tag last token" { + try testCanonical( + \\test { + \\ const U = union(enum(u32)) {}; + \\} + \\ + \\test { + \\ const U = union(enum(u32)) { foo }; + \\} + \\ + \\test { + \\ const U = union(enum(u32)) { + \\ foo, + \\ }; + \\} + \\ + ); +} + test "zig fmt: allowzero pointer" { try testCanonical( \\const T = [*]allowzero const u8; @@ -729,23 +1034,6 @@ test "zig fmt: linksection" { ); } -test "zig fmt: correctly move doc comments on struct fields" { - try testTransform( - \\pub const section_64 = extern struct { - \\ sectname: [16]u8, /// name of this section - \\ segname: [16]u8, /// segment this section goes in - \\}; - , - \\pub const section_64 = extern struct { - \\ /// name of this section - \\ sectname: [16]u8, - \\ /// segment this section goes in - \\ segname: [16]u8, - \\}; - \\ - ); -} - test "zig fmt: correctly space struct fields with doc comments" { try testTransform( \\pub const S = struct { @@ -934,6 +1222,32 @@ test "zig fmt: doc and line comment following 'zig fmt: on'" { ); } +test "zig fmt: 'zig fmt: (off|on)' works in the middle of code" { + try testTransform( + \\test "" { + \\ const x = 42; + \\ + \\ if (foobar) |y| { + \\ // zig fmt: off + \\ }// zig fmt: on + \\ + \\ const z = 420; + \\} + \\ + , + \\test "" { + \\ const x = 42; + \\ + \\ if (foobar) |y| { + \\ // zig fmt: off + \\ }// zig fmt: on + \\ + \\ const z = 420; + \\} + \\ + ); +} + test "zig fmt: pointer of unknown length" { try testCanonical( \\fn foo(ptr: [*]u8) void {} @@ -975,6 +1289,23 @@ test "zig fmt: 2nd arg multiline string" { \\} \\ ); + try testTransform( + \\comptime { + \\ cases.addAsm("hello world linux x86_64", + \\ \\.text + \\ , "Hello, world!\n",); + \\} + , + \\comptime { + \\ cases.addAsm( + \\ "hello world linux x86_64", + \\ \\.text + \\ , + \\ "Hello, world!\n", + \\ ); + \\} + \\ + ); } test "zig fmt: 2nd arg multiline string many args" { @@ -1104,7 +1435,7 @@ test "zig fmt: if condition has line break but must not wrap" { ); } -test "zig fmt: if condition has line break but must not wrap" { +test "zig fmt: if condition has line break but must not wrap (no fn call comma)" { try testCanonical( \\comptime { \\ if (self.user_input_options.put(name, UserInputOption{ @@ -1137,31 +1468,6 @@ test "zig fmt: function call with multiline argument" { ); } -test "zig fmt: same-line doc comment on variable declaration" { - try testTransform( - \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space - \\pub const MAP_FILE = 0x0000; /// map from file (default) - \\ - \\pub const EMEDIUMTYPE = 124; /// Wrong medium type - \\ - \\// nameserver query return codes - \\pub const ENSROK = 0; /// DNS server returned answer with no data - , - \\/// allocated from memory, swap space - \\pub const MAP_ANONYMOUS = 0x1000; - \\/// map from file (default) - \\pub const MAP_FILE = 0x0000; - \\ - \\/// Wrong medium type - \\pub const EMEDIUMTYPE = 124; - \\ - \\// nameserver query return codes - \\/// DNS server returned answer with no data - \\pub const ENSROK = 0; - \\ - ); -} - test "zig fmt: if-else with comment before else" { try testCanonical( \\comptime { @@ -1190,12 +1496,14 @@ test "zig fmt: if nested" { \\ GE_EQUAL \\ else \\ GE_GREATER + \\ // comment \\ else if (aInt > bInt) \\ GE_LESS \\ else if (aInt == bInt) \\ GE_EQUAL \\ else \\ GE_GREATER; + \\ // comment \\} \\ ); @@ -1275,8 +1583,11 @@ test "zig fmt: struct literal no trailing comma" { \\const a = foo{ .x = 1, .y = 2 }; \\const a = foo{ .x = 1, \\ .y = 2 }; + \\const a = foo{ .x = 1, + \\ .y = 2, }; , \\const a = foo{ .x = 1, .y = 2 }; + \\const a = foo{ .x = 1, .y = 2 }; \\const a = foo{ \\ .x = 1, \\ .y = 2, @@ -1291,17 +1602,26 @@ test "zig fmt: struct literal containing a multiline expression" { \\const a = A{ .x = if (f1()) 10 else 20, }; \\const a = A{ .x = if (f1()) \\ 10 else 20 }; + \\const a = A{ .x = if (f1()) + \\ 10 else 20,}; \\const a = A{ .x = if (f1()) 10 else 20, .y = f2() + 100 }; \\const a = A{ .x = if (f1()) 10 else 20, .y = f2() + 100, }; \\const a = A{ .x = if (f1()) \\ 10 else 20}; + \\const a = A{ .x = if (f1()) + \\ 10 else 20,}; \\const a = A{ .x = switch(g) {0 => "ok", else => "no"} }; + \\const a = A{ .x = switch(g) {0 => "ok", else => "no"}, }; \\ , \\const a = A{ .x = if (f1()) 10 else 20 }; \\const a = A{ \\ .x = if (f1()) 10 else 20, \\}; + \\const a = A{ .x = if (f1()) + \\ 10 + \\else + \\ 20 }; \\const a = A{ \\ .x = if (f1()) \\ 10 @@ -1313,12 +1633,20 @@ test "zig fmt: struct literal containing a multiline expression" { \\ .x = if (f1()) 10 else 20, \\ .y = f2() + 100, \\}; + \\const a = A{ .x = if (f1()) + \\ 10 + \\else + \\ 20 }; \\const a = A{ \\ .x = if (f1()) \\ 10 \\ else \\ 20, \\}; + \\const a = A{ .x = switch (g) { + \\ 0 => "ok", + \\ else => "no", + \\} }; \\const a = A{ \\ .x = switch (g) { \\ 0 => "ok", @@ -1368,19 +1696,19 @@ test "zig fmt: array literal with hint" { \\}; , \\const a = []u8{ - \\ 1, 2, + \\ 1, 2, // \\ 3, 4, \\ 5, 6, \\ 7, \\}; \\const a = []u8{ - \\ 1, 2, + \\ 1, 2, // \\ 3, 4, \\ 5, 6, \\ 7, 8, \\}; \\const a = []u8{ - \\ 1, 2, + \\ 1, 2, // \\ 3, 4, \\ 5, \\ 6, // blah @@ -1388,21 +1716,19 @@ test "zig fmt: array literal with hint" { \\ 8, \\}; \\const a = []u8{ - \\ 1, 2, + \\ 1, 2, // \\ 3, // \\ 4, - \\ 5, 6, + \\ 5, + \\ 6, \\ 7, \\}; \\const a = []u8{ \\ 1, \\ 2, - \\ 3, - \\ 4, - \\ 5, - \\ 6, - \\ 7, - \\ 8, + \\ 3, 4, // + \\ 5, 6, // + \\ 7, 8, // \\}; \\ ); @@ -1508,11 +1834,21 @@ test "zig fmt: empty block with only comment" { ); } -test "zig fmt: no trailing comma on struct decl" { - try testCanonical( +test "zig fmt: trailing commas on struct decl" { + try testTransform( \\const RoundParam = struct { \\ k: usize, s: u32, t: u32 \\}; + \\const RoundParam = struct { + \\ k: usize, s: u32, t: u32, + \\}; + , + \\const RoundParam = struct { k: usize, s: u32, t: u32 }; + \\const RoundParam = struct { + \\ k: usize, + \\ s: u32, + \\ t: u32, + \\}; \\ ); } @@ -1560,11 +1896,7 @@ test "zig fmt: simple asm" { \\ : [a] "x" (-> i32) \\ : [a] "x" (1) \\ ); - \\ asm ("still not real assembly" - \\ : - \\ : - \\ : "a", "b" - \\ ); + \\ asm ("still not real assembly" ::: "a", "b"); \\} \\ ); @@ -1581,7 +1913,7 @@ test "zig fmt: nested struct literal with one item" { test "zig fmt: switch cases trailing comma" { try testTransform( - \\fn switch_cases(x: i32) void { + \\test "switch cases trailing comma"{ \\ switch (x) { \\ 1,2,3 => {}, \\ 4,5, => {}, @@ -1590,7 +1922,7 @@ test "zig fmt: switch cases trailing comma" { \\ } \\} , - \\fn switch_cases(x: i32) void { + \\test "switch cases trailing comma" { \\ switch (x) { \\ 1, 2, 3 => {}, \\ 4, @@ -1657,18 +1989,18 @@ test "zig fmt: line comment after doc comment" { ); } -test "zig fmt: float literal with exponent" { +test "zig fmt: bit field alignment" { try testCanonical( - \\test "bit field alignment" { + \\test { \\ assert(@TypeOf(&blah.b) == *align(1:3:6) const u3); \\} \\ ); } -test "zig fmt: float literal with exponent" { +test "zig fmt: nested switch" { try testCanonical( - \\test "aoeu" { + \\test { \\ switch (state) { \\ TermState.Start => switch (c) { \\ '\x1b' => state = TermState.Escape, @@ -1679,6 +2011,7 @@ test "zig fmt: float literal with exponent" { \\ ); } + test "zig fmt: float literal with exponent" { try testCanonical( \\pub const f64_true_min = 4.94065645841246544177e-324; @@ -2135,7 +2468,7 @@ test "zig fmt: preserve spacing" { test "zig fmt: return types" { try testCanonical( \\pub fn main() !void {} - \\pub fn main() anytype {} + \\pub fn main() FooBar {} \\pub fn main() i32 {} \\ ); @@ -2207,6 +2540,33 @@ test "zig fmt: return" { ); } +test "zig fmt: function attributes" { + try testCanonical( + \\export fn foo() void {} + \\pub export fn foo() void {} + \\extern fn foo() void; + \\pub extern fn foo() void; + \\extern "c" fn foo() void; + \\pub extern "c" fn foo() void; + \\noinline fn foo() void {} + \\pub noinline fn foo() void {} + \\ + ); +} + +test "zig fmt: nested pointers with ** tokens" { + try testCanonical( + \\const x: *u32 = undefined; + \\const x: **u32 = undefined; + \\const x: ***u32 = undefined; + \\const x: ****u32 = undefined; + \\const x: *****u32 = undefined; + \\const x: ******u32 = undefined; + \\const x: *******u32 = undefined; + \\ + ); +} + test "zig fmt: pointer attributes" { try testCanonical( \\extern fn f1(s: *align(*u8) u8) c_int; @@ -2220,11 +2580,11 @@ test "zig fmt: pointer attributes" { test "zig fmt: slice attributes" { try testCanonical( - \\extern fn f1(s: *align(*u8) u8) c_int; - \\extern fn f2(s: **align(1) *const *volatile u8) c_int; - \\extern fn f3(s: *align(1) const *align(1) volatile *const volatile u8) c_int; - \\extern fn f4(s: *align(1) const volatile u8) c_int; - \\extern fn f5(s: [*:0]align(1) const volatile u8) c_int; + \\extern fn f1(s: []align(*u8) u8) c_int; + \\extern fn f2(s: []align(1) []const []volatile u8) c_int; + \\extern fn f3(s: []align(1) const [:0]align(1) volatile []const volatile u8) c_int; + \\extern fn f4(s: []align(1) const volatile u8) c_int; + \\extern fn f5(s: [:0]align(1) const volatile u8) c_int; \\ ); } @@ -2241,7 +2601,7 @@ test "zig fmt: test declaration" { test "zig fmt: infix operators" { try testCanonical( - \\test "infix operators" { + \\test { \\ var i = undefined; \\ i = 2; \\ i *= 2; @@ -2345,7 +2705,7 @@ test "zig fmt: call expression" { test "zig fmt: anytype type" { try testCanonical( - \\fn print(args: anytype) anytype {} + \\fn print(args: anytype) @This() {} \\ ); } @@ -2355,17 +2715,17 @@ test "zig fmt: functions" { \\extern fn puts(s: *const u8) c_int; \\extern "c" fn puts(s: *const u8) c_int; \\export fn puts(s: *const u8) c_int; - \\inline fn puts(s: *const u8) c_int; + \\fn puts(s: *const u8) callconv(.Inline) c_int; \\noinline fn puts(s: *const u8) c_int; \\pub extern fn puts(s: *const u8) c_int; \\pub extern "c" fn puts(s: *const u8) c_int; \\pub export fn puts(s: *const u8) c_int; - \\pub inline fn puts(s: *const u8) c_int; + \\pub fn puts(s: *const u8) callconv(.Inline) c_int; \\pub noinline fn puts(s: *const u8) c_int; \\pub extern fn puts(s: *const u8) align(2 + 2) c_int; \\pub extern "c" fn puts(s: *const u8) align(2 + 2) c_int; \\pub export fn puts(s: *const u8) align(2 + 2) c_int; - \\pub inline fn puts(s: *const u8) align(2 + 2) c_int; + \\pub fn puts(s: *const u8) align(2 + 2) callconv(.Inline) c_int; \\pub noinline fn puts(s: *const u8) align(2 + 2) c_int; \\ ); @@ -2570,7 +2930,11 @@ test "zig fmt: catch" { \\test "catch" { \\ const a: anyerror!u8 = 0; \\ _ = a catch return; + \\ _ = a catch + \\ return; \\ _ = a catch |err| return; + \\ _ = a catch |err| + \\ return; \\} \\ ); @@ -2746,12 +3110,6 @@ test "zig fmt: for" { \\ d => {}, \\ }; \\ - \\ for (a) |b| - \\ switch (b) { - \\ c => {}, - \\ d => {}, - \\ }; - \\ \\ const res = for (a) |v, i| { \\ break v; \\ } else { @@ -2777,7 +3135,8 @@ test "zig fmt: for" { \\test "fix for" { \\ for (a) |x| \\ f(x) - \\ else continue; + \\ else + \\ continue; \\} \\ ); @@ -2948,7 +3307,7 @@ test "zig fmt: nosuspend" { test "zig fmt: Block after if" { try testCanonical( - \\test "Block after if" { + \\test { \\ if (true) { \\ const a = 0; \\ } @@ -2961,7 +3320,7 @@ test "zig fmt: Block after if" { ); } -test "zig fmt: use" { +test "zig fmt: usingnamespace" { try testCanonical( \\usingnamespace @import("std"); \\pub usingnamespace @import("std"); @@ -3025,10 +3384,7 @@ test "zig fmt: inline asm parameter alignment" { \\ asm volatile ( \\ \\ foo \\ \\ bar - \\ : - \\ : - \\ : "", "" - \\ ); + \\ ::: "", ""); \\ asm volatile ( \\ \\ foo \\ \\ bar @@ -3087,16 +3443,12 @@ test "zig fmt: file ends with struct field" { } test "zig fmt: comment after empty comment" { - try testTransform( + try testCanonical( \\const x = true; // \\// \\// \\//a \\ - , - \\const x = true; - \\//a - \\ ); } @@ -3113,7 +3465,8 @@ test "zig fmt: line comment in array" { , \\test "a" { \\ var arr = [_]u32{ - \\ 0, // 1, + \\ 0, + \\ // 1, \\ // 2, \\ }; \\} @@ -3141,7 +3494,8 @@ test "zig fmt: comment after params" { \\ , \\fn a( - \\ b: u32, // c: u32, + \\ b: u32, + \\ // c: u32, \\ // d: u32, \\) void {} \\ @@ -3174,13 +3528,17 @@ test "zig fmt: comment in array initializer/access" { \\ var c = b[ //aa \\ 0 \\ ]; - \\ var d = [_ + \\ var d = [ + \\ _ \\ //aa + \\ : + \\ 0 \\ ]x{ //aa \\ //bb \\ 9, \\ }; - \\ var e = d[0 + \\ var e = d[ + \\ 0 \\ //aa \\ ]; \\} @@ -3199,7 +3557,8 @@ test "zig fmt: comments at several places in struct init" { , \\var bar = Bar{ \\ .x = 10, // test - \\ .y = "test", // test + \\ .y = "test", + \\ // test \\}; \\ ); @@ -3214,7 +3573,7 @@ test "zig fmt: comments at several places in struct init" { ); } -test "zig fmt: top level doc comments" { +test "zig fmt: container doc comments" { try testCanonical( \\//! tld 1 \\//! tld 2 @@ -3235,25 +3594,25 @@ test "zig fmt: top level doc comments" { \\ //! B tld 2 \\ //! B tld 3 \\ - \\ /// b doc + \\ /// B doc \\ b: u32, \\}; \\ \\/// C doc - \\const C = struct { + \\const C = union(enum) { // comment \\ //! C tld 1 \\ //! C tld 2 \\ //! C tld 3 + \\}; \\ - \\ /// c1 doc - \\ c1: u32, - \\ - \\ //! C tld 4 - \\ //! C tld 5 - \\ //! C tld 6 + \\/// D doc + \\const D = union(Foo) { + \\ //! D tld 1 + \\ //! D tld 2 + \\ //! D tld 3 \\ - \\ /// c2 doc - \\ c2: u32, + \\ /// D doc + \\ b: u32, \\}; \\ ); @@ -3275,8 +3634,31 @@ test "zig fmt: extern without container keyword returns error" { \\const container = extern {}; \\ , &[_]Error{ - .ExpectedExpr, - .ExpectedVarDeclOrFn, + .expected_container, + }); +} + +test "zig fmt: same line doc comment returns error" { + try testError( + \\const Foo = struct{ + \\ bar: u32, /// comment + \\ foo: u32, /// comment + \\ /// commment + \\}; + \\ + \\const a = 42; /// comment + \\ + \\extern fn foo() void; /// comment + \\ + \\/// comment + \\ + , &[_]Error{ + .same_line_doc_comment, + .same_line_doc_comment, + .unattached_doc_comment, + .same_line_doc_comment, + .same_line_doc_comment, + .unattached_doc_comment, }); } @@ -3350,26 +3732,6 @@ test "zig fmt: hexadeciaml float literals with underscore separators" { ); } -test "zig fmt: convert async fn into callconv(.Async)" { - try testTransform( - \\async fn foo() void {} - , - \\fn foo() callconv(.Async) void {} - \\ - ); -} - -test "zig fmt: convert extern fn proto into callconv(.C)" { - try testTransform( - \\extern fn foo0() void {} - \\const foo1 = extern fn () void; - , - \\extern fn foo0() void {} - \\const foo1 = fn () callconv(.C) void; - \\ - ); -} - test "zig fmt: C var args" { try testCanonical( \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; @@ -3458,6 +3820,54 @@ test "zig fmt: test comments in field access chain" { ); } +test "zig fmt: allow line break before field access" { + try testCanonical( + \\test { + \\ const w = foo.bar().zippy(zag).iguessthisisok(); + \\ + \\ const x = foo + \\ .bar() + \\ . // comment + \\ // comment + \\ swooop().zippy(zag) + \\ .iguessthisisok(); + \\ + \\ const y = view.output.root.server.input_manager.default_seat.wlr_seat.name; + \\ + \\ const z = view.output.root.server + \\ .input_manager // + \\ .default_seat + \\ . // comment + \\ // another comment + \\ wlr_seat.name; + \\} + \\ + ); + try testTransform( + \\test { + \\ const x = foo. + \\ bar() + \\ .zippy(zag).iguessthisisok(); + \\ + \\ const z = view.output.root.server. + \\ input_manager. + \\ default_seat.wlr_seat.name; + \\} + \\ + , + \\test { + \\ const x = foo + \\ .bar() + \\ .zippy(zag).iguessthisisok(); + \\ + \\ const z = view.output.root.server + \\ .input_manager + \\ .default_seat.wlr_seat.name; + \\} + \\ + ); +} + test "zig fmt: Indent comma correctly after multiline string literals in arg list (trailing comma)" { try testCanonical( \\fn foo() void { @@ -3495,8 +3905,7 @@ test "zig fmt: Control flow statement as body of blockless if" { \\ \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| { \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; - \\ } else null else - \\ focused_node; + \\ } else null else focused_node; \\ \\ const zoom_node = if (focused_node == layout_first) \\ if (it.next()) { @@ -3513,14 +3922,13 @@ test "zig fmt: Control flow statement as body of blockless if" { \\ \\ const zoom_node = if (focused_node == layout_first) switch (nodes) { \\ 0 => 0, - \\ } else - \\ focused_node; + \\ } else focused_node; \\} \\ ); } -test "zig fmt: " { +test "zig fmt: regression test for #5722" { try testCanonical( \\pub fn sendViewTags(self: Self) void { \\ var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); @@ -3580,8 +3988,8 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ 0, \\ }}}}}}}}; \\ myFunc(.{ - \\ "aaaaaaa", "bbbbbb", "ccccc", - \\ "dddd", ("eee"), ("fff"), + \\ "aaaaaaa", "bbbbbb", "ccccc", + \\ "dddd", ("eee"), ("fff"), \\ ("gggg"), \\ // Line comment \\ \\Multiline String Literals can be quite long @@ -3610,9 +4018,11 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ ( \\ \\ xxx \\ ), - \\ "xxx", "xxx", + \\ "xxx", + \\ "xxx", \\ }, - \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, .{ "xxxxxxx", "xxx", "xxx", "xxx" }, + \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, + \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, \\ "aaaaaaa", "bbbbbb", "ccccc", // - \\ "dddd", ("eee"), ("fff"), \\ .{ @@ -3620,7 +4030,8 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ ( \\ \\ xxx \\ ), - \\ "xxxxxxxxxxxxxx", "xxx", + \\ "xxxxxxxxxxxxxx", + \\ "xxx", \\ }, \\ .{ \\ ( @@ -3636,10 +4047,10 @@ test "zig fmt: multiline string literals should play nice with array initializer ); } -test "zig fmt: use of comments and Multiline string literals may force the parameters over multiple lines" { +test "zig fmt: use of comments and multiline string literals may force the parameters over multiple lines" { try testCanonical( \\pub fn makeMemUndefined(qzz: []u8) i1 { - \\ cases.add( // fixed bug #2032 + \\ cases.add( // fixed bug foo \\ "compile diagnostic string for top level decl type", \\ \\export fn entry() void { \\ \\ var foo: u32 = @This(){}; @@ -3661,7 +4072,7 @@ test "zig fmt: use of comments and Multiline string literals may force the param \\const rparen = tree.prevToken( \\// the first token for the annotation expressions is the left \\// parenthesis, hence the need for two prevToken - \\ if (fn_proto.getAlignExpr()) |align_expr| + \\if (fn_proto.getAlignExpr()) |align_expr| \\ tree.prevToken(tree.prevToken(align_expr.firstToken())) \\else if (fn_proto.getSectionExpr()) |section_expr| \\ tree.prevToken(tree.prevToken(section_expr.firstToken())) @@ -3714,7 +4125,7 @@ test "zig fmt: function params should align nicely" { \\pub fn foo() void { \\ cases.addRuntimeSafety("slicing operator with sentinel", \\ \\const std = @import("std"); - \\ ++ check_panic_msg ++ + \\ ++ check_panic_msg ++ \\ \\pub fn main() void { \\ \\ var buf = [4]u8{'a','b','c',0}; \\ \\ const slice = buf[0..:0]; @@ -3725,6 +4136,419 @@ test "zig fmt: function params should align nicely" { ); } +test "zig fmt: fn proto end with anytype and comma" { + try testCanonical( + \\pub fn format( + \\ out_stream: anytype, + \\) !void {} + \\ + ); +} + +test "zig fmt: space after top level doc comment" { + try testCanonical( + \\//! top level doc comment + \\ + \\field: i32, + \\ + ); +} + +test "zig fmt: for loop with ptr payload and index" { + try testCanonical( + \\test { + \\ for (self.entries.items) |*item, i| {} + \\ for (self.entries.items) |*item, i| + \\ a = b; + \\ for (self.entries.items) |*item, i| a = b; + \\} + \\ + ); +} + +test "zig fmt: proper indent line comment after multi-line single expr while loop" { + try testCanonical( + \\test { + \\ while (a) : (b) + \\ foo(); + \\ + \\ // bar + \\ baz(); + \\} + \\ + ); +} + +test "zig fmt: function with labeled block as return type" { + try testCanonical( + \\fn foo() t: { + \\ break :t bar; + \\} { + \\ baz(); + \\} + \\ + ); +} + +test "zig fmt: extern function with missing param name" { + try testCanonical( + \\extern fn a( + \\ *b, + \\ c: *d, + \\) e; + \\extern fn f(*g, h: *i) j; + \\ + ); +} + +test "zig fmt: line comment after multiline single expr if statement with multiline string" { + try testCanonical( + \\test { + \\ if (foo) + \\ x = + \\ \\hello + \\ \\hello + \\ \\ + \\ ; + \\ + \\ // bar + \\ baz(); + \\ + \\ if (foo) + \\ x = + \\ \\hello + \\ \\hello + \\ \\ + \\ else + \\ y = + \\ \\hello + \\ \\hello + \\ \\ + \\ ; + \\ + \\ // bar + \\ baz(); + \\} + \\ + ); +} + +test "zig fmt: respect extra newline between fn and pub usingnamespace" { + try testCanonical( + \\fn foo() void { + \\ bar(); + \\} + \\ + \\pub usingnamespace baz; + \\ + ); +} + +test "zig fmt: respect extra newline between switch items" { + try testCanonical( + \\const a = switch (b) { + \\ .c => {}, + \\ + \\ .d, + \\ .e, + \\ => f, + \\}; + \\ + ); +} + +test "zig fmt: insert trailing comma if there are comments between switch values" { + try testTransform( + \\const a = switch (b) { + \\ .c => {}, + \\ + \\ .d, // foobar + \\ .e + \\ => f, + \\ + \\ .g, .h + \\ // comment + \\ => i, + \\}; + \\ + , + \\const a = switch (b) { + \\ .c => {}, + \\ + \\ .d, // foobar + \\ .e, + \\ => f, + \\ + \\ .g, + \\ .h, + \\ // comment + \\ => i, + \\}; + \\ + ); +} + +test "zig fmt: error for invalid bit range" { + try testError( + \\var x: []align(0:0:0)u8 = bar; + , &[_]Error{ + .invalid_bit_range, + }); +} + +test "zig fmt: error for invalid align" { + try testError( + \\var x: [10]align(10)u8 = bar; + , &[_]Error{ + .invalid_align, + }); +} + +test "recovery: top level" { + try testError( + \\test "" {inline} + \\test "" {inline} + , &[_]Error{ + .expected_inlinable, + .expected_inlinable, + }); +} + +test "recovery: block statements" { + try testError( + \\test "" { + \\ foo + +; + \\ inline; + \\} + , &[_]Error{ + .invalid_token, + .expected_inlinable, + }); +} + +test "recovery: missing comma" { + try testError( + \\test "" { + \\ switch (foo) { + \\ 2 => {} + \\ 3 => {} + \\ else => { + \\ foo && bar +; + \\ } + \\ } + \\} + , &[_]Error{ + .expected_token, + .expected_token, + .invalid_and, + .invalid_token, + }); +} + +test "recovery: extra qualifier" { + try testError( + \\const a: *const const u8; + \\test "" + , &[_]Error{ + .extra_const_qualifier, + .expected_block, + }); +} + +test "recovery: missing return type" { + try testError( + \\fn foo() { + \\ a && b; + \\} + \\test "" + , &[_]Error{ + .expected_return_type, + .invalid_and, + .expected_block, + }); +} + +test "recovery: continue after invalid decl" { + try testError( + \\fn foo { + \\ inline; + \\} + \\pub test "" { + \\ async a && b; + \\} + , &[_]Error{ + .expected_token, + .expected_pub_item, + .expected_param_list, + .invalid_and, + }); + try testError( + \\threadlocal test "" { + \\ @a && b; + \\} + , &[_]Error{ + .expected_var_decl, + .expected_param_list, + .invalid_and, + }); +} + +test "recovery: invalid extern/inline" { + try testError( + \\inline test "" { a && b; } + , &[_]Error{ + .expected_fn, + .invalid_and, + }); + try testError( + \\extern "" test "" { a && b; } + , &[_]Error{ + .expected_var_decl_or_fn, + .invalid_and, + }); +} + +test "recovery: missing semicolon" { + try testError( + \\test "" { + \\ comptime a && b + \\ c && d + \\ @foo + \\} + , &[_]Error{ + .invalid_and, + .expected_token, + .invalid_and, + .expected_token, + .expected_param_list, + .expected_token, + }); +} + +test "recovery: invalid container members" { + try testError( + \\usingnamespace; + \\foo+ + \\bar@, + \\while (a == 2) { test "" {}} + \\test "" { + \\ a && b + \\} + , &[_]Error{ + .expected_expr, + .expected_token, + .expected_container_members, + .invalid_and, + .expected_token, + }); +} + +// TODO after https://github.com/ziglang/zig/issues/35 is implemented, +// we should be able to recover from this *at any indentation level*, +// reporting a parse error and yet also parsing all the decls even +// inside structs. +test "recovery: extra '}' at top level" { + try testError( + \\}}} + \\test "" { + \\ a && b; + \\} + , &[_]Error{ + .expected_token, + }); +} + +test "recovery: mismatched bracket at top level" { + try testError( + \\const S = struct { + \\ arr: 128]?G + \\}; + , &[_]Error{ + .expected_token, + }); +} + +test "recovery: invalid global error set access" { + try testError( + \\test "" { + \\ error && foo; + \\} + , &[_]Error{ + .expected_token, + .expected_token, + .invalid_and, + }); +} + +test "recovery: invalid asterisk after pointer dereference" { + try testError( + \\test "" { + \\ var sequence = "repeat".*** 10; + \\} + , &[_]Error{ + .asterisk_after_ptr_deref, + }); + try testError( + \\test "" { + \\ var sequence = "repeat".** 10&&a; + \\} + , &[_]Error{ + .asterisk_after_ptr_deref, + .invalid_and, + }); +} + +test "recovery: missing semicolon after if, for, while stmt" { + try testError( + \\test "" { + \\ if (foo) bar + \\ for (foo) |a| bar + \\ while (foo) bar + \\ a && b; + \\} + , &[_]Error{ + .expected_semi_or_else, + .expected_semi_or_else, + .expected_semi_or_else, + .invalid_and, + }); +} + +test "recovery: invalid comptime" { + try testError( + \\comptime + , &[_]Error{ + .expected_block_or_field, + }); +} + +test "recovery: missing block after for/while loops" { + try testError( + \\test "" { while (foo) } + , &[_]Error{ + .expected_block_or_assignment, + }); + try testError( + \\test "" { for (foo) |bar| } + , &[_]Error{ + .expected_block_or_assignment, + }); +} + +test "recovery: missing for payload" { + try testError( + \\comptime { + \\ const a = for(a) {}; + \\ const a: for(a) {}; + \\ for(a) {} + \\} + , &[_]Error{ + .expected_loop_payload, + .expected_loop_payload, + .expected_loop_payload, + }); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; @@ -3734,29 +4558,23 @@ const maxInt = std.math.maxInt; var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *bool) ![]u8 { - const stderr = io.getStdErr().outStream(); + const stderr = io.getStdErr().writer(); - const tree = try std.zig.parse(allocator, source); - defer tree.deinit(); + var tree = try std.zig.parse(allocator, source); + defer tree.deinit(allocator); - for (tree.errors) |*parse_error| { - const token = tree.token_locs[parse_error.loc()]; - const loc = tree.tokenLocation(0, parse_error.loc()); - try stderr.print("(memory buffer):{}:{}: error: ", .{ loc.line + 1, loc.column + 1 }); + for (tree.errors) |parse_error| { + const token_start = tree.tokens.items(.start)[parse_error.token]; + const loc = tree.tokenLocation(0, parse_error.token); + try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); try tree.renderError(parse_error, stderr); - try stderr.print("\n{}\n", .{source[loc.line_start..loc.line_end]}); + try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); { var i: usize = 0; while (i < loc.column) : (i += 1) { try stderr.writeAll(" "); } - } - { - const caret_count = token.end - token.start; - var i: usize = 0; - while (i < caret_count) : (i += 1) { - try stderr.writeAll("~"); - } + try stderr.writeAll("^"); } try stderr.writeAll("\n"); } @@ -3764,12 +4582,9 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b return error.ParseError; } - var buffer = std.ArrayList(u8).init(allocator); - errdefer buffer.deinit(); - - const outStream = buffer.outStream(); - anything_changed.* = try std.zig.render(allocator, outStream, tree); - return buffer.toOwnedSlice(); + const formatted = try tree.render(allocator); + anything_changed.* = !mem.eql(u8, formatted, source); + return formatted; } fn testTransform(source: []const u8, expected_source: []const u8) !void { const needed_alloc_count = x: { @@ -3800,7 +4615,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { error.OutOfMemory => { if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { warn( - "\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", + "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\n", .{ fail_index, needed_alloc_count, @@ -3822,14 +4637,14 @@ fn testCanonical(source: []const u8) !void { return testTransform(source, source); } -const Error = @TagType(std.zig.ast.Error); +const Error = std.zig.ast.Error.Tag; fn testError(source: []const u8, expected_errors: []const Error) !void { - const tree = try std.zig.parse(std.testing.allocator, source); - defer tree.deinit(); + var tree = try std.zig.parse(std.testing.allocator, source); + defer tree.deinit(std.testing.allocator); std.testing.expect(tree.errors.len == expected_errors.len); for (expected_errors) |expected, i| { - std.testing.expect(expected == tree.errors[i]); + std.testing.expectEqual(expected, tree.errors[i].tag); } } diff --git a/lib/std/zig/perf_test.zig b/lib/std/zig/perf_test.zig index 30ae99e57c..b111170902 100644 --- a/lib/std/zig/perf_test.zig +++ b/lib/std/zig/perf_test.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -29,7 +29,7 @@ pub fn main() !void { const mb_per_sec = bytes_per_sec / (1024 * 1024); var stdout_file = std.io.getStdOut(); - const stdout = stdout_file.outStream(); + const stdout = stdout_file.writer(); try stdout.print("{:.3} MiB/s, {} KiB used \n", .{ mb_per_sec, memory_used / 1024 }); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index f73979aa6b..e12f7bc733 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("../std.zig"); const assert = std.debug.assert; const mem = std.mem; +const Allocator = std.mem.Allocator; const meta = std.meta; const ast = std.zig.ast; const Token = std.zig.Token; @@ -13,2654 +14,2557 @@ const Token = std.zig.Token; const indent_delta = 4; const asm_indent_delta = 2; -pub const Error = error{ - /// Ran out of memory allocating call stack frames to complete rendering. - OutOfMemory, -}; - -/// Returns whether anything changed -pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@TypeOf(stream).Error || Error)!bool { - // cannot render an invalid tree - std.debug.assert(tree.errors.len == 0); +pub const Error = ast.Tree.RenderError; - var change_detection_stream = std.io.changeDetectionStream(tree.source, stream); - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, change_detection_stream.writer()); +const Ais = AutoIndentingStream(std.ArrayList(u8).Writer); - try renderRoot(allocator, &auto_indenting_stream, tree); +pub fn renderTree(buffer: *std.ArrayList(u8), tree: ast.Tree) Error!void { + assert(tree.errors.len == 0); // Cannot render an invalid tree. + var auto_indenting_stream = Ais{ + .indent_delta = indent_delta, + .underlying_writer = buffer.writer(), + }; + const ais = &auto_indenting_stream; - return change_detection_stream.changeDetected(); -} + // Render all the line comments at the beginning of the file. + const comment_end_loc = tree.tokens.items(.start)[0]; + _ = try renderComments(ais, tree, 0, comment_end_loc); -fn renderRoot( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, -) (@TypeOf(ais.*).Error || Error)!void { - - // render all the line comments at the beginning of the file - for (tree.token_ids) |token_id, i| { - if (token_id != .LineComment) break; - const token_loc = tree.token_locs[i]; - try ais.writer().print("{}\n", .{mem.trimRight(u8, tree.tokenSliceLoc(token_loc), " ")}); - const next_token = tree.token_locs[i + 1]; - const loc = tree.tokenLocationLoc(token_loc.end, next_token); - if (loc.line >= 2) { - try ais.insertNewline(); - } + if (tree.tokens.items(.tag)[0] == .container_doc_comment) { + try renderContainerDocComments(ais, tree, 0); } - var decl_i: ast.NodeIndex = 0; - const root_decls = tree.root_node.decls(); + try renderMembers(buffer.allocator, ais, tree, tree.rootDecls()); - if (root_decls.len == 0) return; - while (true) { - var decl = root_decls[decl_i]; - - // This loop does the following: - // - // - Iterates through line/doc comment tokens that precedes the current - // decl. - // - Figures out the first token index (`copy_start_token_index`) which - // hasn't been copied to the output stream yet. - // - Detects `zig fmt: (off|on)` in the line comment tokens, and - // determines whether the current decl should be reformatted or not. - // - var token_index = decl.firstToken(); - var fmt_active = true; - var found_fmt_directive = false; - - var copy_start_token_index = token_index; - - while (token_index != 0) { - token_index -= 1; - const token_id = tree.token_ids[token_index]; - switch (token_id) { - .LineComment => {}, - .DocComment => { - copy_start_token_index = token_index; - continue; - }, - else => break, - } + if (ais.disabled_offset) |disabled_offset| { + try writeFixingWhitespace(ais.underlying_writer, tree.source[disabled_offset..]); + } +} - const token_loc = tree.token_locs[token_index]; - if (mem.eql(u8, mem.trim(u8, tree.tokenSliceLoc(token_loc)[2..], " "), "zig fmt: off")) { - if (!found_fmt_directive) { - fmt_active = false; - found_fmt_directive = true; - } - } else if (mem.eql(u8, mem.trim(u8, tree.tokenSliceLoc(token_loc)[2..], " "), "zig fmt: on")) { - if (!found_fmt_directive) { - fmt_active = true; - found_fmt_directive = true; - } - } - } +/// Render all members in the given slice, keeping empty lines where appropriate +fn renderMembers(gpa: *Allocator, ais: *Ais, tree: ast.Tree, members: []const ast.Node.Index) Error!void { + if (members.len == 0) return; + try renderMember(gpa, ais, tree, members[0], .newline); + for (members[1..]) |member| { + try renderExtraNewline(ais, tree, member); + try renderMember(gpa, ais, tree, member, .newline); + } +} - if (!fmt_active) { - // Reformatting is disabled for the current decl and possibly some - // more decls that follow. - // Find the next `decl` for which reformatting is re-enabled. - token_index = decl.firstToken(); - - while (!fmt_active) { - decl_i += 1; - if (decl_i >= root_decls.len) { - // If there's no next reformatted `decl`, just copy the - // remaining input tokens and bail out. - const start = tree.token_locs[copy_start_token_index].start; - try copyFixingWhitespace(ais, tree.source[start..]); - return; +fn renderMember(gpa: *Allocator, ais: *Ais, tree: ast.Tree, decl: ast.Node.Index, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + const datas = tree.nodes.items(.data); + try renderDocComments(ais, tree, tree.firstToken(decl)); + switch (tree.nodes.items(.tag)[decl]) { + .fn_decl => { + // Some examples: + // pub extern "foo" fn ... + // export fn ... + const fn_proto = datas[decl].lhs; + const fn_token = main_tokens[fn_proto]; + // Go back to the first token we should render here. + var i = fn_token; + while (i > 0) { + i -= 1; + switch (token_tags[i]) { + .keyword_extern, + .keyword_export, + .keyword_pub, + .string_literal, + .keyword_inline, + .keyword_noinline, + => continue, + + else => { + i += 1; + break; + }, } - decl = root_decls[decl_i]; - var decl_first_token_index = decl.firstToken(); - - while (token_index < decl_first_token_index) : (token_index += 1) { - const token_id = tree.token_ids[token_index]; - switch (token_id) { - .LineComment => {}, - .Eof => unreachable, - else => continue, - } - const token_loc = tree.token_locs[token_index]; - if (mem.eql(u8, mem.trim(u8, tree.tokenSliceLoc(token_loc)[2..], " "), "zig fmt: on")) { - fmt_active = true; - } else if (mem.eql(u8, mem.trim(u8, tree.tokenSliceLoc(token_loc)[2..], " "), "zig fmt: off")) { - fmt_active = false; - } + } + while (i < fn_token) : (i += 1) { + if (token_tags[i] == .keyword_inline) { + // TODO remove this special case when 0.9.0 is released. + // See the commit that introduced this comment for more details. + continue; } + try renderToken(ais, tree, i, .space); } - - // Found the next `decl` for which reformatting is enabled. Copy - // the input tokens before the `decl` that haven't been copied yet. - var copy_end_token_index = decl.firstToken(); - token_index = copy_end_token_index; - while (token_index != 0) { - token_index -= 1; - const token_id = tree.token_ids[token_index]; - switch (token_id) { - .LineComment => {}, - .DocComment => { - copy_end_token_index = token_index; - continue; + assert(datas[decl].rhs != 0); + try renderExpression(gpa, ais, tree, fn_proto, .space); + return renderExpression(gpa, ais, tree, datas[decl].rhs, space); + }, + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + => { + // Extern function prototypes are parsed as these tags. + // Go back to the first token we should render here. + const fn_token = main_tokens[decl]; + var i = fn_token; + while (i > 0) { + i -= 1; + switch (token_tags[i]) { + .keyword_extern, + .keyword_export, + .keyword_pub, + .string_literal, + .keyword_inline, + .keyword_noinline, + => continue, + + else => { + i += 1; + break; }, - else => break, } } - - const start = tree.token_locs[copy_start_token_index].start; - const end = tree.token_locs[copy_end_token_index].start; - try copyFixingWhitespace(ais, tree.source[start..end]); - } - - try renderTopLevelDecl(allocator, ais, tree, decl); - decl_i += 1; - if (decl_i >= root_decls.len) return; - try renderExtraNewline(tree, ais, root_decls[decl_i]); - } -} - -fn renderExtraNewline(tree: *ast.Tree, ais: anytype, node: *ast.Node) @TypeOf(ais.*).Error!void { - return renderExtraNewlineToken(tree, ais, node.firstToken()); -} - -fn renderExtraNewlineToken( - tree: *ast.Tree, - ais: anytype, - first_token: ast.TokenIndex, -) @TypeOf(ais.*).Error!void { - var prev_token = first_token; - if (prev_token == 0) return; - var newline_threshold: usize = 2; - while (tree.token_ids[prev_token - 1] == .DocComment) { - if (tree.tokenLocation(tree.token_locs[prev_token - 1].end, prev_token).line == 1) { - newline_threshold += 1; - } - prev_token -= 1; - } - const prev_token_end = tree.token_locs[prev_token - 1].end; - const loc = tree.tokenLocation(prev_token_end, first_token); - if (loc.line >= newline_threshold) { - try ais.insertNewline(); - } -} - -fn renderTopLevelDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node) (@TypeOf(ais.*).Error || Error)!void { - try renderContainerDecl(allocator, ais, tree, decl, .Newline); -} - -fn renderContainerDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node, space: Space) (@TypeOf(ais.*).Error || Error)!void { - switch (decl.tag) { - .FnProto => { - const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - - try renderDocComments(tree, ais, fn_proto, fn_proto.getDocComments()); - - if (fn_proto.getBodyNode()) |body_node| { - try renderExpression(allocator, ais, tree, decl, .Space); - try renderExpression(allocator, ais, tree, body_node, space); - } else { - try renderExpression(allocator, ais, tree, decl, .None); - try renderToken(tree, ais, tree.nextToken(decl.lastToken()), space); + while (i < fn_token) : (i += 1) { + try renderToken(ais, tree, i, .space); } + try renderExpression(gpa, ais, tree, decl, .none); + return renderToken(ais, tree, tree.lastToken(decl) + 1, space); // semicolon }, - .Use => { - const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl); - - if (use_decl.visib_token) |visib_token| { - try renderToken(tree, ais, visib_token, .Space); // pub + .@"usingnamespace" => { + const main_token = main_tokens[decl]; + const expr = datas[decl].lhs; + if (main_token > 0 and token_tags[main_token - 1] == .keyword_pub) { + try renderToken(ais, tree, main_token - 1, .space); // pub } - try renderToken(tree, ais, use_decl.use_token, .Space); // usingnamespace - try renderExpression(allocator, ais, tree, use_decl.expr, .None); - try renderToken(tree, ais, use_decl.semicolon_token, space); // ; + try renderToken(ais, tree, main_token, .space); // usingnamespace + try renderExpression(gpa, ais, tree, expr, .none); + return renderToken(ais, tree, tree.lastToken(expr) + 1, space); // ; }, - .VarDecl => { - const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); + .global_var_decl => return renderVarDecl(gpa, ais, tree, tree.globalVarDecl(decl)), + .local_var_decl => return renderVarDecl(gpa, ais, tree, tree.localVarDecl(decl)), + .simple_var_decl => return renderVarDecl(gpa, ais, tree, tree.simpleVarDecl(decl)), + .aligned_var_decl => return renderVarDecl(gpa, ais, tree, tree.alignedVarDecl(decl)), - try renderDocComments(tree, ais, var_decl, var_decl.getDocComments()); - try renderVarDecl(allocator, ais, tree, var_decl); + .test_decl => { + const test_token = main_tokens[decl]; + try renderToken(ais, tree, test_token, .space); + if (token_tags[test_token + 1] == .string_literal) { + try renderToken(ais, tree, test_token + 1, .space); + } + try renderExpression(gpa, ais, tree, datas[decl].rhs, space); }, - .TestDecl => { - const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); + .container_field_init => return renderContainerField(gpa, ais, tree, tree.containerFieldInit(decl), space), + .container_field_align => return renderContainerField(gpa, ais, tree, tree.containerFieldAlign(decl), space), + .container_field => return renderContainerField(gpa, ais, tree, tree.containerField(decl), space), + .@"comptime" => return renderExpression(gpa, ais, tree, decl, space), - try renderDocComments(tree, ais, test_decl, test_decl.doc_comments); - try renderToken(tree, ais, test_decl.test_token, .Space); - try renderExpression(allocator, ais, tree, test_decl.name, .Space); - try renderExpression(allocator, ais, tree, test_decl.body_node, space); - }, + .root => unreachable, + else => unreachable, + } +} - .ContainerField => { - const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); +/// Render all expressions in the slice, keeping empty lines where appropriate +fn renderExpressions(gpa: *Allocator, ais: *Ais, tree: ast.Tree, expressions: []const ast.Node.Index, space: Space) Error!void { + if (expressions.len == 0) return; + try renderExpression(gpa, ais, tree, expressions[0], space); + for (expressions[1..]) |expression| { + try renderExtraNewline(ais, tree, expression); + try renderExpression(gpa, ais, tree, expression, space); + } +} - try renderDocComments(tree, ais, field, field.doc_comments); - if (field.comptime_token) |t| { - try renderToken(tree, ais, t, .Space); // comptime - } +fn renderExpression(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + const node_tags = tree.nodes.items(.tag); + const datas = tree.nodes.items(.data); + switch (node_tags[node]) { + .identifier, + .integer_literal, + .float_literal, + .char_literal, + .true_literal, + .false_literal, + .null_literal, + .unreachable_literal, + .undefined_literal, + .anyframe_literal, + .string_literal, + => return renderToken(ais, tree, main_tokens[node], space), + + .multiline_string_literal => { + var locked_indents = ais.lockOneShotIndent(); + try ais.maybeInsertNewline(); - const src_has_trailing_comma = blk: { - const maybe_comma = tree.nextToken(field.lastToken()); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; + var i = datas[node].lhs; + while (i <= datas[node].rhs) : (i += 1) try renderToken(ais, tree, i, .newline); - // The trailing comma is emitted at the end, but if it's not present - // we still have to respect the specified `space` parameter - const last_token_space: Space = if (src_has_trailing_comma) .None else space; - - if (field.type_expr == null and field.value_expr == null) { - try renderToken(tree, ais, field.name_token, last_token_space); // name - } else if (field.type_expr != null and field.value_expr == null) { - try renderToken(tree, ais, field.name_token, .None); // name - try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : - - if (field.align_expr) |align_value_expr| { - try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type - const lparen_token = tree.prevToken(align_value_expr.firstToken()); - const align_kw = tree.prevToken(lparen_token); - const rparen_token = tree.nextToken(align_value_expr.lastToken()); - try renderToken(tree, ais, align_kw, .None); // align - try renderToken(tree, ais, lparen_token, .None); // ( - try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment - try renderToken(tree, ais, rparen_token, last_token_space); // ) - } else { - try renderExpression(allocator, ais, tree, field.type_expr.?, last_token_space); // type - } - } else if (field.type_expr == null and field.value_expr != null) { - try renderToken(tree, ais, field.name_token, .Space); // name - try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // = - try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value - } else { - try renderToken(tree, ais, field.name_token, .None); // name - try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : - - if (field.align_expr) |align_value_expr| { - try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type - const lparen_token = tree.prevToken(align_value_expr.firstToken()); - const align_kw = tree.prevToken(lparen_token); - const rparen_token = tree.nextToken(align_value_expr.lastToken()); - try renderToken(tree, ais, align_kw, .None); // align - try renderToken(tree, ais, lparen_token, .None); // ( - try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment - try renderToken(tree, ais, rparen_token, .Space); // ) - } else { - try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type - } - try renderToken(tree, ais, tree.prevToken(field.value_expr.?.firstToken()), .Space); // = - try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value - } + while (locked_indents > 0) : (locked_indents -= 1) ais.popIndent(); - if (src_has_trailing_comma) { - const comma = tree.nextToken(field.lastToken()); - try renderToken(tree, ais, comma, space); + switch (space) { + .none, .space, .newline, .skip => {}, + .semicolon => if (token_tags[i] == .semicolon) try renderToken(ais, tree, i, .newline), + .comma => if (token_tags[i] == .comma) try renderToken(ais, tree, i, .newline), + .comma_space => if (token_tags[i] == .comma) try renderToken(ais, tree, i, .space), } }, - .Comptime => { - assert(!decl.requireSemiColon()); - try renderExpression(allocator, ais, tree, decl, space); + .error_value => { + try renderToken(ais, tree, main_tokens[node], .none); + try renderToken(ais, tree, main_tokens[node] + 1, .none); + return renderToken(ais, tree, main_tokens[node] + 2, space); }, - .DocComment => { - const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl); - const kind = tree.token_ids[comment.first_line]; - try renderToken(tree, ais, comment.first_line, .Newline); - var tok_i = comment.first_line + 1; - while (true) : (tok_i += 1) { - const tok_id = tree.token_ids[tok_i]; - if (tok_id == kind) { - try renderToken(tree, ais, tok_i, .Newline); - } else if (tok_id == .LineComment) { - continue; - } else { - break; - } - } - }, - else => unreachable, - } -} + .@"anytype" => return renderToken(ais, tree, main_tokens[node], space), -fn renderExpression( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - base: *ast.Node, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - switch (base.tag) { - .Identifier, - .IntegerLiteral, - .FloatLiteral, - .StringLiteral, - .CharLiteral, - .BoolLiteral, - .NullLiteral, - .Unreachable, - .ErrorType, - .UndefinedLiteral, + .block_two, + .block_two_semicolon, => { - const casted_node = base.cast(ast.Node.OneToken).?; - return renderToken(tree, ais, casted_node.token, space); - }, - - .AnyType => { - const any_type = base.castTag(.AnyType).?; - if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { - // TODO remove in next release cycle - try ais.writer().writeAll("anytype"); - if (space == .Comma) try ais.writer().writeAll(",\n"); - return; - } - return renderToken(tree, ais, any_type.token, space); - }, - - .Block, .LabeledBlock => { - const block: struct { - label: ?ast.TokenIndex, - statements: []*ast.Node, - lbrace: ast.TokenIndex, - rbrace: ast.TokenIndex, - } = b: { - if (base.castTag(.Block)) |block| { - break :b .{ - .label = null, - .statements = block.statements(), - .lbrace = block.lbrace, - .rbrace = block.rbrace, - }; - } else if (base.castTag(.LabeledBlock)) |block| { - break :b .{ - .label = block.label, - .statements = block.statements(), - .lbrace = block.lbrace, - .rbrace = block.rbrace, - }; - } else { - unreachable; - } - }; - - if (block.label) |label| { - try renderToken(tree, ais, label, Space.None); - try renderToken(tree, ais, tree.nextToken(label), Space.Space); - } - - if (block.statements.len == 0) { - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, block.lbrace, Space.None); + const statements = [2]ast.Node.Index{ datas[node].lhs, datas[node].rhs }; + if (datas[node].lhs == 0) { + return renderBlock(gpa, ais, tree, node, statements[0..0], space); + } else if (datas[node].rhs == 0) { + return renderBlock(gpa, ais, tree, node, statements[0..1], space); } else { - ais.pushIndentNextLine(); - defer ais.popIndent(); - - try renderToken(tree, ais, block.lbrace, Space.Newline); - - for (block.statements) |statement, i| { - try renderStatement(allocator, ais, tree, statement); - - if (i + 1 < block.statements.len) { - try renderExtraNewline(tree, ais, block.statements[i + 1]); - } - } + return renderBlock(gpa, ais, tree, node, statements[0..2], space); } - return renderToken(tree, ais, block.rbrace, space); + }, + .block, + .block_semicolon, + => { + const statements = tree.extra_data[datas[node].lhs..datas[node].rhs]; + return renderBlock(gpa, ais, tree, node, statements, space); }, - .Defer => { - const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base); + .@"errdefer" => { + const defer_token = main_tokens[node]; + const payload_token = datas[node].lhs; + const expr = datas[node].rhs; - try renderToken(tree, ais, defer_node.defer_token, Space.Space); - if (defer_node.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); + try renderToken(ais, tree, defer_token, .space); + if (payload_token != 0) { + try renderToken(ais, tree, payload_token - 1, .none); // | + try renderToken(ais, tree, payload_token, .none); // identifier + try renderToken(ais, tree, payload_token + 1, .space); // | } - return renderExpression(allocator, ais, tree, defer_node.expr, space); + return renderExpression(gpa, ais, tree, expr, space); }, - .Comptime => { - const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base); - try renderToken(tree, ais, comptime_node.comptime_token, Space.Space); - return renderExpression(allocator, ais, tree, comptime_node.expr, space); + .@"defer" => { + const defer_token = main_tokens[node]; + const expr = datas[node].rhs; + try renderToken(ais, tree, defer_token, .space); + return renderExpression(gpa, ais, tree, expr, space); }, - .Nosuspend => { - const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base); - if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) { - // TODO: remove this - try ais.writer().writeAll("nosuspend "); + .@"comptime", .@"nosuspend" => { + const comptime_token = main_tokens[node]; + const block = datas[node].lhs; + try renderToken(ais, tree, comptime_token, .space); + return renderExpression(gpa, ais, tree, block, space); + }, + + .@"suspend" => { + const suspend_token = main_tokens[node]; + const body = datas[node].lhs; + if (body != 0) { + try renderToken(ais, tree, suspend_token, .space); + return renderExpression(gpa, ais, tree, body, space); } else { - try renderToken(tree, ais, nosuspend_node.nosuspend_token, Space.Space); + return renderToken(ais, tree, suspend_token, space); } - return renderExpression(allocator, ais, tree, nosuspend_node.expr, space); }, - .Suspend => { - const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); + .@"catch" => { + const main_token = main_tokens[node]; + const fallback_first = tree.firstToken(datas[node].rhs); - if (suspend_node.body) |body| { - try renderToken(tree, ais, suspend_node.suspend_token, Space.Space); - return renderExpression(allocator, ais, tree, body, space); + const same_line = tree.tokensOnSameLine(main_token, fallback_first); + const after_op_space = if (same_line) Space.space else Space.newline; + + try renderExpression(gpa, ais, tree, datas[node].lhs, .space); // target + + if (token_tags[fallback_first - 1] == .pipe) { + try renderToken(ais, tree, main_token, .space); // catch keyword + try renderToken(ais, tree, main_token + 1, .none); // pipe + try renderToken(ais, tree, main_token + 2, .none); // payload identifier + try renderToken(ais, tree, main_token + 3, after_op_space); // pipe } else { - return renderToken(tree, ais, suspend_node.suspend_token, space); + assert(token_tags[fallback_first - 1] == .keyword_catch); + try renderToken(ais, tree, main_token, after_op_space); // catch keyword } + + ais.pushIndentOneShot(); + try renderExpression(gpa, ais, tree, datas[node].rhs, space); // fallback }, - .Catch => { - const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base); + .field_access => { + const main_token = main_tokens[node]; + const field_access = datas[node]; - const op_space = Space.Space; - try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space); + try renderExpression(gpa, ais, tree, field_access.lhs, .none); - const after_op_space = blk: { - const same_line = tree.tokensOnSameLine(infix_op_node.op_token, tree.nextToken(infix_op_node.op_token)); - break :blk if (same_line) op_space else Space.Newline; - }; + // Allow a line break between the lhs and the dot if the lhs and rhs + // are on different lines. + const lhs_last_token = tree.lastToken(field_access.lhs); + const same_line = tree.tokensOnSameLine(lhs_last_token, main_token + 1); + if (!same_line) { + if (!hasComment(tree, lhs_last_token, main_token)) try ais.insertNewline(); + ais.pushIndentOneShot(); + } - try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + try renderToken(ais, tree, main_token, .none); - if (infix_op_node.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); + // This check ensures that zag() is indented in the following example: + // const x = foo + // .bar() + // . // comment + // zag(); + if (!same_line and hasComment(tree, main_token, main_token + 1)) { + ais.pushIndentOneShot(); } - ais.pushIndentOneShot(); - return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); + return renderToken(ais, tree, field_access.rhs, space); }, - .Add, - .AddWrap, - .ArrayCat, - .ArrayMult, - .Assign, - .AssignBitAnd, - .AssignBitOr, - .AssignBitShiftLeft, - .AssignBitShiftRight, - .AssignBitXor, - .AssignDiv, - .AssignSub, - .AssignSubWrap, - .AssignMod, - .AssignAdd, - .AssignAddWrap, - .AssignMul, - .AssignMulWrap, - .BangEqual, - .BitAnd, - .BitOr, - .BitShiftLeft, - .BitShiftRight, - .BitXor, - .BoolAnd, - .BoolOr, - .Div, - .EqualEqual, - .ErrorUnion, - .GreaterOrEqual, - .GreaterThan, - .LessOrEqual, - .LessThan, - .MergeErrorSets, - .Mod, - .Mul, - .MulWrap, - .Period, - .Range, - .Sub, - .SubWrap, - .OrElse, + .error_union, + .switch_range, => { - const infix_op_node = @fieldParentPtr(ast.Node.SimpleInfixOp, "base", base); - - const op_space = switch (base.tag) { - .Period, .ErrorUnion, .Range => Space.None, - else => Space.Space, - }; - try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space); - - const after_op_space = blk: { - const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); - break :blk if (loc.line == 0) op_space else Space.Newline; - }; + const infix = datas[node]; + try renderExpression(gpa, ais, tree, infix.lhs, .none); + try renderToken(ais, tree, main_tokens[node], .none); + return renderExpression(gpa, ais, tree, infix.rhs, space); + }, - { + .add, + .add_wrap, + .array_cat, + .array_mult, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_mul, + .assign_mul_wrap, + .bang_equal, + .bit_and, + .bit_or, + .bit_shift_left, + .bit_shift_right, + .bit_xor, + .bool_and, + .bool_or, + .div, + .equal_equal, + .greater_or_equal, + .greater_than, + .less_or_equal, + .less_than, + .merge_error_sets, + .mod, + .mul, + .mul_wrap, + .sub, + .sub_wrap, + .@"orelse", + => { + const infix = datas[node]; + try renderExpression(gpa, ais, tree, infix.lhs, .space); + const op_token = main_tokens[node]; + if (tree.tokensOnSameLine(op_token, op_token + 1)) { + try renderToken(ais, tree, op_token, .space); + } else { ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + try renderToken(ais, tree, op_token, .newline); + ais.popIndent(); } ais.pushIndentOneShot(); - return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); + return renderExpression(gpa, ais, tree, infix.rhs, space); }, - .BitNot, - .BoolNot, - .Negation, - .NegationWrap, - .OptionalType, - .AddressOf, + .bit_not, + .bool_not, + .negation, + .negation_wrap, + .optional_type, + .address_of, => { - const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); - try renderToken(tree, ais, casted_node.op_token, Space.None); - return renderExpression(allocator, ais, tree, casted_node.rhs, space); + try renderToken(ais, tree, main_tokens[node], .none); + return renderExpression(gpa, ais, tree, datas[node].lhs, space); }, - .Try, - .Resume, - .Await, + .@"try", + .@"resume", + .@"await", => { - const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); - try renderToken(tree, ais, casted_node.op_token, Space.Space); - return renderExpression(allocator, ais, tree, casted_node.rhs, space); + try renderToken(ais, tree, main_tokens[node], .space); + return renderExpression(gpa, ais, tree, datas[node].lhs, space); }, - .ArrayType => { - const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base); - return renderArrayType( - allocator, - ais, - tree, - array_type.op_token, - array_type.rhs, - array_type.len_expr, - null, - space, - ); + .array_type => return renderArrayType(gpa, ais, tree, tree.arrayType(node), space), + .array_type_sentinel => return renderArrayType(gpa, ais, tree, tree.arrayTypeSentinel(node), space), + + .ptr_type_aligned => return renderPtrType(gpa, ais, tree, tree.ptrTypeAligned(node), space), + .ptr_type_sentinel => return renderPtrType(gpa, ais, tree, tree.ptrTypeSentinel(node), space), + .ptr_type => return renderPtrType(gpa, ais, tree, tree.ptrType(node), space), + .ptr_type_bit_range => return renderPtrType(gpa, ais, tree, tree.ptrTypeBitRange(node), space), + + .array_init_one, .array_init_one_comma => { + var elements: [1]ast.Node.Index = undefined; + return renderArrayInit(gpa, ais, tree, tree.arrayInitOne(&elements, node), space); }, - .ArrayTypeSentinel => { - const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base); - return renderArrayType( - allocator, - ais, - tree, - array_type.op_token, - array_type.rhs, - array_type.len_expr, - array_type.sentinel, - space, - ); + .array_init_dot_two, .array_init_dot_two_comma => { + var elements: [2]ast.Node.Index = undefined; + return renderArrayInit(gpa, ais, tree, tree.arrayInitDotTwo(&elements, node), space); + }, + .array_init_dot, + .array_init_dot_comma, + => return renderArrayInit(gpa, ais, tree, tree.arrayInitDot(node), space), + .array_init, + .array_init_comma, + => return renderArrayInit(gpa, ais, tree, tree.arrayInit(node), space), + + .struct_init_one, .struct_init_one_comma => { + var fields: [1]ast.Node.Index = undefined; + return renderStructInit(gpa, ais, tree, node, tree.structInitOne(&fields, node), space); + }, + .struct_init_dot_two, .struct_init_dot_two_comma => { + var fields: [2]ast.Node.Index = undefined; + return renderStructInit(gpa, ais, tree, node, tree.structInitDotTwo(&fields, node), space); + }, + .struct_init_dot, + .struct_init_dot_comma, + => return renderStructInit(gpa, ais, tree, node, tree.structInitDot(node), space), + .struct_init, + .struct_init_comma, + => return renderStructInit(gpa, ais, tree, node, tree.structInit(node), space), + + .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => { + var params: [1]ast.Node.Index = undefined; + return renderCall(gpa, ais, tree, tree.callOne(¶ms, node), space); }, - .PtrType => { - const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base); - const op_tok_id = tree.token_ids[ptr_type.op_token]; - switch (op_tok_id) { - .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'), - .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier) - try ais.writer().writeAll("[*c") - else - try ais.writer().writeAll("[*"), - else => unreachable, - } - if (ptr_type.ptr_info.sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, ais, colon_token, Space.None); // : - const sentinel_space = switch (op_tok_id) { - .LBracket => Space.None, - else => Space.Space, - }; - try renderExpression(allocator, ais, tree, sentinel, sentinel_space); - } - switch (op_tok_id) { - .Asterisk, .AsteriskAsterisk => {}, - .LBracket => try ais.writer().writeByte(']'), - else => unreachable, - } - if (ptr_type.ptr_info.allowzero_token) |allowzero_token| { - try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero - } - if (ptr_type.ptr_info.align_info) |align_info| { - const lparen_token = tree.prevToken(align_info.node.firstToken()); - const align_token = tree.prevToken(lparen_token); - - try renderToken(tree, ais, align_token, Space.None); // align - try renderToken(tree, ais, lparen_token, Space.None); // ( + .call, + .call_comma, + .async_call, + .async_call_comma, + => return renderCall(gpa, ais, tree, tree.callFull(node), space), + + .array_access => { + const suffix = datas[node]; + const lbracket = tree.firstToken(suffix.rhs) - 1; + const rbracket = tree.lastToken(suffix.rhs) + 1; + const one_line = tree.tokensOnSameLine(lbracket, rbracket); + const inner_space = if (one_line) Space.none else Space.newline; + try renderExpression(gpa, ais, tree, suffix.lhs, .none); + ais.pushIndentNextLine(); + try renderToken(ais, tree, lbracket, inner_space); // [ + try renderExpression(gpa, ais, tree, suffix.rhs, inner_space); + ais.popIndent(); + return renderToken(ais, tree, rbracket, space); // ] + }, - try renderExpression(allocator, ais, tree, align_info.node, Space.None); + .slice_open => return renderSlice(gpa, ais, tree, tree.sliceOpen(node), space), + .slice => return renderSlice(gpa, ais, tree, tree.slice(node), space), + .slice_sentinel => return renderSlice(gpa, ais, tree, tree.sliceSentinel(node), space), - if (align_info.bit_range) |bit_range| { - const colon1 = tree.prevToken(bit_range.start.firstToken()); - const colon2 = tree.prevToken(bit_range.end.firstToken()); + .deref => { + try renderExpression(gpa, ais, tree, datas[node].lhs, .none); + return renderToken(ais, tree, main_tokens[node], space); + }, - try renderToken(tree, ais, colon1, Space.None); // : - try renderExpression(allocator, ais, tree, bit_range.start, Space.None); - try renderToken(tree, ais, colon2, Space.None); // : - try renderExpression(allocator, ais, tree, bit_range.end, Space.None); + .unwrap_optional => { + try renderExpression(gpa, ais, tree, datas[node].lhs, .none); + try renderToken(ais, tree, main_tokens[node], .none); + return renderToken(ais, tree, datas[node].rhs, space); + }, - const rparen_token = tree.nextToken(bit_range.end.lastToken()); - try renderToken(tree, ais, rparen_token, Space.Space); // ) - } else { - const rparen_token = tree.nextToken(align_info.node.lastToken()); - try renderToken(tree, ais, rparen_token, Space.Space); // ) - } + .@"break" => { + const main_token = main_tokens[node]; + const label_token = datas[node].lhs; + const target = datas[node].rhs; + if (label_token == 0 and target == 0) { + try renderToken(ais, tree, main_token, space); // break keyword + } else if (label_token == 0 and target != 0) { + try renderToken(ais, tree, main_token, .space); // break keyword + try renderExpression(gpa, ais, tree, target, space); + } else if (label_token != 0 and target == 0) { + try renderToken(ais, tree, main_token, .space); // break keyword + try renderToken(ais, tree, label_token - 1, .none); // colon + try renderToken(ais, tree, label_token, space); // identifier + } else if (label_token != 0 and target != 0) { + try renderToken(ais, tree, main_token, .space); // break keyword + try renderToken(ais, tree, label_token - 1, .none); // colon + try renderToken(ais, tree, label_token, .space); // identifier + try renderExpression(gpa, ais, tree, target, space); } - if (ptr_type.ptr_info.const_token) |const_token| { - try renderToken(tree, ais, const_token, Space.Space); // const - } - if (ptr_type.ptr_info.volatile_token) |volatile_token| { - try renderToken(tree, ais, volatile_token, Space.Space); // volatile - } - return renderExpression(allocator, ais, tree, ptr_type.rhs, space); }, - .SliceType => { - const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base); - try renderToken(tree, ais, slice_type.op_token, Space.None); // [ - if (slice_type.ptr_info.sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, ais, colon_token, Space.None); // : - try renderExpression(allocator, ais, tree, sentinel, Space.None); - try renderToken(tree, ais, tree.nextToken(sentinel.lastToken()), Space.None); // ] + .@"continue" => { + const main_token = main_tokens[node]; + const label = datas[node].lhs; + if (label != 0) { + try renderToken(ais, tree, main_token, .space); // continue + try renderToken(ais, tree, label - 1, .none); // : + return renderToken(ais, tree, label, space); // label } else { - try renderToken(tree, ais, tree.nextToken(slice_type.op_token), Space.None); // ] - } - - if (slice_type.ptr_info.allowzero_token) |allowzero_token| { - try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero - } - if (slice_type.ptr_info.align_info) |align_info| { - const lparen_token = tree.prevToken(align_info.node.firstToken()); - const align_token = tree.prevToken(lparen_token); - - try renderToken(tree, ais, align_token, Space.None); // align - try renderToken(tree, ais, lparen_token, Space.None); // ( - - try renderExpression(allocator, ais, tree, align_info.node, Space.None); - - if (align_info.bit_range) |bit_range| { - const colon1 = tree.prevToken(bit_range.start.firstToken()); - const colon2 = tree.prevToken(bit_range.end.firstToken()); - - try renderToken(tree, ais, colon1, Space.None); // : - try renderExpression(allocator, ais, tree, bit_range.start, Space.None); - try renderToken(tree, ais, colon2, Space.None); // : - try renderExpression(allocator, ais, tree, bit_range.end, Space.None); - - const rparen_token = tree.nextToken(bit_range.end.lastToken()); - try renderToken(tree, ais, rparen_token, Space.Space); // ) - } else { - const rparen_token = tree.nextToken(align_info.node.lastToken()); - try renderToken(tree, ais, rparen_token, Space.Space); // ) - } - } - if (slice_type.ptr_info.const_token) |const_token| { - try renderToken(tree, ais, const_token, Space.Space); - } - if (slice_type.ptr_info.volatile_token) |volatile_token| { - try renderToken(tree, ais, volatile_token, Space.Space); + return renderToken(ais, tree, main_token, space); // continue } - return renderExpression(allocator, ais, tree, slice_type.rhs, space); }, - .ArrayInitializer, .ArrayInitializerDot => { - var rtoken: ast.TokenIndex = undefined; - var exprs: []*ast.Node = undefined; - const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.tag) { - .ArrayInitializerDot => blk: { - const casted = @fieldParentPtr(ast.Node.ArrayInitializerDot, "base", base); - rtoken = casted.rtoken; - exprs = casted.list(); - break :blk .{ .dot = casted.dot }; - }, - .ArrayInitializer => blk: { - const casted = @fieldParentPtr(ast.Node.ArrayInitializer, "base", base); - rtoken = casted.rtoken; - exprs = casted.list(); - break :blk .{ .node = casted.lhs }; - }, - else => unreachable, - }; - - const lbrace = switch (lhs) { - .dot => |dot| tree.nextToken(dot), - .node => |node| tree.nextToken(node.lastToken()), - }; - - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - - if (exprs.len == 0) { - try renderToken(tree, ais, lbrace, Space.None); - return renderToken(tree, ais, rtoken, space); - } - - if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { - const expr = exprs[0]; - - try renderToken(tree, ais, lbrace, Space.None); - try renderExpression(allocator, ais, tree, expr, Space.None); - return renderToken(tree, ais, rtoken, space); - } - - // scan to find row size - if (rowSize(tree, exprs, rtoken) != null) { - { - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, lbrace, Space.Newline); - - var expr_index: usize = 0; - while (rowSize(tree, exprs[expr_index..], rtoken)) |row_size| { - const row_exprs = exprs[expr_index..]; - // A place to store the width of each expression and its column's maximum - var widths = try allocator.alloc(usize, row_exprs.len + row_size); - defer allocator.free(widths); - mem.set(usize, widths, 0); - - var expr_newlines = try allocator.alloc(bool, row_exprs.len); - defer allocator.free(expr_newlines); - mem.set(bool, expr_newlines, false); - - var expr_widths = widths[0 .. widths.len - row_size]; - var column_widths = widths[widths.len - row_size ..]; - - // Find next row with trailing comment (if any) to end the current section - var section_end = sec_end: { - var this_line_first_expr: usize = 0; - var this_line_size = rowSize(tree, row_exprs, rtoken); - for (row_exprs) |expr, i| { - // Ignore comment on first line of this section - if (i == 0 or tree.tokensOnSameLine(row_exprs[0].firstToken(), expr.lastToken())) continue; - // Track start of line containing comment - if (!tree.tokensOnSameLine(row_exprs[this_line_first_expr].firstToken(), expr.lastToken())) { - this_line_first_expr = i; - this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rtoken); - } - - const maybe_comma = expr.lastToken() + 1; - const maybe_comment = expr.lastToken() + 2; - if (maybe_comment < tree.token_ids.len) { - if (tree.token_ids[maybe_comma] == .Comma and - tree.token_ids[maybe_comment] == .LineComment and - tree.tokensOnSameLine(expr.lastToken(), maybe_comment)) - { - var comment_token_loc = tree.token_locs[maybe_comment]; - const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(comment_token_loc), " ").len == 2; - if (!comment_is_empty) { - // Found row ending in comment - break :sec_end i - this_line_size.? + 1; - } - } - } - } - break :sec_end row_exprs.len; - }; - expr_index += section_end; - - const section_exprs = row_exprs[0..section_end]; - - // Null stream for counting the printed length of each expression - var line_find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); - var counting_stream = std.io.countingOutStream(line_find_stream.writer()); - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); - - // Calculate size of columns in current section - var column_counter: usize = 0; - var single_line = true; - for (section_exprs) |expr, i| { - if (i + 1 < section_exprs.len) { - counting_stream.bytes_written = 0; - line_find_stream.byte_found = false; - try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); - const width = @intCast(usize, counting_stream.bytes_written); - expr_widths[i] = width; - expr_newlines[i] = line_find_stream.byte_found; - - if (!line_find_stream.byte_found) { - const column = column_counter % row_size; - column_widths[column] = std.math.max(column_widths[column], width); - - const expr_last_token = expr.*.lastToken() + 1; - const next_expr = section_exprs[i + 1]; - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, next_expr.*.firstToken()); - - column_counter += 1; - - if (loc.line != 0) single_line = false; - } else { - single_line = false; - column_counter = 0; - } - } else { - counting_stream.bytes_written = 0; - try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); - const width = @intCast(usize, counting_stream.bytes_written); - expr_widths[i] = width; - expr_newlines[i] = line_find_stream.byte_found; - - if (!line_find_stream.byte_found) { - const column = column_counter % row_size; - column_widths[column] = std.math.max(column_widths[column], width); - } - break; - } - } - - // Render exprs in current section - column_counter = 0; - var last_col_index: usize = row_size - 1; - for (section_exprs) |expr, i| { - if (i + 1 < section_exprs.len) { - const next_expr = section_exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - - const comma = tree.nextToken(expr.*.lastToken()); - - if (column_counter != last_col_index) { - if (!expr_newlines[i] and !expr_newlines[i + 1]) { - // Neither the current or next expression is multiline - try renderToken(tree, ais, comma, Space.Space); // , - assert(column_widths[column_counter % row_size] >= expr_widths[i]); - const padding = column_widths[column_counter % row_size] - expr_widths[i]; - try ais.writer().writeByteNTimes(' ', padding); - - column_counter += 1; - continue; - } - } - if (single_line and row_size != 1) { - try renderToken(tree, ais, comma, Space.Space); // , - continue; - } - - column_counter = 0; - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewline(tree, ais, next_expr); - } else { - const maybe_comma = tree.nextToken(expr.*.lastToken()); - if (tree.token_ids[maybe_comma] == .Comma) { - try renderExpression(allocator, ais, tree, expr, Space.None); // , - try renderToken(tree, ais, maybe_comma, Space.Newline); // , - } else { - try renderExpression(allocator, ais, tree, expr, Space.Comma); // , - } - } - } - - if (expr_index == exprs.len) { - break; - } - } - } - - return renderToken(tree, ais, rtoken, space); - } - - // Single line - try renderToken(tree, ais, lbrace, Space.Space); - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const next_expr = exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - const comma = tree.nextToken(expr.*.lastToken()); - try renderToken(tree, ais, comma, Space.Space); // , - } else { - try renderExpression(allocator, ais, tree, expr, Space.Space); - } + .@"return" => { + if (datas[node].lhs != 0) { + try renderToken(ais, tree, main_tokens[node], .space); + try renderExpression(gpa, ais, tree, datas[node].lhs, space); + } else { + try renderToken(ais, tree, main_tokens[node], space); } - - return renderToken(tree, ais, rtoken, space); }, - .StructInitializer, .StructInitializerDot => { - var rtoken: ast.TokenIndex = undefined; - var field_inits: []*ast.Node = undefined; - const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.tag) { - .StructInitializerDot => blk: { - const casted = @fieldParentPtr(ast.Node.StructInitializerDot, "base", base); - rtoken = casted.rtoken; - field_inits = casted.list(); - break :blk .{ .dot = casted.dot }; - }, - .StructInitializer => blk: { - const casted = @fieldParentPtr(ast.Node.StructInitializer, "base", base); - rtoken = casted.rtoken; - field_inits = casted.list(); - break :blk .{ .node = casted.lhs }; - }, - else => unreachable, - }; - - const lbrace = switch (lhs) { - .dot => |dot| tree.nextToken(dot), - .node => |node| tree.nextToken(node.lastToken()), - }; - - if (field_inits.len == 0) { - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - - { - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, lbrace, Space.None); - } - - return renderToken(tree, ais, rtoken, space); - } - - const src_has_trailing_comma = blk: { - const maybe_comma = tree.prevToken(rtoken); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; - - const src_same_line = blk: { - const loc = tree.tokenLocation(tree.token_locs[lbrace].end, rtoken); - break :blk loc.line == 0; - }; - - const expr_outputs_one_line = blk: { - // render field expressions until a LF is found - for (field_inits) |field_init| { - var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer()); - - try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None); - if (find_stream.byte_found) break :blk false; - } - break :blk true; - }; - - if (field_inits.len == 1) blk: { - const field_init = field_inits[0].cast(ast.Node.FieldInitializer).?; - - switch (field_init.expr.tag) { - .StructInitializer, - .StructInitializerDot, - => break :blk, - else => {}, - } - - // if the expression outputs to multiline, make this struct multiline - if (!expr_outputs_one_line or src_has_trailing_comma) { - break :blk; - } - - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - try renderToken(tree, ais, lbrace, Space.Space); - try renderExpression(allocator, ais, tree, &field_init.base, Space.Space); - return renderToken(tree, ais, rtoken, space); - } - - if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) { - // render all on one line, no trailing comma - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - try renderToken(tree, ais, lbrace, Space.Space); + .grouped_expression => { + try renderToken(ais, tree, main_tokens[node], .none); // lparen + ais.pushIndentOneShot(); + try renderExpression(gpa, ais, tree, datas[node].lhs, .none); + return renderToken(ais, tree, datas[node].rhs, space); // rparen + }, - for (field_inits) |field_init, i| { - if (i + 1 < field_inits.len) { - try renderExpression(allocator, ais, tree, field_init, Space.None); + .container_decl, + .container_decl_trailing, + => return renderContainerDecl(gpa, ais, tree, node, tree.containerDecl(node), space), - const comma = tree.nextToken(field_init.lastToken()); - try renderToken(tree, ais, comma, Space.Space); - } else { - try renderExpression(allocator, ais, tree, field_init, Space.Space); - } - } + .container_decl_two, .container_decl_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return renderContainerDecl(gpa, ais, tree, node, tree.containerDeclTwo(&buffer, node), space); + }, + .container_decl_arg, + .container_decl_arg_trailing, + => return renderContainerDecl(gpa, ais, tree, node, tree.containerDeclArg(node), space), - return renderToken(tree, ais, rtoken, space); - } - - { - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } + .tagged_union, + .tagged_union_trailing, + => return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnion(node), space), + .tagged_union_two, .tagged_union_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnionTwo(&buffer, node), space); + }, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnionEnumTag(node), space), + + .error_set_decl => { + const error_token = main_tokens[node]; + const lbrace = error_token + 1; + const rbrace = datas[node].rhs; + + try renderToken(ais, tree, error_token, .none); + + if (lbrace + 1 == rbrace) { + // There is nothing between the braces so render condensed: `error{}` + try renderToken(ais, tree, lbrace, .none); + return renderToken(ais, tree, rbrace, space); + } else if (lbrace + 2 == rbrace and token_tags[lbrace + 1] == .identifier) { + // There is exactly one member and no trailing comma or + // comments, so render without surrounding spaces: `error{Foo}` + try renderToken(ais, tree, lbrace, .none); + try renderToken(ais, tree, lbrace + 1, .none); // identifier + return renderToken(ais, tree, rbrace, space); + } else if (token_tags[rbrace - 1] == .comma) { + // There is a trailing comma so render each member on a new line. ais.pushIndentNextLine(); - defer ais.popIndent(); - - try renderToken(tree, ais, lbrace, Space.Newline); - - for (field_inits) |field_init, i| { - if (i + 1 < field_inits.len) { - const next_field_init = field_inits[i + 1]; - try renderExpression(allocator, ais, tree, field_init, Space.None); - - const comma = tree.nextToken(field_init.lastToken()); - try renderToken(tree, ais, comma, Space.Newline); - - try renderExtraNewline(tree, ais, next_field_init); - } else { - try renderExpression(allocator, ais, tree, field_init, Space.Comma); + try renderToken(ais, tree, lbrace, .newline); + var i = lbrace + 1; + while (i < rbrace) : (i += 1) { + if (i > lbrace + 1) try renderExtraNewlineToken(ais, tree, i); + switch (token_tags[i]) { + .doc_comment => try renderToken(ais, tree, i, .newline), + .identifier => try renderToken(ais, tree, i, .comma), + .comma => {}, + else => unreachable, } } - } - - return renderToken(tree, ais, rtoken, space); - }, - - .Call => { - const call = @fieldParentPtr(ast.Node.Call, "base", base); - if (call.async_token) |async_token| { - try renderToken(tree, ais, async_token, Space.Space); - } - - try renderExpression(allocator, ais, tree, call.lhs, Space.None); - - const lparen = tree.nextToken(call.lhs.lastToken()); - - if (call.params_len == 0) { - try renderToken(tree, ais, lparen, Space.None); - return renderToken(tree, ais, call.rtoken, space); - } - - const src_has_trailing_comma = blk: { - const maybe_comma = tree.prevToken(call.rtoken); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; - - if (src_has_trailing_comma) { - { - ais.pushIndent(); - defer ais.popIndent(); - - try renderToken(tree, ais, lparen, Space.Newline); // ( - const params = call.params(); - for (params) |param_node, i| { - if (i + 1 < params.len) { - const next_node = params[i + 1]; - try renderExpression(allocator, ais, tree, param_node, Space.None); - - // Unindent the comma for multiline string literals - const maybe_multiline_string = param_node.firstToken(); - const is_multiline_string = tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine; - if (is_multiline_string) ais.popIndent(); - defer if (is_multiline_string) ais.pushIndent(); - - const comma = tree.nextToken(param_node.lastToken()); - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewline(tree, ais, next_node); - } else { - try renderExpression(allocator, ais, tree, param_node, Space.Comma); - } + ais.popIndent(); + return renderToken(ais, tree, rbrace, space); + } else { + // There is no trailing comma so render everything on one line. + try renderToken(ais, tree, lbrace, .space); + var i = lbrace + 1; + while (i < rbrace) : (i += 1) { + switch (token_tags[i]) { + .doc_comment => unreachable, // TODO + .identifier => try renderToken(ais, tree, i, .comma_space), + .comma => {}, + else => unreachable, } } - return renderToken(tree, ais, call.rtoken, space); - } - - try renderToken(tree, ais, lparen, Space.None); // ( - - const params = call.params(); - for (params) |param_node, i| { - const maybe_comment = param_node.firstToken() - 1; - const maybe_multiline_string = param_node.firstToken(); - if (tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine or tree.token_ids[maybe_comment] == .LineComment) { - ais.pushIndentOneShot(); - } - - try renderExpression(allocator, ais, tree, param_node, Space.None); - - if (i + 1 < params.len) { - const comma = tree.nextToken(param_node.lastToken()); - try renderToken(tree, ais, comma, Space.Space); - } - } - return renderToken(tree, ais, call.rtoken, space); // ) - }, - - .ArrayAccess => { - const suffix_op = base.castTag(.ArrayAccess).?; - - const lbracket = tree.nextToken(suffix_op.lhs.lastToken()); - const rbracket = tree.nextToken(suffix_op.index_expr.lastToken()); - - try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); - try renderToken(tree, ais, lbracket, Space.None); // [ - - const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; - const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; - { - const new_space = if (ends_with_comment) Space.Newline else Space.None; - - ais.pushIndent(); - defer ais.popIndent(); - try renderExpression(allocator, ais, tree, suffix_op.index_expr, new_space); + return renderToken(ais, tree, rbrace, space); } - if (starts_with_comment) try ais.maybeInsertNewline(); - return renderToken(tree, ais, rbracket, space); // ] }, - .Slice => { - const suffix_op = base.castTag(.Slice).?; - try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); - - const lbracket = tree.prevToken(suffix_op.start.firstToken()); - const dotdot = tree.nextToken(suffix_op.start.lastToken()); - - const after_start_space_bool = nodeCausesSliceOpSpace(suffix_op.start) or - (if (suffix_op.end) |end| nodeCausesSliceOpSpace(end) else false); - const after_start_space = if (after_start_space_bool) Space.Space else Space.None; - const after_op_space = if (suffix_op.end != null) after_start_space else Space.None; - - try renderToken(tree, ais, lbracket, Space.None); // [ - try renderExpression(allocator, ais, tree, suffix_op.start, after_start_space); - try renderToken(tree, ais, dotdot, after_op_space); // .. - if (suffix_op.end) |end| { - const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None; - try renderExpression(allocator, ais, tree, end, after_end_space); - } - if (suffix_op.sentinel) |sentinel| { - const colon = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, ais, colon, Space.None); // : - try renderExpression(allocator, ais, tree, sentinel, Space.None); + .builtin_call_two, .builtin_call_two_comma => { + if (datas[node].lhs == 0) { + return renderBuiltinCall(gpa, ais, tree, main_tokens[node], &.{}, space); + } else if (datas[node].rhs == 0) { + return renderBuiltinCall(gpa, ais, tree, main_tokens[node], &.{datas[node].lhs}, space); + } else { + return renderBuiltinCall(gpa, ais, tree, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs }, space); } - return renderToken(tree, ais, suffix_op.rtoken, space); // ] }, - - .Deref => { - const suffix_op = base.castTag(.Deref).?; - - try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); - return renderToken(tree, ais, suffix_op.rtoken, space); // .* + .builtin_call, .builtin_call_comma => { + const params = tree.extra_data[datas[node].lhs..datas[node].rhs]; + return renderBuiltinCall(gpa, ais, tree, main_tokens[node], params, space); }, - .UnwrapOptional => { - const suffix_op = base.castTag(.UnwrapOptional).?; - try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); - try renderToken(tree, ais, tree.prevToken(suffix_op.rtoken), Space.None); // . - return renderToken(tree, ais, suffix_op.rtoken, space); // ? + .fn_proto_simple => { + var params: [1]ast.Node.Index = undefined; + return renderFnProto(gpa, ais, tree, tree.fnProtoSimple(¶ms, node), space); }, - - .Break => { - const flow_expr = base.castTag(.Break).?; - const maybe_rhs = flow_expr.getRHS(); - const maybe_label = flow_expr.getLabel(); - - if (maybe_label == null and maybe_rhs == null) { - return renderToken(tree, ais, flow_expr.ltoken, space); // break - } - - try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // break - if (maybe_label) |label| { - const colon = tree.nextToken(flow_expr.ltoken); - try renderToken(tree, ais, colon, Space.None); // : - - if (maybe_rhs == null) { - return renderToken(tree, ais, label, space); // label - } - try renderToken(tree, ais, label, Space.Space); // label - } - return renderExpression(allocator, ais, tree, maybe_rhs.?, space); + .fn_proto_multi => return renderFnProto(gpa, ais, tree, tree.fnProtoMulti(node), space), + .fn_proto_one => { + var params: [1]ast.Node.Index = undefined; + return renderFnProto(gpa, ais, tree, tree.fnProtoOne(¶ms, node), space); }, - - .Continue => { - const flow_expr = base.castTag(.Continue).?; - if (flow_expr.getLabel()) |label| { - try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // continue - const colon = tree.nextToken(flow_expr.ltoken); - try renderToken(tree, ais, colon, Space.None); // : - return renderToken(tree, ais, label, space); // label + .fn_proto => return renderFnProto(gpa, ais, tree, tree.fnProto(node), space), + + .anyframe_type => { + const main_token = main_tokens[node]; + if (datas[node].rhs != 0) { + try renderToken(ais, tree, main_token, .none); // anyframe + try renderToken(ais, tree, main_token + 1, .none); // -> + return renderExpression(gpa, ais, tree, datas[node].rhs, space); } else { - return renderToken(tree, ais, flow_expr.ltoken, space); // continue + return renderToken(ais, tree, main_token, space); // anyframe } }, - .Return => { - const flow_expr = base.castTag(.Return).?; - if (flow_expr.getRHS()) |rhs| { - try renderToken(tree, ais, flow_expr.ltoken, Space.Space); - return renderExpression(allocator, ais, tree, rhs, space); + .@"switch", + .switch_comma, + => { + const switch_token = main_tokens[node]; + const condition = datas[node].lhs; + const extra = tree.extraData(datas[node].rhs, ast.Node.SubRange); + const cases = tree.extra_data[extra.start..extra.end]; + const rparen = tree.lastToken(condition) + 1; + + try renderToken(ais, tree, switch_token, .space); // switch keyword + try renderToken(ais, tree, switch_token + 1, .none); // lparen + try renderExpression(gpa, ais, tree, condition, .none); // condtion expression + try renderToken(ais, tree, rparen, .space); // rparen + + ais.pushIndentNextLine(); + if (cases.len == 0) { + try renderToken(ais, tree, rparen + 1, .none); // lbrace } else { - return renderToken(tree, ais, flow_expr.ltoken, space); + try renderToken(ais, tree, rparen + 1, .newline); // lbrace + try renderExpressions(gpa, ais, tree, cases, .comma); } + ais.popIndent(); + return renderToken(ais, tree, tree.lastToken(node), space); // rbrace }, - .Payload => { - const payload = @fieldParentPtr(ast.Node.Payload, "base", base); + .switch_case_one => return renderSwitchCase(gpa, ais, tree, tree.switchCaseOne(node), space), + .switch_case => return renderSwitchCase(gpa, ais, tree, tree.switchCase(node), space), - try renderToken(tree, ais, payload.lpipe, Space.None); - try renderExpression(allocator, ais, tree, payload.error_symbol, Space.None); - return renderToken(tree, ais, payload.rpipe, space); - }, + .while_simple => return renderWhile(gpa, ais, tree, tree.whileSimple(node), space), + .while_cont => return renderWhile(gpa, ais, tree, tree.whileCont(node), space), + .@"while" => return renderWhile(gpa, ais, tree, tree.whileFull(node), space), + .for_simple => return renderWhile(gpa, ais, tree, tree.forSimple(node), space), + .@"for" => return renderWhile(gpa, ais, tree, tree.forFull(node), space), - .PointerPayload => { - const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base); + .if_simple => return renderIf(gpa, ais, tree, tree.ifSimple(node), space), + .@"if" => return renderIf(gpa, ais, tree, tree.ifFull(node), space), - try renderToken(tree, ais, payload.lpipe, Space.None); - if (payload.ptr_token) |ptr_token| { - try renderToken(tree, ais, ptr_token, Space.None); - } - try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None); - return renderToken(tree, ais, payload.rpipe, space); - }, + .asm_simple => return renderAsm(gpa, ais, tree, tree.asmSimple(node), space), + .@"asm" => return renderAsm(gpa, ais, tree, tree.asmFull(node), space), - .PointerIndexPayload => { - const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base); + .enum_literal => { + try renderToken(ais, tree, main_tokens[node] - 1, .none); // . + return renderToken(ais, tree, main_tokens[node], space); // name + }, - try renderToken(tree, ais, payload.lpipe, Space.None); - if (payload.ptr_token) |ptr_token| { - try renderToken(tree, ais, ptr_token, Space.None); - } - try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None); + .fn_decl => unreachable, + .container_field => unreachable, + .container_field_init => unreachable, + .container_field_align => unreachable, + .root => unreachable, + .global_var_decl => unreachable, + .local_var_decl => unreachable, + .simple_var_decl => unreachable, + .aligned_var_decl => unreachable, + .@"usingnamespace" => unreachable, + .test_decl => unreachable, + .asm_output => unreachable, + .asm_input => unreachable, + } +} - if (payload.index_symbol) |index_symbol| { - const comma = tree.nextToken(payload.value_symbol.lastToken()); +fn renderArrayType( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + array_type: ast.full.ArrayType, + space: Space, +) Error!void { + const rbracket = tree.firstToken(array_type.ast.elem_type) - 1; + const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket); + const inner_space = if (one_line) Space.none else Space.newline; + ais.pushIndentNextLine(); + try renderToken(ais, tree, array_type.ast.lbracket, inner_space); // lbracket + try renderExpression(gpa, ais, tree, array_type.ast.elem_count, inner_space); + if (array_type.ast.sentinel) |sentinel| { + try renderToken(ais, tree, tree.firstToken(sentinel) - 1, inner_space); // colon + try renderExpression(gpa, ais, tree, sentinel, inner_space); + } + ais.popIndent(); + try renderToken(ais, tree, rbracket, .none); // rbracket + return renderExpression(gpa, ais, tree, array_type.ast.elem_type, space); +} - try renderToken(tree, ais, comma, Space.Space); - try renderExpression(allocator, ais, tree, index_symbol, Space.None); +fn renderPtrType( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + ptr_type: ast.full.PtrType, + space: Space, +) Error!void { + switch (ptr_type.size) { + .One => { + // Since ** tokens exist and the same token is shared by two + // nested pointer types, we check to see if we are the parent + // in such a relationship. If so, skip rendering anything for + // this pointer type and rely on the child to render our asterisk + // as well when it renders the ** token. + if (tree.tokens.items(.tag)[ptr_type.ast.main_token] == .asterisk_asterisk and + ptr_type.ast.main_token == tree.nodes.items(.main_token)[ptr_type.ast.child_type]) + { + return renderExpression(gpa, ais, tree, ptr_type.ast.child_type, space); } - - return renderToken(tree, ais, payload.rpipe, space); + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // asterisk }, - - .GroupedExpression => { - const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base); - - try renderToken(tree, ais, grouped_expr.lparen, Space.None); - { - ais.pushIndentOneShot(); - try renderExpression(allocator, ais, tree, grouped_expr.expr, Space.None); + .Many => { + if (ptr_type.ast.sentinel == 0) { + try renderToken(ais, tree, ptr_type.ast.main_token - 1, .none); // lbracket + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // asterisk + try renderToken(ais, tree, ptr_type.ast.main_token + 1, .none); // rbracket + } else { + try renderToken(ais, tree, ptr_type.ast.main_token - 1, .none); // lbracket + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // asterisk + try renderToken(ais, tree, ptr_type.ast.main_token + 1, .none); // colon + try renderExpression(gpa, ais, tree, ptr_type.ast.sentinel, .none); + try renderToken(ais, tree, tree.lastToken(ptr_type.ast.sentinel) + 1, .none); // rbracket } - return renderToken(tree, ais, grouped_expr.rparen, space); }, - - .FieldInitializer => { - const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base); - - try renderToken(tree, ais, field_init.period_token, Space.None); // . - try renderToken(tree, ais, field_init.name_token, Space.Space); // name - try renderToken(tree, ais, tree.nextToken(field_init.name_token), Space.Space); // = - return renderExpression(allocator, ais, tree, field_init.expr, space); + .C => { + try renderToken(ais, tree, ptr_type.ast.main_token - 1, .none); // lbracket + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // asterisk + try renderToken(ais, tree, ptr_type.ast.main_token + 1, .none); // c + try renderToken(ais, tree, ptr_type.ast.main_token + 2, .none); // rbracket }, + .Slice => { + if (ptr_type.ast.sentinel == 0) { + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // lbracket + try renderToken(ais, tree, ptr_type.ast.main_token + 1, .none); // rbracket + } else { + try renderToken(ais, tree, ptr_type.ast.main_token, .none); // lbracket + try renderToken(ais, tree, ptr_type.ast.main_token + 1, .none); // colon + try renderExpression(gpa, ais, tree, ptr_type.ast.sentinel, .none); + try renderToken(ais, tree, tree.lastToken(ptr_type.ast.sentinel) + 1, .none); // rbracket + } + }, + } - .ContainerDecl => { - const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); + if (ptr_type.allowzero_token) |allowzero_token| { + try renderToken(ais, tree, allowzero_token, .space); + } - if (container_decl.layout_token) |layout_token| { - try renderToken(tree, ais, layout_token, Space.Space); - } + if (ptr_type.ast.align_node != 0) { + const align_first = tree.firstToken(ptr_type.ast.align_node); + try renderToken(ais, tree, align_first - 2, .none); // align + try renderToken(ais, tree, align_first - 1, .none); // lparen + try renderExpression(gpa, ais, tree, ptr_type.ast.align_node, .none); + if (ptr_type.ast.bit_range_start != 0) { + assert(ptr_type.ast.bit_range_end != 0); + try renderToken(ais, tree, tree.firstToken(ptr_type.ast.bit_range_start) - 1, .none); // colon + try renderExpression(gpa, ais, tree, ptr_type.ast.bit_range_start, .none); + try renderToken(ais, tree, tree.firstToken(ptr_type.ast.bit_range_end) - 1, .none); // colon + try renderExpression(gpa, ais, tree, ptr_type.ast.bit_range_end, .none); + try renderToken(ais, tree, tree.lastToken(ptr_type.ast.bit_range_end) + 1, .space); // rparen + } else { + try renderToken(ais, tree, tree.lastToken(ptr_type.ast.align_node) + 1, .space); // rparen + } + } - switch (container_decl.init_arg_expr) { - .None => { - try renderToken(tree, ais, container_decl.kind_token, Space.Space); // union - }, - .Enum => |enum_tag_type| { - try renderToken(tree, ais, container_decl.kind_token, Space.None); // union + if (ptr_type.const_token) |const_token| { + try renderToken(ais, tree, const_token, .space); + } - const lparen = tree.nextToken(container_decl.kind_token); - const enum_token = tree.nextToken(lparen); + if (ptr_type.volatile_token) |volatile_token| { + try renderToken(ais, tree, volatile_token, .space); + } - try renderToken(tree, ais, lparen, Space.None); // ( - try renderToken(tree, ais, enum_token, Space.None); // enum + try renderExpression(gpa, ais, tree, ptr_type.ast.child_type, space); +} - if (enum_tag_type) |expr| { - try renderToken(tree, ais, tree.nextToken(enum_token), Space.None); // ( - try renderExpression(allocator, ais, tree, expr, Space.None); +fn renderSlice( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + slice: ast.full.Slice, + space: Space, +) Error!void { + const node_tags = tree.nodes.items(.tag); + const after_start_space_bool = nodeCausesSliceOpSpace(node_tags[slice.ast.start]) or + if (slice.ast.end != 0) nodeCausesSliceOpSpace(node_tags[slice.ast.end]) else false; + const after_start_space = if (after_start_space_bool) Space.space else Space.none; + const after_dots_space = if (slice.ast.end != 0) after_start_space else Space.none; + + try renderExpression(gpa, ais, tree, slice.ast.sliced, .none); + try renderToken(ais, tree, slice.ast.lbracket, .none); // lbracket + + const start_last = tree.lastToken(slice.ast.start); + try renderExpression(gpa, ais, tree, slice.ast.start, after_start_space); + try renderToken(ais, tree, start_last + 1, after_dots_space); // ellipsis2 ("..") + if (slice.ast.end == 0) { + return renderToken(ais, tree, start_last + 2, space); // rbracket + } - const rparen = tree.nextToken(expr.lastToken()); - try renderToken(tree, ais, rparen, Space.None); // ) - try renderToken(tree, ais, tree.nextToken(rparen), Space.Space); // ) - } else { - try renderToken(tree, ais, tree.nextToken(enum_token), Space.Space); // ) - } - }, - .Type => |type_expr| { - try renderToken(tree, ais, container_decl.kind_token, Space.None); // union + const end_last = tree.lastToken(slice.ast.end); + const after_end_space = if (slice.ast.sentinel != 0) Space.space else Space.none; + try renderExpression(gpa, ais, tree, slice.ast.end, after_end_space); + if (slice.ast.sentinel == 0) { + return renderToken(ais, tree, end_last + 1, space); // rbracket + } - const lparen = tree.nextToken(container_decl.kind_token); - const rparen = tree.nextToken(type_expr.lastToken()); + try renderToken(ais, tree, end_last + 1, .none); // colon + try renderExpression(gpa, ais, tree, slice.ast.sentinel, .none); + try renderToken(ais, tree, tree.lastToken(slice.ast.sentinel) + 1, space); // rbracket +} - try renderToken(tree, ais, lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, type_expr, Space.None); - try renderToken(tree, ais, rparen, Space.Space); // ) - }, - } +fn renderAsmOutput( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + asm_output: ast.Node.Index, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + const datas = tree.nodes.items(.data); + assert(node_tags[asm_output] == .asm_output); + const symbolic_name = main_tokens[asm_output]; + + try renderToken(ais, tree, symbolic_name - 1, .none); // lbracket + try renderToken(ais, tree, symbolic_name, .none); // ident + try renderToken(ais, tree, symbolic_name + 1, .space); // rbracket + try renderToken(ais, tree, symbolic_name + 2, .space); // "constraint" + try renderToken(ais, tree, symbolic_name + 3, .none); // lparen + + if (token_tags[symbolic_name + 4] == .arrow) { + try renderToken(ais, tree, symbolic_name + 4, .space); // -> + try renderExpression(gpa, ais, tree, datas[asm_output].lhs, Space.none); + return renderToken(ais, tree, datas[asm_output].rhs, space); // rparen + } else { + try renderToken(ais, tree, symbolic_name + 4, .none); // ident + return renderToken(ais, tree, symbolic_name + 5, space); // rparen + } +} - if (container_decl.fields_and_decls_len == 0) { - { - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, container_decl.lbrace_token, Space.None); // { - } - return renderToken(tree, ais, container_decl.rbrace_token, space); // } - } +fn renderAsmInput( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + asm_input: ast.Node.Index, + space: Space, +) Error!void { + const node_tags = tree.nodes.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + const datas = tree.nodes.items(.data); + assert(node_tags[asm_input] == .asm_input); + const symbolic_name = main_tokens[asm_input]; + + try renderToken(ais, tree, symbolic_name - 1, .none); // lbracket + try renderToken(ais, tree, symbolic_name, .none); // ident + try renderToken(ais, tree, symbolic_name + 1, .space); // rbracket + try renderToken(ais, tree, symbolic_name + 2, .space); // "constraint" + try renderToken(ais, tree, symbolic_name + 3, .none); // lparen + try renderExpression(gpa, ais, tree, datas[asm_input].lhs, Space.none); + return renderToken(ais, tree, datas[asm_input].rhs, space); // rparen +} - const src_has_trailing_comma = blk: { - var maybe_comma = tree.prevToken(container_decl.lastToken()); - // Doc comments for a field may also appear after the comma, eg. - // field_name: T, // comment attached to field_name - if (tree.token_ids[maybe_comma] == .DocComment) - maybe_comma = tree.prevToken(maybe_comma); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; +fn renderVarDecl(gpa: *Allocator, ais: *Ais, tree: ast.Tree, var_decl: ast.full.VarDecl) Error!void { + if (var_decl.visib_token) |visib_token| { + try renderToken(ais, tree, visib_token, Space.space); // pub + } - const fields_and_decls = container_decl.fieldsAndDecls(); + if (var_decl.extern_export_token) |extern_export_token| { + try renderToken(ais, tree, extern_export_token, Space.space); // extern - // Check if the first declaration and the { are on the same line - const src_has_newline = !tree.tokensOnSameLine( - container_decl.lbrace_token, - fields_and_decls[0].firstToken(), - ); + if (var_decl.lib_name) |lib_name| { + try renderToken(ais, tree, lib_name, Space.space); // "lib" + } + } - // We can only print all the elements in-line if all the - // declarations inside are fields - const src_has_only_fields = blk: { - for (fields_and_decls) |decl| { - if (decl.tag != .ContainerField) break :blk false; - } - break :blk true; - }; + if (var_decl.threadlocal_token) |thread_local_token| { + try renderToken(ais, tree, thread_local_token, Space.space); // threadlocal + } - if (src_has_trailing_comma or !src_has_only_fields) { - // One declaration per line - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // { + if (var_decl.comptime_token) |comptime_token| { + try renderToken(ais, tree, comptime_token, Space.space); // comptime + } - for (fields_and_decls) |decl, i| { - try renderContainerDecl(allocator, ais, tree, decl, .Newline); + try renderToken(ais, tree, var_decl.ast.mut_token, .space); // var - if (i + 1 < fields_and_decls.len) { - try renderExtraNewline(tree, ais, fields_and_decls[i + 1]); - } - } - } else if (src_has_newline) { - // All the declarations on the same line, but place the items on - // their own line - try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // { + const name_space = if (var_decl.ast.type_node == 0 and + (var_decl.ast.align_node != 0 or + var_decl.ast.section_node != 0 or + var_decl.ast.init_node != 0)) + Space.space + else + Space.none; + try renderToken(ais, tree, var_decl.ast.mut_token + 1, name_space); // name - ais.pushIndent(); - defer ais.popIndent(); + if (var_decl.ast.type_node != 0) { + try renderToken(ais, tree, var_decl.ast.mut_token + 2, Space.space); // : + if (var_decl.ast.align_node != 0 or var_decl.ast.section_node != 0 or + var_decl.ast.init_node != 0) + { + try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .space); + } else { + try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .none); + const semicolon = tree.lastToken(var_decl.ast.type_node) + 1; + return renderToken(ais, tree, semicolon, Space.newline); // ; + } + } - for (fields_and_decls) |decl, i| { - const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space; - try renderContainerDecl(allocator, ais, tree, decl, space_after_decl); - } - } else { - // All the declarations on the same line - try renderToken(tree, ais, container_decl.lbrace_token, .Space); // { + if (var_decl.ast.align_node != 0) { + const lparen = tree.firstToken(var_decl.ast.align_node) - 1; + const align_kw = lparen - 1; + const rparen = tree.lastToken(var_decl.ast.align_node) + 1; + try renderToken(ais, tree, align_kw, Space.none); // align + try renderToken(ais, tree, lparen, Space.none); // ( + try renderExpression(gpa, ais, tree, var_decl.ast.align_node, Space.none); + if (var_decl.ast.section_node != 0 or var_decl.ast.init_node != 0) { + try renderToken(ais, tree, rparen, .space); // ) + } else { + try renderToken(ais, tree, rparen, .none); // ) + return renderToken(ais, tree, rparen + 1, Space.newline); // ; + } + } - for (fields_and_decls) |decl| { - try renderContainerDecl(allocator, ais, tree, decl, .Space); - } - } + if (var_decl.ast.section_node != 0) { + const lparen = tree.firstToken(var_decl.ast.section_node) - 1; + const section_kw = lparen - 1; + const rparen = tree.lastToken(var_decl.ast.section_node) + 1; + try renderToken(ais, tree, section_kw, Space.none); // linksection + try renderToken(ais, tree, lparen, Space.none); // ( + try renderExpression(gpa, ais, tree, var_decl.ast.section_node, Space.none); + if (var_decl.ast.init_node != 0) { + try renderToken(ais, tree, rparen, .space); // ) + } else { + try renderToken(ais, tree, rparen, .none); // ) + return renderToken(ais, tree, rparen + 1, Space.newline); // ; + } + } - return renderToken(tree, ais, container_decl.rbrace_token, space); // } - }, + assert(var_decl.ast.init_node != 0); + const eq_token = tree.firstToken(var_decl.ast.init_node) - 1; + const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; + { + ais.pushIndent(); + try renderToken(ais, tree, eq_token, eq_space); // = + ais.popIndent(); + } + ais.pushIndentOneShot(); + try renderExpression(gpa, ais, tree, var_decl.ast.init_node, .semicolon); +} - .ErrorSetDecl => { - const err_set_decl = @fieldParentPtr(ast.Node.ErrorSetDecl, "base", base); +fn renderIf(gpa: *Allocator, ais: *Ais, tree: ast.Tree, if_node: ast.full.If, space: Space) Error!void { + return renderWhile(gpa, ais, tree, .{ + .ast = .{ + .while_token = if_node.ast.if_token, + .cond_expr = if_node.ast.cond_expr, + .cont_expr = 0, + .then_expr = if_node.ast.then_expr, + .else_expr = if_node.ast.else_expr, + }, + .inline_token = null, + .label_token = null, + .payload_token = if_node.payload_token, + .else_token = if_node.else_token, + .error_token = if_node.error_token, + }, space); +} - const lbrace = tree.nextToken(err_set_decl.error_token); +/// Note that this function is additionally used to render if and for expressions, with +/// respective values set to null. +fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full.While, space: Space) Error!void { + const node_tags = tree.nodes.items(.tag); + const token_tags = tree.tokens.items(.tag); - if (err_set_decl.decls_len == 0) { - try renderToken(tree, ais, err_set_decl.error_token, Space.None); - try renderToken(tree, ais, lbrace, Space.None); - return renderToken(tree, ais, err_set_decl.rbrace_token, space); - } + if (while_node.label_token) |label| { + try renderToken(ais, tree, label, .none); // label + try renderToken(ais, tree, label + 1, .space); // : + } - if (err_set_decl.decls_len == 1) blk: { - const node = err_set_decl.decls()[0]; + if (while_node.inline_token) |inline_token| { + try renderToken(ais, tree, inline_token, .space); // inline + } - // if there are any doc comments or same line comments - // don't try to put it all on one line - if (node.cast(ast.Node.ErrorTag)) |tag| { - if (tag.doc_comments != null) break :blk; + try renderToken(ais, tree, while_node.ast.while_token, .space); // if + try renderToken(ais, tree, while_node.ast.while_token + 1, .none); // lparen + try renderExpression(gpa, ais, tree, while_node.ast.cond_expr, .none); // condition + + const then_tag = node_tags[while_node.ast.then_expr]; + if (nodeIsBlock(then_tag) and !nodeIsIf(then_tag)) { + if (while_node.payload_token) |payload_token| { + try renderToken(ais, tree, payload_token - 2, .space); // rparen + try renderToken(ais, tree, payload_token - 1, .none); // | + const ident = blk: { + if (token_tags[payload_token] == .asterisk) { + try renderToken(ais, tree, payload_token, .none); // * + break :blk payload_token + 1; } else { - break :blk; + break :blk payload_token; } - - try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error - try renderToken(tree, ais, lbrace, Space.None); // { - try renderExpression(allocator, ais, tree, node, Space.None); - return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } - } - - try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error - - const src_has_trailing_comma = blk: { - const maybe_comma = tree.prevToken(err_set_decl.rbrace_token); - break :blk tree.token_ids[maybe_comma] == .Comma; }; - - if (src_has_trailing_comma) { - { - ais.pushIndent(); - defer ais.popIndent(); - - try renderToken(tree, ais, lbrace, Space.Newline); // { - const decls = err_set_decl.decls(); - for (decls) |node, i| { - if (i + 1 < decls.len) { - try renderExpression(allocator, ais, tree, node, Space.None); - try renderToken(tree, ais, tree.nextToken(node.lastToken()), Space.Newline); // , - - try renderExtraNewline(tree, ais, decls[i + 1]); - } else { - try renderExpression(allocator, ais, tree, node, Space.Comma); - } - } + try renderToken(ais, tree, ident, .none); // identifier + const pipe = blk: { + if (token_tags[ident + 1] == .comma) { + try renderToken(ais, tree, ident + 1, .space); // , + try renderToken(ais, tree, ident + 2, .none); // index + break :blk ident + 3; + } else { + break :blk ident + 1; } + }; + const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented()) + Space.newline + else + Space.space; + try renderToken(ais, tree, pipe, brace_space); // | + } else { + const rparen = tree.lastToken(while_node.ast.cond_expr) + 1; + const brace_space = if (while_node.ast.cont_expr == 0 and ais.isLineOverIndented()) + Space.newline + else + Space.space; + try renderToken(ais, tree, rparen, brace_space); // rparen + } + if (while_node.ast.cont_expr != 0) { + const rparen = tree.lastToken(while_node.ast.cont_expr) + 1; + const lparen = tree.firstToken(while_node.ast.cont_expr) - 1; + try renderToken(ais, tree, lparen - 1, .space); // : + try renderToken(ais, tree, lparen, .none); // lparen + try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); + const brace_space: Space = if (ais.isLineOverIndented()) .newline else .space; + try renderToken(ais, tree, rparen, brace_space); // rparen + } + if (while_node.ast.else_expr != 0) { + try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.space); + try renderToken(ais, tree, while_node.else_token, .space); // else + if (while_node.error_token) |error_token| { + try renderToken(ais, tree, error_token - 1, .none); // | + try renderToken(ais, tree, error_token, .none); // identifier + try renderToken(ais, tree, error_token + 1, .space); // | + } + return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); + } else { + return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); + } + } - return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } + const rparen = tree.lastToken(while_node.ast.cond_expr) + 1; + const last_then_token = tree.lastToken(while_node.ast.then_expr); + const src_has_newline = !tree.tokensOnSameLine(rparen, last_then_token); + + if (src_has_newline) { + if (while_node.payload_token) |payload_token| { + try renderToken(ais, tree, payload_token - 2, .space); // rparen + try renderToken(ais, tree, payload_token - 1, .none); // | + const ident = blk: { + if (token_tags[payload_token] == .asterisk) { + try renderToken(ais, tree, payload_token, .none); // * + break :blk payload_token + 1; + } else { + break :blk payload_token; + } + }; + try renderToken(ais, tree, ident, .none); // identifier + const pipe = blk: { + if (token_tags[ident + 1] == .comma) { + try renderToken(ais, tree, ident + 1, .space); // , + try renderToken(ais, tree, ident + 2, .none); // index + break :blk ident + 3; + } else { + break :blk ident + 1; + } + }; + const after_space: Space = if (while_node.ast.cont_expr != 0) .space else .newline; + try renderToken(ais, tree, pipe, after_space); // | + } else { + ais.pushIndent(); + const after_space: Space = if (while_node.ast.cont_expr != 0) .space else .newline; + try renderToken(ais, tree, rparen, after_space); // rparen + ais.popIndent(); + } + if (while_node.ast.cont_expr != 0) { + const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1; + const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1; + try renderToken(ais, tree, cont_lparen - 1, .space); // : + try renderToken(ais, tree, cont_lparen, .none); // lparen + try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); + try renderToken(ais, tree, cont_rparen, .newline); // rparen + } + if (while_node.ast.else_expr != 0) { + ais.pushIndent(); + try renderExpression(gpa, ais, tree, while_node.ast.then_expr, Space.newline); + ais.popIndent(); + const else_is_block = nodeIsBlock(node_tags[while_node.ast.else_expr]); + if (else_is_block) { + try renderToken(ais, tree, while_node.else_token, .space); // else + if (while_node.error_token) |error_token| { + try renderToken(ais, tree, error_token - 1, .none); // | + try renderToken(ais, tree, error_token, .none); // identifier + try renderToken(ais, tree, error_token + 1, .space); // | + } + return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); } else { - try renderToken(tree, ais, lbrace, Space.Space); // { - - const decls = err_set_decl.decls(); - for (decls) |node, i| { - if (i + 1 < decls.len) { - try renderExpression(allocator, ais, tree, node, Space.None); - - const comma_token = tree.nextToken(node.lastToken()); - assert(tree.token_ids[comma_token] == .Comma); - try renderToken(tree, ais, comma_token, Space.Space); // , - try renderExtraNewline(tree, ais, decls[i + 1]); - } else { - try renderExpression(allocator, ais, tree, node, Space.Space); - } + if (while_node.error_token) |error_token| { + try renderToken(ais, tree, while_node.else_token, .space); // else + try renderToken(ais, tree, error_token - 1, .none); // | + try renderToken(ais, tree, error_token, .none); // identifier + try renderToken(ais, tree, error_token + 1, .space); // | + } else { + try renderToken(ais, tree, while_node.else_token, .newline); // else } - - return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } + try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space); + return; } - }, - - .ErrorTag => { - const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base); - - try renderDocComments(tree, ais, tag, tag.doc_comments); - return renderToken(tree, ais, tag.name_token, space); // name - }, - - .MultilineStringLiteral => { - const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base); + } else { + try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space); + return; + } + } - { - const locked_indents = ais.lockOneShotIndent(); - defer { - var i: u8 = 0; - while (i < locked_indents) : (i += 1) ais.popIndent(); - } - try ais.maybeInsertNewline(); + // Render everything on a single line. - for (multiline_str_literal.lines()) |t| try renderToken(tree, ais, t, Space.None); + if (while_node.payload_token) |payload_token| { + assert(payload_token - 2 == rparen); + try renderToken(ais, tree, payload_token - 2, .space); // ) + try renderToken(ais, tree, payload_token - 1, .none); // | + const ident = blk: { + if (token_tags[payload_token] == .asterisk) { + try renderToken(ais, tree, payload_token, .none); // * + break :blk payload_token + 1; + } else { + break :blk payload_token; } - }, - - .BuiltinCall => { - const builtin_call = @fieldParentPtr(ast.Node.BuiltinCall, "base", base); - - // TODO remove after 0.7.0 release - if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType")) - return ais.writer().writeAll("opaque {}"); - - // TODO remove after 0.7.0 release - { - const params = builtin_call.paramsConst(); - if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@Type") and - params.len == 1) - { - if (params[0].castTag(.EnumLiteral)) |enum_literal| - if (mem.eql(u8, tree.tokenSlice(enum_literal.name), "Opaque")) - return ais.writer().writeAll("opaque {}"); - } + }; + try renderToken(ais, tree, ident, .none); // identifier + const pipe = blk: { + if (token_tags[ident + 1] == .comma) { + try renderToken(ais, tree, ident + 1, .space); // , + try renderToken(ais, tree, ident + 2, .none); // index + break :blk ident + 3; + } else { + break :blk ident + 1; } + }; + try renderToken(ais, tree, pipe, .space); // | + } else { + try renderToken(ais, tree, rparen, .space); // ) + } - try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name + if (while_node.ast.cont_expr != 0) { + const cont_rparen = tree.lastToken(while_node.ast.cont_expr) + 1; + const cont_lparen = tree.firstToken(while_node.ast.cont_expr) - 1; + try renderToken(ais, tree, cont_lparen - 1, .space); // : + try renderToken(ais, tree, cont_lparen, .none); // lparen + try renderExpression(gpa, ais, tree, while_node.ast.cont_expr, .none); + try renderToken(ais, tree, cont_rparen, .space); // rparen + } - const src_params_trailing_comma = blk: { - if (builtin_call.params_len == 0) break :blk false; - const last_node = builtin_call.params()[builtin_call.params_len - 1]; - const maybe_comma = tree.nextToken(last_node.lastToken()); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; + if (while_node.ast.else_expr != 0) { + try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space); + try renderToken(ais, tree, while_node.else_token, .space); // else - const lparen = tree.nextToken(builtin_call.builtin_token); + if (while_node.error_token) |error_token| { + try renderToken(ais, tree, error_token - 1, .none); // | + try renderToken(ais, tree, error_token, .none); // identifier + try renderToken(ais, tree, error_token + 1, .space); // | + } - if (!src_params_trailing_comma) { - try renderToken(tree, ais, lparen, Space.None); // ( + return renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); + } else { + return renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); + } +} - // render all on one line, no trailing comma - const params = builtin_call.params(); - for (params) |param_node, i| { - const maybe_comment = param_node.firstToken() - 1; - if (param_node.*.tag == .MultilineStringLiteral or tree.token_ids[maybe_comment] == .LineComment) { - ais.pushIndentOneShot(); - } - try renderExpression(allocator, ais, tree, param_node, Space.None); +fn renderContainerField( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + field: ast.full.ContainerField, + space: Space, +) Error!void { + const main_tokens = tree.nodes.items(.main_token); + if (field.comptime_token) |t| { + try renderToken(ais, tree, t, .space); // comptime + } + if (field.ast.type_expr == 0 and field.ast.value_expr == 0) { + return renderTokenComma(ais, tree, field.ast.name_token, space); // name + } + if (field.ast.type_expr != 0 and field.ast.value_expr == 0) { + try renderToken(ais, tree, field.ast.name_token, .none); // name + try renderToken(ais, tree, field.ast.name_token + 1, .space); // : + + if (field.ast.align_expr != 0) { + try renderExpression(gpa, ais, tree, field.ast.type_expr, .space); // type + const align_token = tree.firstToken(field.ast.align_expr) - 2; + try renderToken(ais, tree, align_token, .none); // align + try renderToken(ais, tree, align_token + 1, .none); // ( + try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment + const rparen = tree.lastToken(field.ast.align_expr) + 1; + return renderTokenComma(ais, tree, rparen, space); // ) + } else { + return renderExpressionComma(gpa, ais, tree, field.ast.type_expr, space); // type + } + } + if (field.ast.type_expr == 0 and field.ast.value_expr != 0) { + try renderToken(ais, tree, field.ast.name_token, .space); // name + try renderToken(ais, tree, field.ast.name_token + 1, .space); // = + return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value + } - if (i + 1 < params.len) { - const comma_token = tree.nextToken(param_node.lastToken()); - try renderToken(tree, ais, comma_token, Space.Space); // , - } - } - } else { - // one param per line - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, lparen, Space.Newline); // ( + try renderToken(ais, tree, field.ast.name_token, .none); // name + try renderToken(ais, tree, field.ast.name_token + 1, .space); // : + try renderExpression(gpa, ais, tree, field.ast.type_expr, .space); // type + + if (field.ast.align_expr != 0) { + const lparen_token = tree.firstToken(field.ast.align_expr) - 1; + const align_kw = lparen_token - 1; + const rparen_token = tree.lastToken(field.ast.align_expr) + 1; + try renderToken(ais, tree, align_kw, .none); // align + try renderToken(ais, tree, lparen_token, .none); // ( + try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment + try renderToken(ais, tree, rparen_token, .space); // ) + } + const eq_token = tree.firstToken(field.ast.value_expr) - 1; + try renderToken(ais, tree, eq_token, .space); // = + return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value +} - for (builtin_call.params()) |param_node| { - try renderExpression(allocator, ais, tree, param_node, Space.Comma); - } - } +fn renderBuiltinCall( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + builtin_token: ast.TokenIndex, + params: []const ast.Node.Index, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); - return renderToken(tree, ais, builtin_call.rparen_token, space); // ) - }, + try renderToken(ais, tree, builtin_token, .none); // @name - .FnProto => { - const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base); + if (params.len == 0) { + try renderToken(ais, tree, builtin_token + 1, .none); // ( + return renderToken(ais, tree, builtin_token + 2, space); // ) + } - if (fn_proto.getVisibToken()) |visib_token_index| { - const visib_token = tree.token_ids[visib_token_index]; - assert(visib_token == .Keyword_pub or visib_token == .Keyword_export); + const last_param = params[params.len - 1]; + const after_last_param_token = tree.lastToken(last_param) + 1; - try renderToken(tree, ais, visib_token_index, Space.Space); // pub - } + if (token_tags[after_last_param_token] != .comma) { + // Render all on one line, no trailing comma. + try renderToken(ais, tree, builtin_token + 1, .none); // ( - if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| { - if (fn_proto.getIsExternPrototype() == null) - try renderToken(tree, ais, extern_export_inline_token, Space.Space); // extern/export/inline + for (params) |param_node, i| { + const first_param_token = tree.firstToken(param_node); + if (token_tags[first_param_token] == .multiline_string_literal_line or + hasSameLineComment(tree, first_param_token - 1)) + { + ais.pushIndentOneShot(); } + try renderExpression(gpa, ais, tree, param_node, .none); - if (fn_proto.getLibName()) |lib_name| { - try renderExpression(allocator, ais, tree, lib_name, Space.Space); + if (i + 1 < params.len) { + const comma_token = tree.lastToken(param_node) + 1; + try renderToken(ais, tree, comma_token, .space); // , } + } + return renderToken(ais, tree, after_last_param_token, space); // ) + } else { + // Render one param per line. + ais.pushIndent(); + try renderToken(ais, tree, builtin_token + 1, Space.newline); // ( + + for (params) |param_node| { + try renderExpression(gpa, ais, tree, param_node, .comma); + } + ais.popIndent(); - const lparen = if (fn_proto.getNameToken()) |name_token| blk: { - try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn - try renderToken(tree, ais, name_token, Space.None); // name - break :blk tree.nextToken(name_token); - } else blk: { - try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn - break :blk tree.nextToken(fn_proto.fn_token); - }; - assert(tree.token_ids[lparen] == .LParen); - - const rparen = tree.prevToken( - // the first token for the annotation expressions is the left - // parenthesis, hence the need for two prevToken - if (fn_proto.getAlignExpr()) |align_expr| - tree.prevToken(tree.prevToken(align_expr.firstToken())) - else if (fn_proto.getSectionExpr()) |section_expr| - tree.prevToken(tree.prevToken(section_expr.firstToken())) - else if (fn_proto.getCallconvExpr()) |callconv_expr| - tree.prevToken(tree.prevToken(callconv_expr.firstToken())) - else switch (fn_proto.return_type) { - .Explicit => |node| node.firstToken(), - .InferErrorSet => |node| tree.prevToken(node.firstToken()), - .Invalid => unreachable, - }, - ); - assert(tree.token_ids[rparen] == .RParen); - - const src_params_trailing_comma = blk: { - const maybe_comma = tree.token_ids[rparen - 1]; - break :blk maybe_comma == .Comma or maybe_comma == .LineComment; - }; - - if (!src_params_trailing_comma) { - try renderToken(tree, ais, lparen, Space.None); // ( - - // render all on one line, no trailing comma - for (fn_proto.params()) |param_decl, i| { - try renderParamDecl(allocator, ais, tree, param_decl, Space.None); - - if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) { - const comma = tree.nextToken(param_decl.lastToken()); - try renderToken(tree, ais, comma, Space.Space); // , - } - } - if (fn_proto.getVarArgsToken()) |var_args_token| { - try renderToken(tree, ais, var_args_token, Space.None); - } - } else { - // one param per line - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, lparen, Space.Newline); // ( + return renderToken(ais, tree, after_last_param_token + 1, space); // ) + } +} - for (fn_proto.params()) |param_decl| { - try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma); - } - if (fn_proto.getVarArgsToken()) |var_args_token| { - try renderToken(tree, ais, var_args_token, Space.Comma); - } +fn renderFnProto(gpa: *Allocator, ais: *Ais, tree: ast.Tree, fn_proto: ast.full.FnProto, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const token_starts = tree.tokens.items(.start); + + const is_inline = fn_proto.ast.fn_token > 0 and + token_tags[fn_proto.ast.fn_token - 1] == .keyword_inline; + + const after_fn_token = fn_proto.ast.fn_token + 1; + const lparen = if (token_tags[after_fn_token] == .identifier) blk: { + try renderToken(ais, tree, fn_proto.ast.fn_token, .space); // fn + try renderToken(ais, tree, after_fn_token, .none); // name + break :blk after_fn_token + 1; + } else blk: { + try renderToken(ais, tree, fn_proto.ast.fn_token, .space); // fn + break :blk fn_proto.ast.fn_token + 1; + }; + assert(token_tags[lparen] == .l_paren); + + const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; + const rparen = blk: { + // These may appear in any order, so we have to check the token_starts array + // to find out which is first. + var rparen = if (token_tags[maybe_bang] == .bang) maybe_bang - 1 else maybe_bang; + var smallest_start = token_starts[maybe_bang]; + if (fn_proto.ast.align_expr != 0) { + const tok = tree.firstToken(fn_proto.ast.align_expr) - 3; + const start = token_starts[tok]; + if (start < smallest_start) { + rparen = tok; + smallest_start = start; } - - try renderToken(tree, ais, rparen, Space.Space); // ) - - if (fn_proto.getAlignExpr()) |align_expr| { - const align_rparen = tree.nextToken(align_expr.lastToken()); - const align_lparen = tree.prevToken(align_expr.firstToken()); - const align_kw = tree.prevToken(align_lparen); - - try renderToken(tree, ais, align_kw, Space.None); // align - try renderToken(tree, ais, align_lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, align_expr, Space.None); - try renderToken(tree, ais, align_rparen, Space.Space); // ) + } + if (fn_proto.ast.section_expr != 0) { + const tok = tree.firstToken(fn_proto.ast.section_expr) - 3; + const start = token_starts[tok]; + if (start < smallest_start) { + rparen = tok; + smallest_start = start; } - - if (fn_proto.getSectionExpr()) |section_expr| { - const section_rparen = tree.nextToken(section_expr.lastToken()); - const section_lparen = tree.prevToken(section_expr.firstToken()); - const section_kw = tree.prevToken(section_lparen); - - try renderToken(tree, ais, section_kw, Space.None); // section - try renderToken(tree, ais, section_lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, section_expr, Space.None); - try renderToken(tree, ais, section_rparen, Space.Space); // ) + } + if (fn_proto.ast.callconv_expr != 0) { + const tok = tree.firstToken(fn_proto.ast.callconv_expr) - 3; + const start = token_starts[tok]; + if (start < smallest_start) { + rparen = tok; + smallest_start = start; } - - if (fn_proto.getCallconvExpr()) |callconv_expr| { - const callconv_rparen = tree.nextToken(callconv_expr.lastToken()); - const callconv_lparen = tree.prevToken(callconv_expr.firstToken()); - const callconv_kw = tree.prevToken(callconv_lparen); - - try renderToken(tree, ais, callconv_kw, Space.None); // callconv - try renderToken(tree, ais, callconv_lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, callconv_expr, Space.None); - try renderToken(tree, ais, callconv_rparen, Space.Space); // ) - } else if (fn_proto.getIsExternPrototype() != null) { - try ais.writer().writeAll("callconv(.C) "); - } else if (fn_proto.getIsAsync() != null) { - try ais.writer().writeAll("callconv(.Async) "); + } + break :blk rparen; + }; + assert(token_tags[rparen] == .r_paren); + + // The params list is a sparse set that does *not* include anytype or ... parameters. + + const trailing_comma = token_tags[rparen - 1] == .comma; + if (!trailing_comma and !hasComment(tree, lparen, rparen)) { + // Render all on one line, no trailing comma. + try renderToken(ais, tree, lparen, .none); // ( + + var param_i: usize = 0; + var last_param_token = lparen; + while (true) { + last_param_token += 1; + switch (token_tags[last_param_token]) { + .doc_comment => { + try renderToken(ais, tree, last_param_token, .newline); + continue; + }, + .ellipsis3 => { + try renderToken(ais, tree, last_param_token, .none); // ... + break; + }, + .keyword_noalias, .keyword_comptime => { + try renderToken(ais, tree, last_param_token, .space); + last_param_token += 1; + }, + .identifier => {}, + .keyword_anytype => { + try renderToken(ais, tree, last_param_token, .none); // anytype + continue; + }, + .r_paren => break, + .comma => { + try renderToken(ais, tree, last_param_token, .space); // , + continue; + }, + else => {}, // Parameter type without a name. } - - switch (fn_proto.return_type) { - .Explicit => |node| { - return renderExpression(allocator, ais, tree, node, space); + if (token_tags[last_param_token] == .identifier and + token_tags[last_param_token + 1] == .colon) + { + try renderToken(ais, tree, last_param_token, .none); // name + last_param_token += 1; + try renderToken(ais, tree, last_param_token, .space); // : + last_param_token += 1; + } + if (token_tags[last_param_token] == .keyword_anytype) { + try renderToken(ais, tree, last_param_token, .none); // anytype + continue; + } + const param = fn_proto.ast.params[param_i]; + param_i += 1; + try renderExpression(gpa, ais, tree, param, .none); + last_param_token = tree.lastToken(param); + } + } else { + // One param per line. + ais.pushIndent(); + try renderToken(ais, tree, lparen, .newline); // ( + + var param_i: usize = 0; + var last_param_token = lparen; + while (true) { + last_param_token += 1; + switch (token_tags[last_param_token]) { + .doc_comment => { + try renderToken(ais, tree, last_param_token, .newline); + continue; + }, + .ellipsis3 => { + try renderToken(ais, tree, last_param_token, .comma); // ... + break; }, - .InferErrorSet => |node| { - try renderToken(tree, ais, tree.prevToken(node.firstToken()), Space.None); // ! - return renderExpression(allocator, ais, tree, node, space); + .keyword_noalias, .keyword_comptime => { + try renderToken(ais, tree, last_param_token, .space); + last_param_token += 1; }, - .Invalid => unreachable, + .identifier => {}, + .keyword_anytype => { + try renderToken(ais, tree, last_param_token, .comma); // anytype + if (token_tags[last_param_token + 1] == .comma) + last_param_token += 1; + continue; + }, + .r_paren => break, + else => {}, // Parameter type without a name. } - }, - - .AnyFrameType => { - const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base); + if (token_tags[last_param_token] == .identifier and + token_tags[last_param_token + 1] == .colon) + { + try renderToken(ais, tree, last_param_token, .none); // name + last_param_token += 1; + try renderToken(ais, tree, last_param_token, .space); // : + last_param_token += 1; + } + if (token_tags[last_param_token] == .keyword_anytype) { + try renderToken(ais, tree, last_param_token, .comma); // anytype + if (token_tags[last_param_token + 1] == .comma) + last_param_token += 1; + continue; + } + const param = fn_proto.ast.params[param_i]; + param_i += 1; + try renderExpression(gpa, ais, tree, param, .comma); + last_param_token = tree.lastToken(param); + if (token_tags[last_param_token + 1] == .comma) last_param_token += 1; + } + ais.popIndent(); + } - if (anyframe_type.result) |result| { - try renderToken(tree, ais, anyframe_type.anyframe_token, Space.None); // anyframe - try renderToken(tree, ais, result.arrow_token, Space.None); // -> - return renderExpression(allocator, ais, tree, result.return_type, space); - } else { - return renderToken(tree, ais, anyframe_type.anyframe_token, space); // anyframe - } - }, + try renderToken(ais, tree, rparen, .space); // ) - .DocComment => unreachable, // doc comments are attached to nodes + if (fn_proto.ast.align_expr != 0) { + const align_lparen = tree.firstToken(fn_proto.ast.align_expr) - 1; + const align_rparen = tree.lastToken(fn_proto.ast.align_expr) + 1; - .Switch => { - const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base); + try renderToken(ais, tree, align_lparen - 1, .none); // align + try renderToken(ais, tree, align_lparen, .none); // ( + try renderExpression(gpa, ais, tree, fn_proto.ast.align_expr, .none); + try renderToken(ais, tree, align_rparen, .space); // ) + } - try renderToken(tree, ais, switch_node.switch_token, Space.Space); // switch - try renderToken(tree, ais, tree.nextToken(switch_node.switch_token), Space.None); // ( + if (fn_proto.ast.section_expr != 0) { + const section_lparen = tree.firstToken(fn_proto.ast.section_expr) - 1; + const section_rparen = tree.lastToken(fn_proto.ast.section_expr) + 1; - const rparen = tree.nextToken(switch_node.expr.lastToken()); - const lbrace = tree.nextToken(rparen); + try renderToken(ais, tree, section_lparen - 1, .none); // section + try renderToken(ais, tree, section_lparen, .none); // ( + try renderExpression(gpa, ais, tree, fn_proto.ast.section_expr, .none); + try renderToken(ais, tree, section_rparen, .space); // ) + } - if (switch_node.cases_len == 0) { - try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); - try renderToken(tree, ais, rparen, Space.Space); // ) - try renderToken(tree, ais, lbrace, Space.None); // { - return renderToken(tree, ais, switch_node.rbrace, space); // } - } + if (fn_proto.ast.callconv_expr != 0) { + const callconv_lparen = tree.firstToken(fn_proto.ast.callconv_expr) - 1; + const callconv_rparen = tree.lastToken(fn_proto.ast.callconv_expr) + 1; - try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); - try renderToken(tree, ais, rparen, Space.Space); // ) + try renderToken(ais, tree, callconv_lparen - 1, .none); // callconv + try renderToken(ais, tree, callconv_lparen, .none); // ( + try renderExpression(gpa, ais, tree, fn_proto.ast.callconv_expr, .none); + try renderToken(ais, tree, callconv_rparen, .space); // ) + } else if (is_inline) { + try ais.writer().writeAll("callconv(.Inline) "); + } - { - ais.pushIndentNextLine(); - defer ais.popIndent(); - try renderToken(tree, ais, lbrace, Space.Newline); // { + if (token_tags[maybe_bang] == .bang) { + try renderToken(ais, tree, maybe_bang, .none); // ! + } + return renderExpression(gpa, ais, tree, fn_proto.ast.return_type, space); +} - const cases = switch_node.cases(); - for (cases) |node, i| { - try renderExpression(allocator, ais, tree, node, Space.Comma); +fn renderSwitchCase( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + switch_case: ast.full.SwitchCase, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const trailing_comma = token_tags[switch_case.ast.arrow_token - 1] == .comma; + + // Render everything before the arrow + if (switch_case.ast.values.len == 0) { + try renderToken(ais, tree, switch_case.ast.arrow_token - 1, .space); // else keyword + } else if (switch_case.ast.values.len == 1) { + // render on one line and drop the trailing comma if any + try renderExpression(gpa, ais, tree, switch_case.ast.values[0], .space); + } else if (trailing_comma or + hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token)) + { + // Render each value on a new line + try renderExpressions(gpa, ais, tree, switch_case.ast.values, .comma); + } else { + // Render on one line + for (switch_case.ast.values) |value_expr| { + try renderExpression(gpa, ais, tree, value_expr, .comma_space); + } + } - if (i + 1 < cases.len) { - try renderExtraNewline(tree, ais, cases[i + 1]); - } - } - } + // Render the arrow and everything after it + try renderToken(ais, tree, switch_case.ast.arrow_token, .space); - return renderToken(tree, ais, switch_node.rbrace, space); // } - }, + if (switch_case.payload_token) |payload_token| { + try renderToken(ais, tree, payload_token - 1, .none); // pipe + if (token_tags[payload_token] == .asterisk) { + try renderToken(ais, tree, payload_token, .none); // asterisk + try renderToken(ais, tree, payload_token + 1, .none); // identifier + try renderToken(ais, tree, payload_token + 2, .space); // pipe + } else { + try renderToken(ais, tree, payload_token, .none); // identifier + try renderToken(ais, tree, payload_token + 1, .space); // pipe + } + } - .SwitchCase => { - const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); + try renderExpression(gpa, ais, tree, switch_case.ast.target_expr, space); +} - assert(switch_case.items_len != 0); - const src_has_trailing_comma = blk: { - const last_node = switch_case.items()[switch_case.items_len - 1]; - const maybe_comma = tree.nextToken(last_node.lastToken()); - break :blk tree.token_ids[maybe_comma] == .Comma; - }; +fn renderBlock( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + block_node: ast.Node.Index, + statements: []const ast.Node.Index, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); + const nodes_data = tree.nodes.items(.data); + const lbrace = tree.nodes.items(.main_token)[block_node]; + + if (token_tags[lbrace - 1] == .colon and + token_tags[lbrace - 2] == .identifier) + { + try renderToken(ais, tree, lbrace - 2, .none); + try renderToken(ais, tree, lbrace - 1, .space); + } - if (switch_case.items_len == 1 or !src_has_trailing_comma) { - const items = switch_case.items(); - for (items) |node, i| { - if (i + 1 < items.len) { - try renderExpression(allocator, ais, tree, node, Space.None); - - const comma_token = tree.nextToken(node.lastToken()); - try renderToken(tree, ais, comma_token, Space.Space); // , - try renderExtraNewline(tree, ais, items[i + 1]); - } else { - try renderExpression(allocator, ais, tree, node, Space.Space); - } - } - } else { - const items = switch_case.items(); - for (items) |node, i| { - if (i + 1 < items.len) { - try renderExpression(allocator, ais, tree, node, Space.None); - - const comma_token = tree.nextToken(node.lastToken()); - try renderToken(tree, ais, comma_token, Space.Newline); // , - try renderExtraNewline(tree, ais, items[i + 1]); - } else { - try renderExpression(allocator, ais, tree, node, Space.Comma); - } - } + ais.pushIndentNextLine(); + if (statements.len == 0) { + try renderToken(ais, tree, lbrace, .none); + } else { + try renderToken(ais, tree, lbrace, .newline); + for (statements) |stmt, i| { + if (i != 0) try renderExtraNewline(ais, tree, stmt); + switch (node_tags[stmt]) { + .global_var_decl => try renderVarDecl(gpa, ais, tree, tree.globalVarDecl(stmt)), + .local_var_decl => try renderVarDecl(gpa, ais, tree, tree.localVarDecl(stmt)), + .simple_var_decl => try renderVarDecl(gpa, ais, tree, tree.simpleVarDecl(stmt)), + .aligned_var_decl => try renderVarDecl(gpa, ais, tree, tree.alignedVarDecl(stmt)), + else => try renderExpression(gpa, ais, tree, stmt, .semicolon), } + } + } + ais.popIndent(); - try renderToken(tree, ais, switch_case.arrow_token, Space.Space); // => + try renderToken(ais, tree, tree.lastToken(block_node), space); // rbrace +} - if (switch_case.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); - } +fn renderStructInit( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + struct_node: ast.Node.Index, + struct_init: ast.full.StructInit, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + if (struct_init.ast.type_expr == 0) { + try renderToken(ais, tree, struct_init.ast.lbrace - 1, .none); // . + } else { + try renderExpression(gpa, ais, tree, struct_init.ast.type_expr, .none); // T + } + if (struct_init.ast.fields.len == 0) { + ais.pushIndentNextLine(); + try renderToken(ais, tree, struct_init.ast.lbrace, .none); // lbrace + ais.popIndent(); + return renderToken(ais, tree, struct_init.ast.lbrace + 1, space); // rbrace + } - return renderExpression(allocator, ais, tree, switch_case.expr, space); - }, - .SwitchElse => { - const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base); - return renderToken(tree, ais, switch_else.token, space); - }, - .Else => { - const else_node = @fieldParentPtr(ast.Node.Else, "base", base); + const rbrace = tree.lastToken(struct_node); + const trailing_comma = token_tags[rbrace - 1] == .comma; + if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) { + // Render one field init per line. + ais.pushIndentNextLine(); + try renderToken(ais, tree, struct_init.ast.lbrace, .newline); + + try renderToken(ais, tree, struct_init.ast.lbrace + 1, .none); // . + try renderToken(ais, tree, struct_init.ast.lbrace + 2, .space); // name + try renderToken(ais, tree, struct_init.ast.lbrace + 3, .space); // = + try renderExpression(gpa, ais, tree, struct_init.ast.fields[0], .comma); + + for (struct_init.ast.fields[1..]) |field_init| { + const init_token = tree.firstToken(field_init); + try renderExtraNewlineToken(ais, tree, init_token - 3); + try renderToken(ais, tree, init_token - 3, .none); // . + try renderToken(ais, tree, init_token - 2, .space); // name + try renderToken(ais, tree, init_token - 1, .space); // = + try renderExpression(gpa, ais, tree, field_init, .comma); + } - const body_is_block = nodeIsBlock(else_node.body); - const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken()); + ais.popIndent(); + } else { + // Render all on one line, no trailing comma. + try renderToken(ais, tree, struct_init.ast.lbrace, .space); + + for (struct_init.ast.fields) |field_init| { + const init_token = tree.firstToken(field_init); + try renderToken(ais, tree, init_token - 3, .none); // . + try renderToken(ais, tree, init_token - 2, .space); // name + try renderToken(ais, tree, init_token - 1, .space); // = + try renderExpression(gpa, ais, tree, field_init, .comma_space); + } + } - const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline; - try renderToken(tree, ais, else_node.else_token, after_else_space); + return renderToken(ais, tree, rbrace, space); +} - if (else_node.payload) |payload| { - const payload_space = if (same_line) Space.Space else Space.Newline; - try renderExpression(allocator, ais, tree, payload, payload_space); - } +// TODO: handle comments between elements +fn renderArrayInit( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + array_init: ast.full.ArrayInit, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const token_starts = tree.tokens.items(.start); + + if (array_init.ast.type_expr == 0) { + try renderToken(ais, tree, array_init.ast.lbrace - 1, .none); // . + } else { + try renderExpression(gpa, ais, tree, array_init.ast.type_expr, .none); // T + } - if (same_line) { - return renderExpression(allocator, ais, tree, else_node.body, space); - } else { - ais.pushIndent(); - defer ais.popIndent(); - return renderExpression(allocator, ais, tree, else_node.body, space); - } - }, + if (array_init.ast.elements.len == 0) { + ais.pushIndentNextLine(); + try renderToken(ais, tree, array_init.ast.lbrace, .none); // lbrace + ais.popIndent(); + return renderToken(ais, tree, array_init.ast.lbrace + 1, space); // rbrace + } - .While => { - const while_node = @fieldParentPtr(ast.Node.While, "base", base); + const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1]; + const last_elem_token = tree.lastToken(last_elem); + const trailing_comma = token_tags[last_elem_token + 1] == .comma; + const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1; + assert(token_tags[rbrace] == .r_brace); + + if (array_init.ast.elements.len == 1) { + const only_elem = array_init.ast.elements[0]; + const first_token = tree.firstToken(only_elem); + if (token_tags[first_token] != .multiline_string_literal_line and + !anythingBetween(tree, last_elem_token, rbrace)) + { + try renderToken(ais, tree, array_init.ast.lbrace, .none); + try renderExpression(gpa, ais, tree, only_elem, .none); + return renderToken(ais, tree, rbrace, space); + } + } - if (while_node.label) |label| { - try renderToken(tree, ais, label, Space.None); // label - try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : - } + const contains_newlines = !tree.tokensOnSameLine(array_init.ast.lbrace, rbrace); - if (while_node.inline_token) |inline_token| { - try renderToken(tree, ais, inline_token, Space.Space); // inline + if (!trailing_comma and !contains_newlines) { + // Render all on one line, no trailing comma. + if (array_init.ast.elements.len == 1) { + // If there is only one element, we don't use spaces + try renderToken(ais, tree, array_init.ast.lbrace, .none); + try renderExpression(gpa, ais, tree, array_init.ast.elements[0], .none); + } else { + try renderToken(ais, tree, array_init.ast.lbrace, .space); + for (array_init.ast.elements) |elem| { + try renderExpression(gpa, ais, tree, elem, .comma_space); } + } + return renderToken(ais, tree, last_elem_token + 1, space); // rbrace + } - try renderToken(tree, ais, while_node.while_token, Space.Space); // while - try renderToken(tree, ais, tree.nextToken(while_node.while_token), Space.None); // ( - try renderExpression(allocator, ais, tree, while_node.condition, Space.None); + ais.pushIndentNextLine(); + try renderToken(ais, tree, array_init.ast.lbrace, .newline); + + var expr_index: usize = 0; + while (rowSize(tree, array_init.ast.elements[expr_index..], rbrace)) |row_size| { + const row_exprs = array_init.ast.elements[expr_index..]; + // A place to store the width of each expression and its column's maximum + const widths = try gpa.alloc(usize, row_exprs.len + row_size); + defer gpa.free(widths); + mem.set(usize, widths, 0); + + const expr_newlines = try gpa.alloc(bool, row_exprs.len); + defer gpa.free(expr_newlines); + mem.set(bool, expr_newlines, false); + + const expr_widths = widths[0..row_exprs.len]; + const column_widths = widths[row_exprs.len..]; + + // Find next row with trailing comment (if any) to end the current section. + const section_end = sec_end: { + var this_line_first_expr: usize = 0; + var this_line_size = rowSize(tree, row_exprs, rbrace); + for (row_exprs) |expr, i| { + // Ignore comment on first line of this section. + if (i == 0) continue; + const expr_last_token = tree.lastToken(expr); + if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token)) + continue; + // Track start of line containing comment. + if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) { + this_line_first_expr = i; + this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace); + } - const cond_rparen = tree.nextToken(while_node.condition.lastToken()); + const maybe_comma = expr_last_token + 1; + if (token_tags[maybe_comma] == .comma) { + if (hasSameLineComment(tree, maybe_comma)) + break :sec_end i - this_line_size.? + 1; + } + } + break :sec_end row_exprs.len; + }; + expr_index += section_end; - const body_is_block = nodeIsBlock(while_node.body); + const section_exprs = row_exprs[0..section_end]; - var block_start_space: Space = undefined; - var after_body_space: Space = undefined; + var sub_expr_buffer = std.ArrayList(u8).init(gpa); + defer sub_expr_buffer.deinit(); - if (body_is_block) { - block_start_space = Space.BlockStart; - after_body_space = if (while_node.@"else" == null) space else Space.SpaceOrOutdent; - } else if (tree.tokensOnSameLine(cond_rparen, while_node.body.lastToken())) { - block_start_space = Space.Space; - after_body_space = if (while_node.@"else" == null) space else Space.Space; + var auto_indenting_stream = Ais{ + .indent_delta = indent_delta, + .underlying_writer = sub_expr_buffer.writer(), + }; + + // Calculate size of columns in current section + var column_counter: usize = 0; + var single_line = true; + var contains_newline = false; + for (section_exprs) |expr, i| { + sub_expr_buffer.shrinkRetainingCapacity(0); + if (i + 1 < section_exprs.len) { + try renderExpression(gpa, &auto_indenting_stream, tree, expr, .none); + const width = sub_expr_buffer.items.len; + const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items, '\n') != null; + contains_newline = contains_newline or this_contains_newline; + expr_widths[i] = width; + expr_newlines[i] = this_contains_newline; + + if (!this_contains_newline) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + + const expr_last_token = tree.lastToken(expr) + 1; + const next_expr = section_exprs[i + 1]; + column_counter += 1; + if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false; + } else { + single_line = false; + column_counter = 0; + } } else { - block_start_space = Space.Newline; - after_body_space = if (while_node.@"else" == null) space else Space.Newline; + try renderExpression(gpa, &auto_indenting_stream, tree, expr, .none); + const width = sub_expr_buffer.items.len; + contains_newline = contains_newline or mem.indexOfScalar(u8, sub_expr_buffer.items, '\n') != null; + expr_widths[i] = width; + expr_newlines[i] = contains_newline; + + if (!contains_newline) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + } + break; } + } - { - const rparen_space = if (while_node.payload != null or while_node.continue_expr != null) Space.Space else block_start_space; - try renderToken(tree, ais, cond_rparen, rparen_space); // ) - } + // Render exprs in current section. + column_counter = 0; + var last_col_index: usize = row_size - 1; + for (section_exprs) |expr, i| { + if (i + 1 < section_exprs.len) { + const next_expr = section_exprs[i + 1]; + try renderExpression(gpa, ais, tree, expr, .none); + + const comma = tree.lastToken(expr) + 1; + + if (column_counter != last_col_index) { + if (!expr_newlines[i] and !expr_newlines[i + 1]) { + // Neither the current or next expression is multiline + try renderToken(ais, tree, comma, .space); // , + assert(column_widths[column_counter % row_size] >= expr_widths[i]); + const padding = column_widths[column_counter % row_size] - expr_widths[i]; + try ais.writer().writeByteNTimes(' ', padding); + + column_counter += 1; + continue; + } + } + if (single_line and row_size != 1) { + try renderToken(ais, tree, comma, .space); // , + continue; + } - if (while_node.payload) |payload| { - const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space; - try renderExpression(allocator, ais, tree, payload, payload_space); + column_counter = 0; + try renderToken(ais, tree, comma, .newline); // , + try renderExtraNewline(ais, tree, next_expr); + } else { + try renderExpression(gpa, ais, tree, expr, .comma); // , } + } - if (while_node.continue_expr) |continue_expr| { - const rparen = tree.nextToken(continue_expr.lastToken()); - const lparen = tree.prevToken(continue_expr.firstToken()); - const colon = tree.prevToken(lparen); - - try renderToken(tree, ais, colon, Space.Space); // : - try renderToken(tree, ais, lparen, Space.None); // ( - - try renderExpression(allocator, ais, tree, continue_expr, Space.None); + if (expr_index == array_init.ast.elements.len) + break; + } - try renderToken(tree, ais, rparen, block_start_space); // ) - } + ais.popIndent(); + return renderToken(ais, tree, rbrace, space); // rbrace +} - { - if (!body_is_block) ais.pushIndent(); - defer if (!body_is_block) ais.popIndent(); - try renderExpression(allocator, ais, tree, while_node.body, after_body_space); - } +fn renderContainerDecl( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + container_decl_node: ast.Node.Index, + container_decl: ast.full.ContainerDecl, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); - if (while_node.@"else") |@"else"| { - return renderExpression(allocator, ais, tree, &@"else".base, space); - } - }, + if (container_decl.layout_token) |layout_token| { + try renderToken(ais, tree, layout_token, .space); + } - .For => { - const for_node = @fieldParentPtr(ast.Node.For, "base", base); + var lbrace: ast.TokenIndex = undefined; + if (container_decl.ast.enum_token) |enum_token| { + try renderToken(ais, tree, container_decl.ast.main_token, .none); // union + try renderToken(ais, tree, enum_token - 1, .none); // lparen + try renderToken(ais, tree, enum_token, .none); // enum + if (container_decl.ast.arg != 0) { + try renderToken(ais, tree, enum_token + 1, .none); // lparen + try renderExpression(gpa, ais, tree, container_decl.ast.arg, .none); + const rparen = tree.lastToken(container_decl.ast.arg) + 1; + try renderToken(ais, tree, rparen, .none); // rparen + try renderToken(ais, tree, rparen + 1, .space); // rparen + lbrace = rparen + 2; + } else { + try renderToken(ais, tree, enum_token + 1, .space); // rparen + lbrace = enum_token + 2; + } + } else if (container_decl.ast.arg != 0) { + try renderToken(ais, tree, container_decl.ast.main_token, .none); // union + try renderToken(ais, tree, container_decl.ast.main_token + 1, .none); // lparen + try renderExpression(gpa, ais, tree, container_decl.ast.arg, .none); + const rparen = tree.lastToken(container_decl.ast.arg) + 1; + try renderToken(ais, tree, rparen, .space); // rparen + lbrace = rparen + 1; + } else { + try renderToken(ais, tree, container_decl.ast.main_token, .space); // union + lbrace = container_decl.ast.main_token + 1; + } - if (for_node.label) |label| { - try renderToken(tree, ais, label, Space.None); // label - try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : - } + const rbrace = tree.lastToken(container_decl_node); + if (container_decl.ast.members.len == 0) { + ais.pushIndentNextLine(); + if (token_tags[lbrace + 1] == .container_doc_comment) { + try renderToken(ais, tree, lbrace, .newline); // lbrace + try renderContainerDocComments(ais, tree, lbrace + 1); + } else { + try renderToken(ais, tree, lbrace, .none); // lbrace + } + ais.popIndent(); + return renderToken(ais, tree, rbrace, space); // rbrace + } - if (for_node.inline_token) |inline_token| { - try renderToken(tree, ais, inline_token, Space.Space); // inline - } + const src_has_trailing_comma = token_tags[rbrace - 1] == .comma; + if (!src_has_trailing_comma) one_line: { + // We can only print all the members in-line if all the members are fields. + for (container_decl.ast.members) |member| { + if (!node_tags[member].isContainerField()) break :one_line; + } + // All the declarations on the same line. + try renderToken(ais, tree, lbrace, .space); // lbrace + for (container_decl.ast.members) |member| { + try renderMember(gpa, ais, tree, member, .space); + } + return renderToken(ais, tree, rbrace, space); // rbrace + } - try renderToken(tree, ais, for_node.for_token, Space.Space); // for - try renderToken(tree, ais, tree.nextToken(for_node.for_token), Space.None); // ( - try renderExpression(allocator, ais, tree, for_node.array_expr, Space.None); + // One member per line. + ais.pushIndentNextLine(); + try renderToken(ais, tree, lbrace, .newline); // lbrace + if (token_tags[lbrace + 1] == .container_doc_comment) { + try renderContainerDocComments(ais, tree, lbrace + 1); + } + try renderMembers(gpa, ais, tree, container_decl.ast.members); + ais.popIndent(); - const rparen = tree.nextToken(for_node.array_expr.lastToken()); + return renderToken(ais, tree, rbrace, space); // rbrace +} - const body_is_block = for_node.body.tag.isBlock(); - const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); - const body_on_same_line = body_is_block or src_one_line_to_body; +fn renderAsm( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + asm_node: ast.full.Asm, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); - try renderToken(tree, ais, rparen, Space.Space); // ) + try renderToken(ais, tree, asm_node.ast.asm_token, .space); // asm - const space_after_payload = if (body_on_same_line) Space.Space else Space.Newline; - try renderExpression(allocator, ais, tree, for_node.payload, space_after_payload); // |x| + if (asm_node.volatile_token) |volatile_token| { + try renderToken(ais, tree, volatile_token, .space); // volatile + try renderToken(ais, tree, volatile_token + 1, .none); // lparen + } else { + try renderToken(ais, tree, asm_node.ast.asm_token + 1, .none); // lparen + } - const space_after_body = blk: { - if (for_node.@"else") |@"else"| { - const src_one_line_to_else = tree.tokensOnSameLine(rparen, @"else".firstToken()); - if (body_is_block or src_one_line_to_else) { - break :blk Space.Space; - } else { - break :blk Space.Newline; - } - } else { - break :blk space; + if (asm_node.ast.items.len == 0) { + ais.pushIndent(); + if (asm_node.first_clobber) |first_clobber| { + // asm ("foo" ::: "a", "b") + // asm ("foo" ::: "a", "b",) + try renderExpression(gpa, ais, tree, asm_node.ast.template, .space); + // Render the three colons. + try renderToken(ais, tree, first_clobber - 3, .none); + try renderToken(ais, tree, first_clobber - 2, .none); + try renderToken(ais, tree, first_clobber - 1, .space); + + var tok_i = first_clobber; + while (true) : (tok_i += 1) { + try renderToken(ais, tree, tok_i, .none); + tok_i += 1; + switch (token_tags[tok_i]) { + .r_paren => { + ais.popIndent(); + return renderToken(ais, tree, tok_i, space); + }, + .comma => { + if (token_tags[tok_i + 1] == .r_paren) { + ais.popIndent(); + return renderToken(ais, tree, tok_i + 1, space); + } else { + try renderToken(ais, tree, tok_i, .space); + } + }, + else => unreachable, } - }; - - { - if (!body_on_same_line) ais.pushIndent(); - defer if (!body_on_same_line) ais.popIndent(); - try renderExpression(allocator, ais, tree, for_node.body, space_after_body); // { body } } + } else { + // asm ("foo") + try renderExpression(gpa, ais, tree, asm_node.ast.template, .none); + ais.popIndent(); + return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen + } + } - if (for_node.@"else") |@"else"| { - return renderExpression(allocator, ais, tree, &@"else".base, space); // else + ais.pushIndent(); + try renderExpression(gpa, ais, tree, asm_node.ast.template, .newline); + ais.setIndentDelta(asm_indent_delta); + const colon1 = tree.lastToken(asm_node.ast.template) + 1; + + const colon2 = if (asm_node.outputs.len == 0) colon2: { + try renderToken(ais, tree, colon1, .newline); // : + break :colon2 colon1 + 1; + } else colon2: { + try renderToken(ais, tree, colon1, .space); // : + + ais.pushIndent(); + for (asm_node.outputs) |asm_output, i| { + if (i + 1 < asm_node.outputs.len) { + const next_asm_output = asm_node.outputs[i + 1]; + try renderAsmOutput(gpa, ais, tree, asm_output, .none); + + const comma = tree.firstToken(next_asm_output) - 1; + try renderToken(ais, tree, comma, .newline); // , + try renderExtraNewlineToken(ais, tree, tree.firstToken(next_asm_output)); + } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) { + try renderAsmOutput(gpa, ais, tree, asm_output, .newline); + ais.popIndent(); + ais.setIndentDelta(indent_delta); + ais.popIndent(); + return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen + } else { + try renderAsmOutput(gpa, ais, tree, asm_output, .newline); + const comma_or_colon = tree.lastToken(asm_output) + 1; + ais.popIndent(); + break :colon2 switch (token_tags[comma_or_colon]) { + .comma => comma_or_colon + 1, + else => comma_or_colon, + }; } - }, - - .If => { - const if_node = @fieldParentPtr(ast.Node.If, "base", base); - - const lparen = tree.nextToken(if_node.if_token); - const rparen = tree.nextToken(if_node.condition.lastToken()); - - try renderToken(tree, ais, if_node.if_token, Space.Space); // if - try renderToken(tree, ais, lparen, Space.None); // ( - - try renderExpression(allocator, ais, tree, if_node.condition, Space.None); // condition - - const body_is_if_block = if_node.body.tag == .If; - const body_is_block = nodeIsBlock(if_node.body); - - if (body_is_if_block) { - try renderExtraNewline(tree, ais, if_node.body); - } else if (body_is_block) { - const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space; - try renderToken(tree, ais, rparen, after_rparen_space); // ) - - if (if_node.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.BlockStart); // |x| - } + } else unreachable; + }; - if (if_node.@"else") |@"else"| { - try renderExpression(allocator, ais, tree, if_node.body, Space.SpaceOrOutdent); - return renderExpression(allocator, ais, tree, &@"else".base, space); - } else { - return renderExpression(allocator, ais, tree, if_node.body, space); - } + const colon3 = if (asm_node.inputs.len == 0) colon3: { + try renderToken(ais, tree, colon2, .newline); // : + break :colon3 colon2 + 1; + } else colon3: { + try renderToken(ais, tree, colon2, .space); // : + ais.pushIndent(); + for (asm_node.inputs) |asm_input, i| { + if (i + 1 < asm_node.inputs.len) { + const next_asm_input = asm_node.inputs[i + 1]; + try renderAsmInput(gpa, ais, tree, asm_input, .none); + + const first_token = tree.firstToken(next_asm_input); + try renderToken(ais, tree, first_token - 1, .newline); // , + try renderExtraNewlineToken(ais, tree, first_token); + } else if (asm_node.first_clobber == null) { + try renderAsmInput(gpa, ais, tree, asm_input, .newline); + ais.popIndent(); + ais.setIndentDelta(indent_delta); + ais.popIndent(); + return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen + } else { + try renderAsmInput(gpa, ais, tree, asm_input, .newline); + const comma_or_colon = tree.lastToken(asm_input) + 1; + ais.popIndent(); + break :colon3 switch (token_tags[comma_or_colon]) { + .comma => comma_or_colon + 1, + else => comma_or_colon, + }; } + } + unreachable; + }; - const src_has_newline = !tree.tokensOnSameLine(rparen, if_node.body.lastToken()); - - if (src_has_newline) { - const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space; + try renderToken(ais, tree, colon3, .space); // : + const first_clobber = asm_node.first_clobber.?; + var tok_i = first_clobber; + while (true) { + switch (token_tags[tok_i + 1]) { + .r_paren => { + ais.setIndentDelta(indent_delta); + ais.popIndent(); + try renderToken(ais, tree, tok_i, .newline); + return renderToken(ais, tree, tok_i + 1, space); + }, + .comma => { + try renderToken(ais, tree, tok_i, .none); + try renderToken(ais, tree, tok_i + 1, .space); + tok_i += 2; + }, + else => unreachable, + } + } else unreachable; // TODO shouldn't need this on while(true) +} - { - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, rparen, after_rparen_space); // ) - } +fn renderCall( + gpa: *Allocator, + ais: *Ais, + tree: ast.Tree, + call: ast.full.Call, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); + const main_tokens = tree.nodes.items(.main_token); - if (if_node.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Newline); - } + if (call.async_token) |async_token| { + try renderToken(ais, tree, async_token, .space); + } + try renderExpression(gpa, ais, tree, call.ast.fn_expr, .none); + + const lparen = call.ast.lparen; + const params = call.ast.params; + if (params.len == 0) { + ais.pushIndentNextLine(); + try renderToken(ais, tree, lparen, .none); + ais.popIndent(); + return renderToken(ais, tree, lparen + 1, space); // ) + } - if (if_node.@"else") |@"else"| { - const else_is_block = nodeIsBlock(@"else".body); + const last_param = params[params.len - 1]; + const after_last_param_tok = tree.lastToken(last_param) + 1; + if (token_tags[after_last_param_tok] == .comma) { + ais.pushIndentNextLine(); + try renderToken(ais, tree, lparen, .newline); // ( + for (params) |param_node, i| { + if (i + 1 < params.len) { + try renderExpression(gpa, ais, tree, param_node, .none); - { - ais.pushIndent(); - defer ais.popIndent(); - try renderExpression(allocator, ais, tree, if_node.body, Space.Newline); - } + // Unindent the comma for multiline string literals. + const is_multiline_string = + token_tags[tree.firstToken(param_node)] == .multiline_string_literal_line; + if (is_multiline_string) ais.popIndent(); - if (else_is_block) { - try renderToken(tree, ais, @"else".else_token, Space.Space); // else + const comma = tree.lastToken(param_node) + 1; + try renderToken(ais, tree, comma, .newline); // , - if (@"else".payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); - } + if (is_multiline_string) ais.pushIndent(); - return renderExpression(allocator, ais, tree, @"else".body, space); - } else { - const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space; - try renderToken(tree, ais, @"else".else_token, after_else_space); // else + try renderExtraNewline(ais, tree, params[i + 1]); + } else { + try renderExpression(gpa, ais, tree, param_node, .comma); + } + } + ais.popIndent(); + return renderToken(ais, tree, after_last_param_tok + 1, space); // ) + } - if (@"else".payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Newline); - } + try renderToken(ais, tree, lparen, .none); // ( - ais.pushIndent(); - defer ais.popIndent(); - return renderExpression(allocator, ais, tree, @"else".body, space); - } - } else { - ais.pushIndent(); - defer ais.popIndent(); - return renderExpression(allocator, ais, tree, if_node.body, space); - } - } + for (params) |param_node, i| { + const first_param_token = tree.firstToken(param_node); + if (token_tags[first_param_token] == .multiline_string_literal_line or + hasSameLineComment(tree, first_param_token - 1)) + { + ais.pushIndentOneShot(); + } + try renderExpression(gpa, ais, tree, param_node, .none); + + if (i + 1 < params.len) { + const comma = tree.lastToken(param_node) + 1; + const next_multiline_string = + token_tags[tree.firstToken(params[i + 1])] == .multiline_string_literal_line; + const comma_space: Space = if (next_multiline_string) .none else .space; + try renderToken(ais, tree, comma, comma_space); + } + } - // Single line if statement + return renderToken(ais, tree, after_last_param_tok, space); // ) +} - try renderToken(tree, ais, rparen, Space.Space); // ) +/// Renders the given expression indented, popping the indent before rendering +/// any following line comments +fn renderExpressionIndented(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void { + const token_starts = tree.tokens.items(.start); + const token_tags = tree.tokens.items(.tag); - if (if_node.payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); - } + ais.pushIndent(); - if (if_node.@"else") |@"else"| { - try renderExpression(allocator, ais, tree, if_node.body, Space.Space); - try renderToken(tree, ais, @"else".else_token, Space.Space); + var last_token = tree.lastToken(node); + const punctuation = switch (space) { + .none, .space, .newline, .skip => false, + .comma => true, + .comma_space => token_tags[last_token + 1] == .comma, + .semicolon => token_tags[last_token + 1] == .semicolon, + }; - if (@"else".payload) |payload| { - try renderExpression(allocator, ais, tree, payload, Space.Space); - } + try renderExpression(gpa, ais, tree, node, if (punctuation) .none else .skip); - return renderExpression(allocator, ais, tree, @"else".body, space); + switch (space) { + .none, .space, .newline, .skip => {}, + .comma => { + if (token_tags[last_token + 1] == .comma) { + try renderToken(ais, tree, last_token + 1, .skip); + last_token += 1; } else { - return renderExpression(allocator, ais, tree, if_node.body, space); + try ais.writer().writeByte(','); } }, + .comma_space => if (token_tags[last_token + 1] == .comma) { + try renderToken(ais, tree, last_token + 1, .skip); + last_token += 1; + }, + .semicolon => if (token_tags[last_token + 1] == .semicolon) { + try renderToken(ais, tree, last_token + 1, .skip); + last_token += 1; + }, + } - .Asm => { - const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base); - - try renderToken(tree, ais, asm_node.asm_token, Space.Space); // asm - - if (asm_node.volatile_token) |volatile_token| { - try renderToken(tree, ais, volatile_token, Space.Space); // volatile - try renderToken(tree, ais, tree.nextToken(volatile_token), Space.None); // ( - } else { - try renderToken(tree, ais, tree.nextToken(asm_node.asm_token), Space.None); // ( - } + ais.popIndent(); - asmblk: { - ais.pushIndent(); - defer ais.popIndent(); + if (space == .skip) return; - if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { - try renderExpression(allocator, ais, tree, asm_node.template, Space.None); - break :asmblk; - } + const comment_start = token_starts[last_token] + tokenSliceForRender(tree, last_token).len; + const comment = try renderComments(ais, tree, comment_start, token_starts[last_token + 1]); - try renderExpression(allocator, ais, tree, asm_node.template, Space.Newline); + if (!comment) switch (space) { + .none => {}, + .space, + .comma_space, + => try ais.writer().writeByte(' '), + .newline, + .comma, + .semicolon, + => try ais.insertNewline(), + .skip => unreachable, + }; +} - ais.setIndentDelta(asm_indent_delta); - defer ais.setIndentDelta(indent_delta); +/// Render an expression, and the comma that follows it, if it is present in the source. +fn renderExpressionComma(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const maybe_comma = tree.lastToken(node) + 1; + if (token_tags[maybe_comma] == .comma) { + try renderExpression(gpa, ais, tree, node, .none); + return renderToken(ais, tree, maybe_comma, space); + } else { + return renderExpression(gpa, ais, tree, node, space); + } +} - const colon1 = tree.nextToken(asm_node.template.lastToken()); +fn renderTokenComma(ais: *Ais, tree: ast.Tree, token: ast.TokenIndex, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const maybe_comma = token + 1; + if (token_tags[maybe_comma] == .comma) { + try renderToken(ais, tree, token, .none); + return renderToken(ais, tree, maybe_comma, space); + } else { + return renderToken(ais, tree, token, space); + } +} - const colon2 = if (asm_node.outputs.len == 0) blk: { - try renderToken(tree, ais, colon1, Space.Newline); // : +const Space = enum { + /// Output the token lexeme only. + none, + /// Output the token lexeme followed by a single space. + space, + /// Output the token lexeme followed by a newline. + newline, + /// If the next token is a comma, render it as well. If not, insert one. + /// In either case, a newline will be inserted afterwards. + comma, + /// Additionally consume the next token if it is a comma. + /// In either case, a space will be inserted afterwards. + comma_space, + /// Additionally consume the next token if it is a semicolon. + /// In either case, a newline will be inserted afterwards. + semicolon, + /// Skip rendering whitespace and comments. If this is used, the caller + /// *must* handle handle whitespace and comments manually. + skip, +}; - break :blk tree.nextToken(colon1); - } else blk: { - try renderToken(tree, ais, colon1, Space.Space); // : +fn renderToken(ais: *Ais, tree: ast.Tree, token_index: ast.TokenIndex, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + const token_starts = tree.tokens.items(.start); - ais.pushIndent(); - defer ais.popIndent(); + const token_start = token_starts[token_index]; + const lexeme = tokenSliceForRender(tree, token_index); - for (asm_node.outputs) |*asm_output, i| { - if (i + 1 < asm_node.outputs.len) { - const next_asm_output = asm_node.outputs[i + 1]; - try renderAsmOutput(allocator, ais, tree, asm_output, Space.None); + try ais.writer().writeAll(lexeme); - const comma = tree.prevToken(next_asm_output.firstToken()); - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken()); - } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { - try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); - break :asmblk; - } else { - try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); - const comma_or_colon = tree.nextToken(asm_output.lastToken()); - break :blk switch (tree.token_ids[comma_or_colon]) { - .Comma => tree.nextToken(comma_or_colon), - else => comma_or_colon, - }; - } - } - unreachable; - }; + if (space == .skip) return; - const colon3 = if (asm_node.inputs.len == 0) blk: { - try renderToken(tree, ais, colon2, Space.Newline); // : - break :blk tree.nextToken(colon2); - } else blk: { - try renderToken(tree, ais, colon2, Space.Space); // : - ais.pushIndent(); - defer ais.popIndent(); - for (asm_node.inputs) |*asm_input, i| { - if (i + 1 < asm_node.inputs.len) { - const next_asm_input = &asm_node.inputs[i + 1]; - try renderAsmInput(allocator, ais, tree, asm_input, Space.None); - - const comma = tree.prevToken(next_asm_input.firstToken()); - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewlineToken(tree, ais, next_asm_input.firstToken()); - } else if (asm_node.clobbers.len == 0) { - try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline); - break :asmblk; - } else { - try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline); - const comma_or_colon = tree.nextToken(asm_input.lastToken()); - break :blk switch (tree.token_ids[comma_or_colon]) { - .Comma => tree.nextToken(comma_or_colon), - else => comma_or_colon, - }; - } - } - unreachable; - }; + if (space == .comma and token_tags[token_index + 1] != .comma) { + try ais.writer().writeByte(','); + } - try renderToken(tree, ais, colon3, Space.Space); // : - ais.pushIndent(); - defer ais.popIndent(); - for (asm_node.clobbers) |clobber_node, i| { - if (i + 1 >= asm_node.clobbers.len) { - try renderExpression(allocator, ais, tree, clobber_node, Space.Newline); - break :asmblk; - } else { - try renderExpression(allocator, ais, tree, clobber_node, Space.None); - const comma = tree.nextToken(clobber_node.lastToken()); - try renderToken(tree, ais, comma, Space.Space); // , - } - } - } + const comment = try renderComments(ais, tree, token_start + lexeme.len, token_starts[token_index + 1]); + switch (space) { + .none => {}, + .space => if (!comment) try ais.writer().writeByte(' '), + .newline => if (!comment) try ais.insertNewline(), - return renderToken(tree, ais, asm_node.rparen, space); + .comma => if (token_tags[token_index + 1] == .comma) { + try renderToken(ais, tree, token_index + 1, .newline); + } else if (!comment) { + try ais.insertNewline(); }, - .EnumLiteral => { - const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base); + .comma_space => if (token_tags[token_index + 1] == .comma) { + try renderToken(ais, tree, token_index + 1, .space); + } else if (!comment) { + try ais.writer().writeByte(' '); + }, - try renderToken(tree, ais, enum_literal.dot, Space.None); // . - return renderToken(tree, ais, enum_literal.name, space); // name + .semicolon => if (token_tags[token_index + 1] == .semicolon) { + try renderToken(ais, tree, token_index + 1, .newline); + } else if (!comment) { + try ais.insertNewline(); }, - .ContainerField, - .Root, - .VarDecl, - .Use, - .TestDecl, - => unreachable, + .skip => unreachable, } } -fn renderArrayType( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - lbracket: ast.TokenIndex, - rhs: *ast.Node, - len_expr: *ast.Node, - opt_sentinel: ?*ast.Node, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - const rbracket = tree.nextToken(if (opt_sentinel) |sentinel| - sentinel.lastToken() - else - len_expr.lastToken()); +/// Returns true if there exists a comment between the start of token +/// `start_token` and the start of token `end_token`. This is used to determine +/// if e.g. a fn_proto should be wrapped and have a trailing comma inserted +/// even if there is none in the source. +fn hasComment(tree: ast.Tree, start_token: ast.TokenIndex, end_token: ast.TokenIndex) bool { + const token_starts = tree.tokens.items(.start); - const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; - const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; - const new_space = if (ends_with_comment) Space.Newline else Space.None; - { - const do_indent = (starts_with_comment or ends_with_comment); - if (do_indent) ais.pushIndent(); - defer if (do_indent) ais.popIndent(); - - try renderToken(tree, ais, lbracket, Space.None); // [ - try renderExpression(allocator, ais, tree, len_expr, new_space); + const start = token_starts[start_token]; + const end = token_starts[end_token]; - if (starts_with_comment) { - try ais.maybeInsertNewline(); - } - if (opt_sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, ais, colon_token, Space.None); // : - try renderExpression(allocator, ais, tree, sentinel, Space.None); - } - if (starts_with_comment) { - try ais.maybeInsertNewline(); - } - } - try renderToken(tree, ais, rbracket, Space.None); // ] - - return renderExpression(allocator, ais, tree, rhs, space); + return mem.indexOf(u8, tree.source[start..end], "//") != null; } -fn renderAsmOutput( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - asm_output: *const ast.Node.Asm.Output, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - try ais.writer().writeAll("["); - try renderExpression(allocator, ais, tree, asm_output.symbolic_name, Space.None); - try ais.writer().writeAll("] "); - try renderExpression(allocator, ais, tree, asm_output.constraint, Space.None); - try ais.writer().writeAll(" ("); - - switch (asm_output.kind) { - .Variable => |variable_name| { - try renderExpression(allocator, ais, tree, &variable_name.base, Space.None); - }, - .Return => |return_type| { - try ais.writer().writeAll("-> "); - try renderExpression(allocator, ais, tree, return_type, Space.None); - }, - } - - return renderToken(tree, ais, asm_output.lastToken(), space); // ) -} +/// Assumes that start is the first byte past the previous token and +/// that end is the last byte before the next token. +fn renderComments(ais: *Ais, tree: ast.Tree, start: usize, end: usize) Error!bool { + var index: usize = start; + while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| { + const comment_start = index + offset; -fn renderAsmInput( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - asm_input: *const ast.Node.Asm.Input, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - try ais.writer().writeAll("["); - try renderExpression(allocator, ais, tree, asm_input.symbolic_name, Space.None); - try ais.writer().writeAll("] "); - try renderExpression(allocator, ais, tree, asm_input.constraint, Space.None); - try ais.writer().writeAll(" ("); - try renderExpression(allocator, ais, tree, asm_input.expr, Space.None); - return renderToken(tree, ais, asm_input.lastToken(), space); // ) -} + // If there is no newline, the comment ends with EOF + const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n'); + const newline = if (newline_index) |i| comment_start + i else null; -fn renderVarDecl( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - var_decl: *ast.Node.VarDecl, -) (@TypeOf(ais.*).Error || Error)!void { - if (var_decl.getVisibToken()) |visib_token| { - try renderToken(tree, ais, visib_token, Space.Space); // pub - } + const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len]; + const trimmed_comment = mem.trimRight(u8, untrimmed_comment, &std.ascii.spaces); - if (var_decl.getExternExportToken()) |extern_export_token| { - try renderToken(tree, ais, extern_export_token, Space.Space); // extern - - if (var_decl.getLibName()) |lib_name| { - try renderExpression(allocator, ais, tree, lib_name, Space.Space); // "lib" + // Don't leave any whitespace at the start of the file + if (index != 0) { + if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) { + // Leave up to one empty line before the first comment + try ais.insertNewline(); + try ais.insertNewline(); + } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) { + // Respect the newline directly before the comment. + // Note: This allows an empty line between comments + try ais.insertNewline(); + } else if (index == start) { + // Otherwise if the first comment is on the same line as + // the token before it, prefix it with a single space. + try ais.writer().writeByte(' '); + } } - } - if (var_decl.getComptimeToken()) |comptime_token| { - try renderToken(tree, ais, comptime_token, Space.Space); // comptime - } + try ais.writer().print("{s}\n", .{trimmed_comment}); + index = 1 + (newline orelse return true); - if (var_decl.getThreadLocalToken()) |thread_local_token| { - try renderToken(tree, ais, thread_local_token, Space.Space); // threadlocal + if (ais.disabled_offset) |disabled_offset| { + if (mem.eql(u8, trimmed_comment, "// zig fmt: on")) { + // write the source for which formatting was disabled directly + // to the underlying writer, fixing up invaild whitespace + try writeFixingWhitespace(ais.underlying_writer, tree.source[disabled_offset..index]); + ais.disabled_offset = null; + } + } else if (mem.eql(u8, trimmed_comment, "// zig fmt: off")) { + ais.disabled_offset = index; + } } - try renderToken(tree, ais, var_decl.mut_token, Space.Space); // var - - const name_space = if (var_decl.getTypeNode() == null and - (var_decl.getAlignNode() != null or - var_decl.getSectionNode() != null or - var_decl.getInitNode() != null)) - Space.Space - else - Space.None; - try renderToken(tree, ais, var_decl.name_token, name_space); - - if (var_decl.getTypeNode()) |type_node| { - try renderToken(tree, ais, tree.nextToken(var_decl.name_token), Space.Space); - const s = if (var_decl.getAlignNode() != null or - var_decl.getSectionNode() != null or - var_decl.getInitNode() != null) Space.Space else Space.None; - try renderExpression(allocator, ais, tree, type_node, s); - } - - if (var_decl.getAlignNode()) |align_node| { - const lparen = tree.prevToken(align_node.firstToken()); - const align_kw = tree.prevToken(lparen); - const rparen = tree.nextToken(align_node.lastToken()); - try renderToken(tree, ais, align_kw, Space.None); // align - try renderToken(tree, ais, lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, align_node, Space.None); - const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None; - try renderToken(tree, ais, rparen, s); // ) - } - - if (var_decl.getSectionNode()) |section_node| { - const lparen = tree.prevToken(section_node.firstToken()); - const section_kw = tree.prevToken(lparen); - const rparen = tree.nextToken(section_node.lastToken()); - try renderToken(tree, ais, section_kw, Space.None); // linksection - try renderToken(tree, ais, lparen, Space.None); // ( - try renderExpression(allocator, ais, tree, section_node, Space.None); - const s = if (var_decl.getInitNode() != null) Space.Space else Space.None; - try renderToken(tree, ais, rparen, s); // ) - } - - if (var_decl.getInitNode()) |init_node| { - const eq_token = var_decl.getEqToken().?; - const eq_space = blk: { - const loc = tree.tokenLocation(tree.token_locs[eq_token].end, tree.nextToken(eq_token)); - break :blk if (loc.line == 0) Space.Space else Space.Newline; - }; - { - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, eq_token, eq_space); // = - } - ais.pushIndentOneShot(); - try renderExpression(allocator, ais, tree, init_node, Space.None); + if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) { + try ais.insertNewline(); } - try renderToken(tree, ais, var_decl.semicolon_token, Space.Newline); + return index != start; } -fn renderParamDecl( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - param_decl: ast.Node.FnProto.ParamDecl, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - try renderDocComments(tree, ais, param_decl, param_decl.doc_comments); - - if (param_decl.comptime_token) |comptime_token| { - try renderToken(tree, ais, comptime_token, Space.Space); - } - if (param_decl.noalias_token) |noalias_token| { - try renderToken(tree, ais, noalias_token, Space.Space); - } - if (param_decl.name_token) |name_token| { - try renderToken(tree, ais, name_token, Space.None); - try renderToken(tree, ais, tree.nextToken(name_token), Space.Space); // : - } - switch (param_decl.param_type) { - .any_type, .type_expr => |node| try renderExpression(allocator, ais, tree, node, space), - } +fn renderExtraNewline(ais: *Ais, tree: ast.Tree, node: ast.Node.Index) Error!void { + return renderExtraNewlineToken(ais, tree, tree.firstToken(node)); } -fn renderStatement( - allocator: *mem.Allocator, - ais: anytype, - tree: *ast.Tree, - base: *ast.Node, -) (@TypeOf(ais.*).Error || Error)!void { - switch (base.tag) { - .VarDecl => { - const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); - try renderVarDecl(allocator, ais, tree, var_decl); - }, - else => { - if (base.requireSemiColon()) { - try renderExpression(allocator, ais, tree, base, Space.None); - - const semicolon_index = tree.nextToken(base.lastToken()); - assert(tree.token_ids[semicolon_index] == .Semicolon); - try renderToken(tree, ais, semicolon_index, Space.Newline); - } else { - try renderExpression(allocator, ais, tree, base, Space.Newline); - } - }, +/// Check if there is an empty line immediately before the given token. If so, render it. +fn renderExtraNewlineToken(ais: *Ais, tree: ast.Tree, token_index: ast.TokenIndex) Error!void { + const token_starts = tree.tokens.items(.start); + const token_start = token_starts[token_index]; + if (token_start == 0) return; + const prev_token_end = if (token_index == 0) + 0 + else + token_starts[token_index - 1] + tokenSliceForRender(tree, token_index - 1).len; + + // If there is a comment present, it will handle the empty line + if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return; + + // Iterate backwards to the end of the previous token, stopping if a + // non-whitespace character is encountered or two newlines have been found. + var i = token_start - 1; + var newlines: u2 = 0; + while (std.ascii.isSpace(tree.source[i])) : (i -= 1) { + if (tree.source[i] == '\n') newlines += 1; + if (newlines == 2) return ais.insertNewline(); + if (i == prev_token_end) break; } } -const Space = enum { - None, - Newline, - Comma, - Space, - SpaceOrOutdent, - NoNewline, - NoComment, - BlockStart, -}; - -fn renderTokenOffset( - tree: *ast.Tree, - ais: anytype, - token_index: ast.TokenIndex, - space: Space, - token_skip_bytes: usize, -) (@TypeOf(ais.*).Error || Error)!void { - if (space == Space.BlockStart) { - // If placing the lbrace on the current line would cause an uggly gap then put the lbrace on the next line - const new_space = if (ais.isLineOverIndented()) Space.Newline else Space.Space; - return renderToken(tree, ais, token_index, new_space); +/// end_token is the token one past the last doc comment token. This function +/// searches backwards from there. +fn renderDocComments(ais: *Ais, tree: ast.Tree, end_token: ast.TokenIndex) Error!void { + // Search backwards for the first doc comment. + const token_tags = tree.tokens.items(.tag); + if (end_token == 0) return; + var tok = end_token - 1; + while (token_tags[tok] == .doc_comment) { + if (tok == 0) break; + tok -= 1; + } else { + tok += 1; } + const first_tok = tok; + if (first_tok == end_token) return; + try renderExtraNewlineToken(ais, tree, first_tok); - var token_loc = tree.token_locs[token_index]; - try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(token_loc)[token_skip_bytes..], " ")); - - if (space == Space.NoComment) - return; - - var next_token_id = tree.token_ids[token_index + 1]; - var next_token_loc = tree.token_locs[token_index + 1]; - - if (space == Space.Comma) switch (next_token_id) { - .Comma => return renderToken(tree, ais, token_index + 1, Space.Newline), - .LineComment => { - try ais.writer().writeAll(", "); - return renderToken(tree, ais, token_index + 1, Space.Newline); - }, - else => { - if (token_index + 2 < tree.token_ids.len and - tree.token_ids[token_index + 2] == .MultilineStringLiteralLine) - { - try ais.writer().writeAll(","); - return; - } else { - try ais.writer().writeAll(","); - try ais.insertNewline(); - return; - } - }, - }; - - // Skip over same line doc comments - var offset: usize = 1; - if (next_token_id == .DocComment) { - const loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); - if (loc.line == 0) { - offset += 1; - next_token_id = tree.token_ids[token_index + offset]; - next_token_loc = tree.token_locs[token_index + offset]; - } + while (token_tags[tok] == .doc_comment) : (tok += 1) { + try renderToken(ais, tree, tok, .newline); } +} - if (next_token_id != .LineComment) { - switch (space) { - Space.None, Space.NoNewline => return, - Space.Newline => { - if (next_token_id == .MultilineStringLiteralLine) { - return; - } else { - try ais.insertNewline(); - return; - } - }, - Space.Space, Space.SpaceOrOutdent => { - if (next_token_id == .MultilineStringLiteralLine) - return; - try ais.writer().writeByte(' '); - return; - }, - Space.NoComment, Space.Comma, Space.BlockStart => unreachable, - } +/// start_token is first container doc comment token. +fn renderContainerDocComments(ais: *Ais, tree: ast.Tree, start_token: ast.TokenIndex) Error!void { + const token_tags = tree.tokens.items(.tag); + var tok = start_token; + while (token_tags[tok] == .container_doc_comment) : (tok += 1) { + try renderToken(ais, tree, tok, .newline); } - - while (true) { - const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ").len == 2; - if (comment_is_empty) { - switch (space) { - Space.Newline => { - offset += 1; - token_loc = next_token_loc; - next_token_id = tree.token_ids[token_index + offset]; - next_token_loc = tree.token_locs[token_index + offset]; - if (next_token_id != .LineComment) { - try ais.insertNewline(); - return; - } - }, - else => break, - } - } else { - break; - } - } - - var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); - if (loc.line == 0) { - if (tree.token_ids[token_index] != .MultilineStringLiteralLine) { - try ais.writer().writeByte(' '); - } - try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); - offset = 2; - token_loc = next_token_loc; - next_token_loc = tree.token_locs[token_index + offset]; - next_token_id = tree.token_ids[token_index + offset]; - if (next_token_id != .LineComment) { - switch (space) { - .None, .Space, .SpaceOrOutdent => { - try ais.insertNewline(); - }, - .Newline => { - if (next_token_id == .MultilineStringLiteralLine) { - return; - } else { - try ais.insertNewline(); - return; - } - }, - .NoNewline => {}, - .NoComment, .Comma, .BlockStart => unreachable, - } - return; - } - loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); + // Render extra newline if there is one between final container doc comment and + // the next token. If the next token is a doc comment, that code path + // will have its own logic to insert a newline. + if (token_tags[tok] != .doc_comment) { + try renderExtraNewlineToken(ais, tree, tok); } +} - while (true) { - // translate-c doesn't generate correct newlines - // in generated code (loc.line == 0) so treat that case - // as though there was meant to be a newline between the tokens - var newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2); - while (newline_count > 0) : (newline_count -= 1) try ais.insertNewline(); - try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); - - offset += 1; - token_loc = next_token_loc; - next_token_loc = tree.token_locs[token_index + offset]; - next_token_id = tree.token_ids[token_index + offset]; - if (next_token_id != .LineComment) { - switch (space) { - .Newline => { - if (next_token_id == .MultilineStringLiteralLine) { - return; - } else { - try ais.insertNewline(); - return; - } - }, - .None, .Space, .SpaceOrOutdent => { - try ais.insertNewline(); - }, - .NoNewline => {}, - .NoComment, .Comma, .BlockStart => unreachable, - } - return; - } - loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); +fn tokenSliceForRender(tree: ast.Tree, token_index: ast.TokenIndex) []const u8 { + var ret = tree.tokenSlice(token_index); + if (tree.tokens.items(.tag)[token_index] == .multiline_string_literal_line) { + assert(ret[ret.len - 1] == '\n'); + ret.len -= 1; } + return ret; } -fn renderToken( - tree: *ast.Tree, - ais: anytype, - token_index: ast.TokenIndex, - space: Space, -) (@TypeOf(ais.*).Error || Error)!void { - return renderTokenOffset(tree, ais, token_index, space, 0); +fn hasSameLineComment(tree: ast.Tree, token_index: ast.TokenIndex) bool { + const token_starts = tree.tokens.items(.start); + const between_source = tree.source[token_starts[token_index]..token_starts[token_index + 1]]; + for (between_source) |byte| switch (byte) { + '\n' => return false, + '/' => return true, + else => continue, + }; + return false; } -fn renderDocComments( - tree: *ast.Tree, - ais: anytype, - node: anytype, - doc_comments: ?*ast.Node.DocComment, -) (@TypeOf(ais.*).Error || Error)!void { - const comment = doc_comments orelse return; - return renderDocCommentsToken(tree, ais, comment, node.firstToken()); +/// Returns `true` if and only if there are any tokens or line comments between +/// start_token and end_token. +fn anythingBetween(tree: ast.Tree, start_token: ast.TokenIndex, end_token: ast.TokenIndex) bool { + if (start_token + 1 != end_token) return true; + const token_starts = tree.tokens.items(.start); + const between_source = tree.source[token_starts[start_token]..token_starts[start_token + 1]]; + for (between_source) |byte| switch (byte) { + '/' => return true, + else => continue, + }; + return false; } -fn renderDocCommentsToken( - tree: *ast.Tree, - ais: anytype, - comment: *ast.Node.DocComment, - first_token: ast.TokenIndex, -) (@TypeOf(ais.*).Error || Error)!void { - var tok_i = comment.first_line; - while (true) : (tok_i += 1) { - switch (tree.token_ids[tok_i]) { - .DocComment, .ContainerDocComment => { - if (comment.first_line < first_token) { - try renderToken(tree, ais, tok_i, Space.Newline); - } else { - try renderToken(tree, ais, tok_i, Space.NoComment); - try ais.insertNewline(); - } - }, - .LineComment => continue, - else => break, - } - } +fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void { + for (slice) |byte| switch (byte) { + '\t' => try writer.writeAll(" " ** 4), + '\r' => {}, + else => try writer.writeByte(byte), + }; } -fn nodeIsBlock(base: *const ast.Node) bool { - return switch (base.tag) { - .Block, - .LabeledBlock, - .If, - .For, - .While, - .Switch, +fn nodeIsBlock(tag: ast.Node.Tag) bool { + return switch (tag) { + .block, + .block_semicolon, + .block_two, + .block_two_semicolon, + .@"if", + .if_simple, + .@"for", + .for_simple, + .@"while", + .while_simple, + .while_cont, + .@"switch", + .switch_comma, => true, else => false, }; } -fn nodeCausesSliceOpSpace(base: *ast.Node) bool { - return switch (base.tag) { - .Catch, - .Add, - .AddWrap, - .ArrayCat, - .ArrayMult, - .Assign, - .AssignBitAnd, - .AssignBitOr, - .AssignBitShiftLeft, - .AssignBitShiftRight, - .AssignBitXor, - .AssignDiv, - .AssignSub, - .AssignSubWrap, - .AssignMod, - .AssignAdd, - .AssignAddWrap, - .AssignMul, - .AssignMulWrap, - .BangEqual, - .BitAnd, - .BitOr, - .BitShiftLeft, - .BitShiftRight, - .BitXor, - .BoolAnd, - .BoolOr, - .Div, - .EqualEqual, - .ErrorUnion, - .GreaterOrEqual, - .GreaterThan, - .LessOrEqual, - .LessThan, - .MergeErrorSets, - .Mod, - .Mul, - .MulWrap, - .Range, - .Sub, - .SubWrap, - .OrElse, - => true, - +fn nodeIsIf(tag: ast.Node.Tag) bool { + return switch (tag) { + .@"if", .if_simple => true, else => false, }; } -fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!void { - for (slice) |byte| switch (byte) { - '\t' => try ais.writer().writeAll(" "), - '\r' => {}, - else => try ais.writer().writeByte(byte), +fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool { + return switch (tag) { + .@"catch", + .add, + .add_wrap, + .array_cat, + .array_mult, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_mul, + .assign_mul_wrap, + .bang_equal, + .bit_and, + .bit_or, + .bit_shift_left, + .bit_shift_right, + .bit_xor, + .bool_and, + .bool_or, + .div, + .equal_equal, + .error_union, + .greater_or_equal, + .greater_than, + .less_or_equal, + .less_than, + .merge_error_sets, + .mod, + .mul, + .mul_wrap, + .sub, + .sub_wrap, + .@"orelse", + => true, + + else => false, }; } // Returns the number of nodes in `expr` that are on the same line as `rtoken`, // or null if they all are on the same line. -fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize { - const first_token = exprs[0].firstToken(); - const first_loc = tree.tokenLocation(tree.token_locs[first_token].start, rtoken); - if (first_loc.line == 0) { - const maybe_comma = tree.prevToken(rtoken); - if (tree.token_ids[maybe_comma] == .Comma) +fn rowSize(tree: ast.Tree, exprs: []const ast.Node.Index, rtoken: ast.TokenIndex) ?usize { + const token_tags = tree.tokens.items(.tag); + + const first_token = tree.firstToken(exprs[0]); + if (tree.tokensOnSameLine(first_token, rtoken)) { + const maybe_comma = rtoken - 1; + if (token_tags[maybe_comma] == .comma) return 1; return null; // no newlines } @@ -2668,9 +2572,8 @@ fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize { var count: usize = 1; for (exprs) |expr, i| { if (i + 1 < exprs.len) { - const expr_last_token = expr.lastToken() + 1; - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, exprs[i + 1].firstToken()); - if (loc.line != 0) return count; + const expr_last_token = tree.lastToken(expr) + 1; + if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count; count += 1; } else { return count; @@ -2678,3 +2581,150 @@ fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize { } unreachable; } + +/// Automatically inserts indentation of written data by keeping +/// track of the current indentation level +fn AutoIndentingStream(comptime UnderlyingWriter: type) type { + return struct { + const Self = @This(); + pub const Error = UnderlyingWriter.Error; + pub const Writer = std.io.Writer(*Self, Error, write); + + underlying_writer: UnderlyingWriter, + + /// Offset into the source at which formatting has been disabled with + /// a `zig fmt: off` comment. + /// + /// If non-null, the AutoIndentingStream will not write any bytes + /// to the underlying writer. It will however continue to track the + /// indentation level. + disabled_offset: ?usize = null, + + indent_count: usize = 0, + indent_delta: usize, + current_line_empty: bool = true, + /// automatically popped when applied + indent_one_shot_count: usize = 0, + /// the most recently applied indent + applied_indent: usize = 0, + /// not used until the next line + indent_next_line: usize = 0, + + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + pub fn write(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) + return @as(usize, 0); + + try self.applyIndent(); + return self.writeNoIndent(bytes); + } + + // Change the indent delta without changing the final indentation level + pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void { + if (self.indent_delta == new_indent_delta) { + return; + } else if (self.indent_delta > new_indent_delta) { + assert(self.indent_delta % new_indent_delta == 0); + self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta); + } else { + // assert that the current indentation (in spaces) in a multiple of the new delta + assert((self.indent_count * self.indent_delta) % new_indent_delta == 0); + self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta); + } + self.indent_delta = new_indent_delta; + } + + fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) + return @as(usize, 0); + + if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes); + if (bytes[bytes.len - 1] == '\n') + self.resetLine(); + return bytes.len; + } + + pub fn insertNewline(self: *Self) Error!void { + _ = try self.writeNoIndent("\n"); + } + + fn resetLine(self: *Self) void { + self.current_line_empty = true; + self.indent_next_line = 0; + } + + /// Insert a newline unless the current line is blank + pub fn maybeInsertNewline(self: *Self) Error!void { + if (!self.current_line_empty) + try self.insertNewline(); + } + + /// Push default indentation + /// Doesn't actually write any indentation. + /// Just primes the stream to be able to write the correct indentation if it needs to. + pub fn pushIndent(self: *Self) void { + self.indent_count += 1; + } + + /// Push an indent that is automatically popped after being applied + pub fn pushIndentOneShot(self: *Self) void { + self.indent_one_shot_count += 1; + self.pushIndent(); + } + + /// Turns all one-shot indents into regular indents + /// Returns number of indents that must now be manually popped + pub fn lockOneShotIndent(self: *Self) usize { + var locked_count = self.indent_one_shot_count; + self.indent_one_shot_count = 0; + return locked_count; + } + + /// Push an indent that should not take effect until the next line + pub fn pushIndentNextLine(self: *Self) void { + self.indent_next_line += 1; + self.pushIndent(); + } + + pub fn popIndent(self: *Self) void { + assert(self.indent_count != 0); + self.indent_count -= 1; + + if (self.indent_next_line > 0) + self.indent_next_line -= 1; + } + + /// Writes ' ' bytes if the current line is empty + fn applyIndent(self: *Self) Error!void { + const current_indent = self.currentIndent(); + if (self.current_line_empty and current_indent > 0) { + if (self.disabled_offset == null) { + try self.underlying_writer.writeByteNTimes(' ', current_indent); + } + self.applied_indent = current_indent; + } + + self.indent_count -= self.indent_one_shot_count; + self.indent_one_shot_count = 0; + self.current_line_empty = false; + } + + /// Checks to see if the most recent indentation exceeds the currently pushed indents + pub fn isLineOverIndented(self: *Self) bool { + if (self.current_line_empty) return false; + return self.applied_indent > self.currentIndent(); + } + + fn currentIndent(self: *Self) usize { + var indent_current: usize = 0; + if (self.indent_count > 0) { + const indent_count = self.indent_count - self.indent_next_line; + indent_current = indent_count * self.indent_delta; + } + return indent_current; + } + }; +} diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index b92d795eee..78d4e63bfe 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index af0b000328..2d9f286dd6 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -14,21 +14,24 @@ const process = std.process; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const macos = @import("system/macos.zig"); - -const is_windows = Target.current.os.tag == .windows; +pub const windows = @import("system/windows.zig"); pub const getSDKPath = macos.getSDKPath; pub const NativePaths = struct { include_dirs: ArrayList([:0]u8), lib_dirs: ArrayList([:0]u8), + framework_dirs: ArrayList([:0]u8), rpaths: ArrayList([:0]u8), warnings: ArrayList([:0]u8), - pub fn detect(allocator: *Allocator) !NativePaths { + pub fn detect(allocator: *Allocator, native_info: NativeTargetInfo) !NativePaths { + const native_target = native_info.target; + var self: NativePaths = .{ .include_dirs = ArrayList([:0]u8).init(allocator), .lib_dirs = ArrayList([:0]u8).init(allocator), + .framework_dirs = ArrayList([:0]u8).init(allocator), .rpaths = ArrayList([:0]u8).init(allocator), .warnings = ArrayList([:0]u8).init(allocator), }; @@ -49,7 +52,7 @@ pub const NativePaths = struct { }; try self.addIncludeDir(include_path); } else { - try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}", .{word}); + try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word}); break; } } @@ -75,7 +78,7 @@ pub const NativePaths = struct { const lib_path = word[2..]; try self.addLibDir(lib_path); } else { - try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {}", .{word}); + try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word}); break; } } @@ -88,9 +91,22 @@ pub const NativePaths = struct { return self; } - if (!is_windows) { - const triple = try Target.current.linuxTriple(allocator); - const qual = Target.current.cpu.arch.ptrBitWidth(); + if (comptime Target.current.isDarwin()) { + try self.addIncludeDir("/usr/include"); + try self.addIncludeDir("/usr/local/include"); + + try self.addLibDir("/usr/lib"); + try self.addLibDir("/usr/local/lib"); + + try self.addFrameworkDir("/Library/Frameworks"); + try self.addFrameworkDir("/System/Library/Frameworks"); + + return self; + } + + if (native_target.os.tag != .windows) { + const triple = try native_target.linuxTriple(allocator); + const qual = native_target.cpu.arch.ptrBitWidth(); // TODO: $ ld --verbose | grep SEARCH_DIR // the output contains some paths that end with lib64, maybe include them too? @@ -98,22 +114,22 @@ pub const NativePaths = struct { // TODO: some of these are suspect and should only be added on some systems. audit needed. try self.addIncludeDir("/usr/local/include"); - try self.addLibDirFmt("/usr/local/lib{}", .{qual}); + try self.addLibDirFmt("/usr/local/lib{d}", .{qual}); try self.addLibDir("/usr/local/lib"); - try self.addIncludeDirFmt("/usr/include/{}", .{triple}); - try self.addLibDirFmt("/usr/lib/{}", .{triple}); + try self.addIncludeDirFmt("/usr/include/{s}", .{triple}); + try self.addLibDirFmt("/usr/lib/{s}", .{triple}); try self.addIncludeDir("/usr/include"); - try self.addLibDirFmt("/lib{}", .{qual}); + try self.addLibDirFmt("/lib{d}", .{qual}); try self.addLibDir("/lib"); - try self.addLibDirFmt("/usr/lib{}", .{qual}); + try self.addLibDirFmt("/usr/lib{d}", .{qual}); try self.addLibDir("/usr/lib"); // example: on a 64-bit debian-based linux distro, with zlib installed from apt: // zlib.h is in /usr/include (added above) // libz.so.1 is in /lib/x86_64-linux-gnu (added here) - try self.addLibDirFmt("/lib/{}", .{triple}); + try self.addLibDirFmt("/lib/{s}", .{triple}); } return self; @@ -122,6 +138,7 @@ pub const NativePaths = struct { pub fn deinit(self: *NativePaths) void { deinitArray(&self.include_dirs); deinitArray(&self.lib_dirs); + deinitArray(&self.framework_dirs); deinitArray(&self.rpaths); deinitArray(&self.warnings); self.* = undefined; @@ -158,6 +175,16 @@ pub const NativePaths = struct { return self.appendArray(&self.warnings, s); } + pub fn addFrameworkDir(self: *NativePaths, s: []const u8) !void { + return self.appendArray(&self.framework_dirs, s); + } + + pub fn addFrameworkDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { + const item = try std.fmt.allocPrint0(self.framework_dirs.allocator, fmt, args); + errdefer self.framework_dirs.allocator.free(item); + try self.framework_dirs.append(item); + } + pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { const item = try std.fmt.allocPrint0(self.warnings.allocator, fmt, args); errdefer self.warnings.allocator.free(item); @@ -195,6 +222,7 @@ pub const NativeTargetInfo = struct { ProcessFdQuotaExceeded, SystemFdQuotaExceeded, DeviceBusy, + OSVersionDetectionFail, }; /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected @@ -223,77 +251,11 @@ pub const NativeTargetInfo = struct { } }, .windows => { - var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined; - version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info)); - - switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { - .SUCCESS => {}, - else => unreachable, - } - - // Starting from the system infos build a NTDDI-like version - // constant whose format is: - // B0 B1 B2 B3 - // `---` `` ``--> Sub-version (Starting from Windows 10 onwards) - // \ `--> Service pack (Always zero in the constants defined) - // `--> OS version (Major & minor) - const os_ver: u16 = - @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 | - @intCast(u16, version_info.dwMinorVersion & 0xff); - const sp_ver: u8 = 0; - const sub_ver: u8 = if (os_ver >= 0x0A00) subver: { - // There's no other way to obtain this info beside - // checking the build number against a known set of - // values - const known_build_numbers = [_]u32{ - 10240, 10586, 14393, 15063, 16299, 17134, 17763, - 18362, 19041, - }; - var last_idx: usize = 0; - for (known_build_numbers) |build, i| { - if (version_info.dwBuildNumber >= build) - last_idx = i; - } - break :subver @truncate(u8, last_idx); - } else 0; - - const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver; - - os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version); - os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version); - }, - .macos => { - var scbuf: [32]u8 = undefined; - var size: usize = undefined; - - // The osproductversion sysctl was introduced first with 10.13.4 High Sierra. - const key_osproductversion = "kern.osproductversion"; // eg. "10.15.4" - size = scbuf.len; - if (std.os.sysctlbynameZ(key_osproductversion, &scbuf, &size, null, 0)) |_| { - const string_version = scbuf[0 .. size - 1]; - if (std.builtin.Version.parse(string_version)) |ver| { - os.version_range.semver.min = ver; - os.version_range.semver.max = ver; - } else |err| switch (err) { - error.Overflow => {}, - error.InvalidCharacter => {}, - error.InvalidVersion => {}, - } - } else |err| switch (err) { - error.UnknownName => { - const key_osversion = "kern.osversion"; // eg. "19E287" - size = scbuf.len; - std.os.sysctlbynameZ(key_osversion, &scbuf, &size, null, 0) catch { - @panic("unable to detect macOS version: " ++ key_osversion); - }; - if (macos.version_from_build(scbuf[0 .. size - 1])) |ver| { - os.version_range.semver.min = ver; - os.version_range.semver.max = ver; - } else |_| {} - }, - else => @panic("unable to detect macOS version: " ++ key_osproductversion), - } + const detected_version = windows.detectRuntimeVersion(); + os.version_range.windows.min = detected_version; + os.version_range.windows.max = detected_version; }, + .macos => try macos.detect(&os), .freebsd => { var osreldate: u32 = undefined; var len: usize = undefined; @@ -936,6 +898,6 @@ pub const NativeTargetInfo = struct { } }; -test "" { +test { _ = @import("system/macos.zig"); } diff --git a/lib/std/zig/system/macos.zig b/lib/std/zig/system/macos.zig index 6895fa3f3a..abe844d2c4 100644 --- a/lib/std/zig/system/macos.zig +++ b/lib/std/zig/system/macos.zig @@ -1,458 +1,408 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("std"); const assert = std.debug.assert; const mem = std.mem; +const testing = std.testing; -pub fn version_from_build(build: []const u8) !std.builtin.Version { - // build format: - // 19E287 (example) - // xxyzzz +/// Detect macOS version. +/// `os` is not modified in case of error. +pub fn detect(os: *std.Target.Os) !void { + // Drop use of osproductversion sysctl because: + // 1. only available 10.13.4 High Sierra and later + // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version // - // major = 10 - // minor = x - 4 = 19 - 4 = 15 - // patch = ascii(y) - 'A' = 'E' - 'A' = 69 - 65 = 4 - // answer: 10.15.4 + // NEW APPROACH, STEP 1, parse file: // - // note: patch is typical but with some older releases (before 10.8) zzz is considered - - // never return anything below 10.0.0 - var result = std.builtin.Version{ .major = 10, .minor = 0, .patch = 0 }; + // /System/Library/CoreServices/SystemVersion.plist + // + // NOTE: Historically `SystemVersion.plist` first appeared circa '2003 + // with the release of Mac OS X 10.3.0 Panther. + // + // and if it contains a `10.16` value where the `16` is `>= 16` then it is non-canonical, + // discarded, and we move on to next step. Otherwise we accept the version. + // + // BACKGROUND: `10.(16+)` is not a proper version and does not have enough fidelity to + // indicate minor/point version of Big Sur and later. It is a context-sensitive result + // issued by the kernel for backwards compatibility purposes. Likely the kernel checks + // if the executable was linked against an SDK older than Big Sur. + // + // STEP 2, parse next file: + // + // /System/Library/CoreServices/.SystemVersionPlatform.plist + // + // NOTE: Historically `SystemVersionPlatform.plist` first appeared circa '2020 + // with the release of macOS 11.0 Big Sur. + // + // Accessing the content via this path circumvents a context-sensitive result and + // yields a canonical Big Sur version. + // + // At this time there is no other known way for a < SDK 11.0 executable to obtain a + // canonical Big Sur version. + // + // This implementation uses a reasonably simplified approach to parse .plist file + // that while it is an xml document, we have good history on the file and its format + // such that I am comfortable with implementing a minimalistic parser. + // Things like string and general escapes are not supported. + const prefixSlash = "/System/Library/CoreServices/"; + const paths = [_][]const u8{ + prefixSlash ++ "SystemVersion.plist", + prefixSlash ++ ".SystemVersionPlatform.plist", + }; + for (paths) |path| { + // approx. 4 times historical file size + var buf: [2048]u8 = undefined; - // parse format-x - var yindex: usize = 0; - { - while (yindex < build.len) : (yindex += 1) { - if (build[yindex] < '0' or build[yindex] > '9') break; + if (std.fs.cwd().readFile(path, &buf)) |bytes| { + if (parseSystemVersion(bytes)) |ver| { + // never return non-canonical `10.(16+)` + if (!(ver.major == 10 and ver.minor >= 16)) { + os.version_range.semver.min = ver; + os.version_range.semver.max = ver; + return; + } + continue; + } else |err| { + return error.OSVersionDetectionFail; + } + } else |err| { + return error.OSVersionDetectionFail; } - if (yindex == 0) return result; - const x = std.fmt.parseUnsigned(u16, build[0..yindex], 10) catch return error.InvalidVersion; - if (x < 4) return result; - result.minor = x - 4; } + return error.OSVersionDetectionFail; +} - // parse format-y, format-z - { - // expect two more - if (build.len < yindex + 2) return error.InvalidVersion; - const y = build[yindex]; - if (y < 'A') return error.InvalidVersion; - var zend = yindex + 1; - while (zend < build.len) { - if (build[zend] < '0' or build[zend] > '9') break; - zend += 1; - } - if (zend == yindex + 1) return error.InvalidVersion; - const z = std.fmt.parseUnsigned(u16, build[yindex + 1 .. zend], 10) catch return error.InvalidVersion; - - result.patch = switch (result.minor) { - // TODO: compiler complains without explicit @as() coercion - 0 => @as(u32, switch (y) { // Cheetah: 10.0 - 'K' => 0, - 'L' => 1, - 'P' => @as(u32, block: { - if (z < 13) break :block 2; - break :block 3; - }), - 'Q' => 4, - else => return error.InvalidVersion, - }), - 1 => @as(u32, switch (y) { // Puma: 10.1 - 'G' => 0, - 'M' => 1, - 'P' => 2, - 'Q' => @as(u32, block: { - if (z < 125) break :block 3; - break :block 4; - }), - 'S' => 5, - else => return error.InvalidVersion, - }), - 2 => @as(u32, switch (y) { // Jaguar: 10.2 - 'C' => 0, - 'D' => 1, - 'F' => 2, - 'G' => 3, - 'I' => 4, - 'L' => @as(u32, block: { - if (z < 60) break :block 5; - break :block 6; - }), - 'R' => @as(u32, block: { - if (z < 73) break :block 7; - break :block 8; - }), - 'S' => 8, - else => return error.InvalidVersion, - }), - 3 => @as(u32, switch (y) { // Panther: 10.3 - 'B' => 0, - 'C' => 1, - 'D' => 2, - 'F' => 3, - 'H' => 4, - 'M' => 5, - 'R' => 6, - 'S' => 7, - 'U' => 8, - 'W' => 9, - else => return error.InvalidVersion, - }), - 4 => @as(u32, switch (y) { // Tiger: 10.4 - 'A' => 0, - 'B' => 1, - 'C', - 'E', - => 2, - 'F' => 3, - 'G' => @as(u32, block: { - if (z >= 1454) break :block 5; - break :block 4; - }), - 'H' => 5, - 'I' => 6, - 'J', - 'K', - 'N', - => 7, - 'L' => 8, - 'P' => 9, - 'R' => 10, - 'S' => 11, - else => return error.InvalidVersion, - }), - 5 => @as(u32, switch (y) { // Leopard: 10.5 - 'A' => 0, - 'B' => 1, - 'C' => 2, - 'D' => 3, - 'E' => 4, - 'F' => 5, - 'G' => 6, - 'J' => 7, - 'L' => 8, - else => return error.InvalidVersion, - }), - 6 => @as(u32, switch (y) { // Snow Leopard: 10.6 - 'A' => 0, - 'B' => 1, - 'C' => 2, - 'D' => 3, - 'F' => 4, - 'H' => 5, - 'J' => @as(u32, block: { - if (z < 869) break :block 6; - break :block 7; - }), - 'K' => 8, - else => return error.InvalidVersion, - }), - 7 => @as(u32, switch (y) { // Snow Leopard: 10.6 - 'A' => 0, - 'B' => 1, - 'C' => 2, - 'D' => 3, - 'E' => 4, - 'G' => 5, - else => return error.InvalidVersion, - }), - else => y - 'A', - }; +fn parseSystemVersion(buf: []const u8) !std.builtin.Version { + var svt = SystemVersionTokenizer{ .bytes = buf }; + try svt.skipUntilTag(.start, "dict"); + while (true) { + try svt.skipUntilTag(.start, "key"); + const content = try svt.expectContent(); + try svt.skipUntilTag(.end, "key"); + if (std.mem.eql(u8, content, "ProductVersion")) break; } - return result; -} + try svt.skipUntilTag(.start, "string"); + const ver = try svt.expectContent(); + try svt.skipUntilTag(.end, "string"); -test "version_from_build" { - // see https://en.wikipedia.org/wiki/MacOS_version_history#Releases - const known = [_][2][]const u8{ - .{ "4K78", "10.0.0" }, - .{ "4L13", "10.0.1" }, - .{ "4P12", "10.0.2" }, - .{ "4P13", "10.0.3" }, - .{ "4Q12", "10.0.4" }, + return std.builtin.Version.parse(ver); +} - .{ "5G64", "10.1.0" }, - .{ "5M28", "10.1.1" }, - .{ "5P48", "10.1.2" }, - .{ "5Q45", "10.1.3" }, - .{ "5Q125", "10.1.4" }, - .{ "5S60", "10.1.5" }, +const SystemVersionTokenizer = struct { + bytes: []const u8, + index: usize = 0, + state: State = .begin, - .{ "6C115", "10.2.0" }, - .{ "6C115a", "10.2.0" }, - .{ "6D52", "10.2.1" }, - .{ "6F21", "10.2.2" }, - .{ "6G30", "10.2.3" }, - .{ "6G37", "10.2.3" }, - .{ "6G50", "10.2.3" }, - .{ "6I32", "10.2.4" }, - .{ "6L29", "10.2.5" }, - .{ "6L60", "10.2.6" }, - .{ "6R65", "10.2.7" }, - .{ "6R73", "10.2.8" }, - .{ "6S90", "10.2.8" }, + fn next(self: *@This()) !?Token { + var mark: usize = self.index; + var tag = Tag{}; + var content: []const u8 = ""; - .{ "7B85", "10.3.0" }, - .{ "7B86", "10.3.0" }, - .{ "7C107", "10.3.1" }, - .{ "7D24", "10.3.2" }, - .{ "7D28", "10.3.2" }, - .{ "7F44", "10.3.3" }, - .{ "7H63", "10.3.4" }, - .{ "7M34", "10.3.5" }, - .{ "7R28", "10.3.6" }, - .{ "7S215", "10.3.7" }, - .{ "7U16", "10.3.8" }, - .{ "7W98", "10.3.9" }, + while (self.index < self.bytes.len) { + const char = self.bytes[self.index]; + switch (self.state) { + .begin => switch (char) { + '<' => { + self.state = .tag0; + self.index += 1; + tag = Tag{}; + mark = self.index; + }, + '>' => { + return error.BadToken; + }, + else => { + self.state = .content; + content = ""; + mark = self.index; + }, + }, + .tag0 => switch (char) { + '<' => { + return error.BadToken; + }, + '>' => { + self.state = .begin; + self.index += 1; + tag.name = self.bytes[mark..self.index]; + return Token{ .tag = tag }; + }, + '"' => { + self.state = .tag_string; + self.index += 1; + }, + '/' => { + self.state = .tag0_end_or_empty; + self.index += 1; + }, + 'A'...'Z', 'a'...'z' => { + self.state = .tagN; + tag.kind = .start; + self.index += 1; + }, + else => { + self.state = .tagN; + self.index += 1; + }, + }, + .tag0_end_or_empty => switch (char) { + '<' => { + return error.BadToken; + }, + '>' => { + self.state = .begin; + tag.kind = .empty; + tag.name = self.bytes[self.index..self.index]; + self.index += 1; + return Token{ .tag = tag }; + }, + else => { + self.state = .tagN; + tag.kind = .end; + mark = self.index; + self.index += 1; + }, + }, + .tagN => switch (char) { + '<' => { + return error.BadToken; + }, + '>' => { + self.state = .begin; + tag.name = self.bytes[mark..self.index]; + self.index += 1; + return Token{ .tag = tag }; + }, + '"' => { + self.state = .tag_string; + self.index += 1; + }, + '/' => { + self.state = .tagN_end; + tag.kind = .end; + self.index += 1; + }, + else => { + self.index += 1; + }, + }, + .tagN_end => switch (char) { + '>' => { + self.state = .begin; + tag.name = self.bytes[mark..self.index]; + self.index += 1; + return Token{ .tag = tag }; + }, + else => { + return error.BadToken; + }, + }, + .tag_string => switch (char) { + '"' => { + self.state = .tagN; + self.index += 1; + }, + else => { + self.index += 1; + }, + }, + .content => switch (char) { + '<' => { + self.state = .tag0; + content = self.bytes[mark..self.index]; + self.index += 1; + tag = Tag{}; + mark = self.index; + return Token{ .content = content }; + }, + '>' => { + return error.BadToken; + }, + else => { + self.index += 1; + }, + }, + } + } - .{ "8A428", "10.4.0" }, - .{ "8A432", "10.4.0" }, - .{ "8B15", "10.4.1" }, - .{ "8B17", "10.4.1" }, - .{ "8C46", "10.4.2" }, - .{ "8C47", "10.4.2" }, - .{ "8E102", "10.4.2" }, - .{ "8E45", "10.4.2" }, - .{ "8E90", "10.4.2" }, - .{ "8F46", "10.4.3" }, - .{ "8G32", "10.4.4" }, - .{ "8G1165", "10.4.4" }, - .{ "8H14", "10.4.5" }, - .{ "8G1454", "10.4.5" }, - .{ "8I127", "10.4.6" }, - .{ "8I1119", "10.4.6" }, - .{ "8J135", "10.4.7" }, - .{ "8J2135a", "10.4.7" }, - .{ "8K1079", "10.4.7" }, - .{ "8N5107", "10.4.7" }, - .{ "8L127", "10.4.8" }, - .{ "8L2127", "10.4.8" }, - .{ "8P135", "10.4.9" }, - .{ "8P2137", "10.4.9" }, - .{ "8R218", "10.4.10" }, - .{ "8R2218", "10.4.10" }, - .{ "8R2232", "10.4.10" }, - .{ "8S165", "10.4.11" }, - .{ "8S2167", "10.4.11" }, + return null; + } - .{ "9A581", "10.5.0" }, - .{ "9B18", "10.5.1" }, - .{ "9C31", "10.5.2" }, - .{ "9C7010", "10.5.2" }, - .{ "9D34", "10.5.3" }, - .{ "9E17", "10.5.4" }, - .{ "9F33", "10.5.5" }, - .{ "9G55", "10.5.6" }, - .{ "9G66", "10.5.6" }, - .{ "9J61", "10.5.7" }, - .{ "9L30", "10.5.8" }, + fn expectContent(self: *@This()) ![]const u8 { + if (try self.next()) |tok| { + switch (tok) { + .content => |content| { + return content; + }, + else => {}, + } + } + return error.UnexpectedToken; + } - .{ "10A432", "10.6.0" }, - .{ "10A433", "10.6.0" }, - .{ "10B504", "10.6.1" }, - .{ "10C540", "10.6.2" }, - .{ "10D573", "10.6.3" }, - .{ "10D575", "10.6.3" }, - .{ "10D578", "10.6.3" }, - .{ "10F569", "10.6.4" }, - .{ "10H574", "10.6.5" }, - .{ "10J567", "10.6.6" }, - .{ "10J869", "10.6.7" }, - .{ "10J3250", "10.6.7" }, - .{ "10J4138", "10.6.7" }, - .{ "10K540", "10.6.8" }, - .{ "10K549", "10.6.8" }, + fn skipUntilTag(self: *@This(), kind: Tag.Kind, name: []const u8) !void { + while (try self.next()) |tok| { + switch (tok) { + .tag => |tag| { + if (tag.kind == kind and std.mem.eql(u8, tag.name, name)) return; + }, + else => {}, + } + } + return error.TagNotFound; + } - .{ "11A511", "10.7.0" }, - .{ "11A511s", "10.7.0" }, - .{ "11A2061", "10.7.0" }, - .{ "11A2063", "10.7.0" }, - .{ "11B26", "10.7.1" }, - .{ "11B2118", "10.7.1" }, - .{ "11C74", "10.7.2" }, - .{ "11D50", "10.7.3" }, - .{ "11E53", "10.7.4" }, - .{ "11G56", "10.7.5" }, - .{ "11G63", "10.7.5" }, + const State = enum { + begin, + tag0, + tag0_end_or_empty, + tagN, + tagN_end, + tag_string, + content, + }; - .{ "12A269", "10.8.0" }, - .{ "12B19", "10.8.1" }, - .{ "12C54", "10.8.2" }, - .{ "12C60", "10.8.2" }, - .{ "12C2034", "10.8.2" }, - .{ "12C3104", "10.8.2" }, - .{ "12D78", "10.8.3" }, - .{ "12E55", "10.8.4" }, - .{ "12E3067", "10.8.4" }, - .{ "12E4022", "10.8.4" }, - .{ "12F37", "10.8.5" }, - .{ "12F45", "10.8.5" }, - .{ "12F2501", "10.8.5" }, - .{ "12F2518", "10.8.5" }, - .{ "12F2542", "10.8.5" }, - .{ "12F2560", "10.8.5" }, + const Token = union(enum) { + tag: Tag, + content: []const u8, + }; - .{ "13A603", "10.9.0" }, - .{ "13B42", "10.9.1" }, - .{ "13C64", "10.9.2" }, - .{ "13C1021", "10.9.2" }, - .{ "13D65", "10.9.3" }, - .{ "13E28", "10.9.4" }, - .{ "13F34", "10.9.5" }, - .{ "13F1066", "10.9.5" }, - .{ "13F1077", "10.9.5" }, - .{ "13F1096", "10.9.5" }, - .{ "13F1112", "10.9.5" }, - .{ "13F1134", "10.9.5" }, - .{ "13F1507", "10.9.5" }, - .{ "13F1603", "10.9.5" }, - .{ "13F1712", "10.9.5" }, - .{ "13F1808", "10.9.5" }, - .{ "13F1911", "10.9.5" }, + const Tag = struct { + kind: Kind = .unknown, + name: []const u8 = "", - .{ "14A389", "10.10.0" }, - .{ "14B25", "10.10.1" }, - .{ "14C109", "10.10.2" }, - .{ "14C1510", "10.10.2" }, - .{ "14C1514", "10.10.2" }, - .{ "14C2043", "10.10.2" }, - .{ "14C2513", "10.10.2" }, - .{ "14D131", "10.10.3" }, - .{ "14D136", "10.10.3" }, - .{ "14E46", "10.10.4" }, - .{ "14F27", "10.10.5" }, - .{ "14F1021", "10.10.5" }, - .{ "14F1505", "10.10.5" }, - .{ "14F1509", "10.10.5" }, - .{ "14F1605", "10.10.5" }, - .{ "14F1713", "10.10.5" }, - .{ "14F1808", "10.10.5" }, - .{ "14F1909", "10.10.5" }, - .{ "14F1912", "10.10.5" }, - .{ "14F2009", "10.10.5" }, - .{ "14F2109", "10.10.5" }, - .{ "14F2315", "10.10.5" }, - .{ "14F2411", "10.10.5" }, - .{ "14F2511", "10.10.5" }, + const Kind = enum { unknown, start, end, empty }; + }; +}; - .{ "15A284", "10.11.0" }, - .{ "15B42", "10.11.1" }, - .{ "15C50", "10.11.2" }, - .{ "15D21", "10.11.3" }, - .{ "15E65", "10.11.4" }, - .{ "15F34", "10.11.5" }, - .{ "15G31", "10.11.6" }, - .{ "15G1004", "10.11.6" }, - .{ "15G1011", "10.11.6" }, - .{ "15G1108", "10.11.6" }, - .{ "15G1212", "10.11.6" }, - .{ "15G1217", "10.11.6" }, - .{ "15G1421", "10.11.6" }, - .{ "15G1510", "10.11.6" }, - .{ "15G1611", "10.11.6" }, - .{ "15G17023", "10.11.6" }, - .{ "15G18013", "10.11.6" }, - .{ "15G19009", "10.11.6" }, - .{ "15G20015", "10.11.6" }, - .{ "15G21013", "10.11.6" }, - .{ "15G22010", "10.11.6" }, +test "detect" { + const cases = .{ + .{ + \\<?xml version="1.0" encoding="UTF-8"?> + \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + \\<plist version="1.0"> + \\<dict> + \\ <key>ProductBuildVersion</key> + \\ <string>7B85</string> + \\ <key>ProductCopyright</key> + \\ <string>Apple Computer, Inc. 1983-2003</string> + \\ <key>ProductName</key> + \\ <string>Mac OS X</string> + \\ <key>ProductUserVisibleVersion</key> + \\ <string>10.3</string> + \\ <key>ProductVersion</key> + \\ <string>10.3</string> + \\</dict> + \\</plist> + , + .{ .major = 10, .minor = 3 }, + }, + .{ + \\<?xml version="1.0" encoding="UTF-8"?> + \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + \\<plist version="1.0"> + \\<dict> + \\ <key>ProductBuildVersion</key> + \\ <string>7W98</string> + \\ <key>ProductCopyright</key> + \\ <string>Apple Computer, Inc. 1983-2004</string> + \\ <key>ProductName</key> + \\ <string>Mac OS X</string> + \\ <key>ProductUserVisibleVersion</key> + \\ <string>10.3.9</string> + \\ <key>ProductVersion</key> + \\ <string>10.3.9</string> + \\</dict> + \\</plist> + , + .{ .major = 10, .minor = 3, .patch = 9 }, + }, + .{ + \\<?xml version="1.0" encoding="UTF-8"?> + \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + \\<plist version="1.0"> + \\<dict> + \\ <key>ProductBuildVersion</key> + \\ <string>19G68</string> + \\ <key>ProductCopyright</key> + \\ <string>1983-2020 Apple Inc.</string> + \\ <key>ProductName</key> + \\ <string>Mac OS X</string> + \\ <key>ProductUserVisibleVersion</key> + \\ <string>10.15.6</string> + \\ <key>ProductVersion</key> + \\ <string>10.15.6</string> + \\ <key>iOSSupportVersion</key> + \\ <string>13.6</string> + \\</dict> + \\</plist> + , + .{ .major = 10, .minor = 15, .patch = 6 }, + }, + .{ + \\<?xml version="1.0" encoding="UTF-8"?> + \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + \\<plist version="1.0"> + \\<dict> + \\ <key>ProductBuildVersion</key> + \\ <string>20A2408</string> + \\ <key>ProductCopyright</key> + \\ <string>1983-2020 Apple Inc.</string> + \\ <key>ProductName</key> + \\ <string>macOS</string> + \\ <key>ProductUserVisibleVersion</key> + \\ <string>11.0</string> + \\ <key>ProductVersion</key> + \\ <string>11.0</string> + \\ <key>iOSSupportVersion</key> + \\ <string>14.2</string> + \\</dict> + \\</plist> + , + .{ .major = 11, .minor = 0 }, + }, + .{ + \\<?xml version="1.0" encoding="UTF-8"?> + \\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + \\<plist version="1.0"> + \\<dict> + \\ <key>ProductBuildVersion</key> + \\ <string>20C63</string> + \\ <key>ProductCopyright</key> + \\ <string>1983-2020 Apple Inc.</string> + \\ <key>ProductName</key> + \\ <string>macOS</string> + \\ <key>ProductUserVisibleVersion</key> + \\ <string>11.1</string> + \\ <key>ProductVersion</key> + \\ <string>11.1</string> + \\ <key>iOSSupportVersion</key> + \\ <string>14.3</string> + \\</dict> + \\</plist> + , + .{ .major = 11, .minor = 1 }, + }, + }; - .{ "16A323", "10.12.0" }, - .{ "16B2555", "10.12.1" }, - .{ "16B2657", "10.12.1" }, - .{ "16C67", "10.12.2" }, - .{ "16C68", "10.12.2" }, - .{ "16D32", "10.12.3" }, - .{ "16E195", "10.12.4" }, - .{ "16F73", "10.12.5" }, - .{ "16F2073", "10.12.5" }, - .{ "16G29", "10.12.6" }, - .{ "16G1036", "10.12.6" }, - .{ "16G1114", "10.12.6" }, - .{ "16G1212", "10.12.6" }, - .{ "16G1314", "10.12.6" }, - .{ "16G1408", "10.12.6" }, - .{ "16G1510", "10.12.6" }, - .{ "16G1618", "10.12.6" }, - .{ "16G1710", "10.12.6" }, - .{ "16G1815", "10.12.6" }, - .{ "16G1917", "10.12.6" }, - .{ "16G1918", "10.12.6" }, - .{ "16G2016", "10.12.6" }, - .{ "16G2127", "10.12.6" }, - .{ "16G2128", "10.12.6" }, - .{ "16G2136", "10.12.6" }, + inline for (cases) |case| { + const ver0 = try parseSystemVersion(case[0]); + const ver1: std.builtin.Version = case[1]; + try testVersionEquality(ver1, ver0); + } +} - .{ "17A365", "10.13.0" }, - .{ "17A405", "10.13.0" }, - .{ "17B48", "10.13.1" }, - .{ "17B1002", "10.13.1" }, - .{ "17B1003", "10.13.1" }, - .{ "17C88", "10.13.2" }, - .{ "17C89", "10.13.2" }, - .{ "17C205", "10.13.2" }, - .{ "17C2205", "10.13.2" }, - .{ "17D47", "10.13.3" }, - .{ "17D2047", "10.13.3" }, - .{ "17D102", "10.13.3" }, - .{ "17D2102", "10.13.3" }, - .{ "17E199", "10.13.4" }, - .{ "17E202", "10.13.4" }, - .{ "17F77", "10.13.5" }, - .{ "17G65", "10.13.6" }, - .{ "17G2208", "10.13.6" }, - .{ "17G3025", "10.13.6" }, - .{ "17G4015", "10.13.6" }, - .{ "17G5019", "10.13.6" }, - .{ "17G6029", "10.13.6" }, - .{ "17G6030", "10.13.6" }, - .{ "17G7024", "10.13.6" }, - .{ "17G8029", "10.13.6" }, - .{ "17G8030", "10.13.6" }, - .{ "17G8037", "10.13.6" }, - .{ "17G9016", "10.13.6" }, - .{ "17G10021", "10.13.6" }, - .{ "17G11023", "10.13.6" }, - .{ "17G12034", "10.13.6" }, +fn testVersionEquality(expected: std.builtin.Version, got: std.builtin.Version) !void { + var b_expected: [64]u8 = undefined; + const s_expected: []const u8 = try std.fmt.bufPrint(b_expected[0..], "{}", .{expected}); - .{ "18A391", "10.14.0" }, - .{ "18B75", "10.14.1" }, - .{ "18B2107", "10.14.1" }, - .{ "18B3094", "10.14.1" }, - .{ "18C54", "10.14.2" }, - .{ "18D42", "10.14.3" }, - .{ "18D43", "10.14.3" }, - .{ "18D109", "10.14.3" }, - .{ "18E226", "10.14.4" }, - .{ "18E227", "10.14.4" }, - .{ "18F132", "10.14.5" }, - .{ "18G84", "10.14.6" }, - .{ "18G87", "10.14.6" }, - .{ "18G95", "10.14.6" }, - .{ "18G103", "10.14.6" }, - .{ "18G1012", "10.14.6" }, - .{ "18G2022", "10.14.6" }, - .{ "18G3020", "10.14.6" }, - .{ "18G4032", "10.14.6" }, + var b_got: [64]u8 = undefined; + const s_got: []const u8 = try std.fmt.bufPrint(b_got[0..], "{}", .{got}); - .{ "19A583", "10.15.0" }, - .{ "19A602", "10.15.0" }, - .{ "19A603", "10.15.0" }, - .{ "19B88", "10.15.1" }, - .{ "19C57", "10.15.2" }, - .{ "19D76", "10.15.3" }, - .{ "19E266", "10.15.4" }, - .{ "19E287", "10.15.4" }, - }; - for (known) |pair| { - var buf: [32]u8 = undefined; - const ver = try version_from_build(pair[0]); - const sver = try std.fmt.bufPrint(buf[0..], "{}.{}.{}", .{ ver.major, ver.minor, ver.patch }); - std.testing.expect(std.mem.eql(u8, sver, pair[1])); - } + testing.expectEqualStrings(s_expected, s_got); } /// Detect SDK path on Darwin. @@ -468,7 +418,7 @@ pub fn getSDKPath(allocator: *mem.Allocator) ![]u8 { allocator.free(result.stdout); } if (result.stderr.len != 0) { - std.log.err("unexpected 'xcrun --show-sdk-path' stderr: {}", .{result.stderr}); + std.log.err("unexpected 'xcrun --show-sdk-path' stderr: {s}", .{result.stderr}); } if (result.term.Exited != 0) { return error.ProcessTerminated; diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig new file mode 100644 index 0000000000..d32b28f607 --- /dev/null +++ b/lib/std/zig/system/windows.zig @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. +const std = @import("std"); + +pub const WindowsVersion = std.Target.Os.WindowsVersion; + +/// Returns the highest known WindowsVersion deduced from reported runtime information. +/// Discards information about in-between versions we don't differentiate. +pub fn detectRuntimeVersion() WindowsVersion { + var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined; + version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info)); + + switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { + .SUCCESS => {}, + else => unreachable, + } + + // Starting from the system infos build a NTDDI-like version + // constant whose format is: + // B0 B1 B2 B3 + // `---` `` ``--> Sub-version (Starting from Windows 10 onwards) + // \ `--> Service pack (Always zero in the constants defined) + // `--> OS version (Major & minor) + const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 | + @intCast(u16, version_info.dwMinorVersion & 0xff); + const sp_ver: u8 = 0; + const sub_ver: u8 = if (os_ver >= 0x0A00) subver: { + // There's no other way to obtain this info beside + // checking the build number against a known set of + // values + var last_idx: usize = 0; + for (WindowsVersion.known_win10_build_numbers) |build, i| { + if (version_info.dwBuildNumber >= build) + last_idx = i; + } + break :subver @truncate(u8, last_idx); + } else 0; + + const version: u32 = @as(u32, os_ver) << 16 | @as(u16, sp_ver) << 8 | sub_ver; + + return @intToEnum(WindowsVersion, version); +} diff --git a/lib/std/zig/system/x86.zig b/lib/std/zig/system/x86.zig index cbede308dc..bda9a17c95 100644 --- a/lib/std/zig/system/x86.zig +++ b/lib/std/zig/system/x86.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -19,11 +19,11 @@ fn setFeature(cpu: *Target.Cpu, feature: Target.x86.Feature, enabled: bool) void if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx); } -inline fn bit(input: u32, offset: u5) bool { +fn bit(input: u32, offset: u5) callconv(.Inline) bool { return (input >> offset) & 1 != 0; } -inline fn hasMask(input: u32, mask: u32) bool { +fn hasMask(input: u32, mask: u32) callconv(.Inline) bool { return (input & mask) == mask; } diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index a0e2806e9a..88feabd021 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors +// Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. @@ -7,7 +7,7 @@ const std = @import("../std.zig"); const mem = std.mem; pub const Token = struct { - id: Id, + tag: Tag, loc: Loc, pub const Loc = struct { @@ -15,315 +15,315 @@ pub const Token = struct { end: usize, }; - pub const keywords = std.ComptimeStringMap(Id, .{ - .{ "align", .Keyword_align }, - .{ "allowzero", .Keyword_allowzero }, - .{ "and", .Keyword_and }, - .{ "anyframe", .Keyword_anyframe }, - .{ "anytype", .Keyword_anytype }, - .{ "asm", .Keyword_asm }, - .{ "async", .Keyword_async }, - .{ "await", .Keyword_await }, - .{ "break", .Keyword_break }, - .{ "callconv", .Keyword_callconv }, - .{ "catch", .Keyword_catch }, - .{ "comptime", .Keyword_comptime }, - .{ "const", .Keyword_const }, - .{ "continue", .Keyword_continue }, - .{ "defer", .Keyword_defer }, - .{ "else", .Keyword_else }, - .{ "enum", .Keyword_enum }, - .{ "errdefer", .Keyword_errdefer }, - .{ "error", .Keyword_error }, - .{ "export", .Keyword_export }, - .{ "extern", .Keyword_extern }, - .{ "false", .Keyword_false }, - .{ "fn", .Keyword_fn }, - .{ "for", .Keyword_for }, - .{ "if", .Keyword_if }, - .{ "inline", .Keyword_inline }, - .{ "noalias", .Keyword_noalias }, - .{ "noasync", .Keyword_nosuspend }, // TODO: remove this - .{ "noinline", .Keyword_noinline }, - .{ "nosuspend", .Keyword_nosuspend }, - .{ "null", .Keyword_null }, - .{ "opaque", .Keyword_opaque }, - .{ "or", .Keyword_or }, - .{ "orelse", .Keyword_orelse }, - .{ "packed", .Keyword_packed }, - .{ "pub", .Keyword_pub }, - .{ "resume", .Keyword_resume }, - .{ "return", .Keyword_return }, - .{ "linksection", .Keyword_linksection }, - .{ "struct", .Keyword_struct }, - .{ "suspend", .Keyword_suspend }, - .{ "switch", .Keyword_switch }, - .{ "test", .Keyword_test }, - .{ "threadlocal", .Keyword_threadlocal }, - .{ "true", .Keyword_true }, - .{ "try", .Keyword_try }, - .{ "undefined", .Keyword_undefined }, - .{ "union", .Keyword_union }, - .{ "unreachable", .Keyword_unreachable }, - .{ "usingnamespace", .Keyword_usingnamespace }, - .{ "var", .Keyword_var }, - .{ "volatile", .Keyword_volatile }, - .{ "while", .Keyword_while }, + pub const keywords = std.ComptimeStringMap(Tag, .{ + .{ "align", .keyword_align }, + .{ "allowzero", .keyword_allowzero }, + .{ "and", .keyword_and }, + .{ "anyframe", .keyword_anyframe }, + .{ "anytype", .keyword_anytype }, + .{ "asm", .keyword_asm }, + .{ "async", .keyword_async }, + .{ "await", .keyword_await }, + .{ "break", .keyword_break }, + .{ "callconv", .keyword_callconv }, + .{ "catch", .keyword_catch }, + .{ "comptime", .keyword_comptime }, + .{ "const", .keyword_const }, + .{ "continue", .keyword_continue }, + .{ "defer", .keyword_defer }, + .{ "else", .keyword_else }, + .{ "enum", .keyword_enum }, + .{ "errdefer", .keyword_errdefer }, + .{ "error", .keyword_error }, + .{ "export", .keyword_export }, + .{ "extern", .keyword_extern }, + .{ "false", .keyword_false }, + .{ "fn", .keyword_fn }, + .{ "for", .keyword_for }, + .{ "if", .keyword_if }, + .{ "inline", .keyword_inline }, + .{ "noalias", .keyword_noalias }, + .{ "noinline", .keyword_noinline }, + .{ "nosuspend", .keyword_nosuspend }, + .{ "null", .keyword_null }, + .{ "opaque", .keyword_opaque }, + .{ "or", .keyword_or }, + .{ "orelse", .keyword_orelse }, + .{ "packed", .keyword_packed }, + .{ "pub", .keyword_pub }, + .{ "resume", .keyword_resume }, + .{ "return", .keyword_return }, + .{ "linksection", .keyword_linksection }, + .{ "struct", .keyword_struct }, + .{ "suspend", .keyword_suspend }, + .{ "switch", .keyword_switch }, + .{ "test", .keyword_test }, + .{ "threadlocal", .keyword_threadlocal }, + .{ "true", .keyword_true }, + .{ "try", .keyword_try }, + .{ "undefined", .keyword_undefined }, + .{ "union", .keyword_union }, + .{ "unreachable", .keyword_unreachable }, + .{ "usingnamespace", .keyword_usingnamespace }, + .{ "var", .keyword_var }, + .{ "volatile", .keyword_volatile }, + .{ "while", .keyword_while }, }); - pub fn getKeyword(bytes: []const u8) ?Id { + pub fn getKeyword(bytes: []const u8) ?Tag { return keywords.get(bytes); } - pub const Id = enum { - Invalid, - Invalid_ampersands, - Invalid_periodasterisks, - Identifier, - StringLiteral, - MultilineStringLiteralLine, - CharLiteral, - Eof, - Builtin, - Bang, - Pipe, - PipePipe, - PipeEqual, - Equal, - EqualEqual, - EqualAngleBracketRight, - BangEqual, - LParen, - RParen, - Semicolon, - Percent, - PercentEqual, - LBrace, - RBrace, - LBracket, - RBracket, - Period, - PeriodAsterisk, - Ellipsis2, - Ellipsis3, - Caret, - CaretEqual, - Plus, - PlusPlus, - PlusEqual, - PlusPercent, - PlusPercentEqual, - Minus, - MinusEqual, - MinusPercent, - MinusPercentEqual, - Asterisk, - AsteriskEqual, - AsteriskAsterisk, - AsteriskPercent, - AsteriskPercentEqual, - Arrow, - Colon, - Slash, - SlashEqual, - Comma, - Ampersand, - AmpersandEqual, - QuestionMark, - AngleBracketLeft, - AngleBracketLeftEqual, - AngleBracketAngleBracketLeft, - AngleBracketAngleBracketLeftEqual, - AngleBracketRight, - AngleBracketRightEqual, - AngleBracketAngleBracketRight, - AngleBracketAngleBracketRightEqual, - Tilde, - IntegerLiteral, - FloatLiteral, - LineComment, - DocComment, - ContainerDocComment, - ShebangLine, - Keyword_align, - Keyword_allowzero, - Keyword_and, - Keyword_anyframe, - Keyword_anytype, - Keyword_asm, - Keyword_async, - Keyword_await, - Keyword_break, - Keyword_callconv, - Keyword_catch, - Keyword_comptime, - Keyword_const, - Keyword_continue, - Keyword_defer, - Keyword_else, - Keyword_enum, - Keyword_errdefer, - Keyword_error, - Keyword_export, - Keyword_extern, - Keyword_false, - Keyword_fn, - Keyword_for, - Keyword_if, - Keyword_inline, - Keyword_noalias, - Keyword_noinline, - Keyword_nosuspend, - Keyword_null, - Keyword_opaque, - Keyword_or, - Keyword_orelse, - Keyword_packed, - Keyword_pub, - Keyword_resume, - Keyword_return, - Keyword_linksection, - Keyword_struct, - Keyword_suspend, - Keyword_switch, - Keyword_test, - Keyword_threadlocal, - Keyword_true, - Keyword_try, - Keyword_undefined, - Keyword_union, - Keyword_unreachable, - Keyword_usingnamespace, - Keyword_var, - Keyword_volatile, - Keyword_while, - - pub fn symbol(id: Id) []const u8 { - return switch (id) { - .Invalid => "Invalid", - .Invalid_ampersands => "&&", - .Invalid_periodasterisks => ".**", - .Identifier => "Identifier", - .StringLiteral => "StringLiteral", - .MultilineStringLiteralLine => "MultilineStringLiteralLine", - .CharLiteral => "CharLiteral", - .Eof => "Eof", - .Builtin => "Builtin", - .IntegerLiteral => "IntegerLiteral", - .FloatLiteral => "FloatLiteral", - .LineComment => "LineComment", - .DocComment => "DocComment", - .ContainerDocComment => "ContainerDocComment", - .ShebangLine => "ShebangLine", - - .Bang => "!", - .Pipe => "|", - .PipePipe => "||", - .PipeEqual => "|=", - .Equal => "=", - .EqualEqual => "==", - .EqualAngleBracketRight => "=>", - .BangEqual => "!=", - .LParen => "(", - .RParen => ")", - .Semicolon => ";", - .Percent => "%", - .PercentEqual => "%=", - .LBrace => "{", - .RBrace => "}", - .LBracket => "[", - .RBracket => "]", - .Period => ".", - .PeriodAsterisk => ".*", - .Ellipsis2 => "..", - .Ellipsis3 => "...", - .Caret => "^", - .CaretEqual => "^=", - .Plus => "+", - .PlusPlus => "++", - .PlusEqual => "+=", - .PlusPercent => "+%", - .PlusPercentEqual => "+%=", - .Minus => "-", - .MinusEqual => "-=", - .MinusPercent => "-%", - .MinusPercentEqual => "-%=", - .Asterisk => "*", - .AsteriskEqual => "*=", - .AsteriskAsterisk => "**", - .AsteriskPercent => "*%", - .AsteriskPercentEqual => "*%=", - .Arrow => "->", - .Colon => ":", - .Slash => "/", - .SlashEqual => "/=", - .Comma => ",", - .Ampersand => "&", - .AmpersandEqual => "&=", - .QuestionMark => "?", - .AngleBracketLeft => "<", - .AngleBracketLeftEqual => "<=", - .AngleBracketAngleBracketLeft => "<<", - .AngleBracketAngleBracketLeftEqual => "<<=", - .AngleBracketRight => ">", - .AngleBracketRightEqual => ">=", - .AngleBracketAngleBracketRight => ">>", - .AngleBracketAngleBracketRightEqual => ">>=", - .Tilde => "~", - .Keyword_align => "align", - .Keyword_allowzero => "allowzero", - .Keyword_and => "and", - .Keyword_anyframe => "anyframe", - .Keyword_anytype => "anytype", - .Keyword_asm => "asm", - .Keyword_async => "async", - .Keyword_await => "await", - .Keyword_break => "break", - .Keyword_callconv => "callconv", - .Keyword_catch => "catch", - .Keyword_comptime => "comptime", - .Keyword_const => "const", - .Keyword_continue => "continue", - .Keyword_defer => "defer", - .Keyword_else => "else", - .Keyword_enum => "enum", - .Keyword_errdefer => "errdefer", - .Keyword_error => "error", - .Keyword_export => "export", - .Keyword_extern => "extern", - .Keyword_false => "false", - .Keyword_fn => "fn", - .Keyword_for => "for", - .Keyword_if => "if", - .Keyword_inline => "inline", - .Keyword_noalias => "noalias", - .Keyword_noinline => "noinline", - .Keyword_nosuspend => "nosuspend", - .Keyword_null => "null", - .Keyword_opaque => "opaque", - .Keyword_or => "or", - .Keyword_orelse => "orelse", - .Keyword_packed => "packed", - .Keyword_pub => "pub", - .Keyword_resume => "resume", - .Keyword_return => "return", - .Keyword_linksection => "linksection", - .Keyword_struct => "struct", - .Keyword_suspend => "suspend", - .Keyword_switch => "switch", - .Keyword_test => "test", - .Keyword_threadlocal => "threadlocal", - .Keyword_true => "true", - .Keyword_try => "try", - .Keyword_undefined => "undefined", - .Keyword_union => "union", - .Keyword_unreachable => "unreachable", - .Keyword_usingnamespace => "usingnamespace", - .Keyword_var => "var", - .Keyword_volatile => "volatile", - .Keyword_while => "while", + pub const Tag = enum { + invalid, + invalid_ampersands, + invalid_periodasterisks, + identifier, + string_literal, + multiline_string_literal_line, + char_literal, + eof, + builtin, + bang, + pipe, + pipe_pipe, + pipe_equal, + equal, + equal_equal, + equal_angle_bracket_right, + bang_equal, + l_paren, + r_paren, + semicolon, + percent, + percent_equal, + l_brace, + r_brace, + l_bracket, + r_bracket, + period, + period_asterisk, + ellipsis2, + ellipsis3, + caret, + caret_equal, + plus, + plus_plus, + plus_equal, + plus_percent, + plus_percent_equal, + minus, + minus_equal, + minus_percent, + minus_percent_equal, + asterisk, + asterisk_equal, + asterisk_asterisk, + asterisk_percent, + asterisk_percent_equal, + arrow, + colon, + slash, + slash_equal, + comma, + ampersand, + ampersand_equal, + question_mark, + angle_bracket_left, + angle_bracket_left_equal, + angle_bracket_angle_bracket_left, + angle_bracket_angle_bracket_left_equal, + angle_bracket_right, + angle_bracket_right_equal, + angle_bracket_angle_bracket_right, + angle_bracket_angle_bracket_right_equal, + tilde, + integer_literal, + float_literal, + doc_comment, + container_doc_comment, + keyword_align, + keyword_allowzero, + keyword_and, + keyword_anyframe, + keyword_anytype, + keyword_asm, + keyword_async, + keyword_await, + keyword_break, + keyword_callconv, + keyword_catch, + keyword_comptime, + keyword_const, + keyword_continue, + keyword_defer, + keyword_else, + keyword_enum, + keyword_errdefer, + keyword_error, + keyword_export, + keyword_extern, + keyword_false, + keyword_fn, + keyword_for, + keyword_if, + keyword_inline, + keyword_noalias, + keyword_noinline, + keyword_nosuspend, + keyword_null, + keyword_opaque, + keyword_or, + keyword_orelse, + keyword_packed, + keyword_pub, + keyword_resume, + keyword_return, + keyword_linksection, + keyword_struct, + keyword_suspend, + keyword_switch, + keyword_test, + keyword_threadlocal, + keyword_true, + keyword_try, + keyword_undefined, + keyword_union, + keyword_unreachable, + keyword_usingnamespace, + keyword_var, + keyword_volatile, + keyword_while, + + pub fn lexeme(tag: Tag) ?[]const u8 { + return switch (tag) { + .invalid, + .identifier, + .string_literal, + .multiline_string_literal_line, + .char_literal, + .eof, + .builtin, + .integer_literal, + .float_literal, + .doc_comment, + .container_doc_comment, + => null, + + .invalid_ampersands => "&&", + .invalid_periodasterisks => ".**", + .bang => "!", + .pipe => "|", + .pipe_pipe => "||", + .pipe_equal => "|=", + .equal => "=", + .equal_equal => "==", + .equal_angle_bracket_right => "=>", + .bang_equal => "!=", + .l_paren => "(", + .r_paren => ")", + .semicolon => ";", + .percent => "%", + .percent_equal => "%=", + .l_brace => "{", + .r_brace => "}", + .l_bracket => "[", + .r_bracket => "]", + .period => ".", + .period_asterisk => ".*", + .ellipsis2 => "..", + .ellipsis3 => "...", + .caret => "^", + .caret_equal => "^=", + .plus => "+", + .plus_plus => "++", + .plus_equal => "+=", + .plus_percent => "+%", + .plus_percent_equal => "+%=", + .minus => "-", + .minus_equal => "-=", + .minus_percent => "-%", + .minus_percent_equal => "-%=", + .asterisk => "*", + .asterisk_equal => "*=", + .asterisk_asterisk => "**", + .asterisk_percent => "*%", + .asterisk_percent_equal => "*%=", + .arrow => "->", + .colon => ":", + .slash => "/", + .slash_equal => "/=", + .comma => ",", + .ampersand => "&", + .ampersand_equal => "&=", + .question_mark => "?", + .angle_bracket_left => "<", + .angle_bracket_left_equal => "<=", + .angle_bracket_angle_bracket_left => "<<", + .angle_bracket_angle_bracket_left_equal => "<<=", + .angle_bracket_right => ">", + .angle_bracket_right_equal => ">=", + .angle_bracket_angle_bracket_right => ">>", + .angle_bracket_angle_bracket_right_equal => ">>=", + .tilde => "~", + .keyword_align => "align", + .keyword_allowzero => "allowzero", + .keyword_and => "and", + .keyword_anyframe => "anyframe", + .keyword_anytype => "anytype", + .keyword_asm => "asm", + .keyword_async => "async", + .keyword_await => "await", + .keyword_break => "break", + .keyword_callconv => "callconv", + .keyword_catch => "catch", + .keyword_comptime => "comptime", + .keyword_const => "const", + .keyword_continue => "continue", + .keyword_defer => "defer", + .keyword_else => "else", + .keyword_enum => "enum", + .keyword_errdefer => "errdefer", + .keyword_error => "error", + .keyword_export => "export", + .keyword_extern => "extern", + .keyword_false => "false", + .keyword_fn => "fn", + .keyword_for => "for", + .keyword_if => "if", + .keyword_inline => "inline", + .keyword_noalias => "noalias", + .keyword_noinline => "noinline", + .keyword_nosuspend => "nosuspend", + .keyword_null => "null", + .keyword_opaque => "opaque", + .keyword_or => "or", + .keyword_orelse => "orelse", + .keyword_packed => "packed", + .keyword_pub => "pub", + .keyword_resume => "resume", + .keyword_return => "return", + .keyword_linksection => "linksection", + .keyword_struct => "struct", + .keyword_suspend => "suspend", + .keyword_switch => "switch", + .keyword_test => "test", + .keyword_threadlocal => "threadlocal", + .keyword_true => "true", + .keyword_try => "try", + .keyword_undefined => "undefined", + .keyword_union => "union", + .keyword_unreachable => "unreachable", + .keyword_usingnamespace => "usingnamespace", + .keyword_var => "var", + .keyword_volatile => "volatile", + .keyword_while => "while", }; } + + pub fn symbol(tag: Tag) []const u8 { + return tag.lexeme() orelse @tagName(tag); + } }; }; @@ -334,7 +334,7 @@ pub const Tokenizer = struct { /// For debugging purposes pub fn dump(self: *Tokenizer, token: *const Token) void { - std.debug.warn("{} \"{}\"\n", .{ @tagName(token.id), self.buffer[token.start..token.end] }); + std.debug.warn("{s} \"{s}\"\n", .{ @tagName(token.tag), self.buffer[token.start..token.end] }); } pub fn init(buffer: []const u8) Tokenizer { @@ -421,7 +421,7 @@ pub const Tokenizer = struct { const start_index = self.index; var state: State = .start; var result = Token{ - .id = .Eof, + .tag = .eof, .loc = .{ .start = self.index, .end = undefined, @@ -438,14 +438,14 @@ pub const Tokenizer = struct { }, '"' => { state = .string_literal; - result.id = .StringLiteral; + result.tag = .string_literal; }, '\'' => { state = .char_literal; }, 'a'...'z', 'A'...'Z', '_' => { state = .identifier; - result.id = .Identifier; + result.tag = .identifier; }, '@' => { state = .saw_at_sign; @@ -460,42 +460,42 @@ pub const Tokenizer = struct { state = .pipe; }, '(' => { - result.id = .LParen; + result.tag = .l_paren; self.index += 1; break; }, ')' => { - result.id = .RParen; + result.tag = .r_paren; self.index += 1; break; }, '[' => { - result.id = .LBracket; + result.tag = .l_bracket; self.index += 1; break; }, ']' => { - result.id = .RBracket; + result.tag = .r_bracket; self.index += 1; break; }, ';' => { - result.id = .Semicolon; + result.tag = .semicolon; self.index += 1; break; }, ',' => { - result.id = .Comma; + result.tag = .comma; self.index += 1; break; }, '?' => { - result.id = .QuestionMark; + result.tag = .question_mark; self.index += 1; break; }, ':' => { - result.id = .Colon; + result.tag = .colon; self.index += 1; break; }, @@ -519,20 +519,20 @@ pub const Tokenizer = struct { }, '\\' => { state = .backslash; - result.id = .MultilineStringLiteralLine; + result.tag = .multiline_string_literal_line; }, '{' => { - result.id = .LBrace; + result.tag = .l_brace; self.index += 1; break; }, '}' => { - result.id = .RBrace; + result.tag = .r_brace; self.index += 1; break; }, '~' => { - result.id = .Tilde; + result.tag = .tilde; self.index += 1; break; }, @@ -550,14 +550,14 @@ pub const Tokenizer = struct { }, '0' => { state = .zero; - result.id = .IntegerLiteral; + result.tag = .integer_literal; }, '1'...'9' => { state = .int_literal_dec; - result.id = .IntegerLiteral; + result.tag = .integer_literal; }, else => { - result.id = .Invalid; + result.tag = .invalid; self.index += 1; break; }, @@ -565,42 +565,42 @@ pub const Tokenizer = struct { .saw_at_sign => switch (c) { '"' => { - result.id = .Identifier; + result.tag = .identifier; state = .string_literal; }, else => { // reinterpret as a builtin self.index -= 1; state = .builtin; - result.id = .Builtin; + result.tag = .builtin; }, }, .ampersand => switch (c) { '&' => { - result.id = .Invalid_ampersands; + result.tag = .invalid_ampersands; self.index += 1; break; }, '=' => { - result.id = .AmpersandEqual; + result.tag = .ampersand_equal; self.index += 1; break; }, else => { - result.id = .Ampersand; + result.tag = .ampersand; break; }, }, .asterisk => switch (c) { '=' => { - result.id = .AsteriskEqual; + result.tag = .asterisk_equal; self.index += 1; break; }, '*' => { - result.id = .AsteriskAsterisk; + result.tag = .asterisk_asterisk; self.index += 1; break; }, @@ -608,43 +608,43 @@ pub const Tokenizer = struct { state = .asterisk_percent; }, else => { - result.id = .Asterisk; + result.tag = .asterisk; break; }, }, .asterisk_percent => switch (c) { '=' => { - result.id = .AsteriskPercentEqual; + result.tag = .asterisk_percent_equal; self.index += 1; break; }, else => { - result.id = .AsteriskPercent; + result.tag = .asterisk_percent; break; }, }, .percent => switch (c) { '=' => { - result.id = .PercentEqual; + result.tag = .percent_equal; self.index += 1; break; }, else => { - result.id = .Percent; + result.tag = .percent; break; }, }, .plus => switch (c) { '=' => { - result.id = .PlusEqual; + result.tag = .plus_equal; self.index += 1; break; }, '+' => { - result.id = .PlusPlus; + result.tag = .plus_plus; self.index += 1; break; }, @@ -652,31 +652,31 @@ pub const Tokenizer = struct { state = .plus_percent; }, else => { - result.id = .Plus; + result.tag = .plus; break; }, }, .plus_percent => switch (c) { '=' => { - result.id = .PlusPercentEqual; + result.tag = .plus_percent_equal; self.index += 1; break; }, else => { - result.id = .PlusPercent; + result.tag = .plus_percent; break; }, }, .caret => switch (c) { '=' => { - result.id = .CaretEqual; + result.tag = .caret_equal; self.index += 1; break; }, else => { - result.id = .Caret; + result.tag = .caret; break; }, }, @@ -684,8 +684,8 @@ pub const Tokenizer = struct { .identifier => switch (c) { 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, else => { - if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |id| { - result.id = id; + if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |tag| { + result.tag = tag; } break; }, @@ -724,7 +724,7 @@ pub const Tokenizer = struct { state = .char_literal_backslash; }, '\'', 0x80...0xbf, 0xf8...0xff => { - result.id = .Invalid; + result.tag = .invalid; break; }, 0xc0...0xdf => { // 110xxxxx @@ -746,7 +746,7 @@ pub const Tokenizer = struct { .char_literal_backslash => switch (c) { '\n' => { - result.id = .Invalid; + result.tag = .invalid; break; }, 'x' => { @@ -769,7 +769,7 @@ pub const Tokenizer = struct { } }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -780,7 +780,7 @@ pub const Tokenizer = struct { seen_escape_digits = 0; }, else => { - result.id = .Invalid; + result.tag = .invalid; state = .char_literal_unicode_invalid; }, }, @@ -791,14 +791,14 @@ pub const Tokenizer = struct { }, '}' => { if (seen_escape_digits == 0) { - result.id = .Invalid; + result.tag = .invalid; state = .char_literal_unicode_invalid; } else { state = .char_literal_end; } }, else => { - result.id = .Invalid; + result.tag = .invalid; state = .char_literal_unicode_invalid; }, }, @@ -813,12 +813,12 @@ pub const Tokenizer = struct { .char_literal_end => switch (c) { '\'' => { - result.id = .CharLiteral; + result.tag = .char_literal; self.index += 1; break; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -831,7 +831,7 @@ pub const Tokenizer = struct { } }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -847,58 +847,58 @@ pub const Tokenizer = struct { .bang => switch (c) { '=' => { - result.id = .BangEqual; + result.tag = .bang_equal; self.index += 1; break; }, else => { - result.id = .Bang; + result.tag = .bang; break; }, }, .pipe => switch (c) { '=' => { - result.id = .PipeEqual; + result.tag = .pipe_equal; self.index += 1; break; }, '|' => { - result.id = .PipePipe; + result.tag = .pipe_pipe; self.index += 1; break; }, else => { - result.id = .Pipe; + result.tag = .pipe; break; }, }, .equal => switch (c) { '=' => { - result.id = .EqualEqual; + result.tag = .equal_equal; self.index += 1; break; }, '>' => { - result.id = .EqualAngleBracketRight; + result.tag = .equal_angle_bracket_right; self.index += 1; break; }, else => { - result.id = .Equal; + result.tag = .equal; break; }, }, .minus => switch (c) { '>' => { - result.id = .Arrow; + result.tag = .arrow; self.index += 1; break; }, '=' => { - result.id = .MinusEqual; + result.tag = .minus_equal; self.index += 1; break; }, @@ -906,19 +906,19 @@ pub const Tokenizer = struct { state = .minus_percent; }, else => { - result.id = .Minus; + result.tag = .minus; break; }, }, .minus_percent => switch (c) { '=' => { - result.id = .MinusPercentEqual; + result.tag = .minus_percent_equal; self.index += 1; break; }, else => { - result.id = .MinusPercent; + result.tag = .minus_percent; break; }, }, @@ -928,24 +928,24 @@ pub const Tokenizer = struct { state = .angle_bracket_angle_bracket_left; }, '=' => { - result.id = .AngleBracketLeftEqual; + result.tag = .angle_bracket_left_equal; self.index += 1; break; }, else => { - result.id = .AngleBracketLeft; + result.tag = .angle_bracket_left; break; }, }, .angle_bracket_angle_bracket_left => switch (c) { '=' => { - result.id = .AngleBracketAngleBracketLeftEqual; + result.tag = .angle_bracket_angle_bracket_left_equal; self.index += 1; break; }, else => { - result.id = .AngleBracketAngleBracketLeft; + result.tag = .angle_bracket_angle_bracket_left; break; }, }, @@ -955,24 +955,24 @@ pub const Tokenizer = struct { state = .angle_bracket_angle_bracket_right; }, '=' => { - result.id = .AngleBracketRightEqual; + result.tag = .angle_bracket_right_equal; self.index += 1; break; }, else => { - result.id = .AngleBracketRight; + result.tag = .angle_bracket_right; break; }, }, .angle_bracket_angle_bracket_right => switch (c) { '=' => { - result.id = .AngleBracketAngleBracketRightEqual; + result.tag = .angle_bracket_angle_bracket_right_equal; self.index += 1; break; }, else => { - result.id = .AngleBracketAngleBracketRight; + result.tag = .angle_bracket_angle_bracket_right; break; }, }, @@ -985,30 +985,30 @@ pub const Tokenizer = struct { state = .period_asterisk; }, else => { - result.id = .Period; + result.tag = .period; break; }, }, .period_2 => switch (c) { '.' => { - result.id = .Ellipsis3; + result.tag = .ellipsis3; self.index += 1; break; }, else => { - result.id = .Ellipsis2; + result.tag = .ellipsis2; break; }, }, .period_asterisk => switch (c) { '*' => { - result.id = .Invalid_periodasterisks; + result.tag = .invalid_periodasterisks; break; }, else => { - result.id = .PeriodAsterisk; + result.tag = .period_asterisk; break; }, }, @@ -1016,15 +1016,14 @@ pub const Tokenizer = struct { .slash => switch (c) { '/' => { state = .line_comment_start; - result.id = .LineComment; }, '=' => { - result.id = .SlashEqual; + result.tag = .slash_equal; self.index += 1; break; }, else => { - result.id = .Slash; + result.tag = .slash; break; }, }, @@ -1033,10 +1032,13 @@ pub const Tokenizer = struct { state = .doc_comment_start; }, '!' => { - result.id = .ContainerDocComment; + result.tag = .container_doc_comment; state = .container_doc_comment; }, - '\n' => break, + '\n' => { + state = .start; + result.loc.start = self.index + 1; + }, '\t', '\r' => state = .line_comment, else => { state = .line_comment; @@ -1048,20 +1050,28 @@ pub const Tokenizer = struct { state = .line_comment; }, '\n' => { - result.id = .DocComment; + result.tag = .doc_comment; break; }, '\t', '\r' => { state = .doc_comment; - result.id = .DocComment; + result.tag = .doc_comment; }, else => { state = .doc_comment; - result.id = .DocComment; + result.tag = .doc_comment; self.checkLiteralCharacter(); }, }, - .line_comment, .doc_comment, .container_doc_comment => switch (c) { + .line_comment => switch (c) { + '\n' => { + state = .start; + result.loc.start = self.index + 1; + }, + '\t', '\r' => {}, + else => self.checkLiteralCharacter(), + }, + .doc_comment, .container_doc_comment => switch (c) { '\n' => break, '\t', '\r' => {}, else => self.checkLiteralCharacter(), @@ -1083,7 +1093,7 @@ pub const Tokenizer = struct { }, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1093,7 +1103,7 @@ pub const Tokenizer = struct { state = .int_literal_bin; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1104,7 +1114,7 @@ pub const Tokenizer = struct { '0'...'1' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1114,7 +1124,7 @@ pub const Tokenizer = struct { state = .int_literal_oct; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1125,7 +1135,7 @@ pub const Tokenizer = struct { '0'...'7' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1135,7 +1145,7 @@ pub const Tokenizer = struct { state = .int_literal_dec; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1145,16 +1155,16 @@ pub const Tokenizer = struct { }, '.' => { state = .num_dot_dec; - result.id = .FloatLiteral; + result.tag = .float_literal; }, 'e', 'E' => { state = .float_exponent_unsigned; - result.id = .FloatLiteral; + result.tag = .float_literal; }, '0'...'9' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1164,7 +1174,7 @@ pub const Tokenizer = struct { state = .int_literal_hex; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1174,23 +1184,23 @@ pub const Tokenizer = struct { }, '.' => { state = .num_dot_hex; - result.id = .FloatLiteral; + result.tag = .float_literal; }, 'p', 'P' => { state = .float_exponent_unsigned; - result.id = .FloatLiteral; + result.tag = .float_literal; }, '0'...'9', 'a'...'f', 'A'...'F' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, }, .num_dot_dec => switch (c) { '.' => { - result.id = .IntegerLiteral; + result.tag = .integer_literal; self.index -= 1; state = .start; break; @@ -1203,14 +1213,14 @@ pub const Tokenizer = struct { }, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, }, .num_dot_hex => switch (c) { '.' => { - result.id = .IntegerLiteral; + result.tag = .integer_literal; self.index -= 1; state = .start; break; @@ -1219,12 +1229,12 @@ pub const Tokenizer = struct { state = .float_exponent_unsigned; }, '0'...'9', 'a'...'f', 'A'...'F' => { - result.id = .FloatLiteral; + result.tag = .float_literal; state = .float_fraction_hex; }, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1234,7 +1244,7 @@ pub const Tokenizer = struct { state = .float_fraction_dec; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1248,7 +1258,7 @@ pub const Tokenizer = struct { '0'...'9' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1258,7 +1268,7 @@ pub const Tokenizer = struct { state = .float_fraction_hex; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1272,7 +1282,7 @@ pub const Tokenizer = struct { '0'...'9', 'a'...'f', 'A'...'F' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1292,7 +1302,7 @@ pub const Tokenizer = struct { state = .float_exponent_num; }, else => { - result.id = .Invalid; + result.tag = .invalid; break; }, }, @@ -1303,7 +1313,7 @@ pub const Tokenizer = struct { '0'...'9' => {}, else => { if (isIdentifierChar(c)) { - result.id = .Invalid; + result.tag = .invalid; } break; }, @@ -1324,21 +1334,20 @@ pub const Tokenizer = struct { .string_literal, // find this error later .multiline_string_literal_line, .builtin, + .line_comment, + .line_comment_start, => {}, .identifier => { - if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |id| { - result.id = id; + if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |tag| { + result.tag = tag; } }, - .line_comment, .line_comment_start => { - result.id = .LineComment; - }, .doc_comment, .doc_comment_start => { - result.id = .DocComment; + result.tag = .doc_comment; }, .container_doc_comment => { - result.id = .ContainerDocComment; + result.tag = .container_doc_comment; }, .int_literal_dec_no_underscore, @@ -1361,80 +1370,81 @@ pub const Tokenizer = struct { .char_literal_unicode, .string_literal_backslash, => { - result.id = .Invalid; + result.tag = .invalid; }, .equal => { - result.id = .Equal; + result.tag = .equal; }, .bang => { - result.id = .Bang; + result.tag = .bang; }, .minus => { - result.id = .Minus; + result.tag = .minus; }, .slash => { - result.id = .Slash; + result.tag = .slash; }, .zero => { - result.id = .IntegerLiteral; + result.tag = .integer_literal; }, .ampersand => { - result.id = .Ampersand; + result.tag = .ampersand; }, .period => { - result.id = .Period; + result.tag = .period; }, .period_2 => { - result.id = .Ellipsis2; + result.tag = .ellipsis2; }, .period_asterisk => { - result.id = .PeriodAsterisk; + result.tag = .period_asterisk; }, .pipe => { - result.id = .Pipe; + result.tag = .pipe; }, .angle_bracket_angle_bracket_right => { - result.id = .AngleBracketAngleBracketRight; + result.tag = .angle_bracket_angle_bracket_right; }, .angle_bracket_right => { - result.id = .AngleBracketRight; + result.tag = .angle_bracket_right; }, .angle_bracket_angle_bracket_left => { - result.id = .AngleBracketAngleBracketLeft; + result.tag = .angle_bracket_angle_bracket_left; }, .angle_bracket_left => { - result.id = .AngleBracketLeft; + result.tag = .angle_bracket_left; }, .plus_percent => { - result.id = .PlusPercent; + result.tag = .plus_percent; }, .plus => { - result.id = .Plus; + result.tag = .plus; }, .percent => { - result.id = .Percent; + result.tag = .percent; }, .caret => { - result.id = .Caret; + result.tag = .caret; }, .asterisk_percent => { - result.id = .AsteriskPercent; + result.tag = .asterisk_percent; }, .asterisk => { - result.id = .Asterisk; + result.tag = .asterisk; }, .minus_percent => { - result.id = .MinusPercent; + result.tag = .minus_percent; }, } } - if (result.id == .Eof) { + if (result.tag == .eof) { if (self.pending_invalid_token) |token| { self.pending_invalid_token = null; return token; } + result.loc.start = self.index; } result.loc.end = self.index; @@ -1446,7 +1456,7 @@ pub const Tokenizer = struct { const invalid_length = self.getInvalidCharacterLength(); if (invalid_length == 0) return; self.pending_invalid_token = .{ - .id = .Invalid, + .tag = .invalid, .loc = .{ .start = self.index, .end = self.index + invalid_length, @@ -1493,220 +1503,218 @@ pub const Tokenizer = struct { }; test "tokenizer" { - testTokenize("test", &[_]Token.Id{.Keyword_test}); + testTokenize("test", &.{.keyword_test}); +} + +test "line comment followed by top-level comptime" { + testTokenize( + \\// line comment + \\comptime {} + \\ + , &.{ + .keyword_comptime, + .l_brace, + .r_brace, + }); } test "tokenizer - unknown length pointer and then c pointer" { testTokenize( \\[*]u8 \\[*c]u8 - , &[_]Token.Id{ - .LBracket, - .Asterisk, - .RBracket, - .Identifier, - .LBracket, - .Asterisk, - .Identifier, - .RBracket, - .Identifier, + , &.{ + .l_bracket, + .asterisk, + .r_bracket, + .identifier, + .l_bracket, + .asterisk, + .identifier, + .r_bracket, + .identifier, }); } -test "tokenizer - char literal with hex escape" { +test "tokenizer - code point literal with hex escape" { testTokenize( \\'\x1b' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\'\x1' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); } -test "tokenizer - char literal with unicode escapes" { +test "tokenizer - code point literal with unicode escapes" { // Valid unicode escapes testTokenize( \\'\u{3}' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\'\u{01}' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\'\u{2a}' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\'\u{3f9}' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\'\u{6E09aBc1523}' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); testTokenize( \\"\u{440}" - , &[_]Token.Id{.StringLiteral}); + , &.{.string_literal}); // Invalid unicode escapes testTokenize( \\'\u' - , &[_]Token.Id{.Invalid}); + , &.{.invalid}); testTokenize( \\'\u{{' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); testTokenize( \\'\u{}' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); testTokenize( \\'\u{s}' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); testTokenize( \\'\u{2z}' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); testTokenize( \\'\u{4a' - , &[_]Token.Id{.Invalid}); + , &.{.invalid}); // Test old-style unicode literals testTokenize( \\'\u0333' - , &[_]Token.Id{ .Invalid, .Invalid }); + , &.{ .invalid, .invalid }); testTokenize( \\'\U0333' - , &[_]Token.Id{ .Invalid, .IntegerLiteral, .Invalid }); + , &.{ .invalid, .integer_literal, .invalid }); } -test "tokenizer - char literal with unicode code point" { +test "tokenizer - code point literal with unicode code point" { testTokenize( \\'💩' - , &[_]Token.Id{.CharLiteral}); + , &.{.char_literal}); } test "tokenizer - float literal e exponent" { - testTokenize("a = 4.94065645841246544177e-324;\n", &[_]Token.Id{ - .Identifier, - .Equal, - .FloatLiteral, - .Semicolon, + testTokenize("a = 4.94065645841246544177e-324;\n", &.{ + .identifier, + .equal, + .float_literal, + .semicolon, }); } test "tokenizer - float literal p exponent" { - testTokenize("a = 0x1.a827999fcef32p+1022;\n", &[_]Token.Id{ - .Identifier, - .Equal, - .FloatLiteral, - .Semicolon, + testTokenize("a = 0x1.a827999fcef32p+1022;\n", &.{ + .identifier, + .equal, + .float_literal, + .semicolon, }); } test "tokenizer - chars" { - testTokenize("'c'", &[_]Token.Id{.CharLiteral}); + testTokenize("'c'", &.{.char_literal}); } test "tokenizer - invalid token characters" { - testTokenize("#", &[_]Token.Id{.Invalid}); - testTokenize("`", &[_]Token.Id{.Invalid}); - testTokenize("'c", &[_]Token.Id{.Invalid}); - testTokenize("'", &[_]Token.Id{.Invalid}); - testTokenize("''", &[_]Token.Id{ .Invalid, .Invalid }); + testTokenize("#", &.{.invalid}); + testTokenize("`", &.{.invalid}); + testTokenize("'c", &.{.invalid}); + testTokenize("'", &.{.invalid}); + testTokenize("''", &.{ .invalid, .invalid }); } test "tokenizer - invalid literal/comment characters" { - testTokenize("\"\x00\"", &[_]Token.Id{ - .StringLiteral, - .Invalid, + testTokenize("\"\x00\"", &.{ + .string_literal, + .invalid, }); - testTokenize("//\x00", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\x00", &.{ + .invalid, }); - testTokenize("//\x1f", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\x1f", &.{ + .invalid, }); - testTokenize("//\x7f", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\x7f", &.{ + .invalid, }); } test "tokenizer - utf8" { - testTokenize("//\xc2\x80", &[_]Token.Id{.LineComment}); - testTokenize("//\xf4\x8f\xbf\xbf", &[_]Token.Id{.LineComment}); + testTokenize("//\xc2\x80", &.{}); + testTokenize("//\xf4\x8f\xbf\xbf", &.{}); } test "tokenizer - invalid utf8" { - testTokenize("//\x80", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\x80", &.{ + .invalid, }); - testTokenize("//\xbf", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xbf", &.{ + .invalid, }); - testTokenize("//\xf8", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xf8", &.{ + .invalid, }); - testTokenize("//\xff", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xff", &.{ + .invalid, }); - testTokenize("//\xc2\xc0", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xc2\xc0", &.{ + .invalid, }); - testTokenize("//\xe0", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xe0", &.{ + .invalid, }); - testTokenize("//\xf0", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xf0", &.{ + .invalid, }); - testTokenize("//\xf0\x90\x80\xc0", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xf0\x90\x80\xc0", &.{ + .invalid, }); } test "tokenizer - illegal unicode codepoints" { // unicode newline characters.U+0085, U+2028, U+2029 - testTokenize("//\xc2\x84", &[_]Token.Id{.LineComment}); - testTokenize("//\xc2\x85", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xc2\x84", &.{}); + testTokenize("//\xc2\x85", &.{ + .invalid, }); - testTokenize("//\xc2\x86", &[_]Token.Id{.LineComment}); - testTokenize("//\xe2\x80\xa7", &[_]Token.Id{.LineComment}); - testTokenize("//\xe2\x80\xa8", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xc2\x86", &.{}); + testTokenize("//\xe2\x80\xa7", &.{}); + testTokenize("//\xe2\x80\xa8", &.{ + .invalid, }); - testTokenize("//\xe2\x80\xa9", &[_]Token.Id{ - .LineComment, - .Invalid, + testTokenize("//\xe2\x80\xa9", &.{ + .invalid, }); - testTokenize("//\xe2\x80\xaa", &[_]Token.Id{.LineComment}); + testTokenize("//\xe2\x80\xaa", &.{}); } test "tokenizer - string identifier and builtin fns" { testTokenize( \\const @"if" = @import("std"); - , &[_]Token.Id{ - .Keyword_const, - .Identifier, - .Equal, - .Builtin, - .LParen, - .StringLiteral, - .RParen, - .Semicolon, + , &.{ + .keyword_const, + .identifier, + .equal, + .builtin, + .l_paren, + .string_literal, + .r_paren, + .semicolon, }); } test "tokenizer - multiline string literal with literal tab" { testTokenize( \\\\foo bar - , &[_]Token.Id{ - .MultilineStringLiteralLine, + , &.{ + .multiline_string_literal_line, }); } @@ -1718,32 +1726,30 @@ test "tokenizer - comments with literal tab" { \\// foo \\/// foo \\/// /foo - , &[_]Token.Id{ - .LineComment, - .ContainerDocComment, - .DocComment, - .LineComment, - .DocComment, - .DocComment, + , &.{ + .container_doc_comment, + .doc_comment, + .doc_comment, + .doc_comment, }); } test "tokenizer - pipe and then invalid" { - testTokenize("||=", &[_]Token.Id{ - .PipePipe, - .Equal, + testTokenize("||=", &.{ + .pipe_pipe, + .equal, }); } test "tokenizer - line comment and doc comment" { - testTokenize("//", &[_]Token.Id{.LineComment}); - testTokenize("// a / b", &[_]Token.Id{.LineComment}); - testTokenize("// /", &[_]Token.Id{.LineComment}); - testTokenize("/// a", &[_]Token.Id{.DocComment}); - testTokenize("///", &[_]Token.Id{.DocComment}); - testTokenize("////", &[_]Token.Id{.LineComment}); - testTokenize("//!", &[_]Token.Id{.ContainerDocComment}); - testTokenize("//!!", &[_]Token.Id{.ContainerDocComment}); + testTokenize("//", &.{}); + testTokenize("// a / b", &.{}); + testTokenize("// /", &.{}); + testTokenize("/// a", &.{.doc_comment}); + testTokenize("///", &.{.doc_comment}); + testTokenize("////", &.{}); + testTokenize("//!", &.{.container_doc_comment}); + testTokenize("//!!", &.{.container_doc_comment}); } test "tokenizer - line comment followed by identifier" { @@ -1751,304 +1757,304 @@ test "tokenizer - line comment followed by identifier" { \\ Unexpected, \\ // another \\ Another, - , &[_]Token.Id{ - .Identifier, - .Comma, - .LineComment, - .Identifier, - .Comma, + , &.{ + .identifier, + .comma, + .identifier, + .comma, }); } test "tokenizer - UTF-8 BOM is recognized and skipped" { - testTokenize("\xEF\xBB\xBFa;\n", &[_]Token.Id{ - .Identifier, - .Semicolon, + testTokenize("\xEF\xBB\xBFa;\n", &.{ + .identifier, + .semicolon, }); } test "correctly parse pointer assignment" { - testTokenize("b.*=3;\n", &[_]Token.Id{ - .Identifier, - .PeriodAsterisk, - .Equal, - .IntegerLiteral, - .Semicolon, + testTokenize("b.*=3;\n", &.{ + .identifier, + .period_asterisk, + .equal, + .integer_literal, + .semicolon, }); } test "correctly parse pointer dereference followed by asterisk" { - testTokenize("\"b\".* ** 10", &[_]Token.Id{ - .StringLiteral, - .PeriodAsterisk, - .AsteriskAsterisk, - .IntegerLiteral, + testTokenize("\"b\".* ** 10", &.{ + .string_literal, + .period_asterisk, + .asterisk_asterisk, + .integer_literal, }); - testTokenize("(\"b\".*)** 10", &[_]Token.Id{ - .LParen, - .StringLiteral, - .PeriodAsterisk, - .RParen, - .AsteriskAsterisk, - .IntegerLiteral, + testTokenize("(\"b\".*)** 10", &.{ + .l_paren, + .string_literal, + .period_asterisk, + .r_paren, + .asterisk_asterisk, + .integer_literal, }); - testTokenize("\"b\".*** 10", &[_]Token.Id{ - .StringLiteral, - .Invalid_periodasterisks, - .AsteriskAsterisk, - .IntegerLiteral, + testTokenize("\"b\".*** 10", &.{ + .string_literal, + .invalid_periodasterisks, + .asterisk_asterisk, + .integer_literal, }); } test "tokenizer - range literals" { - testTokenize("0...9", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); - testTokenize("'0'...'9'", &[_]Token.Id{ .CharLiteral, .Ellipsis3, .CharLiteral }); - testTokenize("0x00...0x09", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); - testTokenize("0b00...0b11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); - testTokenize("0o00...0o11", &[_]Token.Id{ .IntegerLiteral, .Ellipsis3, .IntegerLiteral }); + testTokenize("0...9", &.{ .integer_literal, .ellipsis3, .integer_literal }); + testTokenize("'0'...'9'", &.{ .char_literal, .ellipsis3, .char_literal }); + testTokenize("0x00...0x09", &.{ .integer_literal, .ellipsis3, .integer_literal }); + testTokenize("0b00...0b11", &.{ .integer_literal, .ellipsis3, .integer_literal }); + testTokenize("0o00...0o11", &.{ .integer_literal, .ellipsis3, .integer_literal }); } test "tokenizer - number literals decimal" { - testTokenize("0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("1", &[_]Token.Id{.IntegerLiteral}); - testTokenize("2", &[_]Token.Id{.IntegerLiteral}); - testTokenize("3", &[_]Token.Id{.IntegerLiteral}); - testTokenize("4", &[_]Token.Id{.IntegerLiteral}); - testTokenize("5", &[_]Token.Id{.IntegerLiteral}); - testTokenize("6", &[_]Token.Id{.IntegerLiteral}); - testTokenize("7", &[_]Token.Id{.IntegerLiteral}); - testTokenize("8", &[_]Token.Id{.IntegerLiteral}); - testTokenize("9", &[_]Token.Id{.IntegerLiteral}); - testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 }); - testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1z_1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("9z3", &[_]Token.Id{ .Invalid, .Identifier }); - - testTokenize("0_0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0001", &[_]Token.Id{.IntegerLiteral}); - testTokenize("01234567890", &[_]Token.Id{.IntegerLiteral}); - testTokenize("012_345_6789_0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0_1_2_3_4_5_6_7_8_9_0", &[_]Token.Id{.IntegerLiteral}); - - testTokenize("00_", &[_]Token.Id{.Invalid}); - testTokenize("0_0_", &[_]Token.Id{.Invalid}); - testTokenize("0__0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0_0f", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0_0_f", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0_0_f_00", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1_,", &[_]Token.Id{ .Invalid, .Comma }); - - testTokenize("1.", &[_]Token.Id{.FloatLiteral}); - testTokenize("0.0", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.0", &[_]Token.Id{.FloatLiteral}); - testTokenize("10.0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0e0", &[_]Token.Id{.FloatLiteral}); - testTokenize("1e0", &[_]Token.Id{.FloatLiteral}); - testTokenize("1e100", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.e100", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.0e100", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.0e+100", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.0e-100", &[_]Token.Id{.FloatLiteral}); - testTokenize("1_0_0_0.0_0_0_0_0_1e1_0_0_0", &[_]Token.Id{.FloatLiteral}); - testTokenize("1.+", &[_]Token.Id{ .FloatLiteral, .Plus }); - - testTokenize("1e", &[_]Token.Id{.Invalid}); - testTokenize("1.0e1f0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0p100", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0p-100", &[_]Token.Id{ .Invalid, .Identifier, .Minus, .IntegerLiteral }); - testTokenize("1.0p1f0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0_,", &[_]Token.Id{ .Invalid, .Comma }); - testTokenize("1_.0", &[_]Token.Id{ .Invalid, .Period, .IntegerLiteral }); - testTokenize("1._", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.a", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.z", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1._0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1._+", &[_]Token.Id{ .Invalid, .Identifier, .Plus }); - testTokenize("1._e", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0e", &[_]Token.Id{.Invalid}); - testTokenize("1.0e,", &[_]Token.Id{ .Invalid, .Comma }); - testTokenize("1.0e_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0e+_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0e-_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("1.0e0_+", &[_]Token.Id{ .Invalid, .Plus }); + testTokenize("0", &.{.integer_literal}); + testTokenize("1", &.{.integer_literal}); + testTokenize("2", &.{.integer_literal}); + testTokenize("3", &.{.integer_literal}); + testTokenize("4", &.{.integer_literal}); + testTokenize("5", &.{.integer_literal}); + testTokenize("6", &.{.integer_literal}); + testTokenize("7", &.{.integer_literal}); + testTokenize("8", &.{.integer_literal}); + testTokenize("9", &.{.integer_literal}); + testTokenize("1..", &.{ .integer_literal, .ellipsis2 }); + testTokenize("0a", &.{ .invalid, .identifier }); + testTokenize("9b", &.{ .invalid, .identifier }); + testTokenize("1z", &.{ .invalid, .identifier }); + testTokenize("1z_1", &.{ .invalid, .identifier }); + testTokenize("9z3", &.{ .invalid, .identifier }); + + testTokenize("0_0", &.{.integer_literal}); + testTokenize("0001", &.{.integer_literal}); + testTokenize("01234567890", &.{.integer_literal}); + testTokenize("012_345_6789_0", &.{.integer_literal}); + testTokenize("0_1_2_3_4_5_6_7_8_9_0", &.{.integer_literal}); + + testTokenize("00_", &.{.invalid}); + testTokenize("0_0_", &.{.invalid}); + testTokenize("0__0", &.{ .invalid, .identifier }); + testTokenize("0_0f", &.{ .invalid, .identifier }); + testTokenize("0_0_f", &.{ .invalid, .identifier }); + testTokenize("0_0_f_00", &.{ .invalid, .identifier }); + testTokenize("1_,", &.{ .invalid, .comma }); + + testTokenize("1.", &.{.float_literal}); + testTokenize("0.0", &.{.float_literal}); + testTokenize("1.0", &.{.float_literal}); + testTokenize("10.0", &.{.float_literal}); + testTokenize("0e0", &.{.float_literal}); + testTokenize("1e0", &.{.float_literal}); + testTokenize("1e100", &.{.float_literal}); + testTokenize("1.e100", &.{.float_literal}); + testTokenize("1.0e100", &.{.float_literal}); + testTokenize("1.0e+100", &.{.float_literal}); + testTokenize("1.0e-100", &.{.float_literal}); + testTokenize("1_0_0_0.0_0_0_0_0_1e1_0_0_0", &.{.float_literal}); + testTokenize("1.+", &.{ .float_literal, .plus }); + + testTokenize("1e", &.{.invalid}); + testTokenize("1.0e1f0", &.{ .invalid, .identifier }); + testTokenize("1.0p100", &.{ .invalid, .identifier }); + testTokenize("1.0p-100", &.{ .invalid, .identifier, .minus, .integer_literal }); + testTokenize("1.0p1f0", &.{ .invalid, .identifier }); + testTokenize("1.0_,", &.{ .invalid, .comma }); + testTokenize("1_.0", &.{ .invalid, .period, .integer_literal }); + testTokenize("1._", &.{ .invalid, .identifier }); + testTokenize("1.a", &.{ .invalid, .identifier }); + testTokenize("1.z", &.{ .invalid, .identifier }); + testTokenize("1._0", &.{ .invalid, .identifier }); + testTokenize("1._+", &.{ .invalid, .identifier, .plus }); + testTokenize("1._e", &.{ .invalid, .identifier }); + testTokenize("1.0e", &.{.invalid}); + testTokenize("1.0e,", &.{ .invalid, .comma }); + testTokenize("1.0e_", &.{ .invalid, .identifier }); + testTokenize("1.0e+_", &.{ .invalid, .identifier }); + testTokenize("1.0e-_", &.{ .invalid, .identifier }); + testTokenize("1.0e0_+", &.{ .invalid, .plus }); } test "tokenizer - number literals binary" { - testTokenize("0b0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b1", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b2", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b3", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b4", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b5", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b6", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b7", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b8", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0b9", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0ba", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0bb", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0bc", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0bd", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0be", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0bf", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0bz", &[_]Token.Id{ .Invalid, .Identifier }); - - testTokenize("0b0000_0000", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b1111_1111", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b10_10_10_10", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b0_1_0_1_0_1_0_1", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0b1.", &[_]Token.Id{ .IntegerLiteral, .Period }); - testTokenize("0b1.0", &[_]Token.Id{ .IntegerLiteral, .Period, .IntegerLiteral }); - - testTokenize("0B0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b_0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b1_", &[_]Token.Id{.Invalid}); - testTokenize("0b0__1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b0_1_", &[_]Token.Id{.Invalid}); - testTokenize("0b1e", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b1p", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b1e0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b1p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0b1_,", &[_]Token.Id{ .Invalid, .Comma }); + testTokenize("0b0", &.{.integer_literal}); + testTokenize("0b1", &.{.integer_literal}); + testTokenize("0b2", &.{ .invalid, .integer_literal }); + testTokenize("0b3", &.{ .invalid, .integer_literal }); + testTokenize("0b4", &.{ .invalid, .integer_literal }); + testTokenize("0b5", &.{ .invalid, .integer_literal }); + testTokenize("0b6", &.{ .invalid, .integer_literal }); + testTokenize("0b7", &.{ .invalid, .integer_literal }); + testTokenize("0b8", &.{ .invalid, .integer_literal }); + testTokenize("0b9", &.{ .invalid, .integer_literal }); + testTokenize("0ba", &.{ .invalid, .identifier }); + testTokenize("0bb", &.{ .invalid, .identifier }); + testTokenize("0bc", &.{ .invalid, .identifier }); + testTokenize("0bd", &.{ .invalid, .identifier }); + testTokenize("0be", &.{ .invalid, .identifier }); + testTokenize("0bf", &.{ .invalid, .identifier }); + testTokenize("0bz", &.{ .invalid, .identifier }); + + testTokenize("0b0000_0000", &.{.integer_literal}); + testTokenize("0b1111_1111", &.{.integer_literal}); + testTokenize("0b10_10_10_10", &.{.integer_literal}); + testTokenize("0b0_1_0_1_0_1_0_1", &.{.integer_literal}); + testTokenize("0b1.", &.{ .integer_literal, .period }); + testTokenize("0b1.0", &.{ .integer_literal, .period, .integer_literal }); + + testTokenize("0B0", &.{ .invalid, .identifier }); + testTokenize("0b_", &.{ .invalid, .identifier }); + testTokenize("0b_0", &.{ .invalid, .identifier }); + testTokenize("0b1_", &.{.invalid}); + testTokenize("0b0__1", &.{ .invalid, .identifier }); + testTokenize("0b0_1_", &.{.invalid}); + testTokenize("0b1e", &.{ .invalid, .identifier }); + testTokenize("0b1p", &.{ .invalid, .identifier }); + testTokenize("0b1e0", &.{ .invalid, .identifier }); + testTokenize("0b1p0", &.{ .invalid, .identifier }); + testTokenize("0b1_,", &.{ .invalid, .comma }); } test "tokenizer - number literals octal" { - testTokenize("0o0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o1", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o2", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o3", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o4", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o5", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o6", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o7", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o8", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0o9", &[_]Token.Id{ .Invalid, .IntegerLiteral }); - testTokenize("0oa", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0ob", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0oc", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0od", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0oe", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0of", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0oz", &[_]Token.Id{ .Invalid, .Identifier }); - - testTokenize("0o01234567", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o0123_4567", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o01_23_45_67", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o0_1_2_3_4_5_6_7", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0o7.", &[_]Token.Id{ .IntegerLiteral, .Period }); - testTokenize("0o7.0", &[_]Token.Id{ .IntegerLiteral, .Period, .IntegerLiteral }); - - testTokenize("0O0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o_0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o1_", &[_]Token.Id{.Invalid}); - testTokenize("0o0__1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o0_1_", &[_]Token.Id{.Invalid}); - testTokenize("0o1e", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o1p", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o1e0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o1p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0o_,", &[_]Token.Id{ .Invalid, .Identifier, .Comma }); + testTokenize("0o0", &.{.integer_literal}); + testTokenize("0o1", &.{.integer_literal}); + testTokenize("0o2", &.{.integer_literal}); + testTokenize("0o3", &.{.integer_literal}); + testTokenize("0o4", &.{.integer_literal}); + testTokenize("0o5", &.{.integer_literal}); + testTokenize("0o6", &.{.integer_literal}); + testTokenize("0o7", &.{.integer_literal}); + testTokenize("0o8", &.{ .invalid, .integer_literal }); + testTokenize("0o9", &.{ .invalid, .integer_literal }); + testTokenize("0oa", &.{ .invalid, .identifier }); + testTokenize("0ob", &.{ .invalid, .identifier }); + testTokenize("0oc", &.{ .invalid, .identifier }); + testTokenize("0od", &.{ .invalid, .identifier }); + testTokenize("0oe", &.{ .invalid, .identifier }); + testTokenize("0of", &.{ .invalid, .identifier }); + testTokenize("0oz", &.{ .invalid, .identifier }); + + testTokenize("0o01234567", &.{.integer_literal}); + testTokenize("0o0123_4567", &.{.integer_literal}); + testTokenize("0o01_23_45_67", &.{.integer_literal}); + testTokenize("0o0_1_2_3_4_5_6_7", &.{.integer_literal}); + testTokenize("0o7.", &.{ .integer_literal, .period }); + testTokenize("0o7.0", &.{ .integer_literal, .period, .integer_literal }); + + testTokenize("0O0", &.{ .invalid, .identifier }); + testTokenize("0o_", &.{ .invalid, .identifier }); + testTokenize("0o_0", &.{ .invalid, .identifier }); + testTokenize("0o1_", &.{.invalid}); + testTokenize("0o0__1", &.{ .invalid, .identifier }); + testTokenize("0o0_1_", &.{.invalid}); + testTokenize("0o1e", &.{ .invalid, .identifier }); + testTokenize("0o1p", &.{ .invalid, .identifier }); + testTokenize("0o1e0", &.{ .invalid, .identifier }); + testTokenize("0o1p0", &.{ .invalid, .identifier }); + testTokenize("0o_,", &.{ .invalid, .identifier, .comma }); } test "tokenizer - number literals hexadeciaml" { - testTokenize("0x0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x1", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x2", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x3", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x4", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x5", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x6", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x7", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x8", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x9", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xa", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xb", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xc", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xd", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xe", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xf", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xA", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xB", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xC", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xD", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xE", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0xF", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x0z", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0xz", &[_]Token.Id{ .Invalid, .Identifier }); - - testTokenize("0x0123456789ABCDEF", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x0123_4567_89AB_CDEF", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x01_23_45_67_89AB_CDE_F", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x0_1_2_3_4_5_6_7_8_9_A_B_C_D_E_F", &[_]Token.Id{.IntegerLiteral}); - - testTokenize("0X0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x_", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x_1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x1_", &[_]Token.Id{.Invalid}); - testTokenize("0x0__1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0_1_", &[_]Token.Id{.Invalid}); - testTokenize("0x_,", &[_]Token.Id{ .Invalid, .Identifier, .Comma }); - - testTokenize("0x1.", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x1.0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xF.", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xF.0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xF.F", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xF.Fp0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xF.FP0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x1p0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xfp0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x1.+0xF.", &[_]Token.Id{ .FloatLiteral, .Plus, .FloatLiteral }); - - testTokenize("0x0123456.789ABCDEF", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x0_123_456.789_ABC_DEF", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x0_1_2_3_4_5_6.7_8_9_A_B_C_D_E_F", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x0p0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0x0.0p0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xff.ffp10", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xff.ffP10", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xff.p10", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xffp10", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xff_ff.ff_ffp1_0_0_0", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xf_f_f_f.f_f_f_fp+1_000", &[_]Token.Id{.FloatLiteral}); - testTokenize("0xf_f_f_f.f_f_f_fp-1_00_0", &[_]Token.Id{.FloatLiteral}); - - testTokenize("0x1e", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x1e0", &[_]Token.Id{.IntegerLiteral}); - testTokenize("0x1p", &[_]Token.Id{.Invalid}); - testTokenize("0xfp0z1", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0xff.ffpff", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.p", &[_]Token.Id{.Invalid}); - testTokenize("0x0.z", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0._", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0_.0", &[_]Token.Id{ .Invalid, .Period, .IntegerLiteral }); - testTokenize("0x0_.0.0", &[_]Token.Id{ .Invalid, .Period, .FloatLiteral }); - testTokenize("0x0._0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0_", &[_]Token.Id{.Invalid}); - testTokenize("0x0_p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0_.p0", &[_]Token.Id{ .Invalid, .Period, .Identifier }); - testTokenize("0x0._p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0_p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0._0p0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0p_0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0p+_0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0p-_0", &[_]Token.Id{ .Invalid, .Identifier }); - testTokenize("0x0.0p0_", &[_]Token.Id{ .Invalid, .Eof }); + testTokenize("0x0", &.{.integer_literal}); + testTokenize("0x1", &.{.integer_literal}); + testTokenize("0x2", &.{.integer_literal}); + testTokenize("0x3", &.{.integer_literal}); + testTokenize("0x4", &.{.integer_literal}); + testTokenize("0x5", &.{.integer_literal}); + testTokenize("0x6", &.{.integer_literal}); + testTokenize("0x7", &.{.integer_literal}); + testTokenize("0x8", &.{.integer_literal}); + testTokenize("0x9", &.{.integer_literal}); + testTokenize("0xa", &.{.integer_literal}); + testTokenize("0xb", &.{.integer_literal}); + testTokenize("0xc", &.{.integer_literal}); + testTokenize("0xd", &.{.integer_literal}); + testTokenize("0xe", &.{.integer_literal}); + testTokenize("0xf", &.{.integer_literal}); + testTokenize("0xA", &.{.integer_literal}); + testTokenize("0xB", &.{.integer_literal}); + testTokenize("0xC", &.{.integer_literal}); + testTokenize("0xD", &.{.integer_literal}); + testTokenize("0xE", &.{.integer_literal}); + testTokenize("0xF", &.{.integer_literal}); + testTokenize("0x0z", &.{ .invalid, .identifier }); + testTokenize("0xz", &.{ .invalid, .identifier }); + + testTokenize("0x0123456789ABCDEF", &.{.integer_literal}); + testTokenize("0x0123_4567_89AB_CDEF", &.{.integer_literal}); + testTokenize("0x01_23_45_67_89AB_CDE_F", &.{.integer_literal}); + testTokenize("0x0_1_2_3_4_5_6_7_8_9_A_B_C_D_E_F", &.{.integer_literal}); + + testTokenize("0X0", &.{ .invalid, .identifier }); + testTokenize("0x_", &.{ .invalid, .identifier }); + testTokenize("0x_1", &.{ .invalid, .identifier }); + testTokenize("0x1_", &.{.invalid}); + testTokenize("0x0__1", &.{ .invalid, .identifier }); + testTokenize("0x0_1_", &.{.invalid}); + testTokenize("0x_,", &.{ .invalid, .identifier, .comma }); + + testTokenize("0x1.", &.{.float_literal}); + testTokenize("0x1.0", &.{.float_literal}); + testTokenize("0xF.", &.{.float_literal}); + testTokenize("0xF.0", &.{.float_literal}); + testTokenize("0xF.F", &.{.float_literal}); + testTokenize("0xF.Fp0", &.{.float_literal}); + testTokenize("0xF.FP0", &.{.float_literal}); + testTokenize("0x1p0", &.{.float_literal}); + testTokenize("0xfp0", &.{.float_literal}); + testTokenize("0x1.+0xF.", &.{ .float_literal, .plus, .float_literal }); + + testTokenize("0x0123456.789ABCDEF", &.{.float_literal}); + testTokenize("0x0_123_456.789_ABC_DEF", &.{.float_literal}); + testTokenize("0x0_1_2_3_4_5_6.7_8_9_A_B_C_D_E_F", &.{.float_literal}); + testTokenize("0x0p0", &.{.float_literal}); + testTokenize("0x0.0p0", &.{.float_literal}); + testTokenize("0xff.ffp10", &.{.float_literal}); + testTokenize("0xff.ffP10", &.{.float_literal}); + testTokenize("0xff.p10", &.{.float_literal}); + testTokenize("0xffp10", &.{.float_literal}); + testTokenize("0xff_ff.ff_ffp1_0_0_0", &.{.float_literal}); + testTokenize("0xf_f_f_f.f_f_f_fp+1_000", &.{.float_literal}); + testTokenize("0xf_f_f_f.f_f_f_fp-1_00_0", &.{.float_literal}); + + testTokenize("0x1e", &.{.integer_literal}); + testTokenize("0x1e0", &.{.integer_literal}); + testTokenize("0x1p", &.{.invalid}); + testTokenize("0xfp0z1", &.{ .invalid, .identifier }); + testTokenize("0xff.ffpff", &.{ .invalid, .identifier }); + testTokenize("0x0.p", &.{.invalid}); + testTokenize("0x0.z", &.{ .invalid, .identifier }); + testTokenize("0x0._", &.{ .invalid, .identifier }); + testTokenize("0x0_.0", &.{ .invalid, .period, .integer_literal }); + testTokenize("0x0_.0.0", &.{ .invalid, .period, .float_literal }); + testTokenize("0x0._0", &.{ .invalid, .identifier }); + testTokenize("0x0.0_", &.{.invalid}); + testTokenize("0x0_p0", &.{ .invalid, .identifier }); + testTokenize("0x0_.p0", &.{ .invalid, .period, .identifier }); + testTokenize("0x0._p0", &.{ .invalid, .identifier }); + testTokenize("0x0.0_p0", &.{ .invalid, .identifier }); + testTokenize("0x0._0p0", &.{ .invalid, .identifier }); + testTokenize("0x0.0p_0", &.{ .invalid, .identifier }); + testTokenize("0x0.0p+_0", &.{ .invalid, .identifier }); + testTokenize("0x0.0p-_0", &.{ .invalid, .identifier }); + testTokenize("0x0.0p0_", &.{ .invalid, .eof }); } -fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void { +fn testTokenize(source: []const u8, expected_tokens: []const Token.Tag) void { var tokenizer = Tokenizer.init(source); for (expected_tokens) |expected_token_id| { const token = tokenizer.next(); - if (token.id != expected_token_id) { - std.debug.panic("expected {}, found {}\n", .{ @tagName(expected_token_id), @tagName(token.id) }); + if (token.tag != expected_token_id) { + std.debug.panic("expected {s}, found {s}\n", .{ @tagName(expected_token_id), @tagName(token.tag) }); } } const last_token = tokenizer.next(); - std.testing.expect(last_token.id == .Eof); + std.testing.expect(last_token.tag == .eof); + std.testing.expect(last_token.loc.start == source.len); } |
