diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34d5d62..7453c33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: contents: write env: - ZIG_VERSION: "0.15.2" + ZIG_VERSION: "0.16.0" jobs: # --------------------------------------------------------------------------- @@ -402,7 +402,7 @@ jobs: end end - depends_on "zig" => "0.15.2" + depends_on "zig" => "0.16.0" def install bin.install "bin/run" diff --git a/.github/workflows/runtime-ci.yml b/.github/workflows/runtime-ci.yml index 31f7f35..9acb3df 100644 --- a/.github/workflows/runtime-ci.yml +++ b/.github/workflows/runtime-ci.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Build runtime run: zig build - name: Runtime tests @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v4 - uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Run benchmarks run: zig build bench-runtime - name: Upload results diff --git a/.github/workflows/zig-ci.yml b/.github/workflows/zig-ci.yml index 0ce3a4f..c945d59 100644 --- a/.github/workflows/zig-ci.yml +++ b/.github/workflows/zig-ci.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Build run: zig build @@ -45,7 +45,7 @@ jobs: - name: Set up Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Build compiler run: zig build @@ -68,7 +68,7 @@ jobs: - name: Set up Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Zig test run: zig test src/root.zig @@ -85,7 +85,7 @@ jobs: - name: Set up Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Build with sanitizers run: zig build -Dsanitize=true @@ -146,7 +146,7 @@ jobs: if: steps.changes.outputs.should_run == 'true' uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 - name: Build compiler if: steps.changes.outputs.should_run == 'true' diff --git a/.gitignore b/.gitignore index e5ce5d2..a4db880 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ main.o /test .agents/ .claude/ +/root +/runner +/tmp-*-XXXXXX +/dap-*-XXXXXX +/lsp-*-XXXXXX +/skills-lock.json diff --git a/AGENTS.md b/AGENTS.md index a552434..ae1c14f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # Repository Guidelines ## Canonical Docs -Start with `CLAUDE.md` for architecture, Zig 0.15 conventions, and current workflow rules. Keep shared instructions in `AGENTS.md` and `CLAUDE.md` synchronized in the same PR whenever commands, branch targets, or contributor process changes. +Start with `CLAUDE.md` for architecture, Zig 0.16 conventions, and current workflow rules. Keep shared instructions in `AGENTS.md` and `CLAUDE.md` synchronized in the same PR whenever commands, branch targets, or contributor process changes. ## Project Structure & Module Organization `src/` holds compiler passes and CLI entrypoints such as `main.zig`, `driver.zig`, `formatter.zig`, and `lsp.zig`. `src/runtime/` contains the C runtime, with runtime tests in `src/runtime/tests/`. `stdlib/` stores standard library `.run` packages, `examples/` has sample programs, and `tests/` contains Zig debug/e2e runners plus fuzz corpus inputs. `website/` is the Astro/Starlight docs site, `editors/` contains VS Code and tree-sitter tooling, and `blog/helm/` deploys Strapi. @@ -10,7 +10,7 @@ Start with `CLAUDE.md` for architecture, Zig 0.15 conventions, and current workf `zig build` builds the compiler. `zig build test` runs Zig unit tests, `zig build test-e2e` runs end-to-end compiler cases, and `make test-runtime` runs the runtime C suite. Use `zig build run -- ` for compiler commands such as `check`, `run`, `fmt`, `tokens`, or `ast`. Website work stays in `website/`: `bun run dev` for local docs work and `bun run build` for a production build. Editor examples: `cd editors/tree-sitter-run && bun run test` and `cd editors/vscode && bun run compile`. ## Coding Style & Naming Conventions -Format Zig with `zig fmt`. Follow the Zig 0.15 patterns already used here: prefer `ArrayList.empty`, pass allocators per method call, and match existing module naming. Token tags use the `kw_` prefix; AST tags use `_stmt`, `_decl`, `_literal`, and `type_` for type-related nodes. Runtime C in `src/runtime/` should stay `clang-format` clean. Website code uses `oxfmt` and `oxlint`. +Format Zig with `zig fmt`. Follow the Zig 0.16 patterns already used here: prefer `ArrayList.empty`, pass allocators per method call, and match existing module naming. Token tags use the `kw_` prefix; AST tags use `_stmt`, `_decl`, `_literal`, and `type_` for type-related nodes. Runtime C in `src/runtime/` should stay `clang-format` clean. Website code uses `oxfmt` and `oxlint`. ## Testing Guidelines Keep tests close to the code they validate: inline Zig `test` blocks, runtime files named `src/runtime/tests/test_*.c`, e2e cases in `tests/e2e/cases/*.run`, and stdlib tests as `*_test.run`. Add or update the nearest matching test for every behavior change; there is no published coverage target. For Linux runtime work, `zig build test -Dsanitize=true` matches CI sanitizer coverage. diff --git a/CLAUDE.md b/CLAUDE.md index 97e7cfa..d3afc6b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ Compiler commands: `run`, `build`, `check`, `tokens` (dump lexer output), `ast` ## Architecture -Run is a systems programming language compiler written in Zig 0.15. The full compilation pipeline: +Run is a systems programming language compiler written in Zig 0.16. The full compilation pipeline: ``` Source (.run) → Lexer → Parser → Naming → Resolve → TypeCheck → Lower (IR) → CodegenC → zig cc @@ -60,7 +60,7 @@ Source (.run) → Lexer → Parser → Naming → Resolve → TypeCheck → Lowe Tests are embedded `test` blocks in source files, discovered through root.zig. -## Zig 0.15 Conventions +## Zig 0.16 Conventions These differ from older Zig versions and are critical to get right: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87e695e..b449230 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for your interest in contributing to Run! This document covers how to get ## Development Setup -1. Install [Zig 0.15](https://ziglang.org/download/) or later +1. Install [Zig 0.16](https://ziglang.org/download/) or later 2. Clone the repository: ```bash git clone https://github.com/marsolab/runlang.git @@ -33,7 +33,7 @@ Thanks for your interest in contributing to Run! This document covers how to get - Use `kw_` prefix for keyword tokens - Use `_stmt`, `_decl`, `_literal` suffixes for AST node tags - Use `type_` prefix for type-related AST nodes -- Use `ArrayList.empty` (not `.init(allocator)`) — Zig 0.15 convention +- Use `ArrayList.empty` (not `.init(allocator)`) — Zig 0.16 convention - Pass the allocator to each method call rather than storing it in containers ## Submitting a Pull Request diff --git a/README.md b/README.md index 1dfd0be..a1cc463 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ pub fun main() { ### Prerequisites -Run's compiler is written in [Zig](https://ziglang.org/). You need **Zig 0.15** or later. +Run's compiler is written in [Zig](https://ziglang.org/). You need **Zig 0.16** or later. ### Build from Source diff --git a/benchmarks/bench.zig b/benchmarks/bench.zig index 760aef0..d8d2540 100644 --- a/benchmarks/bench.zig +++ b/benchmarks/bench.zig @@ -1,18 +1,17 @@ const std = @import("std"); -const compiler = @import("compiler"); - -const Lexer = compiler.Lexer; -const Parser = compiler.Parser; +const Lexer = @import("compiler").Lexer; +const Parser = @import("compiler").Parser; +const Dir = std.Io.Dir; +const File = std.Io.File; const WARMUP = 3; const ITERS = 10; -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - const stdout = std.fs.File.stdout().deprecatedWriter(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; try stdout.writeAll("Run Compiler Benchmarks\n"); try stdout.writeAll("=======================\n\n"); @@ -47,13 +46,13 @@ pub fn main() !void { // Measured for (0..ITERS) |iter| { - var timer = try std.time.Timer.start(); + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; var lexer = Lexer.init(source); while (true) { const tok = lexer.next(); if (tok.tag == .eof) break; } - timings[iter] = timer.read(); + timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns); } const stats = computeStats(&timings); @@ -79,14 +78,14 @@ pub fn main() !void { // Measured for (0..ITERS) |iter| { - var timer = try std.time.Timer.start(); + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; var lexer = Lexer.init(source); var tokens = try lexer.tokenize(allocator); defer tokens.deinit(allocator); var parser = Parser.init(allocator, tokens.items, source); defer parser.deinit(); _ = try parser.parseFile(); - timings[iter] = timer.read(); + timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns); } const stats = computeStats(&timings); @@ -102,13 +101,13 @@ pub fn main() !void { defer allocator.free(tmp_path); { - const f = try std.fs.cwd().createFile(tmp_path, .{}); - defer f.close(); - try f.writeAll(source); + const f = try Dir.cwd().createFile(io, tmp_path, .{}); + defer f.close(io); + try f.writeStreamingAll(io, source); } - defer std.fs.cwd().deleteFile(tmp_path) catch {}; + defer Dir.cwd().deleteFile(io, tmp_path) catch {}; - const compiler_path = findCompiler(allocator) catch { + const compiler_path = findCompiler(io, allocator) catch { try stdout.writeAll(" (skipped — compiler binary not found, run 'zig build' first)\n"); break; }; @@ -118,17 +117,17 @@ pub fn main() !void { // Warmup for (0..WARMUP) |_| { - _ = runCompilerCheck(allocator, compiler_path, tmp_path) catch continue; + _ = runCompilerCheck(io, allocator, compiler_path, tmp_path) catch continue; } // Measured for (0..ITERS) |iter| { - var timer = try std.time.Timer.start(); - _ = runCompilerCheck(allocator, compiler_path, tmp_path) catch { + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; + _ = runCompilerCheck(io, allocator, compiler_path, tmp_path) catch { timings[iter] = 0; continue; }; - timings[iter] = timer.read(); + timings[iter] = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns); } const stats = computeStats(&timings); @@ -189,64 +188,62 @@ fn formatDuration(ns: u64) [12]u8 { fn generateSource(allocator: std.mem.Allocator, num_fns: usize) ![]const u8 { var buf: std.ArrayList(u8) = .empty; - const writer = buf.writer(allocator); - try writer.writeAll("package main\n\nuse \"fmt\"\n\n"); + try buf.appendSlice(allocator, "package main\n\nuse \"fmt\"\n\n"); for (0..num_fns) |i| { - try writer.print("fun func_{d}() {{\n", .{i}); - try writer.writeAll(" let x = 42\n"); - try writer.writeAll(" var y = 10\n"); - try writer.writeAll(" y = y + x\n"); - try writer.writeAll(" if y > 50 {\n"); - try writer.writeAll(" fmt.println(\"big\")\n"); - try writer.writeAll(" } else {\n"); - try writer.writeAll(" fmt.println(\"small\")\n"); - try writer.writeAll(" }\n"); - try writer.writeAll(" var i = 0\n"); - try writer.writeAll(" for i < 3 {\n"); - try writer.writeAll(" i = i + 1\n"); - try writer.writeAll(" }\n"); - try writer.writeAll(" fmt.println(y)\n"); - try writer.writeAll("}\n\n"); + try buf.print(allocator, "fun func_{d}() {{\n", .{i}); + try buf.appendSlice(allocator, " let x = 42\n"); + try buf.appendSlice(allocator, " var y = 10\n"); + try buf.appendSlice(allocator, " y = y + x\n"); + try buf.appendSlice(allocator, " if y > 50 {\n"); + try buf.appendSlice(allocator, " fmt.println(\"big\")\n"); + try buf.appendSlice(allocator, " } else {\n"); + try buf.appendSlice(allocator, " fmt.println(\"small\")\n"); + try buf.appendSlice(allocator, " }\n"); + try buf.appendSlice(allocator, " var i = 0\n"); + try buf.appendSlice(allocator, " for i < 3 {\n"); + try buf.appendSlice(allocator, " i = i + 1\n"); + try buf.appendSlice(allocator, " }\n"); + try buf.appendSlice(allocator, " fmt.println(y)\n"); + try buf.appendSlice(allocator, "}\n\n"); } - try writer.writeAll("pub fun main() {\n"); - try writer.writeAll(" func_0()\n"); - try writer.writeAll("}\n"); + try buf.appendSlice(allocator, "pub fun main() {\n"); + try buf.appendSlice(allocator, " func_0()\n"); + try buf.appendSlice(allocator, "}\n"); return buf.toOwnedSlice(allocator); } -fn findCompiler(allocator: std.mem.Allocator) ![]const u8 { +fn findCompiler(io: std.Io, allocator: std.mem.Allocator) ![]const u8 { const local_path = "zig-out/bin/run"; - if (std.fs.cwd().access(local_path, .{})) |_| { + if (Dir.cwd().access(io, local_path, .{})) |_| { return try allocator.dupe(u8, local_path); } else |_| {} return error.CompilerNotFound; } fn runCompilerCheck( + io: std.Io, allocator: std.mem.Allocator, compiler_path: []const u8, source_path: []const u8, ) !void { - var child = std.process.Child.init(&.{ compiler_path, "check", "--no-color", source_path }, allocator); - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - try child.spawn(); - - // Drain output - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stdout.?.read(&buf) catch break; - if (n == 0) break; - } - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stderr.?.read(&buf) catch break; - if (n == 0) break; - } + const result = try std.process.run(allocator, io, .{ + .argv = &.{ compiler_path, "check", "--no-color", source_path }, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), + }); + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + + if (childExitCode(result.term) != 0) return error.CheckFailed; +} - const result = try child.wait(); - if (result.Exited != 0) return error.CheckFailed; +fn childExitCode(term: std.process.Child.Term) u8 { + return switch (term) { + .exited => |code| code, + .signal => |sig| if (@intFromEnum(sig) + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(@intFromEnum(sig) + 128)) else std.math.maxInt(u8), + .stopped, .unknown => 1, + }; } diff --git a/build.zig b/build.zig index 1d82388..03f5ff3 100644 --- a/build.zig +++ b/build.zig @@ -3,14 +3,16 @@ const std = @import("std"); /// Probe for the GCC library directory containing sanitizer runtimes. /// On Ubuntu/Debian, libasan.a lives under /usr/lib/gcc/// /// which is not in the standard library search path. -fn findGccSanitizerLibDir(allocator: std.mem.Allocator) ?[]const u8 { +fn findGccSanitizerLibDir(b: *std.Build) ?[]const u8 { + const allocator = b.allocator; + const io = b.graph.io; const triplets = [_][]const u8{ "x86_64-linux-gnu", "aarch64-linux-gnu" }; const versions = [_][]const u8{ "14", "13", "12", "11" }; for (triplets) |triplet| { for (versions) |ver| { const path = std.fmt.allocPrint(allocator, "/usr/lib/gcc/{s}/{s}/libasan.a", .{ triplet, ver }) catch continue; - if (std.fs.openFileAbsolute(path, .{})) |f| { - f.close(); + if (std.Io.Dir.cwd().openFile(io, path, .{})) |f| { + f.close(io); return std.fmt.allocPrint(allocator, "/usr/lib/gcc/{s}/{s}", .{ triplet, ver }) catch null; } else |_| {} } @@ -42,10 +44,10 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); exe.root_module.addOptions("build_options", build_options); - exe.linkLibC(); b.installArtifact(exe); // libxev dependency (cross-platform event loop for async I/O) @@ -60,6 +62,7 @@ pub fn build(b: *std.Build) void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); @@ -162,33 +165,30 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/runtime/run_xev_bridge.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); obj.root_module.addImport("xev", libxev_dep.module("xev")); - obj.linkLibC(); break :blk obj; } else null; if (xev_bridge_obj) |obj| { - runtime_lib.addObject(obj); - } - if (xev_bridge_obj) |obj| { + runtime_lib.root_module.addObject(obj); // Bundle the already-compiled object into librunxev.a with `ar` so the // driver can still link `-lrunxev`. Going through `ar` directly avoids - // the Zig 0.15.2 archiver race that produced truncated archives on - // Linux x86_64 when librunxev.a was written and read concurrently. + // the Zig archiver race that produced truncated archives on Linux + // x86_64 when librunxev.a was written and read concurrently. const ar_cmd = b.addSystemCommand(&.{ "ar", "rcs" }); const archive = ar_cmd.addOutputFileArg("librunxev.a"); ar_cmd.addArtifactArg(obj); const install_xev_lib = b.addInstallFile(archive, "lib/librunxev.a"); b.getInstallStep().dependOn(&install_xev_lib.step); } - runtime_lib.linkLibC(); - runtime_lib.linkSystemLibrary("pthread"); + runtime_lib.root_module.linkSystemLibrary("pthread", .{}); // Link libunwind for stack traces with DWARF unwinding. // On macOS, libunwind is part of the system (linked automatically). // On Linux, it requires the libunwind-dev package. if (target_info.os.tag == .linux) { - runtime_lib.linkSystemLibrary("unwind"); + runtime_lib.root_module.linkSystemLibrary("unwind", .{}); } // Note: sanitizer runtime libraries are NOT linked into the static archive. // The consuming executable is responsible for linking them. @@ -217,9 +217,9 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); - tests.linkLibC(); const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_tests.step); @@ -230,6 +230,7 @@ pub fn build(b: *std.Build) void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); @@ -277,13 +278,12 @@ pub fn build(b: *std.Build) void { runtime_test_exe.root_module.addIncludePath(b.path("src/runtime/tests")); // Reuse the same object file produced for runtime_lib (see note above). if (xev_bridge_obj) |obj| { - runtime_test_exe.addObject(obj); + runtime_test_exe.root_module.addObject(obj); } - runtime_test_exe.linkLibC(); - runtime_test_exe.linkSystemLibrary("pthread"); + runtime_test_exe.root_module.linkSystemLibrary("pthread", .{}); // Link libunwind for stack trace tests (matches runtime_lib linking). if (target_info.os.tag == .linux) { - runtime_test_exe.linkSystemLibrary("unwind"); + runtime_test_exe.root_module.linkSystemLibrary("unwind", .{}); // On Linux, dladdr only resolves symbols exposed through the dynamic // symbol table. Without --export-dynamic, stack-trace tests that match // on function names (e.g. strstr(trace, "test_runtime_stack")) will @@ -296,16 +296,16 @@ pub fn build(b: *std.Build) void { // (e.g. /usr/lib/gcc/x86_64-linux-gnu/13/) which isn't in the // standard search path. Probe for it at configure time. if (sanitize or tsan) { - if (findGccSanitizerLibDir(b.allocator)) |gcc_dir| { - runtime_test_exe.addLibraryPath(.{ .cwd_relative = gcc_dir }); + if (findGccSanitizerLibDir(b)) |gcc_dir| { + runtime_test_exe.root_module.addLibraryPath(.{ .cwd_relative = gcc_dir }); } } if (sanitize) { - runtime_test_exe.linkSystemLibrary("asan"); - runtime_test_exe.linkSystemLibrary("ubsan"); + runtime_test_exe.root_module.linkSystemLibrary("asan", .{}); + runtime_test_exe.root_module.linkSystemLibrary("ubsan", .{}); } if (tsan) { - runtime_test_exe.linkSystemLibrary("tsan"); + runtime_test_exe.root_module.linkSystemLibrary("tsan", .{}); } b.installArtifact(runtime_test_exe); @@ -320,6 +320,7 @@ pub fn build(b: *std.Build) void { .root_module = b.createModule(.{ .target = target, .optimize = .ReleaseFast, + .link_libc = true, }), }); const runtime_bench_sources = .{ @@ -367,17 +368,16 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/runtime/run_xev_bridge.zig"), .target = target, .optimize = .ReleaseFast, + .link_libc = true, }), }); xev_bench_bridge.root_module.addImport("xev", libxev_dep.module("xev")); - xev_bench_bridge.linkLibC(); - runtime_bench_exe.linkLibrary(xev_bench_bridge); + runtime_bench_exe.root_module.linkLibrary(xev_bench_bridge); } - runtime_bench_exe.linkLibC(); - runtime_bench_exe.linkSystemLibrary("pthread"); + runtime_bench_exe.root_module.linkSystemLibrary("pthread", .{}); // Link libunwind for stack trace support (matches runtime_lib linking). if (target_info.os.tag == .linux) { - runtime_bench_exe.linkSystemLibrary("unwind"); + runtime_bench_exe.root_module.linkSystemLibrary("unwind", .{}); } b.installArtifact(runtime_bench_exe); @@ -393,9 +393,9 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("tests/examples/runner.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); - examples_test_exe.linkLibC(); const run_examples_tests = b.addRunArtifact(examples_test_exe); run_examples_tests.step.dependOn(b.getInstallStep()); const examples_test_step = b.step("test-examples", "Build all example programs"); @@ -408,9 +408,9 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("tests/e2e/runner.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); - e2e_test_exe.linkLibC(); const run_e2e_tests = b.addRunArtifact(e2e_test_exe); run_e2e_tests.step.dependOn(b.getInstallStep()); if (b.args) |args| { @@ -431,9 +431,9 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path(entry[1]), .target = target, .optimize = optimize, + .link_libc = true, }), }); - fuzz_test.linkLibC(); const run_fuzz = b.addRunArtifact(fuzz_test); const fuzz_step = b.step(entry[0], entry[2]); fuzz_step.dependOn(&run_fuzz.step); @@ -444,6 +444,7 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("benchmarks/bench.zig"), .target = target, .optimize = .ReleaseFast, + .link_libc = true, }); // Provide compiler as a single module to avoid file-ownership conflicts bench_root.addImport("compiler", b.createModule(.{ @@ -455,7 +456,6 @@ pub fn build(b: *std.Build) void { .name = "bench", .root_module = bench_root, }); - bench_exe.linkLibC(); // Benchmark depends on compiler binary for pipeline benchmarks const run_bench = b.addRunArtifact(bench_exe); run_bench.step.dependOn(b.getInstallStep()); diff --git a/build.zig.zon b/build.zig.zon index ae0487b..7e68431 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -25,7 +25,7 @@ .fingerprint = 0x8b3b105cafe73f9f, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. @@ -33,8 +33,8 @@ // internet connectivity. .dependencies = .{ .libxev = .{ - .url = "https://github.com/mitchellh/libxev/archive/d398cba9bf711fa4dd21c48bf73d25e4d8986408.tar.gz", - .hash = "libxev-0.0.0-86vtc689EwAjQWysidPm9NKkAwwUNByiE3kZTg_xu0Hp", + .url = "https://github.com/mitchellh/libxev/archive/a82a04eabb46b0611c72d6d0e32db2d9dcf2e745.tar.gz", + .hash = "libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc", }, }, .paths = .{ diff --git a/packaging/deb/control.template b/packaging/deb/control.template index edeba95..2327e45 100644 --- a/packaging/deb/control.template +++ b/packaging/deb/control.template @@ -13,5 +13,5 @@ Description: Systems programming language compiler with Go simplicity and low-le collector or borrow checker, green threads for concurrency, and compiles to native code via C. . - Note: Zig (>= 0.15) is required at runtime as the compiler uses 'zig cc' + Note: Zig (>= 0.16) is required at runtime as the compiler uses 'zig cc' for C compilation and linking. Install Zig separately from https://ziglang.org. diff --git a/packaging/rpm/run.spec b/packaging/rpm/run.spec index e23d09d..fb15c88 100644 --- a/packaging/rpm/run.spec +++ b/packaging/rpm/run.spec @@ -7,7 +7,7 @@ License: MIT URL: https://runlang.dev Source0: https://github.com/marsolab/runlang/releases/download/v%{version}/run-%{version}-linux-%{_run_arch}.tar.gz -Requires: zig >= 0.15 +Requires: zig >= 0.16 # Disable debug package generation — we ship a pre-built binary. %global debug_package %{nil} @@ -22,7 +22,7 @@ It features generational references for memory safety without a garbage collector or borrow checker, green threads for concurrency, and compiles to native code via C. -The compiler requires Zig (>= 0.15) as it uses 'zig cc' for C compilation +The compiler requires Zig (>= 0.16) as it uses 'zig cc' for C compilation and linking. %prep diff --git a/packaging/snap/snapcraft.yaml b/packaging/snap/snapcraft.yaml index 374db3b..da237a0 100644 --- a/packaging/snap/snapcraft.yaml +++ b/packaging/snap/snapcraft.yaml @@ -8,7 +8,7 @@ description: | collector or borrow checker, green threads for concurrency, and compiles to native code via C. - The compiler requires Zig (>= 0.15) as it uses 'zig cc' for C compilation + The compiler requires Zig (>= 0.16) as it uses 'zig cc' for C compilation and linking. Make sure Zig is installed and available in your PATH. Homepage: https://runlang.dev diff --git a/scripts/create-dmg.sh b/scripts/create-dmg.sh index cd4b597..b71cf78 100755 --- a/scripts/create-dmg.sh +++ b/scripts/create-dmg.sh @@ -111,7 +111,7 @@ Quick install: sudo cp include/run/*.h /usr/local/include/run/ Prerequisites: - - Zig >= 0.15 must be installed (the compiler shells out to 'zig cc') + - Zig >= 0.16 must be installed (the compiler shells out to 'zig cc') More information: https://runlang.dev Source code: https://github.com/marsolab/runlang diff --git a/src/codegen_c.zig b/src/codegen_c.zig index 17380a9..d94320c 100644 --- a/src/codegen_c.zig +++ b/src/codegen_c.zig @@ -3,6 +3,14 @@ const ir = @import("ir.zig"); const diagnostics = @import("diagnostics.zig"); pub const CCodegen = struct { + const OutputWriter = struct { + codegen: *CCodegen, + + pub fn print(self: @This(), comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { + try self.codegen.output.print(self.codegen.allocator, fmt, args); + } + }; + output: std.ArrayList(u8), module: *const ir.Module, allocator: std.mem.Allocator, @@ -738,8 +746,12 @@ pub const CCodegen = struct { try self.output.append(self.allocator, '\n'); } - fn writer(self: *CCodegen) std.ArrayList(u8).Writer { - return self.output.writer(self.allocator); + fn print(self: *CCodegen, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { + try self.output.print(self.allocator, fmt, args); + } + + fn writer(self: *CCodegen) OutputWriter { + return .{ .codegen = self }; } }; diff --git a/src/dap.zig b/src/dap.zig index 6e88a84..65a7eff 100644 --- a/src/dap.zig +++ b/src/dap.zig @@ -4,7 +4,7 @@ const debug_engine = @import("debug_engine.zig"); const driver = @import("driver.zig"); const ir = @import("ir.zig"); -const File = std.fs.File; +const File = std.Io.File; /// Debug Adapter Protocol server for the Run language. /// @@ -20,16 +20,10 @@ pub const DapServer = struct { source_path: ?[]const u8, binary_path: ?[]const u8, - pub fn init(allocator: std.mem.Allocator) DapServer { - const stdin = File.stdin().deprecatedReader(); - const stdout = File.stdout().deprecatedWriter(); + pub fn init(allocator: std.mem.Allocator, reader: *std.Io.Reader, writer: *std.Io.Writer) DapServer { return .{ .allocator = allocator, - .transport = lsp.Transport.init( - allocator, - stdin.any(), - stdout.any(), - ), + .transport = lsp.Transport.init(allocator, reader, writer), .engine = null, .seq = 1, .initialized = false, @@ -114,14 +108,14 @@ pub const DapServer = struct { self.initialized = true; // Build capabilities response - var body = std.json.ObjectMap.init(self.allocator); - try body.put("supportsConfigurationDoneRequest", .{ .bool = true }); - try body.put("supportsEvaluateForHovers", .{ .bool = true }); - try body.put("supportsSetVariable", .{ .bool = false }); - try body.put("supportsConditionalBreakpoints", .{ .bool = true }); - try body.put("supportsHitConditionalBreakpoints", .{ .bool = true }); - try body.put("supportsFunctionBreakpoints", .{ .bool = true }); - try body.put("supportsStepBack", .{ .bool = false }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "supportsConfigurationDoneRequest", .{ .bool = true }); + try body.put(self.allocator, "supportsEvaluateForHovers", .{ .bool = true }); + try body.put(self.allocator, "supportsSetVariable", .{ .bool = false }); + try body.put(self.allocator, "supportsConditionalBreakpoints", .{ .bool = true }); + try body.put(self.allocator, "supportsHitConditionalBreakpoints", .{ .bool = true }); + try body.put(self.allocator, "supportsFunctionBreakpoints", .{ .bool = true }); + try body.put(self.allocator, "supportsStepBack", .{ .bool = false }); try self.sendResponse(request_seq, "initialize", true, .{ .object = body }); @@ -189,24 +183,24 @@ pub const DapServer = struct { if (self.engine) |*engine| { const result = engine.setBreakpoint(source_path, line, condition, hit_condition) catch { - var bp_obj = std.json.ObjectMap.init(self.allocator); - try bp_obj.put("verified", .{ .bool = false }); - try bp_obj.put("line", .{ .integer = @intCast(line) }); + var bp_obj = std.json.ObjectMap.empty; + try bp_obj.put(self.allocator, "verified", .{ .bool = false }); + try bp_obj.put(self.allocator, "line", .{ .integer = @intCast(line) }); try breakpoints.append(.{ .object = bp_obj }); continue; }; - var bp_obj = std.json.ObjectMap.init(self.allocator); - try bp_obj.put("id", .{ .integer = @intCast(result.id) }); - try bp_obj.put("verified", .{ .bool = result.verified }); - try bp_obj.put("line", .{ .integer = @intCast(result.line) }); + var bp_obj = std.json.ObjectMap.empty; + try bp_obj.put(self.allocator, "id", .{ .integer = @intCast(result.id) }); + try bp_obj.put(self.allocator, "verified", .{ .bool = result.verified }); + try bp_obj.put(self.allocator, "line", .{ .integer = @intCast(result.line) }); try breakpoints.append(.{ .object = bp_obj }); } } } } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("breakpoints", .{ .array = breakpoints }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "breakpoints", .{ .array = breakpoints }); try self.sendResponse(request_seq, "setBreakpoints", true, .{ .object = body }); } @@ -214,9 +208,9 @@ pub const DapServer = struct { var threads = std.json.Array.init(self.allocator); // Always report the main OS thread - var thread_obj = std.json.ObjectMap.init(self.allocator); - try thread_obj.put("id", .{ .integer = 1 }); - try thread_obj.put("name", .{ .string = "main" }); + var thread_obj = std.json.ObjectMap.empty; + try thread_obj.put(self.allocator, "id", .{ .integer = 1 }); + try thread_obj.put(self.allocator, "name", .{ .string = "main" }); try threads.append(.{ .object = thread_obj }); // Try to surface green threads from the runtime via run_debug_dump_goroutines() @@ -227,8 +221,8 @@ pub const DapServer = struct { _ = gt_result; } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("threads", .{ .array = threads }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "threads", .{ .array = threads }); try self.sendResponse(request_seq, "threads", true, .{ .object = body }); } @@ -239,25 +233,25 @@ pub const DapServer = struct { if (self.engine) |*engine| { const trace = engine.getStackTrace(1) catch &.{}; for (trace, 0..) |frame, i| { - var frame_obj = std.json.ObjectMap.init(self.allocator); - try frame_obj.put("id", .{ .integer = @intCast(i) }); - try frame_obj.put("name", .{ .string = frame.name }); - try frame_obj.put("line", .{ .integer = @intCast(frame.line) }); - try frame_obj.put("column", .{ .integer = @intCast(frame.column) }); + var frame_obj = std.json.ObjectMap.empty; + try frame_obj.put(self.allocator, "id", .{ .integer = @intCast(i) }); + try frame_obj.put(self.allocator, "name", .{ .string = frame.name }); + try frame_obj.put(self.allocator, "line", .{ .integer = @intCast(frame.line) }); + try frame_obj.put(self.allocator, "column", .{ .integer = @intCast(frame.column) }); if (frame.source_path.len > 0) { - var source_obj = std.json.ObjectMap.init(self.allocator); - try source_obj.put("path", .{ .string = frame.source_path }); - try frame_obj.put("source", .{ .object = source_obj }); + var source_obj = std.json.ObjectMap.empty; + try source_obj.put(self.allocator, "path", .{ .string = frame.source_path }); + try frame_obj.put(self.allocator, "source", .{ .object = source_obj }); } try frames.append(.{ .object = frame_obj }); } } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("stackFrames", .{ .array = frames }); - try body.put("totalFrames", .{ .integer = @intCast(frames.items.len) }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "stackFrames", .{ .array = frames }); + try body.put(self.allocator, "totalFrames", .{ .integer = @intCast(frames.items.len) }); try self.sendResponse(request_seq, "stackTrace", true, .{ .object = body }); } @@ -266,14 +260,14 @@ pub const DapServer = struct { var scopes = std.json.Array.init(self.allocator); // Local scope - var local_scope = std.json.ObjectMap.init(self.allocator); - try local_scope.put("name", .{ .string = "Locals" }); - try local_scope.put("variablesReference", .{ .integer = 1 }); - try local_scope.put("expensive", .{ .bool = false }); + var local_scope = std.json.ObjectMap.empty; + try local_scope.put(self.allocator, "name", .{ .string = "Locals" }); + try local_scope.put(self.allocator, "variablesReference", .{ .integer = 1 }); + try local_scope.put(self.allocator, "expensive", .{ .bool = false }); try scopes.append(.{ .object = local_scope }); - var body = std.json.ObjectMap.init(self.allocator); - try body.put("scopes", .{ .array = scopes }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "scopes", .{ .array = scopes }); try self.sendResponse(request_seq, "scopes", true, .{ .object = body }); } @@ -287,17 +281,17 @@ pub const DapServer = struct { // Filter out SSA temporaries if (debug_engine.isSsaTemporary(v.name)) continue; - var var_obj = std.json.ObjectMap.init(self.allocator); - try var_obj.put("name", .{ .string = v.name }); - try var_obj.put("value", .{ .string = v.value }); - try var_obj.put("type", .{ .string = debug_engine.runTypeName(v.type_name) }); - try var_obj.put("variablesReference", .{ .integer = 0 }); + var var_obj = std.json.ObjectMap.empty; + try var_obj.put(self.allocator, "name", .{ .string = v.name }); + try var_obj.put(self.allocator, "value", .{ .string = v.value }); + try var_obj.put(self.allocator, "type", .{ .string = debug_engine.runTypeName(v.type_name) }); + try var_obj.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = var_obj }); } } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("variables", .{ .array = variables }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "variables", .{ .array = variables }); try self.sendResponse(request_seq, "variables", true, .{ .object = body }); } @@ -351,10 +345,10 @@ pub const DapServer = struct { try self.sendErrorResponse(request_seq, "evaluate", "Evaluation failed"); return; }; - var body = std.json.ObjectMap.init(self.allocator); - try body.put("result", .{ .string = result.value }); - try body.put("type", .{ .string = debug_engine.runTypeName(result.type_name) }); - try body.put("variablesReference", .{ .integer = 0 }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "result", .{ .string = result.value }); + try body.put(self.allocator, "type", .{ .string = debug_engine.runTypeName(result.type_name) }); + try body.put(self.allocator, "variablesReference", .{ .integer = 0 }); try self.sendResponse(request_seq, "evaluate", true, .{ .object = body }); } else { try self.sendErrorResponse(request_seq, "evaluate", "No active debug session"); @@ -376,23 +370,23 @@ pub const DapServer = struct { } } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("results", .{ .array = results }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "results", .{ .array = results }); try self.sendResponse(request_seq, "runBatch", true, .{ .object = body }); } /// Dispatch a single command for batch execution, returning a result object. fn dispatchSingleCommand(self: *DapServer, command: []const u8, args: ?std.json.Value) std.json.Value { - var result = std.json.ObjectMap.init(self.allocator); - result.put("command", .{ .string = command }) catch {}; - result.put("success", .{ .bool = true }) catch {}; + var result = std.json.ObjectMap.empty; + result.put(self.allocator, "command", .{ .string = command }) catch {}; + result.put(self.allocator, "success", .{ .bool = true }) catch {}; // Dispatch to engine for supported commands if (self.engine) |*engine| { if (std.mem.eql(u8, command, "setBreakpoints")) { if (args) |a| { const source_obj = a.object.get("source") orelse { - result.put("success", .{ .bool = false }) catch {}; + result.put(self.allocator, "success", .{ .bool = false }) catch {}; return .{ .object = result }; }; const source_path = if (source_obj.object.get("path")) |p| p.string else ""; @@ -403,31 +397,31 @@ pub const DapServer = struct { const condition: ?[]const u8 = if (bp.object.get("condition")) |c| c.string else null; const hit_condition: ?[]const u8 = if (bp.object.get("hitCondition")) |h| h.string else null; const bp_result = engine.setBreakpoint(source_path, line, condition, hit_condition) catch { - var bp_obj = std.json.ObjectMap.init(self.allocator); - bp_obj.put("verified", .{ .bool = false }) catch {}; + var bp_obj = std.json.ObjectMap.empty; + bp_obj.put(self.allocator, "verified", .{ .bool = false }) catch {}; bp_results.append(.{ .object = bp_obj }) catch {}; continue; }; - var bp_obj = std.json.ObjectMap.init(self.allocator); - bp_obj.put("id", .{ .integer = @intCast(bp_result.id) }) catch {}; - bp_obj.put("verified", .{ .bool = bp_result.verified }) catch {}; - bp_obj.put("line", .{ .integer = @intCast(bp_result.line) }) catch {}; + var bp_obj = std.json.ObjectMap.empty; + bp_obj.put(self.allocator, "id", .{ .integer = @intCast(bp_result.id) }) catch {}; + bp_obj.put(self.allocator, "verified", .{ .bool = bp_result.verified }) catch {}; + bp_obj.put(self.allocator, "line", .{ .integer = @intCast(bp_result.line) }) catch {}; bp_results.append(.{ .object = bp_obj }) catch {}; } - var body = std.json.ObjectMap.init(self.allocator); - body.put("breakpoints", .{ .array = bp_results }) catch {}; - result.put("body", .{ .object = body }) catch {}; + var body = std.json.ObjectMap.empty; + body.put(self.allocator, "breakpoints", .{ .array = bp_results }) catch {}; + result.put(self.allocator, "body", .{ .object = body }) catch {}; } } } else if (std.mem.eql(u8, command, "continue")) { const stop = engine.continue_() catch { - result.put("success", .{ .bool = false }) catch {}; + result.put(self.allocator, "success", .{ .bool = false }) catch {}; return .{ .object = result }; }; self.sendStoppedEvent(stop) catch {}; } else if (std.mem.eql(u8, command, "next")) { const stop = engine.next() catch { - result.put("success", .{ .bool = false }) catch {}; + result.put(self.allocator, "success", .{ .bool = false }) catch {}; return .{ .object = result }; }; self.sendStoppedEvent(stop) catch {}; @@ -436,18 +430,18 @@ pub const DapServer = struct { if (a.object.get("expression")) |expr_val| { const translated = translateRunExpr(expr_val.string); const eval_result = engine.evaluate(translated) catch { - result.put("success", .{ .bool = false }) catch {}; + result.put(self.allocator, "success", .{ .bool = false }) catch {}; return .{ .object = result }; }; - var body = std.json.ObjectMap.init(self.allocator); - body.put("result", .{ .string = eval_result.value }) catch {}; - body.put("type", .{ .string = debug_engine.runTypeName(eval_result.type_name) }) catch {}; - result.put("body", .{ .object = body }) catch {}; + var body = std.json.ObjectMap.empty; + body.put(self.allocator, "result", .{ .string = eval_result.value }) catch {}; + body.put(self.allocator, "type", .{ .string = debug_engine.runTypeName(eval_result.type_name) }) catch {}; + result.put(self.allocator, "body", .{ .object = body }) catch {}; } } } } else { - result.put("success", .{ .bool = false }) catch {}; + result.put(self.allocator, "success", .{ .bool = false }) catch {}; } return .{ .object = result }; @@ -479,11 +473,11 @@ pub const DapServer = struct { try self.sendErrorResponse(request_seq, "run/inspectGenRef", "Failed to read ptr field"); return; }; - var ptr_var = std.json.ObjectMap.init(self.allocator); - try ptr_var.put("name", .{ .string = "ptr" }); - try ptr_var.put("value", .{ .string = ptr_result.value }); - try ptr_var.put("type", .{ .string = "ptr" }); - try ptr_var.put("variablesReference", .{ .integer = 0 }); + var ptr_var = std.json.ObjectMap.empty; + try ptr_var.put(self.allocator, "name", .{ .string = "ptr" }); + try ptr_var.put(self.allocator, "value", .{ .string = ptr_result.value }); + try ptr_var.put(self.allocator, "type", .{ .string = "ptr" }); + try ptr_var.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = ptr_var }); // Read the generation field @@ -496,11 +490,11 @@ pub const DapServer = struct { try self.sendErrorResponse(request_seq, "run/inspectGenRef", "Failed to read generation field"); return; }; - var gen_var = std.json.ObjectMap.init(self.allocator); - try gen_var.put("name", .{ .string = "generation" }); - try gen_var.put("value", .{ .string = gen_result.value }); - try gen_var.put("type", .{ .string = "int" }); - try gen_var.put("variablesReference", .{ .integer = 0 }); + var gen_var = std.json.ObjectMap.empty; + try gen_var.put(self.allocator, "name", .{ .string = "generation" }); + try gen_var.put(self.allocator, "value", .{ .string = gen_result.value }); + try gen_var.put(self.allocator, "type", .{ .string = "int" }); + try gen_var.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = gen_var }); // Check validity by calling run_gen_get @@ -511,20 +505,20 @@ pub const DapServer = struct { }, ) catch null; - var valid_var = std.json.ObjectMap.init(self.allocator); - try valid_var.put("name", .{ .string = "valid" }); + var valid_var = std.json.ObjectMap.empty; + try valid_var.put(self.allocator, "name", .{ .string = "valid" }); if (current_gen) |cg| { const is_valid = std.mem.eql(u8, cg.value, gen_result.value); - try valid_var.put("value", .{ .string = if (is_valid) "true" else "false" }); + try valid_var.put(self.allocator, "value", .{ .string = if (is_valid) "true" else "false" }); } else { - try valid_var.put("value", .{ .string = "unknown" }); + try valid_var.put(self.allocator, "value", .{ .string = "unknown" }); } - try valid_var.put("type", .{ .string = "bool" }); - try valid_var.put("variablesReference", .{ .integer = 0 }); + try valid_var.put(self.allocator, "type", .{ .string = "bool" }); + try valid_var.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = valid_var }); - var body = std.json.ObjectMap.init(self.allocator); - try body.put("variables", .{ .array = variables }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "variables", .{ .array = variables }); try self.sendResponse(request_seq, "run/inspectGenRef", true, .{ .object = body }); } @@ -556,16 +550,16 @@ pub const DapServer = struct { for (fields) |f| { const eval_expr = std.fmt.allocPrint(self.allocator, "({s})->{s}", .{ expr.?, f.field }) catch continue; const result = engine.evaluate(eval_expr) catch continue; - var var_obj = std.json.ObjectMap.init(self.allocator); - try var_obj.put("name", .{ .string = f.name }); - try var_obj.put("value", .{ .string = result.value }); - try var_obj.put("type", .{ .string = f.type_name }); - try var_obj.put("variablesReference", .{ .integer = 0 }); + var var_obj = std.json.ObjectMap.empty; + try var_obj.put(self.allocator, "name", .{ .string = f.name }); + try var_obj.put(self.allocator, "value", .{ .string = result.value }); + try var_obj.put(self.allocator, "type", .{ .string = f.type_name }); + try var_obj.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = var_obj }); } - var body = std.json.ObjectMap.init(self.allocator); - try body.put("variables", .{ .array = variables }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "variables", .{ .array = variables }); try self.sendResponse(request_seq, "run/inspectChannel", true, .{ .object = body }); } @@ -593,22 +587,22 @@ pub const DapServer = struct { try self.sendErrorResponse(request_seq, "run/inspectMap", "Failed to evaluate map length"); return; }; - var len_var = std.json.ObjectMap.init(self.allocator); - try len_var.put("name", .{ .string = "length" }); - try len_var.put("value", .{ .string = len_result.value }); - try len_var.put("type", .{ .string = "int" }); - try len_var.put("variablesReference", .{ .integer = 0 }); + var len_var = std.json.ObjectMap.empty; + try len_var.put(self.allocator, "name", .{ .string = "length" }); + try len_var.put(self.allocator, "value", .{ .string = len_result.value }); + try len_var.put(self.allocator, "type", .{ .string = "int" }); + try len_var.put(self.allocator, "variablesReference", .{ .integer = 0 }); try variables.append(.{ .object = len_var }); - var body = std.json.ObjectMap.init(self.allocator); - try body.put("variables", .{ .array = variables }); + var body = std.json.ObjectMap.empty; + try body.put(self.allocator, "variables", .{ .array = variables }); try self.sendResponse(request_seq, "run/inspectMap", true, .{ .object = body }); } // --- DAP Message Helpers --- fn sendStoppedEvent(self: *DapServer, stop: debug_engine.StopEvent) !void { - var body = std.json.ObjectMap.init(self.allocator); + var body = std.json.ObjectMap.empty; const reason_str: []const u8 = switch (stop.reason) { .breakpoint_hit => "breakpoint", .step => "step", @@ -618,67 +612,67 @@ pub const DapServer = struct { .exited => "exited", .unknown => "unknown", }; - try body.put("reason", .{ .string = reason_str }); - try body.put("threadId", .{ .integer = @intCast(stop.thread_id) }); - try body.put("allThreadsStopped", .{ .bool = true }); + try body.put(self.allocator, "reason", .{ .string = reason_str }); + try body.put(self.allocator, "threadId", .{ .integer = @intCast(stop.thread_id) }); + try body.put(self.allocator, "allThreadsStopped", .{ .bool = true }); try self.sendEvent("stopped", .{ .object = body }); } fn sendResponse(self: *DapServer, request_seq: u32, command: []const u8, success: bool, body: ?std.json.Value) !void { - var msg = std.json.ObjectMap.init(self.allocator); - try msg.put("seq", .{ .integer = @intCast(self.seq) }); + var msg = std.json.ObjectMap.empty; + try msg.put(self.allocator, "seq", .{ .integer = @intCast(self.seq) }); self.seq += 1; - try msg.put("type", .{ .string = "response" }); - try msg.put("request_seq", .{ .integer = @intCast(request_seq) }); - try msg.put("success", .{ .bool = success }); - try msg.put("command", .{ .string = command }); + try msg.put(self.allocator, "type", .{ .string = "response" }); + try msg.put(self.allocator, "request_seq", .{ .integer = @intCast(request_seq) }); + try msg.put(self.allocator, "success", .{ .bool = success }); + try msg.put(self.allocator, "command", .{ .string = command }); if (body) |b| { - try msg.put("body", b); + try msg.put(self.allocator, "body", b); } try self.writeJson(.{ .object = msg }); } fn sendErrorResponse(self: *DapServer, request_seq: u32, command: []const u8, message: []const u8) !void { - var body = std.json.ObjectMap.init(self.allocator); - var error_obj = std.json.ObjectMap.init(self.allocator); - try error_obj.put("id", .{ .integer = 1 }); - try error_obj.put("format", .{ .string = message }); - try body.put("error", .{ .object = error_obj }); - - var msg = std.json.ObjectMap.init(self.allocator); - try msg.put("seq", .{ .integer = @intCast(self.seq) }); + var body = std.json.ObjectMap.empty; + var error_obj = std.json.ObjectMap.empty; + try error_obj.put(self.allocator, "id", .{ .integer = 1 }); + try error_obj.put(self.allocator, "format", .{ .string = message }); + try body.put(self.allocator, "error", .{ .object = error_obj }); + + var msg = std.json.ObjectMap.empty; + try msg.put(self.allocator, "seq", .{ .integer = @intCast(self.seq) }); self.seq += 1; - try msg.put("type", .{ .string = "response" }); - try msg.put("request_seq", .{ .integer = @intCast(request_seq) }); - try msg.put("success", .{ .bool = false }); - try msg.put("command", .{ .string = command }); - try msg.put("message", .{ .string = message }); - try msg.put("body", .{ .object = body }); + try msg.put(self.allocator, "type", .{ .string = "response" }); + try msg.put(self.allocator, "request_seq", .{ .integer = @intCast(request_seq) }); + try msg.put(self.allocator, "success", .{ .bool = false }); + try msg.put(self.allocator, "command", .{ .string = command }); + try msg.put(self.allocator, "message", .{ .string = message }); + try msg.put(self.allocator, "body", .{ .object = body }); try self.writeJson(.{ .object = msg }); } fn sendEvent(self: *DapServer, event: []const u8, body: ?std.json.Value) !void { - var msg = std.json.ObjectMap.init(self.allocator); - try msg.put("seq", .{ .integer = @intCast(self.seq) }); + var msg = std.json.ObjectMap.empty; + try msg.put(self.allocator, "seq", .{ .integer = @intCast(self.seq) }); self.seq += 1; - try msg.put("type", .{ .string = "event" }); - try msg.put("event", .{ .string = event }); + try msg.put(self.allocator, "type", .{ .string = "event" }); + try msg.put(self.allocator, "event", .{ .string = event }); if (body) |b| { - try msg.put("body", b); + try msg.put(self.allocator, "body", b); } try self.writeJson(.{ .object = msg }); } fn writeJson(self: *DapServer, value: std.json.Value) !void { - var buf = std.ArrayList(u8).empty; - defer buf.deinit(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writeJsonValue(buf.writer(self.allocator), value); - try self.transport.writeMessage(buf.items); + try writeJsonValue(&writer.writer, value); + try self.transport.writeMessage(writer.writer.buffered()); } }; @@ -704,7 +698,7 @@ fn translateRunExpr(expr: []const u8) []const u8 { } /// Serialize a std.json.Value to a writer as JSON text. -fn writeJsonValue(writer: anytype, value: std.json.Value) @TypeOf(writer).Error!void { +fn writeJsonValue(writer: *std.Io.Writer, value: std.json.Value) std.Io.Writer.Error!void { switch (value) { .null => try writer.writeAll("null"), .bool => |b| try writer.writeAll(if (b) "true" else "false"), @@ -752,7 +746,17 @@ fn writeJsonValue(writer: anytype, value: std.json.Value) @TypeOf(writer).Error! /// Entry point: start the DAP server on stdin/stdout. pub fn serve(allocator: std.mem.Allocator) !void { - var server = DapServer.init(allocator); + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); + + var stdin_buffer: [4096]u8 = undefined; + var stdout_buffer: [4096]u8 = undefined; + var stdin_reader = File.stdin().readerStreaming(io, &stdin_buffer); + var stdout_writer = File.stdout().writerStreaming(io, &stdout_buffer); + defer stdout_writer.flush() catch {}; + + var server = DapServer.init(allocator, &stdin_reader.interface, &stdout_writer.interface); defer server.deinit(); try server.run(); } diff --git a/src/diagnostics.zig b/src/diagnostics.zig index b3ccc22..76493e1 100644 --- a/src/diagnostics.zig +++ b/src/diagnostics.zig @@ -580,28 +580,26 @@ test "DiagnosticList: render output format" { try diags.addError(4, 5, "undefined variable 'x'"); try diags.addWarning(14, 15, "unused variable 'y'"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.render(source, writer); + try diags.render(source, &writer.writer); const expected = "error[1:5]: undefined variable 'x'\n" ++ "warning[2:5]: unused variable 'y'\n"; - try std.testing.expectEqualStrings(expected, buf.items); + try std.testing.expectEqualStrings(expected, writer.writer.buffered()); } test "DiagnosticList: render empty list" { var diags = DiagnosticList.init(std.testing.allocator); defer diags.deinit(); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.render("", writer); - try std.testing.expectEqual(@as(usize, 0), buf.items.len); + try diags.render("", &writer.writer); + try std.testing.expectEqual(@as(usize, 0), writer.writer.buffered().len); } test "DiagnosticList: multiple errors and warnings" { @@ -665,11 +663,10 @@ test "renderRich: single error with source context" { const source = "var x i32 = \"hello\""; try diags.addError(12, 19, "type mismatch"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "test.run", writer, false); + try diags.renderRich(source, "test.run", &writer.writer, false); const expected = "error: type mismatch\n" ++ @@ -678,7 +675,7 @@ test "renderRich: single error with source context" { "1 | var x i32 = \"hello\"\n" ++ " | ^^^^^^^ type mismatch\n" ++ " |\n"; - try std.testing.expectEqualStrings(expected, buf.items); + try std.testing.expectEqualStrings(expected, writer.writer.buffered()); } test "renderRich: warning on second line" { @@ -688,11 +685,10 @@ test "renderRich: warning on second line" { const source = "var x int\nvar y int"; try diags.addWarning(14, 15, "unused variable 'y'"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "main.run", writer, false); + try diags.renderRich(source, "main.run", &writer.writer, false); const expected = "warning: unused variable 'y'\n" ++ @@ -701,7 +697,7 @@ test "renderRich: warning on second line" { "2 | var y int\n" ++ " | ^ unused variable 'y'\n" ++ " |\n"; - try std.testing.expectEqualStrings(expected, buf.items); + try std.testing.expectEqualStrings(expected, writer.writer.buffered()); } test "renderRich: multiple diagnostics" { @@ -712,15 +708,14 @@ test "renderRich: multiple diagnostics" { try diags.addError(4, 5, "error here"); try diags.addWarning(14, 15, "warning here"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "test.run", writer, false); + try diags.renderRich(source, "test.run", &writer.writer, false); // Just check that both are present - try std.testing.expect(std.mem.indexOf(u8, buf.items, "error: error here") != null); - try std.testing.expect(std.mem.indexOf(u8, buf.items, "warning: warning here") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "error: error here") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "warning: warning here") != null); } test "renderRich: error with label shows label on caret line" { @@ -730,16 +725,15 @@ test "renderRich: error with label shows label on caret line" { const source = "var x i32 = \"hello\""; try diags.addErrorWithLabel(12, 19, "type mismatch: expected 'i32', got 'string'", "expected 'i32'"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "test.run", writer, false); + try diags.renderRich(source, "test.run", &writer.writer, false); // Header should have full message - try std.testing.expect(std.mem.indexOf(u8, buf.items, "error: type mismatch: expected 'i32', got 'string'") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "error: type mismatch: expected 'i32', got 'string'") != null); // Caret line should have short label, not full message - try std.testing.expect(std.mem.indexOf(u8, buf.items, "^^^^^^^ expected 'i32'\n") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "^^^^^^^ expected 'i32'\n") != null); } test "renderRich: error with text-only annotation" { @@ -752,15 +746,14 @@ test "renderRich: error with text-only annotation" { }; try diags.addErrorAnnotated(8, 15, "undefined reference to 'pirntln'", "not found in this scope", &annotations); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "test.run", writer, false); + try diags.renderRich(source, "test.run", &writer.writer, false); - try std.testing.expect(std.mem.indexOf(u8, buf.items, "error: undefined reference to 'pirntln'") != null); - try std.testing.expect(std.mem.indexOf(u8, buf.items, "^^^^^^^ not found in this scope") != null); - try std.testing.expect(std.mem.indexOf(u8, buf.items, "= help: did you mean 'println'?") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "error: undefined reference to 'pirntln'") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "^^^^^^^ not found in this scope") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "= help: did you mean 'println'?") != null); } test "renderRich: error with note annotation" { @@ -773,13 +766,12 @@ test "renderRich: error with note annotation" { }; try diags.addErrorAnnotated(12, 19, "type mismatch", null, &annotations); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(std.testing.allocator); - const writer = buf.writer(std.testing.allocator); + var writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer writer.deinit(); - try diags.renderRich(source, "test.run", writer, false); + try diags.renderRich(source, "test.run", &writer.writer, false); - try std.testing.expect(std.mem.indexOf(u8, buf.items, "= note: string literals have type 'string'") != null); + try std.testing.expect(std.mem.indexOf(u8, writer.writer.buffered(), "= note: string literals have type 'string'") != null); } test "digitCount" { diff --git a/src/driver.zig b/src/driver.zig index b966a28..3dc7564 100644 --- a/src/driver.zig +++ b/src/driver.zig @@ -14,8 +14,9 @@ const const_fold = @import("const_fold.zig"); const codegen_c = @import("codegen_c.zig"); const rasm = @import("rasm.zig"); const diag_mod = @import("diagnostics.zig"); +const builtin = @import("builtin"); -const File = std.fs.File; +const File = std.Io.File; pub const Command = enum { build, @@ -57,10 +58,9 @@ pub const RuntimeConfig = union(enum) { /// Try to locate a pre-built librunrt.a relative to the running binary. /// Expected layout: /bin/run, /lib/librunrt.a, /include/run/*.h -fn findInstalledRuntime(allocator: std.mem.Allocator) ?struct { lib_dir: []const u8, include_dir: []const u8 } { - var buf: [std.fs.max_path_bytes]u8 = undefined; - const self_exe_path = std.fs.selfExePath(&buf) catch return null; - const bin_dir = std.fs.path.dirname(self_exe_path) orelse return null; +fn findInstalledRuntime(io: std.Io, allocator: std.mem.Allocator) ?struct { lib_dir: []const u8, include_dir: []const u8 } { + const bin_dir = std.process.executableDirPathAlloc(io, allocator) catch return null; + defer allocator.free(bin_dir); const prefix = std.fs.path.dirname(bin_dir) orelse return null; const lib_dir = std.fs.path.join(allocator, &.{ prefix, "lib" }) catch return null; @@ -71,11 +71,11 @@ fn findInstalledRuntime(allocator: std.mem.Allocator) ?struct { lib_dir: []const defer allocator.free(lib_file); // Verify librunrt.a exists at the expected location - const f = std.fs.cwd().openFile(lib_file, .{}) catch { + const f = std.Io.Dir.cwd().openFile(io, lib_file, .{}) catch { allocator.free(lib_dir); return null; }; - f.close(); + f.close(io); const include_dir = std.fs.path.join(allocator, &.{ prefix, "include", "run" }) catch { allocator.free(lib_dir); @@ -87,8 +87,21 @@ fn findInstalledRuntime(allocator: std.mem.Allocator) ?struct { lib_dir: []const /// Run the full compilation pipeline for the given source file. pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileError!void { + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); + + var stdout_buffer: [4096]u8 = undefined; + var stderr_buffer: [4096]u8 = undefined; + var stdout_writer = File.stdout().writerStreaming(io, &stdout_buffer); + var stderr_writer = File.stderr().writerStreaming(io, &stderr_buffer); + defer stdout_writer.flush() catch {}; + defer stderr_writer.flush() catch {}; + const stdout = &stdout_writer.interface; + const stderr = &stderr_writer.interface; + // 1. Read source file - const source = readFile(allocator, options.input_path) catch { + const source = readFile(io, allocator, options.input_path) catch { return CompileError.ReadFailed; }; defer allocator.free(source); @@ -108,8 +121,6 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr return CompileError.OutOfMemory; }; - const stderr = File.stderr().deprecatedWriter(); - if (parser.tree.errors.items.len > 0) { const use_color = !options.no_color; for (parser.tree.errors.items) |err| { @@ -310,7 +321,6 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr // For "check" command, stop after semantic analysis succeeds. if (options.command == .check) { - const stdout = File.stdout().deprecatedWriter(); stdout.print("check: {s} OK ({d} nodes)\n", .{ options.input_path, parser.tree.nodes.items.len, @@ -374,18 +384,18 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr }; // 9. Write C to a unique temp file and compile with zig cc - const tmp_c = createUniqueTempFile(allocator, "run_generated", ".c") catch { + const tmp_c = createUniqueTempFile(io, allocator, "run_generated", ".c") catch { stderr.writeAll("error: failed to create temp file\n") catch {}; return CompileError.CCompileFailed; }; const tmp_path = tmp_c.path; defer { - if (!options.debug) std.fs.deleteFileAbsolute(tmp_path) catch {}; + if (!options.debug) std.Io.Dir.deleteFileAbsolute(io, tmp_path) catch {}; allocator.free(tmp_path); } { - defer tmp_c.file.close(); - tmp_c.file.writeAll(c_source) catch { + defer tmp_c.file.close(io); + tmp_c.file.writeStreamingAll(io, c_source) catch { stderr.writeAll("error: failed to write temp file\n") catch {}; return CompileError.CCompileFailed; }; @@ -399,7 +409,7 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr }; // Find runtime: prefer pre-built library at install prefix, fall back to source compilation. - const runtime_config: RuntimeConfig = if (findInstalledRuntime(allocator)) |installed| + const runtime_config: RuntimeConfig = if (findInstalledRuntime(io, allocator)) |installed| .{ .installed = .{ .lib_dir = installed.lib_dir, .include_dir = installed.include_dir } } else .{ .source = "src/runtime" }; @@ -431,7 +441,7 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr } for (rasm_files.items) |rasm_path| { - const rasm_source = readFile(allocator, rasm_path) catch continue; + const rasm_source = readFile(io, allocator, rasm_path) catch continue; defer allocator.free(rasm_source); var parsed = rasm.parseRasmFile(allocator, rasm_source) catch continue; @@ -441,20 +451,20 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr defer allocator.free(gas_source); // Write each generated .S file to its own unique path to avoid races. - const asm_temp = createUniqueTempFile(allocator, std.fs.path.stem(rasm_path), ".S") catch continue; + const asm_temp = createUniqueTempFile(io, allocator, std.fs.path.stem(rasm_path), ".S") catch continue; const asm_path = asm_temp.path; - asm_temp.file.writeAll(gas_source) catch { - asm_temp.file.close(); - std.fs.deleteFileAbsolute(asm_path) catch {}; + asm_temp.file.writeStreamingAll(io, gas_source) catch { + asm_temp.file.close(io); + std.Io.Dir.deleteFileAbsolute(io, asm_path) catch {}; allocator.free(asm_path); continue; }; - asm_temp.file.close(); + asm_temp.file.close(io); rasm_asm_files.append(allocator, asm_path) catch continue; } - invokeZigCC(allocator, tmp_path, out_path, runtime_config, rasm_asm_files.items, options.debug) catch { + invokeZigCC(io, allocator, tmp_path, out_path, runtime_config, rasm_asm_files.items, options.debug) catch { stderr.writeAll("error: C compilation failed\n") catch {}; return CompileError.CCompileFailed; }; @@ -462,7 +472,7 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr // Clean up temp assembly files (keep in debug mode for source-level debugging) if (!options.debug) { for (rasm_asm_files.items) |asm_path| { - std.fs.deleteFileAbsolute(asm_path) catch {}; + std.Io.Dir.deleteFileAbsolute(io, asm_path) catch {}; } } @@ -472,7 +482,7 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr try std.fmt.allocPrint(allocator, "./{s}", .{out_path}) else out_path; - const exit_code = executeAndCleanup(allocator, exec_path) catch { + const exit_code = executeAndCleanup(io, allocator, exec_path) catch { stderr.writeAll("error: failed to execute compiled binary\n") catch {}; return CompileError.RunFailed; }; @@ -480,7 +490,6 @@ pub fn compile(allocator: std.mem.Allocator, options: CompileOptions) CompileErr std.process.exit(exit_code); } } else { - const stdout = File.stdout().deprecatedWriter(); stdout.print("compiled: {s}\n", .{out_path}) catch {}; } } @@ -662,6 +671,7 @@ fn findParamCount(start: u32, extra: []const u32) u32 { /// `output_path` is the desired binary output path. /// `runtime` controls how the runtime is linked: pre-built library or source compilation. pub fn invokeZigCC( + io: std.Io, allocator: std.mem.Allocator, c_source_path: []const u8, output_path: []const u8, @@ -673,7 +683,13 @@ pub fn invokeZigCC( var args: std.ArrayList([]const u8) = .empty; defer args.deinit(allocator); - try args.append(allocator, "zig"); + var env_map = try createProcessEnvMap(allocator); + defer env_map.deinit(); + + const zig_exe = try resolveZigExecutable(io, allocator, &env_map); + defer allocator.free(zig_exe); + + try args.append(allocator, zig_exe); try args.append(allocator, "cc"); try args.append(allocator, "-o"); try args.append(allocator, output_path); @@ -688,7 +704,7 @@ pub fn invokeZigCC( // Link the libxev bridge (provides run_xev_* symbols) const xev_lib_path = try std.fs.path.join(allocator, &.{ info.lib_dir, "librunxev.a" }); // Only link if the file exists (not present with legacy-poller builds) - if (std.fs.cwd().access(xev_lib_path, .{})) |_| { + if (std.Io.Dir.accessAbsolute(io, xev_lib_path, .{})) |_| { try args.append(allocator, xev_lib_path); } else |_| {} @@ -697,7 +713,10 @@ pub fn invokeZigCC( try args.append(allocator, include_flag); }, .source => |runtime_dir| { - // Compile individual runtime source files (development mode) + // Compile individual runtime source files in development mode. + // This path is exercised by unit/e2e test binaries running from + // .zig-cache, so keep it self-contained and avoid requiring the + // separately built libxev Zig bridge. const runtime_sources = [_][]const u8{ "run_main.c", "run_alloc.c", @@ -714,6 +733,7 @@ pub fn invokeZigCC( "run_signal.c", "run_runtime_api.c", "run_debug_api.c", + "run_poller_legacy.c", }; for (&runtime_sources) |name| { @@ -732,6 +752,16 @@ pub fn invokeZigCC( try args.append(allocator, asm_path); } + const preempt_asm_source: ?[]const u8 = switch (@import("builtin").cpu.arch) { + .x86_64 => "run_async_preempt_amd64.S", + .aarch64 => "run_async_preempt_arm64.S", + else => null, + }; + if (preempt_asm_source) |asm_name| { + const asm_path = try std.fs.path.join(allocator, &.{ runtime_dir, asm_name }); + try args.append(allocator, asm_path); + } + // Include path for runtime headers const include_flag = try std.fmt.allocPrint(allocator, "-I{s}", .{runtime_dir}); try args.append(allocator, include_flag); @@ -760,58 +790,129 @@ pub fn invokeZigCC( // Link pthread for scheduler try args.append(allocator, "-lpthread"); - var child = std.process.Child.init(args.items, allocator); - child.stderr_behavior = .Inherit; - child.stdout_behavior = .Inherit; - - const term = try child.spawnAndWait(); + var child = try std.process.spawn(io, .{ + .argv = args.items, + .environ_map = &env_map, + .expand_arg0 = .expand, + .stderr = .inherit, + .stdout = .inherit, + }); + const term = try child.wait(io); if (!childExitedSuccessfully(term)) { return error.CCompileFailed; } } +fn createProcessEnvMap(allocator: std.mem.Allocator) !std.process.Environ.Map { + if (builtin.os.tag == .windows) { + return try std.process.Environ.createMap(.{ .block = .global }, allocator); + } + + var env_map = std.process.Environ.Map.init(allocator); + errdefer env_map.deinit(); + if (builtin.link_libc) { + var env_len: usize = 0; + while (std.c.environ[env_len] != null) : (env_len += 1) {} + const env_entries = try allocator.alloc([*:0]const u8, env_len); + defer allocator.free(env_entries); + for (env_entries, 0..) |*entry, i| { + entry.* = std.c.environ[i].?; + } + try env_map.putPosixBlock(.{ .slice = env_entries }); + } + return env_map; +} + +fn resolveZigExecutable( + io: std.Io, + allocator: std.mem.Allocator, + env_map: *const std.process.Environ.Map, +) ![]u8 { + if (env_map.get("PATH")) |path| { + var it = std.mem.tokenizeScalar(u8, path, std.fs.path.delimiter); + const exe_name = if (builtin.os.tag == .windows) "zig.exe" else "zig"; + + while (it.next()) |dir| { + const candidate = try std.fs.path.join(allocator, &.{ dir, exe_name }); + errdefer allocator.free(candidate); + + if (std.fs.path.isAbsolute(candidate)) { + if (std.Io.Dir.accessAbsolute(io, candidate, .{})) |_| return candidate else |_| {} + } else { + if (std.Io.Dir.cwd().access(io, candidate, .{})) |_| return candidate else |_| {} + } + + allocator.free(candidate); + } + } + + const fallbacks: []const []const u8 = switch (builtin.os.tag) { + .windows => &.{ + "C:\\Program Files\\zig\\zig.exe", + "C:\\zig\\zig.exe", + }, + else => &.{ + "/opt/homebrew/bin/zig", + "/usr/local/bin/zig", + "/usr/bin/zig", + }, + }; + for (fallbacks) |candidate| { + if (std.Io.Dir.accessAbsolute(io, candidate, .{})) |_| { + return try allocator.dupe(u8, candidate); + } else |_| {} + } + + return error.FileNotFound; +} + /// Execute a compiled binary and return its exit code. pub fn executeAndCleanup( + io: std.Io, allocator: std.mem.Allocator, binary_path: []const u8, ) !u8 { - var child = std.process.Child.init(&.{binary_path}, allocator); - child.stderr_behavior = .Inherit; - child.stdout_behavior = .Inherit; + _ = allocator; - const result = try child.spawnAndWait(); + var child = try std.process.spawn(io, .{ + .argv = &.{binary_path}, + .stderr = .inherit, + .stdout = .inherit, + }); + const result = try child.wait(io); // Clean up binary after execution for "run" command - std.fs.cwd().deleteFile(binary_path) catch {}; + std.Io.Dir.cwd().deleteFile(io, binary_path) catch {}; return childExitCode(result); } -fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - return try file.readToEndAlloc(allocator, 10 * 1024 * 1024); +fn readFile(io: std.Io, allocator: std.mem.Allocator, path: []const u8) ![]const u8 { + return try std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(10 * 1024 * 1024)); } const TempFile = struct { - file: std.fs.File, + file: std.Io.File, path: []u8, }; fn createUniqueTempFile( + io: std.Io, allocator: std.mem.Allocator, prefix: []const u8, suffix: []const u8, ) !TempFile { - var tmp_dir = try std.fs.openDirAbsolute("/tmp", .{}); - defer tmp_dir.close(); + var tmp_dir = try std.Io.Dir.openDirAbsolute(io, "/tmp", .{}); + defer tmp_dir.close(io); while (true) { - const random_id = std.crypto.random.int(u64); + var random_bytes: [@sizeOf(u64)]u8 = undefined; + io.random(&random_bytes); + const random_id = std.mem.readInt(u64, &random_bytes, .little); const basename = try std.fmt.allocPrint(allocator, "run_{s}_{x}{s}", .{ prefix, random_id, suffix }); errdefer allocator.free(basename); - const file = tmp_dir.createFile(basename, .{ .exclusive = true }) catch |err| switch (err) { + const file = tmp_dir.createFile(io, basename, .{ .exclusive = true }) catch |err| switch (err) { error.PathAlreadyExists => { allocator.free(basename); continue; @@ -827,16 +928,16 @@ fn createUniqueTempFile( fn childExitedSuccessfully(term: std.process.Child.Term) bool { return switch (term) { - .Exited => |code| code == 0, + .exited => |code| code == 0, else => false, }; } fn childExitCode(term: std.process.Child.Term) u8 { return switch (term) { - .Exited => |code| code, - .Signal => |sig| if (sig + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(sig + 128)) else std.math.maxInt(u8), - .Stopped, .Unknown => 1, + .exited => |code| code, + .signal => |sig| if (@intFromEnum(sig) + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(@intFromEnum(sig) + 128)) else std.math.maxInt(u8), + .stopped, .unknown => 1, }; } @@ -845,7 +946,7 @@ test "check performs semantic analysis before succeeding" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - try tmp.dir.writeFile(.{ + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "bad_check.run", .data = \\package main @@ -856,7 +957,7 @@ test "check performs semantic analysis before succeeding" { , }); - const path = try tmp.dir.realpathAlloc(allocator, "bad_check.run"); + const path = try tmp.dir.realPathFileAlloc(std.testing.io, "bad_check.run", allocator); defer allocator.free(path); try std.testing.expectError(CompileError.ParseFailed, compile(allocator, .{ diff --git a/src/formatter.zig b/src/formatter.zig index 20ef34e..4a3daad 100644 --- a/src/formatter.zig +++ b/src/formatter.zig @@ -947,7 +947,7 @@ pub const Formatter = struct { fn formatTypeArray(self: *Formatter, node: Node) !void { try self.write("["); - try self.buf.writer(self.allocator).print("{d}", .{node.data.rhs}); + try self.buf.print(self.allocator, "{d}", .{node.data.rhs}); try self.write("]"); try self.formatNode(node.data.lhs); // element type } diff --git a/src/gdb_mi.zig b/src/gdb_mi.zig index 2d5952b..2682e94 100644 --- a/src/gdb_mi.zig +++ b/src/gdb_mi.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const File = std.Io.File; /// MI backend type — GDB or LLDB (both speak the MI protocol). pub const MiBackend = enum { @@ -18,12 +19,13 @@ pub const MiBackend = enum { /// - Log output: &"text" pub const GdbMi = struct { process: std.process.Child, - stdout_reader: std.io.AnyReader, - stdin_writer: std.io.AnyWriter, + io_threaded: std.Io.Threaded, + stdout_reader: File.Reader, + stdin_writer: File.Writer, allocator: std.mem.Allocator, next_token: u32, - read_buf: std.ArrayList(u8), - line_buf: [4096]u8, + stdout_buffer: [4096]u8, + stdin_buffer: [1024]u8, backend: MiBackend, pub fn init(allocator: std.mem.Allocator, binary_path: []const u8) !GdbMi { @@ -35,23 +37,31 @@ pub const GdbMi = struct { .gdb => &.{ "gdb", "--interpreter=mi3", "--quiet", binary_path }, .lldb => &.{ "lldb-mi", "--interpreter=mi2", binary_path }, }; - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - return .{ + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + errdefer io_threaded.deinit(); + const io = io_threaded.io(); + var child = try std.process.spawn(io, .{ + .argv = argv, + .expand_arg0 = .expand, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }); + + var result = GdbMi{ .process = child, - .stdout_reader = child.stdout.?.deprecatedReader().any(), - .stdin_writer = child.stdin.?.deprecatedWriter().any(), + .io_threaded = io_threaded, + .stdout_reader = undefined, + .stdin_writer = undefined, .allocator = allocator, .next_token = 1, - .read_buf = .empty, - .line_buf = undefined, + .stdout_buffer = undefined, + .stdin_buffer = undefined, .backend = backend, }; + result.stdout_reader = child.stdout.?.readerStreaming(io, &result.stdout_buffer); + result.stdin_writer = child.stdin.?.writerStreaming(io, &result.stdin_buffer); + return result; } /// Auto-detect the best available MI backend. @@ -65,20 +75,30 @@ pub const GdbMi = struct { } fn canRunCommand(allocator: std.mem.Allocator, argv: []const []const u8) bool { - var child = std.process.Child.init(argv, allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; - child.spawn() catch return false; - _ = child.wait() catch return false; - return true; + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); + var child = std.process.spawn(io, .{ + .argv = argv, + .expand_arg0 = .expand, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .ignore, + }) catch return false; + const term = child.wait(io) catch return false; + return switch (term) { + .exited => |code| code == 0, + else => false, + }; } pub fn deinit(self: *GdbMi) void { - self.read_buf.deinit(self.allocator); // Send quit command, ignore errors - self.stdin_writer.writeAll("-gdb-exit\n") catch {}; - _ = self.process.wait() catch {}; + const io = self.io_threaded.io(); + self.stdin_writer.interface.writeAll("-gdb-exit\n") catch {}; + self.stdin_writer.interface.flush() catch {}; + _ = self.process.wait(io) catch {}; + self.io_threaded.deinit(); } /// Send a GDB/MI command and wait for the result record. @@ -91,7 +111,8 @@ pub const GdbMi = struct { var cmd_buf: [4096]u8 = undefined; const cmd = std.fmt.bufPrint(&cmd_buf, "{d}{s}\n", .{ token, command }) catch return error.CommandTooLong; - try self.stdin_writer.writeAll(cmd); + try self.stdin_writer.interface.writeAll(cmd); + try self.stdin_writer.interface.flush(); // Read responses until we get a result record with our token while (true) { @@ -115,14 +136,15 @@ pub const GdbMi = struct { /// Send a raw command without token prefix. pub fn sendRaw(self: *GdbMi, command: []const u8) !void { - try self.stdin_writer.writeAll(command); - try self.stdin_writer.writeAll("\n"); + try self.stdin_writer.interface.writeAll(command); + try self.stdin_writer.interface.writeAll("\n"); + try self.stdin_writer.interface.flush(); } /// Read lines until we get a complete GDB/MI response. pub fn readResponse(self: *GdbMi) !MiResponse { while (true) { - const line = self.stdout_reader.readUntilDelimiter(&self.line_buf, '\n') catch |err| switch (err) { + const line = self.stdout_reader.interface.takeDelimiterExclusive('\n') catch |err| switch (err) { error.EndOfStream => return error.GdbExited, else => return error.ReadFailed, }; diff --git a/src/init.zig b/src/init.zig index 2e142db..688a619 100644 --- a/src/init.zig +++ b/src/init.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const File = std.fs.File; +const File = std.Io.File; +const Dir = std.Io.Dir; pub const InitOptions = struct { name: []const u8, @@ -15,19 +16,21 @@ pub const InitError = error{ }; /// Scaffold a new Run project with the standard project layout. -pub fn initProject(allocator: std.mem.Allocator, options: InitOptions) InitError!void { - const stderr = File.stderr().deprecatedWriter(); - const stdout = File.stdout().deprecatedWriter(); +pub fn initProject(io: std.Io, allocator: std.mem.Allocator, options: InitOptions) InitError!void { + var stderr_file = File.stderr().writer(io, &.{}); + const stderr = &stderr_file.interface; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; if (options.in_place) { // Initialize in current directory - scaffoldFiles(allocator, ".", options.name, options.force, stderr) catch { + scaffoldFiles(io, allocator, ".", options.name, options.force, stderr) catch { return InitError.CreateFailed; }; stdout.print("Initialized project '{s}' in current directory\n", .{options.name}) catch {}; } else { // Create new directory - std.fs.cwd().makeDir(options.name) catch |err| switch (err) { + Dir.cwd().createDir(io, options.name, .default_dir) catch |err| switch (err) { error.PathAlreadyExists => { if (!options.force) { stderr.print("error: directory '{s}' already exists (use --force to overwrite)\n", .{options.name}) catch {}; @@ -40,7 +43,7 @@ pub fn initProject(allocator: std.mem.Allocator, options: InitOptions) InitError }, }; - scaffoldFiles(allocator, options.name, options.name, options.force, stderr) catch { + scaffoldFiles(io, allocator, options.name, options.name, options.force, stderr) catch { return InitError.CreateFailed; }; stdout.print("Initialized project '{s}' in ./{s}/\n", .{ options.name, options.name }) catch {}; @@ -48,24 +51,29 @@ pub fn initProject(allocator: std.mem.Allocator, options: InitOptions) InitError } fn scaffoldFiles( + io: std.Io, allocator: std.mem.Allocator, base_dir: []const u8, project_name: []const u8, force: bool, stderr: anytype, ) !void { + var opened_dir: ?Dir = null; + defer if (opened_dir) |dir| dir.close(io); const dir = if (std.mem.eql(u8, base_dir, ".")) - std.fs.cwd() - else - std.fs.cwd().openDir(base_dir, .{}) catch { + Dir.cwd() + else blk: { + opened_dir = Dir.cwd().openDir(io, base_dir, .{}) catch { stderr.print("error: failed to open directory '{s}'\n", .{base_dir}) catch {}; return error.CreateFailed; }; + break :blk opened_dir.?; + }; // Create subdirectories const dirs = [_][]const u8{ "cmd", "pkg", "lib" }; for (dirs) |d| { - dir.makeDir(d) catch |err| switch (err) { + dir.createDir(io, d, .default_dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => { stderr.print("error: failed to create directory '{s}'\n", .{d}) catch {}; @@ -77,37 +85,37 @@ fn scaffoldFiles( // Generate main.run const main_content = generateMainFile(allocator, project_name) catch return error.OutOfMemory; defer allocator.free(main_content); - writeFileIfNotExists(dir, "cmd/main.run", main_content, force, stderr); + writeFileIfNotExists(io, dir, "cmd/main.run", main_content, force, stderr); // Generate .gitignore - writeFileIfNotExists(dir, ".gitignore", gitignore_content, force, stderr); + writeFileIfNotExists(io, dir, ".gitignore", gitignore_content, force, stderr); // Generate run.toml (project config) const run_toml = generateRunToml(allocator, project_name) catch return error.OutOfMemory; defer allocator.free(run_toml); - writeFileIfNotExists(dir, "run.toml", run_toml, force, stderr); + writeFileIfNotExists(io, dir, "run.toml", run_toml, force, stderr); // Generate README.md const readme = generateReadme(allocator, project_name) catch return error.OutOfMemory; defer allocator.free(readme); - writeFileIfNotExists(dir, "README.md", readme, force, stderr); + writeFileIfNotExists(io, dir, "README.md", readme, force, stderr); } -fn writeFileIfNotExists(dir: std.fs.Dir, path: []const u8, content: []const u8, force: bool, stderr: anytype) void { +fn writeFileIfNotExists(io: std.Io, dir: Dir, path: []const u8, content: []const u8, force: bool, stderr: anytype) void { if (!force) { // Check if file exists - if (dir.statFile(path)) |_| { + if (dir.access(io, path, .{})) |_| { stderr.print("warning: '{s}' already exists, skipping (use --force to overwrite)\n", .{path}) catch {}; return; } else |_| {} } - const file = dir.createFile(path, .{}) catch { + const file = dir.createFile(io, path, .{}) catch { stderr.print("error: failed to create '{s}'\n", .{path}) catch {}; return; }; - defer file.close(); - file.writeAll(content) catch { + defer file.close(io); + file.writeStreamingAll(io, content) catch { stderr.print("error: failed to write '{s}'\n", .{path}) catch {}; }; } @@ -199,24 +207,18 @@ test "generateReadme: contains project name" { } test "initProject: creates directory structure" { - const allocator = std.testing.allocator; + const io = std.testing.io; // Use a temp directory for testing var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - // We need to test with the filesystem, so create in tmp - const tmp_path = tmp.dir.realpathAlloc(allocator, ".") catch return; - defer allocator.free(tmp_path); - - // Create the project dir manually inside tmp - tmp.dir.makeDir("testproj") catch {}; + try tmp.dir.createDir(io, "testproj", .default_dir); + var proj_dir = try tmp.dir.openDir(io, "testproj", .{}); + defer proj_dir.close(io); - const proj_dir = tmp.dir.openDir("testproj", .{}) catch return; - _ = proj_dir; + var stderr_file = File.stderr().writer(io, &.{}); + writeFileIfNotExists(io, proj_dir, "README.md", "hello\n", false, &stderr_file.interface); - // Just verify the helper functions work — full init requires cwd manipulation - const main_content = try generateMainFile(allocator, "testproj"); - defer allocator.free(main_content); - try std.testing.expect(main_content.len > 0); + try proj_dir.access(io, "README.md", .{}); } diff --git a/src/lower.zig b/src/lower.zig index d73c7a6..c3b95cc 100644 --- a/src/lower.zig +++ b/src/lower.zig @@ -662,11 +662,13 @@ const LoweringContext = struct { const else_node = extra[node.data.rhs + 1]; const func = self.current_func orelse return; + const current_bb = self.currentBlockId() orelse return; if (else_node == null_node) { const then_bb = try func.addBlock(self.allocator); const after_bb = try func.addBlock(self.allocator); + self.current_block = func.getBlock(current_bb); try self.emit(ir.makeInst(.br_cond, 0, cond_ref, then_bb)); try self.emit(ir.makeInst(.br, 0, after_bb, 0)); @@ -684,6 +686,7 @@ const LoweringContext = struct { const else_bb = try func.addBlock(self.allocator); const after_bb = try func.addBlock(self.allocator); + self.current_block = func.getBlock(current_bb); try self.emit(ir.makeInst(.br_cond, 0, cond_ref, then_bb)); try self.emit(ir.makeInst(.br, 0, else_bb, 0)); @@ -715,6 +718,7 @@ const LoweringContext = struct { fn lowerForStmt(self: *LoweringContext, node_idx: NodeIndex) LowerError!void { const node = self.tree.nodes.items[node_idx]; const func = self.current_func orelse return; + const current_bb = self.currentBlockId() orelse return; const cond_node = node.data.lhs; const body_node = node.data.rhs; @@ -723,6 +727,7 @@ const LoweringContext = struct { const body_bb = try func.addBlock(self.allocator); const after_bb = try func.addBlock(self.allocator); + self.current_block = func.getBlock(current_bb); try self.emit(ir.makeInst(.br, 0, cond_bb, 0)); self.current_block = func.getBlock(cond_bb); @@ -1078,6 +1083,7 @@ const LoweringContext = struct { const else_node = extra[node.data.rhs + 1]; const func = self.current_func orelse return ir.null_ref; + const current_bb = self.currentBlockId() orelse return ir.null_ref; const result_type = self.typeOfNode(node_idx); const result_c_type = if (result_type == types.null_type) "int64_t" else self.cTypeForTypeId(result_type); const result_alignment = self.alignmentForTypeId(result_type); @@ -1091,6 +1097,7 @@ const LoweringContext = struct { const else_bb = try func.addBlock(self.allocator); const after_bb = try func.addBlock(self.allocator); + self.current_block = func.getBlock(current_bb); try self.emit(ir.makeInst(.br_cond, 0, cond_ref, then_bb)); try self.emit(ir.makeInst(.br, 0, else_bb, 0)); @@ -1973,6 +1980,16 @@ const LoweringContext = struct { return ir.null_ref; } + fn currentBlockId(self: *const LoweringContext) ?ir.BlockId { + const func = self.current_func orelse return null; + const block = self.current_block orelse return null; + + for (func.blocks.items, 0..) |*candidate, idx| { + if (candidate == block) return @intCast(idx); + } + return null; + } + fn tokenSlice(self: *const LoweringContext, tok_index: u32) []const u8 { const tok = self.tokens[tok_index]; return self.tree.source[tok.loc.start..tok.loc.end]; diff --git a/src/lsp.zig b/src/lsp.zig index 755ad71..7abd366 100644 --- a/src/lsp.zig +++ b/src/lsp.zig @@ -7,16 +7,16 @@ const Node = @import("ast.zig").Node; const NodeIndex = @import("ast.zig").NodeIndex; const null_node = @import("ast.zig").null_node; -const File = std.fs.File; +const File = std.Io.File; /// JSON-RPC message header/body reader over stdin/stdout. pub const Transport = struct { - reader: std.io.AnyReader, - writer: std.io.AnyWriter, + reader: *std.Io.Reader, + writer: *std.Io.Writer, read_buf: std.ArrayList(u8), allocator: std.mem.Allocator, - pub fn init(allocator: std.mem.Allocator, reader: std.io.AnyReader, writer: std.io.AnyWriter) Transport { + pub fn init(allocator: std.mem.Allocator, reader: *std.Io.Reader, writer: *std.Io.Writer) Transport { return .{ .reader = reader, .writer = writer, @@ -34,10 +34,9 @@ pub const Transport = struct { pub fn readMessage(self: *Transport) !std.json.Parsed(std.json.Value) { // Read headers until empty line var content_length: ?usize = null; - var header_buf: [1024]u8 = undefined; while (true) { - const line = self.reader.readUntilDelimiter(&header_buf, '\n') catch |err| switch (err) { + const line = self.reader.takeDelimiterExclusive('\n') catch |err| switch (err) { error.EndOfStream => return error.EndOfStream, else => return error.InvalidMessage, }; @@ -58,7 +57,7 @@ pub const Transport = struct { // Read body self.read_buf.clearRetainingCapacity(); try self.read_buf.resize(self.allocator, length); - self.reader.readNoEof(self.read_buf.items) catch return error.InvalidMessage; + self.reader.readSliceAll(self.read_buf.items) catch return error.InvalidMessage; // Parse JSON return std.json.parseFromSlice(std.json.Value, self.allocator, self.read_buf.items, .{ @@ -131,7 +130,7 @@ pub const Server = struct { initialized: bool, shutdown_requested: bool, - pub fn init(allocator: std.mem.Allocator, reader: std.io.AnyReader, writer: std.io.AnyWriter) Server { + pub fn init(allocator: std.mem.Allocator, reader: *std.Io.Reader, writer: *std.Io.Writer) Server { return .{ .allocator = allocator, .transport = Transport.init(allocator, reader, writer), @@ -298,18 +297,17 @@ pub const Server = struct { const hover_text = self.getHoverInfo(doc, tokens, tok_idx) orelse return try self.sendResult(id, .null); // Build hover response - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); const start_loc = offsetToLineCol(doc.source, tok.loc.start); const end_loc = offsetToLineCol(doc.source, tok.loc.end); - try writer.print( + try writer.writer.print( \\{{"contents":{{"kind":"markdown","value":"{s}"}},"range":{{"start":{{"line":{d},"character":{d}}},"end":{{"line":{d},"character":{d}}}}}}} , .{ hover_text, start_loc.line, start_loc.col, end_loc.line, end_loc.col }); - try self.sendResultRaw(id, buf.items); + try self.sendResultRaw(id, writer.writer.buffered()); } fn handleDefinition(self: *Server, id: ?std.json.Value, params: ?std.json.Value) !void { @@ -340,15 +338,14 @@ pub const Server = struct { const start_loc = offsetToLineCol(doc.source, def_loc.start); const end_loc = offsetToLineCol(doc.source, def_loc.end); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.print( + try writer.writer.print( \\{{"uri":"{s}","range":{{"start":{{"line":{d},"character":{d}}},"end":{{"line":{d},"character":{d}}}}}}} , .{ uri, start_loc.line, start_loc.col, end_loc.line, end_loc.col }); - try self.sendResultRaw(id, buf.items); + try self.sendResultRaw(id, writer.writer.buffered()); } fn handleCompletion(self: *Server, id: ?std.json.Value, params: ?std.json.Value) !void { @@ -362,29 +359,27 @@ pub const Server = struct { const tokens = if (doc.tokens) |t| t.items else return try self.sendResultRaw(id, "[]"); const tree = if (doc.tree) |t| t else return try self.sendResultRaw(id, "[]"); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.writeAll("["); + try writer.writer.writeAll("["); var first = true; // Add keywords const kw_list = [_][]const u8{ - "fn", "pub", "var", "let", "return", - "if", "else", "for", "in", "switch", - "break", "continue", "defer", "run", "try", - "package", "use", "struct", "interface", - "implements", "type", "chan", "map", "alloc", - "true", "false", "null", "and", "or", - "not", + "fn", "pub", "var", "let", "return", + "if", "else", "for", "in", "switch", + "break", "continue", "defer", "run", "try", + "package", "use", "struct", "interface", "implements", + "type", "chan", "map", "alloc", "true", + "false", "null", "and", "or", "not", }; for (kw_list) |kw| { - if (!first) try writer.writeAll(","); + if (!first) try writer.writer.writeAll(","); first = false; - try writer.print( + try writer.writer.print( \\{{"label":"{s}","kind":14}} , .{kw}); } @@ -435,7 +430,7 @@ pub const Server = struct { const name = tokens[nt].slice(doc.source); if (!seen.contains(name)) { try seen.put(name, {}); - if (!first) try writer.writeAll(","); + if (!first) try writer.writer.writeAll(","); first = false; const kind: u8 = switch (node.tag) { @@ -445,25 +440,24 @@ pub const Server = struct { .type_alias, .type_decl => 22, // Struct (used for types) else => 1, // Text }; - try writer.print( + try writer.writer.print( \\{{"label":"{s}","kind":{d}}} , .{ name, kind }); } } } - try writer.writeAll("]"); - try self.sendResultRaw(id, buf.items); + try writer.writer.writeAll("]"); + try self.sendResultRaw(id, writer.writer.buffered()); } fn publishDiagnostics(self: *Server, uri: []const u8, doc: *Document) !void { doc.ensureParsed(); - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.writeAll("["); + try writer.writer.writeAll("["); const tree = doc.tree orelse { try self.publishDiagnosticsRaw(uri, "[]"); @@ -472,7 +466,7 @@ pub const Server = struct { var first = true; for (tree.errors.items) |err| { - if (!first) try writer.writeAll(","); + if (!first) try writer.writer.writeAll(","); first = false; const start_loc = offsetToLineCol(doc.source, err.loc.start); @@ -481,25 +475,24 @@ pub const Server = struct { // Escape the tag name for JSON const msg = @tagName(err.tag); - try writer.print( + try writer.writer.print( \\{{"range":{{"start":{{"line":{d},"character":{d}}},"end":{{"line":{d},"character":{d}}}}},"severity":1,"source":"run","message":"{s}"}} , .{ start_loc.line, start_loc.col, end_loc.line, end_loc.col, msg }); } - try writer.writeAll("]"); - try self.publishDiagnosticsRaw(uri, buf.items); + try writer.writer.writeAll("]"); + try self.publishDiagnosticsRaw(uri, writer.writer.buffered()); } fn publishDiagnosticsRaw(self: *Server, uri: []const u8, diagnostics_json: []const u8) !void { - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.print( + try writer.writer.print( \\{{"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{{"uri":"{s}","diagnostics":{s}}}}} , .{ uri, diagnostics_json }); - try self.transport.writeMessage(buf.items); + try self.transport.writeMessage(writer.writer.buffered()); } fn getHoverInfo(self: *Server, doc: *Document, tokens: []const Token, tok_idx: usize) ?[]const u8 { @@ -595,43 +588,41 @@ pub const Server = struct { } fn sendResultRaw(self: *Server, id: ?std.json.Value, result_json: []const u8) !void { - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.writeAll("{\"jsonrpc\":\"2.0\",\"id\":"); + try writer.writer.writeAll("{\"jsonrpc\":\"2.0\",\"id\":"); if (id) |id_val| { switch (id_val) { - .integer => |n| try writer.print("{d}", .{n}), - .string => |s| try writer.print("\"{s}\"", .{s}), - else => try writer.writeAll("null"), + .integer => |n| try writer.writer.print("{d}", .{n}), + .string => |s| try writer.writer.print("\"{s}\"", .{s}), + else => try writer.writer.writeAll("null"), } } else { - try writer.writeAll("null"); + try writer.writer.writeAll("null"); } - try writer.print(",\"result\":{s}}}", .{result_json}); + try writer.writer.print(",\"result\":{s}}}", .{result_json}); - try self.transport.writeMessage(buf.items); + try self.transport.writeMessage(writer.writer.buffered()); } fn sendError(self: *Server, id: ?std.json.Value, code: i32, message: []const u8) !void { - var buf: std.ArrayList(u8) = .empty; - defer buf.deinit(self.allocator); - const writer = buf.writer(self.allocator); + var writer: std.Io.Writer.Allocating = .init(self.allocator); + defer writer.deinit(); - try writer.writeAll("{\"jsonrpc\":\"2.0\",\"id\":"); + try writer.writer.writeAll("{\"jsonrpc\":\"2.0\",\"id\":"); if (id) |id_val| { switch (id_val) { - .integer => |n| try writer.print("{d}", .{n}), - .string => |s| try writer.print("\"{s}\"", .{s}), - else => try writer.writeAll("null"), + .integer => |n| try writer.writer.print("{d}", .{n}), + .string => |s| try writer.writer.print("\"{s}\"", .{s}), + else => try writer.writer.writeAll("null"), } } else { - try writer.writeAll("null"); + try writer.writer.writeAll("null"); } - try writer.print(",\"error\":{{\"code\":{d},\"message\":\"{s}\"}}}}", .{ code, message }); + try writer.writer.print(",\"error\":{{\"code\":{d},\"message\":\"{s}\"}}}}", .{ code, message }); - try self.transport.writeMessage(buf.items); + try self.transport.writeMessage(writer.writer.buffered()); } }; @@ -855,10 +846,17 @@ fn keywordDescription(tag: Token.Tag) []const u8 { /// Entry point: run the LSP server on stdin/stdout. pub fn serve(allocator: std.mem.Allocator) !void { - const stdin = File.stdin().deprecatedReader(); - const stdout = File.stdout().deprecatedWriter(); + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); - var server = Server.init(allocator, stdin.any(), stdout.any()); + var stdin_buffer: [4096]u8 = undefined; + var stdout_buffer: [4096]u8 = undefined; + var stdin_reader = File.stdin().readerStreaming(io, &stdin_buffer); + var stdout_writer = File.stdout().writerStreaming(io, &stdout_buffer); + defer stdout_writer.flush() catch {}; + + var server = Server.init(allocator, &stdin_reader.interface, &stdout_writer.interface); defer server.deinit(); try server.run(); @@ -914,21 +912,20 @@ test "keywordDescription: returns descriptions" { } test "Transport: writeMessage format" { - var out_buf: std.ArrayList(u8) = .empty; - defer out_buf.deinit(std.testing.allocator); - var empty_buf: [0]u8 = .{}; - var empty_stream = std.io.fixedBufferStream(&empty_buf); + var empty_reader = std.Io.Reader.fixed(&empty_buf); + var out_writer: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer out_writer.deinit(); var transport = Transport.init( std.testing.allocator, - empty_stream.reader().any(), - out_buf.writer(std.testing.allocator).any(), + &empty_reader, + &out_writer.writer, ); defer transport.deinit(); try transport.writeMessage("{\"test\":true}"); const expected = "Content-Length: 13\r\n\r\n{\"test\":true}"; - try std.testing.expectEqualStrings(expected, out_buf.items); + try std.testing.expectEqualStrings(expected, out_writer.writer.buffered()); } diff --git a/src/main.zig b/src/main.zig index 6abcca9..8793b9a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,9 +1,7 @@ const std = @import("std"); const build_options = @import("build_options"); -const Token = @import("token.zig").Token; const Lexer = @import("lexer.zig").Lexer; const Parser = @import("parser.zig").Parser; -const naming = @import("naming.zig"); const driver = @import("driver.zig"); const lsp = @import("lsp.zig"); const dap = @import("dap.zig"); @@ -37,45 +35,61 @@ const usage = \\ ; -const File = std.fs.File; +const File = std.Io.File; +const Dir = std.Io.Dir; -pub fn main() !void { - const allocator = std.heap.page_allocator; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; + var arg_iter = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer arg_iter.deinit(); + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; + var stderr_file = File.stderr().writer(io, &.{}); + const stderr = &stderr_file.interface; - if (args.len < 2) { - try File.stderr().writeAll(usage); + var args: std.ArrayList([]const u8) = .empty; + defer { + for (args.items) |arg| allocator.free(arg); + args.deinit(allocator); + } + + while (arg_iter.next()) |arg| { + try args.append(allocator, try allocator.dupe(u8, arg)); + } + + if (args.items.len < 2) { + try stderr.writeAll(usage); std.process.exit(1); } - const command = args[1]; + const command = args.items[1]; if (std.mem.eql(u8, command, "-h") or std.mem.eql(u8, command, "--help")) { - try File.stdout().writeAll(usage); + try stdout.writeAll(usage); return; } if (std.mem.eql(u8, command, "--version") or std.mem.eql(u8, command, "-V")) { - try File.stdout().writeAll("run " ++ build_options.version ++ "\n"); + try stdout.writeAll("run " ++ build_options.version ++ "\n"); return; } if (std.mem.eql(u8, command, "tokens")) { - if (args.len < 3) { - try File.stderr().writeAll("Error: no input file\n"); + if (args.items.len < 3) { + try stderr.writeAll("Error: no input file\n"); std.process.exit(1); } - try cmdTokens(allocator, args[2]); + try cmdTokens(io, allocator, args.items[2]); return; } if (std.mem.eql(u8, command, "ast")) { - if (args.len < 3) { - try File.stderr().writeAll("Error: no input file\n"); + if (args.items.len < 3) { + try stderr.writeAll("Error: no input file\n"); std.process.exit(1); } - try cmdAst(allocator, args[2]); + try cmdAst(io, allocator, args.items[2]); return; } @@ -90,48 +104,47 @@ pub fn main() !void { } if (std.mem.eql(u8, command, "init")) { - cmdInit(allocator, args[2..]); + cmdInit(io, allocator, args.items[2..]); return; } if (std.mem.eql(u8, command, "fmt")) { - try cmdFmt(allocator, args[2..]); + try cmdFmt(io, allocator, args.items[2..]); return; } if (std.mem.eql(u8, command, "test")) { - try cmdTest(allocator, args[2..]); + try cmdTest(io, allocator, args.items[2..]); return; } if (std.mem.eql(u8, command, "build") or std.mem.eql(u8, command, "check") or std.mem.eql(u8, command, "run")) { - try cmdBuild(allocator, args[2..], command); + try cmdBuild(io, allocator, args.items[2..], command); return; } // If the first arg is a .run file, treat it as `run ` if (std.mem.endsWith(u8, command, ".run")) { - try cmdBuild(allocator, args[1..], "run"); + try cmdBuild(io, allocator, args.items[1..], "run"); return; } - try File.stderr().deprecatedWriter().print("Unknown command: {s}\n", .{command}); - try File.stderr().writeAll(usage); + try stderr.print("Unknown command: {s}\n", .{command}); + try stderr.writeAll(usage); std.process.exit(1); } -fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - return try file.readToEndAlloc(allocator, 10 * 1024 * 1024); // 10MB max +fn readFile(io: std.Io, allocator: std.mem.Allocator, path: []const u8) ![]const u8 { + return Dir.cwd().readFileAlloc(io, path, allocator, .limited(10 * 1024 * 1024)); } -fn cmdTokens(allocator: std.mem.Allocator, path: []const u8) !void { - const source = try readFile(allocator, path); +fn cmdTokens(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !void { + const source = try readFile(io, allocator, path); defer allocator.free(source); var lexer = Lexer.init(source); - const stdout = File.stdout().deprecatedWriter(); + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; while (true) { const tok = lexer.next(); @@ -141,8 +154,8 @@ fn cmdTokens(allocator: std.mem.Allocator, path: []const u8) !void { } } -fn cmdAst(allocator: std.mem.Allocator, path: []const u8) !void { - const source = try readFile(allocator, path); +fn cmdAst(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !void { + const source = try readFile(io, allocator, path); defer allocator.free(source); var lexer = Lexer.init(source); @@ -154,10 +167,12 @@ fn cmdAst(allocator: std.mem.Allocator, path: []const u8) !void { _ = try parser.parseFile(); - const stdout = File.stdout().deprecatedWriter(); + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; if (parser.tree.errors.items.len > 0) { - const stderr = File.stderr().deprecatedWriter(); + var stderr_file = File.stderr().writer(io, &.{}); + const stderr = &stderr_file.interface; for (parser.tree.errors.items) |err| { try stderr.print("error: {s} at offset {d}\n", .{ @tagName(err.tag), err.loc.start }); } @@ -180,7 +195,7 @@ fn cmdAst(allocator: std.mem.Allocator, path: []const u8) !void { } } -fn cmdBuild(allocator: std.mem.Allocator, remaining_args: []const []const u8, command: []const u8) !void { +fn cmdBuild(io: std.Io, allocator: std.mem.Allocator, remaining_args: []const []const u8, command: []const u8) !void { var input_path: ?[]const u8 = null; var output_path: ?[]const u8 = null; var no_dce = false; @@ -204,7 +219,8 @@ fn cmdBuild(allocator: std.mem.Allocator, remaining_args: []const []const u8, co } if (input_path == null) { - try File.stderr().writeAll("Error: no input file\n"); + var stderr_file = File.stderr().writer(io, &.{}); + try stderr_file.interface.writeAll("Error: no input file\n"); std.process.exit(1); } @@ -226,13 +242,14 @@ fn cmdBuild(allocator: std.mem.Allocator, remaining_args: []const []const u8, co error.ParseFailed, error.NamingFailed => std.process.exit(1), error.CodegenNotImplemented => return, else => { - File.stderr().deprecatedWriter().print("error: {s}\n", .{@errorName(err)}) catch {}; + var stderr_file = File.stderr().writer(io, &.{}); + stderr_file.interface.print("error: {s}\n", .{@errorName(err)}) catch {}; std.process.exit(1); }, }; } -fn cmdInit(allocator: std.mem.Allocator, remaining_args: []const []const u8) void { +fn cmdInit(io: std.Io, allocator: std.mem.Allocator, remaining_args: []const []const u8) void { var project_name: ?[]const u8 = null; var force = false; @@ -245,23 +262,27 @@ fn cmdInit(allocator: std.mem.Allocator, remaining_args: []const []const u8) voi } if (project_name == null) { - File.stderr().writeAll("Error: missing project name\nUsage: run init [--force]\n run init . [--force]\n") catch {}; + var stderr_file = File.stderr().writer(io, &.{}); + stderr_file.interface.writeAll("Error: missing project name\nUsage: run init [--force]\n run init . [--force]\n") catch {}; std.process.exit(1); } const name = project_name.?; const in_place = std.mem.eql(u8, name, "."); + var cwd_path: ?[:0]u8 = null; + defer if (cwd_path) |path| allocator.free(path); // For in-place init, derive name from current directory const effective_name = if (in_place) blk: { - const cwd_path = std.fs.cwd().realpathAlloc(allocator, ".") catch { - File.stderr().writeAll("Error: failed to determine current directory name\n") catch {}; + cwd_path = std.process.currentPathAlloc(io, allocator) catch { + var stderr_file = File.stderr().writer(io, &.{}); + stderr_file.interface.writeAll("Error: failed to determine current directory name\n") catch {}; std.process.exit(1); }; - break :blk std.fs.path.basename(cwd_path); + break :blk std.fs.path.basename(cwd_path.?); } else name; - init_mod.initProject(allocator, .{ + init_mod.initProject(io, allocator, .{ .name = effective_name, .force = force, .in_place = in_place, @@ -269,13 +290,14 @@ fn cmdInit(allocator: std.mem.Allocator, remaining_args: []const []const u8) voi error.DirectoryExists => std.process.exit(1), error.CreateFailed => std.process.exit(1), error.OutOfMemory => { - File.stderr().writeAll("Error: out of memory\n") catch {}; + var stderr_file = File.stderr().writer(io, &.{}); + stderr_file.interface.writeAll("Error: out of memory\n") catch {}; std.process.exit(1); }, }; } -fn cmdFmt(allocator: std.mem.Allocator, remaining_args: []const []const u8) !void { +fn cmdFmt(io: std.Io, allocator: std.mem.Allocator, remaining_args: []const []const u8) !void { var check_mode = false; var paths: std.ArrayList([]const u8) = .empty; defer paths.deinit(allocator); @@ -289,25 +311,33 @@ fn cmdFmt(allocator: std.mem.Allocator, remaining_args: []const []const u8) !voi } if (paths.items.len == 0) { - try File.stderr().writeAll("Error: no input file or directory\n"); + var stderr_file = File.stderr().writer(io, &.{}); + try stderr_file.interface.writeAll("Error: no input file or directory\n"); std.process.exit(1); } - const stderr = File.stderr().deprecatedWriter(); - const stdout = File.stdout().deprecatedWriter(); + var stderr_file = File.stderr().writer(io, &.{}); + const stderr = &stderr_file.interface; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; var any_diff = false; + const cwd = Dir.cwd(); for (paths.items) |path| { - // Check if path is a directory - const stat = std.fs.cwd().statFile(path) catch { + const stat = cwd.statFile(io, path, .{}) catch { // Try as directory - formatDirectory(allocator, path, check_mode, &any_diff, stderr, stdout) catch |err| { + formatDirectory(io, allocator, path, check_mode, &any_diff, stderr, stdout) catch |err| { stderr.print("error: could not process '{s}': {s}\n", .{ path, @errorName(err) }) catch {}; }; continue; }; - _ = stat; - formatSingleFile(allocator, path, check_mode, &any_diff, stderr, stdout) catch |err| { + if (stat.kind == .directory) { + formatDirectory(io, allocator, path, check_mode, &any_diff, stderr, stdout) catch |err| { + stderr.print("error: could not process '{s}': {s}\n", .{ path, @errorName(err) }) catch {}; + }; + continue; + } + formatSingleFile(io, allocator, path, check_mode, &any_diff, stderr, stdout) catch |err| { stderr.print("error: could not format '{s}': {s}\n", .{ path, @errorName(err) }) catch {}; }; } @@ -318,6 +348,7 @@ fn cmdFmt(allocator: std.mem.Allocator, remaining_args: []const []const u8) !voi } fn formatSingleFile( + io: std.Io, allocator: std.mem.Allocator, path: []const u8, check_mode: bool, @@ -325,7 +356,7 @@ fn formatSingleFile( stderr: anytype, stdout: anytype, ) !void { - const source = readFile(allocator, path) catch { + const source = readFile(io, allocator, path) catch { try stderr.print("error: could not read '{s}'\n", .{path}); return; }; @@ -344,15 +375,16 @@ fn formatSingleFile( } } else { if (!std.mem.eql(u8, source, formatted)) { - const file = try std.fs.cwd().createFile(path, .{}); - defer file.close(); - try file.writeAll(formatted); + const file = try Dir.cwd().createFile(io, path, .{}); + defer file.close(io); + try file.writeStreamingAll(io, formatted); try stdout.print("formatted: {s}\n", .{path}); } } } fn formatDirectory( + io: std.Io, allocator: std.mem.Allocator, dir_path: []const u8, check_mode: bool, @@ -360,23 +392,23 @@ fn formatDirectory( stderr: anytype, stdout: anytype, ) !void { - var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true }); - defer dir.close(); + var dir = try Dir.cwd().openDir(io, dir_path, .{ .iterate = true }); + defer dir.close(io); var walker = try dir.walk(allocator); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.basename, ".run")) continue; const full_path = try std.fs.path.join(allocator, &.{ dir_path, entry.path }); defer allocator.free(full_path); - formatSingleFile(allocator, full_path, check_mode, any_diff, stderr, stdout) catch {}; + formatSingleFile(io, allocator, full_path, check_mode, any_diff, stderr, stdout) catch {}; } } -fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !void { +fn cmdTest(io: std.Io, allocator: std.mem.Allocator, remaining_args: []const []const u8) !void { var filter: ?[]const u8 = null; var verbose = false; var paths: std.ArrayList([]const u8) = .empty; @@ -395,12 +427,15 @@ fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !vo } if (paths.items.len == 0) { - try File.stderr().writeAll("Error: no input file or directory\n"); + var stderr_file = File.stderr().writer(io, &.{}); + try stderr_file.interface.writeAll("Error: no input file or directory\n"); std.process.exit(1); } - const stderr = File.stderr().deprecatedWriter(); - const stdout = File.stdout().deprecatedWriter(); + var stderr_file = File.stderr().writer(io, &.{}); + const stderr = &stderr_file.interface; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; try stdout.writeAll("note: `run test` currently validates that discovered `test_*` functions compile; executing test bodies is not implemented yet.\n\n"); var all_files: std.ArrayList([]const u8) = .empty; @@ -415,7 +450,7 @@ fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !vo try all_files.append(allocator, owned); } else { // Treat as directory - collectRunFiles(allocator, path, &all_files) catch |err| { + collectRunFiles(io, allocator, path, &all_files) catch |err| { stderr.print("error: could not read directory '{s}': {s}\n", .{ path, @errorName(err) }) catch {}; }; } @@ -425,10 +460,10 @@ fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !vo var passed: u32 = 0; var failed: u32 = 0; - var timer = std.time.Timer.start() catch null; + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; for (all_files.items) |file_path| { - const source = readFile(allocator, file_path) catch { + const source = readFile(io, allocator, file_path) catch { stderr.print("error: could not read '{s}'\n", .{file_path}) catch {}; continue; }; @@ -463,7 +498,7 @@ fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !vo } // Summary - const elapsed_ns: u64 = if (timer) |*t| t.read() else 0; + const elapsed_ns: u64 = @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns); const elapsed_ms = elapsed_ns / 1_000_000; try stdout.print("\n{d} passed, {d} failed, {d} total ({d}ms)\n", .{ @@ -475,14 +510,14 @@ fn cmdTest(allocator: std.mem.Allocator, remaining_args: []const []const u8) !vo } } -fn collectRunFiles(allocator: std.mem.Allocator, dir_path: []const u8, files: *std.ArrayList([]const u8)) !void { - var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true }); - defer dir.close(); +fn collectRunFiles(io: std.Io, allocator: std.mem.Allocator, dir_path: []const u8, files: *std.ArrayList([]const u8)) !void { + var dir = try Dir.cwd().openDir(io, dir_path, .{ .iterate = true }); + defer dir.close(io); var walker = try dir.walk(allocator); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.basename, ".run")) continue; diff --git a/src/rasm.zig b/src/rasm.zig index e6c05dc..49958b5 100644 --- a/src/rasm.zig +++ b/src/rasm.zig @@ -9,7 +9,6 @@ /// The build system selects the correct file: platform-specific takes priority /// over portable versions. .rasm files are compiled to .S (GAS) files and /// assembled alongside the generated C code. - const std = @import("std"); const Lexer = @import("lexer.zig").Lexer; const Token = @import("token.zig").Token; @@ -219,7 +218,10 @@ pub fn selectRasmFile(allocator: std.mem.Allocator, base_path: []const u8, arch: } fn fileExists(path: []const u8) bool { - std.fs.cwd().access(path, .{}) catch return false; + var io_threaded: std.Io.Threaded = .init(std.heap.smp_allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); + std.Io.Dir.cwd().access(io, path, .{}) catch return false; return true; } @@ -228,26 +230,25 @@ fn fileExists(path: []const u8) bool { pub fn generateGasFile(allocator: std.mem.Allocator, rasm: *const RasmFile, arch: Arch) ![]const u8 { var output: std.ArrayList(u8) = .empty; defer output.deinit(allocator); - const w = output.writer(allocator); // File header - try w.print("// Generated from .rasm file\n", .{}); - try w.print(".text\n\n", .{}); + try output.print(allocator, "// Generated from .rasm file\n", .{}); + try output.print(allocator, ".text\n\n", .{}); for (rasm.functions.items) |func| { // Mangle name to match Run calling convention - try w.print(".globl run_main__{s}\n", .{func.name}); - try w.print("run_main__{s}:\n", .{func.name}); + try output.print(allocator, ".globl run_main__{s}\n", .{func.name}); + try output.print(allocator, "run_main__{s}:\n", .{func.name}); // Platform-specific prologue switch (arch) { .x86_64 => { - try w.print(" pushq %rbp\n", .{}); - try w.print(" movq %rsp, %rbp\n", .{}); + try output.print(allocator, " pushq %rbp\n", .{}); + try output.print(allocator, " movq %rsp, %rbp\n", .{}); }, .arm64 => { - try w.print(" stp x29, x30, [sp, #-16]!\n", .{}); - try w.print(" mov x29, sp\n", .{}); + try output.print(allocator, " stp x29, x30, [sp, #-16]!\n", .{}); + try output.print(allocator, " mov x29, sp\n", .{}); }, .other => {}, } @@ -259,7 +260,7 @@ pub fn generateGasFile(allocator: std.mem.Allocator, rasm: *const RasmFile, arch while (lines.next()) |line| { const tline = std.mem.trim(u8, line, " \t\r"); if (tline.len > 0) { - try w.print(" {s}\n", .{tline}); + try output.print(allocator, " {s}\n", .{tline}); } } } @@ -267,18 +268,18 @@ pub fn generateGasFile(allocator: std.mem.Allocator, rasm: *const RasmFile, arch // Platform-specific epilogue switch (arch) { .x86_64 => { - try w.print(" popq %rbp\n", .{}); - try w.print(" retq\n", .{}); + try output.print(allocator, " popq %rbp\n", .{}); + try output.print(allocator, " retq\n", .{}); }, .arm64 => { - try w.print(" ldp x29, x30, [sp], #16\n", .{}); - try w.print(" ret\n", .{}); + try output.print(allocator, " ldp x29, x30, [sp], #16\n", .{}); + try output.print(allocator, " ret\n", .{}); }, .other => { - try w.print(" ret\n", .{}); + try output.print(allocator, " ret\n", .{}); }, } - try w.print("\n", .{}); + try output.print(allocator, "\n", .{}); } return try allocator.dupe(u8, output.items); @@ -288,18 +289,21 @@ pub fn generateGasFile(allocator: std.mem.Allocator, rasm: *const RasmFile, arch /// Returns a list of resolved .rasm file paths (platform-specific takes priority). pub fn discoverRasmFiles(allocator: std.mem.Allocator, source_dir: []const u8, arch: Arch) !std.ArrayList([]const u8) { var result: std.ArrayList([]const u8) = .empty; + var io_threaded: std.Io.Threaded = .init(allocator, .{}); + defer io_threaded.deinit(); + const io = io_threaded.io(); - var dir = std.fs.cwd().openDir(source_dir, .{ .iterate = true }) catch { + var dir = std.Io.Dir.cwd().openDir(io, source_dir, .{ .iterate = true }) catch { return result; }; - defer dir.close(); + defer dir.close(io); // Collect all .rasm files, then filter by platform priority var rasm_bases: std.ArrayList([]const u8) = .empty; defer rasm_bases.deinit(allocator); var it = dir.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.kind != .file) continue; const name = entry.name; if (!std.mem.endsWith(u8, name, ".rasm")) continue; diff --git a/src/typecheck.zig b/src/typecheck.zig index 0de3bb5..13084c8 100644 --- a/src/typecheck.zig +++ b/src/typecheck.zig @@ -831,7 +831,8 @@ const TypeChecker = struct { // Skip for nullable return types (implicit null return) and error unions. if (return_type != types.null_type and return_type != types.primitives.void_id and !self.type_pool.isNullable(return_type) and - self.type_pool.unwrapErrorUnion(return_type) == null) { + self.type_pool.unwrapErrorUnion(return_type) == null) + { if (!self.blockAlwaysReturns(body)) { const fn_tok = self.nodeMainToken(node); const loc = self.tokenLoc(fn_tok); @@ -930,7 +931,8 @@ const TypeChecker = struct { // Check return value type matches declared return type. if (expr_type != types.null_type and self.current_fn_return_type != types.null_type) { if (!self.typesCompatible(self.current_fn_return_type, expr_type) and - !std.mem.eql(u8, self.typeName(self.current_fn_return_type), "")) { + !std.mem.eql(u8, self.typeName(self.current_fn_return_type), "")) + { const help = self.typeMismatchHelp(self.current_fn_return_type, expr_type); if (help) |help_msg| { var ann_buf = try self.allocator.alloc(diag_mod.Annotation, 1); @@ -2527,7 +2529,10 @@ const TypeChecker = struct { // list may be incomplete — skip the error in that case. var has_unresolved = false; for (st.fields) |f| { - if (f.type_id == types.null_type) { has_unresolved = true; break; } + if (f.type_id == types.null_type) { + has_unresolved = true; + break; + } } if (!has_unresolved) { // Field not found — emit error with suggestion. @@ -2555,13 +2560,12 @@ const TypeChecker = struct { if (st.fields.len > 0 and st.fields.len <= 10) { // Build available fields list var fields_buf: [512]u8 = undefined; - var fbs = std.io.fixedBufferStream(&fields_buf); - const fbw = fbs.writer(); + var fbw = std.Io.Writer.fixed(&fields_buf); for (st.fields, 0..) |f, fi| { if (fi > 0) fbw.writeAll(", ") catch break; fbw.writeAll(f.name) catch break; } - const fields_list = fbs.getWritten(); + const fields_list = fbw.buffered(); if (fields_list.len > 0) { const note_msg = try std.fmt.allocPrint(self.allocator, "available fields: {s}", .{fields_list}); try self.diagnostics.allocated_messages.append(self.allocator, note_msg); @@ -2692,7 +2696,8 @@ const TypeChecker = struct { const init_type = init_types[i]; if (init_type != types.null_type and decl_field.type_id != types.null_type) { if (!self.typesCompatible(decl_field.type_id, init_type) and - !std.mem.eql(u8, self.typeName(decl_field.type_id), "")) { + !std.mem.eql(u8, self.typeName(decl_field.type_id), "")) + { const loc = self.tokenLoc(init_name_tok); try self.diagnostics.addErrorFmt( loc.start, @@ -2740,9 +2745,15 @@ const TypeChecker = struct { const init_name2 = self.tokenSlice(init_name_tok2); var f = false; for (st.fields) |df| { - if (std.mem.eql(u8, df.name, init_name2)) { f = true; break; } + if (std.mem.eql(u8, df.name, init_name2)) { + f = true; + break; + } + } + if (!f) { + all_found = false; + break; } - if (!f) { all_found = false; break; } } if (all_found) { for (0..decl_field_count) |fi| { diff --git a/tests/e2e/runner.zig b/tests/e2e/runner.zig index 6f72c45..d230385 100644 --- a/tests/e2e/runner.zig +++ b/tests/e2e/runner.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const Dir = std.Io.Dir; +const File = std.Io.File; const Expectations = struct { expected_lines: std.ArrayList([]const u8), @@ -13,33 +15,28 @@ const Expectations = struct { } }; -const TestResult = struct { - name: []const u8, - passed: bool, - message: []const u8, -}; - -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - const stdout = std.fs.File.stdout().deprecatedWriter(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; // Parse --filter arg - var filter: ?[]const u8 = null; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - var i: usize = 1; - while (i < args.len) : (i += 1) { - if (std.mem.eql(u8, args[i], "--filter") and i + 1 < args.len) { - filter = args[i + 1]; - i += 1; + var filter: ?[]u8 = null; + defer if (filter) |f| allocator.free(f); + var arg_iter = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer arg_iter.deinit(); + _ = arg_iter.next(); + while (arg_iter.next()) |arg| { + if (std.mem.eql(u8, arg, "--filter")) { + const value = arg_iter.next() orelse break; + if (filter) |previous| allocator.free(previous); + filter = try allocator.dupe(u8, value); } } // Find the compiler binary - const compiler_path = findCompiler(allocator) catch { + const compiler_path = findCompiler(io, allocator) catch { try stdout.writeAll("error: could not find 'run' compiler binary. Run 'zig build' first.\n"); std.process.exit(1); }; @@ -52,14 +49,14 @@ pub fn main() !void { test_files.deinit(allocator); } - var cases_dir = findCasesDir() catch { + var cases_dir = findCasesDir(io) catch { try stdout.writeAll("error: could not find tests/e2e/cases/ directory\n"); std.process.exit(1); }; - defer cases_dir.close(); + defer cases_dir.close(io); var iter = cases_dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.name, ".run")) continue; if (filter) |f| { @@ -80,28 +77,22 @@ pub fn main() !void { std.process.exit(1); } - var timer = try std.time.Timer.start(); + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; var passed: usize = 0; var failed: usize = 0; - var results: std.ArrayList(TestResult) = .empty; - defer results.deinit(allocator); for (test_files.items) |test_file| { const test_path = try std.fmt.allocPrint(allocator, "tests/e2e/cases/{s}", .{test_file}); defer allocator.free(test_path); - const source = blk: { - const f = try std.fs.cwd().openFile(test_path, .{}); - defer f.close(); - break :blk try f.readToEndAlloc(allocator, 10 * 1024 * 1024); - }; + const source = try Dir.cwd().readFileAlloc(io, test_path, allocator, .limited(10 * 1024 * 1024)); defer allocator.free(source); var expect = parseExpectations(allocator, source); defer expect.deinit(allocator); - const result = runTest(allocator, compiler_path, test_path, &expect) catch |err| { + const result = runTest(io, allocator, compiler_path, test_path, &expect) catch |err| { try stdout.print("FAIL {s}: runner error: {s}\n", .{ test_file, @errorName(err) }); failed += 1; continue; @@ -117,7 +108,7 @@ pub fn main() !void { } } - const elapsed_ms = timer.read() / std.time.ns_per_ms; + const elapsed_ms = @as(u64, @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns)) / std.time.ns_per_ms; try stdout.print("\n{d} passed, {d} failed, {d} total ({d}ms)\n", .{ passed, failed, @@ -160,6 +151,7 @@ fn parseExpectations(allocator: Allocator, source: []const u8) Expectations { } fn runTest( + io: std.Io, allocator: Allocator, compiler_path: []const u8, test_path: []const u8, @@ -171,7 +163,7 @@ fn runTest( defer allocator.free(out_path); // Compile the test file - const compile_result = try runProcess(allocator, &.{ + const compile_result = try runProcess(io, allocator, &.{ compiler_path, "build", "-o", out_path, "--no-color", test_path, }); defer { @@ -183,7 +175,7 @@ fn runTest( // Expect compilation to fail if (compile_result.exit_code == 0) { // Clean up the unexpectedly compiled binary - std.fs.cwd().deleteFile(out_path) catch {}; + Dir.cwd().deleteFile(io, out_path) catch {}; return .{ .passed = false, .message = try allocator.dupe(u8, "expected compilation to fail but it succeeded") }; } if (expect.compile_error_text) |expected_text| { @@ -212,10 +204,10 @@ fn runTest( } // Clean up binary after running - defer std.fs.cwd().deleteFile(out_path) catch {}; + defer Dir.cwd().deleteFile(io, out_path) catch {}; // Run the compiled binary - const run_result = try runProcess(allocator, &.{out_path}); + const run_result = try runProcess(io, allocator, &.{out_path}); defer { allocator.free(run_result.stdout); allocator.free(run_result.stderr); @@ -285,62 +277,43 @@ fn formatLines(allocator: Allocator, lines: []const []const u8) ![]const u8 { } const ProcessResult = struct { - stdout: []const u8, - stderr: []const u8, + stdout: []u8, + stderr: []u8, exit_code: u8, }; -fn runProcess(allocator: Allocator, argv: []const []const u8) !ProcessResult { - var child = std.process.Child.init(argv, allocator); - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - var stdout_buf: std.ArrayList(u8) = .empty; - defer stdout_buf.deinit(allocator); - var stderr_buf: std.ArrayList(u8) = .empty; - defer stderr_buf.deinit(allocator); - - // Read stdout - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stdout.?.read(&buf) catch break; - if (n == 0) break; - try stdout_buf.appendSlice(allocator, buf[0..n]); - } - - // Read stderr - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stderr.?.read(&buf) catch break; - if (n == 0) break; - try stderr_buf.appendSlice(allocator, buf[0..n]); - } - - const result = try child.wait(); +fn runProcess(io: std.Io, allocator: Allocator, argv: []const []const u8) !ProcessResult { + const result = try std.process.run(allocator, io, .{ + .argv = argv, + .stdout_limit = .limited(10 * 1024 * 1024), + .stderr_limit = .limited(10 * 1024 * 1024), + }); return .{ - .stdout = try stdout_buf.toOwnedSlice(allocator), - .stderr = try stderr_buf.toOwnedSlice(allocator), - .exit_code = switch (result) { - .Exited => |code| code, - .Signal => |sig| if (sig + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(sig + 128)) else std.math.maxInt(u8), - .Stopped, .Unknown => 1, - }, + .stdout = result.stdout, + .stderr = result.stderr, + .exit_code = childExitCode(result.term), + }; +} + +fn childExitCode(term: std.process.Child.Term) u8 { + return switch (term) { + .exited => |code| code, + .signal => |sig| if (@intFromEnum(sig) + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(@intFromEnum(sig) + 128)) else std.math.maxInt(u8), + .stopped, .unknown => 1, }; } -fn findCompiler(allocator: Allocator) ![]const u8 { +fn findCompiler(io: std.Io, allocator: Allocator) ![]const u8 { // Try zig-out/bin/run relative to cwd const local_path = "zig-out/bin/run"; - if (std.fs.cwd().access(local_path, .{})) |_| { + if (Dir.cwd().access(io, local_path, .{})) |_| { return try allocator.dupe(u8, local_path); } else |_| {} return error.CompilerNotFound; } -fn findCasesDir() !std.fs.Dir { - return std.fs.cwd().openDir("tests/e2e/cases", .{ .iterate = true }); +fn findCasesDir(io: std.Io) !Dir { + return Dir.cwd().openDir(io, "tests/e2e/cases", .{ .iterate = true }); } diff --git a/tests/examples/runner.zig b/tests/examples/runner.zig index bcce2eb..94aa3b2 100644 --- a/tests/examples/runner.zig +++ b/tests/examples/runner.zig @@ -1,22 +1,17 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const Dir = std.Io.Dir; +const File = std.Io.File; -const TestResult = struct { - name: []const u8, - passed: bool, - message: []const u8, -}; - -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - const stdout = std.fs.File.stdout().deprecatedWriter(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; + var stdout_file = File.stdout().writer(io, &.{}); + const stdout = &stdout_file.interface; // Find the compiler binary - const compiler_path = findCompiler(allocator) catch { + const compiler_path = findCompiler(io, allocator) catch { try stdout.writeAll("error: could not find 'run' compiler binary. Run 'zig build' first.\n"); std.process.exit(1); }; @@ -29,21 +24,21 @@ pub fn main() !void { examples.deinit(allocator); } - var examples_dir = findExamplesDir() catch { + var examples_dir = findExamplesDir(io) catch { try stdout.writeAll("error: could not find examples/ directory\n"); std.process.exit(1); }; - defer examples_dir.close(); + defer examples_dir.close(io); var iter = examples_dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { if (entry.kind != .directory) continue; // Check if directory contains main.run const main_path = try std.fmt.allocPrint(allocator, "examples/{s}/main.run", .{entry.name}); defer allocator.free(main_path); - if (std.fs.cwd().access(main_path, .{})) |_| { + if (Dir.cwd().access(io, main_path, .{})) |_| { try examples.append(allocator, try allocator.dupe(u8, entry.name)); } else |_| {} } @@ -62,7 +57,7 @@ pub fn main() !void { try stdout.print("Found {d} example(s)\n\n", .{examples.items.len}); - var timer = try std.time.Timer.start(); + const start_ns = std.Io.Clock.awake.now(io).nanoseconds; var passed: usize = 0; var failed: usize = 0; @@ -74,7 +69,7 @@ pub fn main() !void { const out_path = try std.fmt.allocPrint(allocator, "/tmp/example_{s}", .{name}); defer allocator.free(out_path); - const result = buildExample(allocator, compiler_path, main_path, out_path) catch |err| { + const result = buildExample(io, allocator, compiler_path, main_path, out_path) catch |err| { try stdout.print("FAIL {s}: runner error: {s}\n", .{ name, @errorName(err) }); failed += 1; continue; @@ -82,7 +77,7 @@ pub fn main() !void { defer allocator.free(result.message); // Clean up compiled binary - std.fs.cwd().deleteFile(out_path) catch {}; + Dir.cwd().deleteFile(io, out_path) catch {}; if (result.passed) { try stdout.print("PASS {s}\n", .{name}); @@ -93,7 +88,7 @@ pub fn main() !void { } } - const elapsed_ms = timer.read() / std.time.ns_per_ms; + const elapsed_ms = @as(u64, @intCast(std.Io.Clock.awake.now(io).nanoseconds - start_ns)) / std.time.ns_per_ms; try stdout.print("\n{d} passed, {d} failed, {d} total ({d}ms)\n", .{ passed, failed, @@ -107,12 +102,13 @@ pub fn main() !void { } fn buildExample( + io: std.Io, allocator: Allocator, compiler_path: []const u8, source_path: []const u8, out_path: []const u8, ) !struct { passed: bool, message: []const u8 } { - const result = try runProcess(allocator, &.{ + const result = try runProcess(io, allocator, &.{ compiler_path, "build", "-o", out_path, "--no-color", source_path, }); defer { @@ -134,55 +130,42 @@ fn buildExample( } const ProcessResult = struct { - stdout: []const u8, - stderr: []const u8, + stdout: []u8, + stderr: []u8, exit_code: u8, }; -fn runProcess(allocator: Allocator, argv: []const []const u8) !ProcessResult { - var child = std.process.Child.init(argv, allocator); - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - var stdout_buf: std.ArrayList(u8) = .empty; - defer stdout_buf.deinit(allocator); - var stderr_buf: std.ArrayList(u8) = .empty; - defer stderr_buf.deinit(allocator); - - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stdout.?.read(&buf) catch break; - if (n == 0) break; - try stdout_buf.appendSlice(allocator, buf[0..n]); - } - - while (true) { - var buf: [4096]u8 = undefined; - const n = child.stderr.?.read(&buf) catch break; - if (n == 0) break; - try stderr_buf.appendSlice(allocator, buf[0..n]); - } - - const result = try child.wait(); +fn runProcess(io: std.Io, allocator: Allocator, argv: []const []const u8) !ProcessResult { + const result = try std.process.run(allocator, io, .{ + .argv = argv, + .stdout_limit = .limited(10 * 1024 * 1024), + .stderr_limit = .limited(10 * 1024 * 1024), + }); return .{ - .stdout = try stdout_buf.toOwnedSlice(allocator), - .stderr = try stderr_buf.toOwnedSlice(allocator), - .exit_code = result.Exited, + .stdout = result.stdout, + .stderr = result.stderr, + .exit_code = childExitCode(result.term), + }; +} + +fn childExitCode(term: std.process.Child.Term) u8 { + return switch (term) { + .exited => |code| code, + .signal => |sig| if (@intFromEnum(sig) + 128 <= std.math.maxInt(u8)) @as(u8, @intCast(@intFromEnum(sig) + 128)) else std.math.maxInt(u8), + .stopped, .unknown => 1, }; } -fn findCompiler(allocator: Allocator) ![]const u8 { +fn findCompiler(io: std.Io, allocator: Allocator) ![]const u8 { const local_path = "zig-out/bin/run"; - if (std.fs.cwd().access(local_path, .{})) |_| { + if (Dir.cwd().access(io, local_path, .{})) |_| { return try allocator.dupe(u8, local_path); } else |_| {} return error.CompilerNotFound; } -fn findExamplesDir() !std.fs.Dir { - return std.fs.cwd().openDir("examples", .{ .iterate = true }); +fn findExamplesDir(io: std.Io) !Dir { + return Dir.cwd().openDir(io, "examples", .{ .iterate = true }); } diff --git a/website/src/components/landing/GettingStarted.astro b/website/src/components/landing/GettingStarted.astro index b48b0ae..13be9ef 100644 --- a/website/src/components/landing/GettingStarted.astro +++ b/website/src/components/landing/GettingStarted.astro @@ -74,7 +74,7 @@

- Requires Zig 0.15 or later. + Requires Zig 0.16 or later.

diff --git a/website/src/content/docs/getting-started/installation.md b/website/src/content/docs/getting-started/installation.md index 2eab890..5472deb 100644 --- a/website/src/content/docs/getting-started/installation.md +++ b/website/src/content/docs/getting-started/installation.md @@ -99,7 +99,7 @@ sudo snap install run-lang ## From Source -Building from source requires [Zig](https://ziglang.org/) version 0.15 or later. +Building from source requires [Zig](https://ziglang.org/) version 0.16 or later. ```bash git clone https://github.com/marsolab/runlang.git @@ -196,7 +196,7 @@ sudo rm /usr/local/bin/run ## Note about Zig -Run's compiler is written in [Zig](https://ziglang.org/), a systems programming language focused on correctness and performance. If you are building from source, you need Zig version **0.15 or later** installed on your system. +Run's compiler is written in [Zig](https://ziglang.org/), a systems programming language focused on correctness and performance. If you are building from source, you need Zig version **0.16 or later** installed on your system. :::caution Older versions of Zig (0.14 and below) are not compatible with the Run compiler due to breaking API changes in the Zig standard library. @@ -212,5 +212,5 @@ brew install zig pacman -S zig # Using zigup (cross-platform) -zigup 0.15.0 +zigup 0.16.0 ``` diff --git a/website/src/content/docs/reference/compiler-status.md b/website/src/content/docs/reference/compiler-status.md index cfa86cf..6d5d8a9 100644 --- a/website/src/content/docs/reference/compiler-status.md +++ b/website/src/content/docs/reference/compiler-status.md @@ -22,7 +22,7 @@ Run is in **active development**. The compiler can compile and run simple progra ## Compiler Architecture -The compiler is written in Zig 0.15 and follows a traditional pipeline: +The compiler is written in Zig 0.16 and follows a traditional pipeline: ``` Source (.run) → Lexer → Token stream → Parser → AST → Name Resolution → Type Check → IR → C codegen diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.envrc b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.envrc new file mode 100644 index 0000000..24e67d2 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.envrc @@ -0,0 +1,5 @@ +# If we are a computer with nix-shell available, then use that to setup +# the build environment with exactly what we need. +if has nix; then + use nix +fi diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/dependabot.yml b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/dependabot.yml new file mode 100644 index 0000000..012385d --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/workflows/test.yml b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/workflows/test.yml new file mode 100644 index 0000000..15b1636 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/workflows/test.yml @@ -0,0 +1,156 @@ +on: [push, pull_request] +name: Test +env: + ZIG_VERSION: 0.16.0 + +jobs: + build: + strategy: + matrix: + os: [namespace-profile-mitchellh-sm] + + target: [ + aarch64-linux-gnu, + aarch64-linux-musl, + x86_64-linux-gnu, + x86_64-linux-musl, + aarch64-macos, + x86_64-macos, + # wasm32-wasi, - regressed in Zig 0.13 + x86_64-windows-gnu + + # Broken but not in any obvious way: + # x86-linux-gnu, + # x86-linux-musl, + # x86-windows, + ] + runs-on: ${{ matrix.os }} + needs: [test-x86_64-linux, test-x86_64-windows] + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@v1.4.2 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + + # Run our checks to catch quick issues + - run: nix flake check + + # Run our go tests within the context of the dev shell from the flake. This + # will ensure we have all our dependencies. + - name: test + run: nix develop -c zig build --summary all -Dtarget=${{ matrix.target }} + + test-x86_64-linux: + strategy: + matrix: + os: [namespace-profile-mitchellh-sm] + runs-on: ${{ matrix.os }} + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@v1.4.2 + with: + path: | + /nix + /zig + + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + + # Run our checks to catch quick issues + - run: nix flake check + + # Run our go tests within the context of the dev shell from the flake. This + # will ensure we have all our dependencies. + - name: test + run: nix develop -c zig build test --summary all + # WASI has regressed since Zig 0.13, we should fix it. + # - name: test wasi + # run: nix develop -c zig build test -Dtarget=wasm32-wasi -fwasmtime --summary all + + - name: build all benchmarks and examples + run: nix develop -c zig build -Demit-example -Demit-bench --summary all + + # Run a full build to ensure that works + - run: nix build + + test-x86_64-freebsd: + runs-on: namespace-profile-mitchellh-sm-systemd + steps: + - name: Checkout code + uses: actions/checkout@v6 + + # Required by Namespace + - run: sudo systemctl start ssh + + - name: test + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + prepare: | + pkg update + pkg upgrade -y + pkg install -y wget + + run: | + wget -O /tmp/zig-x86_64-freebsd-${{ env.ZIG_VERSION }}.tar.xz https://ziglang.org/download/${{ env.ZIG_VERSION }}/zig-x86_64-freebsd-${{ env.ZIG_VERSION }}.tar.xz + tar -xf /tmp/zig-x86_64-freebsd-${{ env.ZIG_VERSION }}.tar.xz -C /tmp + alias zig=/tmp/zig-x86_64-freebsd-${{ env.ZIG_VERSION }}/zig + zig build test --summary all + zig build -Demit-example -Demit-bench --summary all + + test-aarch64-macos: + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install zig + uses: mlugg/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: test + run: zig build test --summary all + + - name: build all benchmarks and examples + run: zig build -Demit-example -Demit-bench --summary all + + test-x86_64-windows: + strategy: + matrix: + os: [windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install zig + uses: mlugg/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: test + run: zig build test --summary all + + - name: build all benchmarks and examples + run: zig build -Demit-example -Demit-bench --summary all diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.gitignore b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.gitignore new file mode 100644 index 0000000..d8c8979 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.gitignore @@ -0,0 +1,2 @@ +.zig-cache +zig-out diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/LICENSE b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/LICENSE new file mode 100644 index 0000000..7504197 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/README.md b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/README.md new file mode 100644 index 0000000..82d2eed --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/README.md @@ -0,0 +1,336 @@ +# libxev + +libxev is a cross-platform event loop. libxev provides a unified event loop +abstraction for non-blocking IO, timers, signals, events, and more that +works on macOS, Windows, Linux, and WebAssembly (browser and WASI). It is +written in [Zig](https://ziglang.org/) but exports a C-compatible API (which +further makes it compatible with any language out there that can communicate +with C APIs). + +**Project Status: Stable for most use cases.** libxev is in daily use by +large projects such as [Ghostty](https://ghostty.org), +[zml](https://github.com/zml/zml), and more. For most use cases, libxev +has been shown to be stable at scale. libxev has a broad featureset and +there are likely less well-used corners of the library, but for most +use cases libxev is already heavily used in production environments. + +**Why a new event loop library?** A few reasons. One, I think Zig lacks +a generalized event loop comparable to libuv in features ("generalized" +being a key word here). Two, I wanted to build a library like this around +the design patterns of [io_uring](https://unixism.net/loti/what_is_io_uring.html), +even mimicking its style on top of other OS primitives ( +[credit to this awesome blog post](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/)). +Three, I wanted an event loop library that could build to WebAssembly +(both WASI and freestanding) and that didn't really fit well +into the goals of API style of existing libraries without bringing in +something super heavy like Emscripten. The motivation for this library +primarily though is scratching my own itch! + +## Features + +**Cross-platform.** Linux (`io_uring` and `epoll`), macOS (`kqueue`), +WebAssembly + WASI (`poll_oneoff`, threaded and non-threaded runtimes). +(Windows support is planned and coming soon) + +**[Proactor API](https://en.wikipedia.org/wiki/Proactor_pattern).** Work +is submitted to the libxev event loop and the caller is notified of +work _completion_, as opposed to work _readiness_. + +**Zero runtime allocations.** This helps make runtime performance more +predictable and makes libxev well suited for embedded environments. + +**Timers, TCP, UDP, Files, Processes.** High-level platform-agnostic APIs for +interacting with timers, TCP/UDP sockets, files, processes, and more. For +platforms that don't support async IO, the file operations are automatically +scheduled to a thread pool. + +**Generic Thread Pool (Optional).** You can create a generic thread pool, +configure its resource utilization, and use this to perform custom background +tasks. The thread pool is used by some backends to do non-blocking tasks that +don't have reliable non-blocking APIs (such as local file operations with +`kqueue`). The thread pool can be shared across multiple threads and event +loops to optimize resource utilization. + +**Low-level and High-Level API.** The high-level API is platform-agnostic +but has some opinionated behavior and limited flexibility. The high-level +API is recommended but the low-level API is always an available escape hatch. +The low-level API is platform-specific and provides a mechanism for libxev +users to squeeze out maximum performance. The low-level API is _just enough +abstraction_ above the OS interface to make it easier to use without +sacrificing noticable performance. + +**Tree Shaking (Zig).** This is a feature of Zig, but substantially benefits +libraries such as libxev. Zig will only include function calls and features +that you actually use. If you don't use a particular kind of high-level +watcher (such as UDP sockets), then the functionality related to that +abstraction is not compiled into your final binary at all. This lets libxev +support optional "nice-to-have" functionality that may be considered +"bloat" in some cases, but the end user doesn't have to pay for it. + +**Dependency-free.** libxev has no dependencies other than the built-in +OS APIs at runtime. The C library depends on libc. This makes it very +easy to cross-compile. + +### Roadmap + +There are plenty of missing features that I still want to add: + +* Pipe high-level API +* Signal handlers +* Filesystem events +* Windows backend +* Freestanding WebAssembly support via an external event loop (i.e. the browser) + +And more... + +### Performance + +There is plenty of room for performance improvements, and I want to be +fully clear that I haven't done a lot of optimization work. Still, +performance is looking good. I've tried to port many of +[libuv benchmarks](https://github.com/libuv/libuv) to use the libxev +API. + +I won't post specific benchmark results until I have a better +environment to run them in. As a _very broad generalization_, +you shouldn't notice a slowdown using libxev compared to other +major event loops. This may differ on a feature-by-feature basis, and +if you can show really poor performance in an issue I'm interested +in resolving it! + +### Integration with Zig 0.16+ std.Io + +Libxev doesn't implement the `std.Io` interface and doesn't take +a `std.Io` for any of its operations. It calls IO directly using system +calls and in the few rare cases it must use a `std.Io` (such as for mutex +operations), libxev uses the global `std.Io.Threaded` implementation. + +The reason for this is because libxev is a very old library that +predates `std.Io`, so it bakes in a lot of assumptions that don't really +fit into the new `std.Io` model. To support this, we'll have to break +our API significantly. + +Additionally, the `std.Io` interface is still very new and unstable and +doesn't expose all the operations necessary to bring parity with libxev. + +We will investigate better, more idiomatic integrations with `std.Io` +in the future. For now, libxev continues to work Zig 0.16 but mostly as +it did in prior Zig versions and doesn't integrate with `std.Io`. + +## Example + +The example below shows an identical program written in Zig and in C +that uses libxev to run a single 5s timer. This is almost silly how +simple it is but is meant to just convey the overall feel of the library +rather than a practical use case. + + + + + + + + + +
Zig C
+ +```zig +const xev = @import("xev"); + +pub fn main() !void { + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + const w = try xev.Timer.init(); + defer w.deinit(); + + // 5s timer + var c: xev.Completion = undefined; + w.run(&loop, &c, 5000, void, null, &timerCallback); + + try loop.run(.until_done); +} + +fn timerCallback( + userdata: ?*void, + loop: *xev.Loop, + c: *xev.Completion, + result: xev.Timer.RunError!void, +) xev.CallbackAction { + _ = userdata; + _ = loop; + _ = c; + _ = result catch unreachable; + return .disarm; +} +``` + + + +```c +#include +#include +#include + +xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) { + return XEV_DISARM; +} + +int main(void) { + xev_loop loop; + if (xev_loop_init(&loop) != 0) { + printf("xev_loop_init failure\n"); + return 1; + } + + xev_watcher w; + if (xev_timer_init(&w) != 0) { + printf("xev_timer_init failure\n"); + return 1; + } + + xev_completion c; + xev_timer_run(&w, &loop, &c, 5000, NULL, &timerCallback); + + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); + + xev_timer_deinit(&w); + xev_loop_deinit(&loop); + return 0; +} +``` + +
+ +## Installation (Zig) + +**These instructions are for Zig downstream users only.** If you are +using the C API to libxev, see the "Build" section. + +This package works with the Zig package manager introduced in Zig 0.11. +Create a `build.zig.zon` file like this: + +```zig +.{ + .name = "my-project", + .version = "0.0.0", + .dependencies = .{ + .libxev = .{ + .url = "https://github.com/mitchellh/libxev/archive/.tar.gz", + .hash = "12208070233b17de6be05e32af096a6760682b48598323234824def41789e993432c", + }, + }, +} +``` + +And in your `build.zig`: + +```zig +const xev = b.dependency("libxev", .{ .target = target, .optimize = optimize }); +exe.addModule("xev", xev.module("xev")); +``` + +## Documentation + +🚧 Documentation is a work-in-progress. 🚧 + +Currently, documentation is available in three forms: **man pages**, +**examples**, and **code comments.** In the future, I plan on writing detailed +guides and API documentation in website form, but that isn't currently +available. + +### Man Pages + +The man pages are relatively detailed! `xev(7)` will +give you a good overview of the entire library. `xev-zig(7)` and +`xev-c(7)` will provide overviews of the Zig and C API, respectively. +From there, API-specifc man pages such as `xev_loop_init(3)` are +available. This is the best documentation currently. + +There are multiple ways to browse the man pages. The most immediately friendly +is to just browse the raw man page sources in the `docs/` directory in +your web browser. The man page source is a _markdown-like_ syntax so it +renders _okay_ in your browser via GitHub. + +Another approach is to run `zig build -Dman-pages` and the man pages +will be available in `zig-out`. This requires +[scdoc](https://git.sr.ht/~sircmpwn/scdoc) +to be installed (this is available in most package managers). +Once you've built the man pages, you can render them by path: + +``` +man zig-out/share/man/man7/xev.7 +``` + +And the final approach is to install libxev via your favorite package +manager (if and when available), which should hopefully put your man pages +into your man path, so you can just do `man 7 xev`. + +### Examples + +There are examples available in the `examples/` folder. The examples are +available in both C and Zig, and you can tell which one is which using +the file extension. + +To build an example, use the following: + +``` +$ zig build -Dexample-name=_basic.zig +... +$ zig-out/bin/example-basic +... +``` + +The `-Dexample-name` value should be the filename including the extension. + +### Code Comments + +The Zig code is well commented. If you're comfortable reading code comments +you can find a lot of insight within them. The source is in the `src/` +directory. + +# Build + +Build requires the installation of the Zig 0.16. libxev follows stable +Zig releases and generally does not support nightly builds. When a stable +release is imminent we may have a branch that supports it. +**libxev has no other build dependencies.** + +Once installed, `zig build install` on its own will build the full library and output +a [FHS-compatible](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard) +directory in `zig-out`. You can customize the output directory with the +`--prefix` flag. + +## Tests + +libxev has a large and growing test suite. To run the tests for the current +platform: + +```sh +$ zig build test +... +``` + +This will run all the tests for all the supported features for the current +host platform. For example, on Linux this will run both the full io_uring +and epoll test suite. + +**You can build and run tests for other platforms** by cross-compiling the + test executable, copying it to a target machine and executing it. For example, + the below shows how to cross-compile and build the tests for macOS from Linux: + + ```sh + $ zig build -Dtarget=aarch64-macos -Dinstall-tests + ... + + $ file zig-out/bin/xev-test + zig-out/bin/xev-test: Mach-O 64-bit arm64 executable + ``` + + **WASI is a special-case.** You can run tests for WASI if you have + [wasmtime](https://wasmtime.dev/) installed: + + ``` + $ zig build test -Dtarget=wasm32-wasi -Dwasmtime + ... + ``` diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig new file mode 100644 index 0000000..ec2da42 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig @@ -0,0 +1,342 @@ +const std = @import("std"); +const Step = std.Build.Step; + +/// A note on my build.zig style: I try to create all the artifacts first, +/// unattached to any steps. At the end of the build() function, I create +/// steps or attach unattached artifacts to predefined steps such as +/// install. This means the only thing affecting the `zig build` user +/// interaction is at the end of the build() file and makes it easier +/// to reason about the structure. +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("xev", .{ + .root_source_file = b.path("src/main.zig"), + }); + + const emit_man = b.option( + bool, + "emit-man-pages", + "Set to true to build man pages. Requires scdoc. Defaults to true if scdoc is found.", + ) orelse if (b.findProgram( + &[_][]const u8{"scdoc"}, + &[_][]const u8{}, + )) |_| + true + else |err| switch (err) { + error.FileNotFound => false, + }; + + const emit_bench = b.option( + bool, + "emit-bench", + "Install the benchmark binaries to zig-out", + ) orelse false; + + const emit_examples = b.option( + bool, + "emit-example", + "Install the example binaries to zig-out", + ) orelse false; + + // Static C lib + const static_lib: ?*Step.Compile = lib: { + if (target.result.os.tag == .wasi) break :lib null; + + const static_lib = b.addLibrary(.{ + .linkage = .static, + .name = "xev", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c_api.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + if (target.result.os.tag == .windows) { + static_lib.root_module.linkSystemLibrary("ws2_32", .{}); + static_lib.root_module.linkSystemLibrary("mswsock", .{}); + } + break :lib static_lib; + }; + + // Dynamic C lib + const dynamic_lib: ?*Step.Compile = lib: { + // We require native so we can link to libxml2 + if (!target.query.isNative()) break :lib null; + + const dynamic_lib = b.addLibrary(.{ + .linkage = .dynamic, + .name = "xev", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c_api.zig"), + .target = target, + .optimize = optimize, + }), + }); + break :lib dynamic_lib; + }; + + // C Headers + const c_header = b.addInstallFileWithDir( + b.path("include/xev.h"), + .header, + "xev.h", + ); + + // pkg-config + const pc: *Step.InstallFile = pc: { + const file = b.addWriteFile("libxev.pc", b.fmt( + \\prefix={s} + \\includedir=${{prefix}}/include + \\libdir=${{prefix}}/lib + \\ + \\Name: libxev + \\URL: https://github.com/mitchellh/libxev + \\Description: High-performance, cross-platform event loop + \\Version: 0.1.0 + \\Cflags: -I${{includedir}} + \\Libs: -L${{libdir}} -lxev + , .{b.install_prefix})); + break :pc b.addInstallFileWithDir( + file.getDirectory().path(b, "libxev.pc"), + .prefix, + "share/pkgconfig/libxev.pc", + ); + }; + + // Man pages + const man = try manPages(b); + + // Benchmarks and examples + const benchmarks = try buildBenchmarks(b, target); + const examples = try buildExamples(b, target, optimize, static_lib); + + // Test Executable + const test_exe: *Step.Compile = test_exe: { + const test_filter = b.option( + []const u8, + "test-filter", + "Filter for test", + ); + break :test_exe b.addTest(.{ + .name = "xev-test", + .filters = if (test_filter) |filter| &.{filter} else &.{}, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = switch (target.result.os.tag) { + .linux, .macos => true, + else => null, + }, + }), + }); + }; + + // "test" Step + { + const tests_run = b.addRunArtifact(test_exe); + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&tests_run.step); + } + + if (static_lib) |v| b.installArtifact(v); + if (dynamic_lib) |v| b.installArtifact(v); + b.getInstallStep().dependOn(&c_header.step); + b.getInstallStep().dependOn(&pc.step); + b.installArtifact(test_exe); + if (emit_man) { + for (man) |step| b.getInstallStep().dependOn(step); + } + if (emit_bench) for (benchmarks) |exe| { + b.getInstallStep().dependOn(&b.addInstallArtifact( + exe, + .{ .dest_dir = .{ .override = .{ + .custom = "bin/bench", + } } }, + ).step); + }; + if (emit_examples) for (examples) |exe| { + b.getInstallStep().dependOn(&b.addInstallArtifact( + exe, + .{ .dest_dir = .{ .override = .{ + .custom = "bin/example", + } } }, + ).step); + }; +} + +fn buildBenchmarks( + b: *std.Build, + target: std.Build.ResolvedTarget, +) ![]const *Step.Compile { + const io = b.graph.io; + const alloc = b.allocator; + var steps: std.ArrayList(*Step.Compile) = .empty; + defer steps.deinit(alloc); + + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join( + b.allocator, + &.{ "src", "bench" }, + ), + .{ .iterate = true }, + ); + defer dir.close(io); + + // Go through and add each as a step + var it = dir.iterate(); + while (try it.next(io)) |entry| { + // Get the index of the last '.' so we can strip the extension. + const index = std.mem.lastIndexOfScalar( + u8, + entry.name, + '.', + ) orelse continue; + if (index == 0) continue; + + // Name of the app and full path to the entrypoint. + const name = entry.name[0..index]; + + // Executable builder. + const exe = b.addExecutable(.{ + .name = name, + .root_module = b.createModule(.{ + .root_source_file = b.path(b.fmt( + "src/bench/{s}", + .{entry.name}, + )), + .target = target, + .optimize = .ReleaseFast, // benchmarks are always release fast + }), + }); + exe.root_module.addImport("xev", b.modules.get("xev").?); + + // Store the mapping + try steps.append(alloc, exe); + } + + return try steps.toOwnedSlice(alloc); +} + +fn buildExamples( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + c_lib_: ?*Step.Compile, +) ![]const *Step.Compile { + const io = b.graph.io; + const alloc = b.allocator; + var steps: std.ArrayList(*Step.Compile) = .empty; + defer steps.deinit(alloc); + + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join( + b.allocator, + &.{"examples"}, + ), + .{ .iterate = true }, + ); + defer dir.close(io); + + // Go through and add each as a step + var it = dir.iterate(); + while (try it.next(io)) |entry| { + // Get the index of the last '.' so we can strip the extension. + const index = std.mem.lastIndexOfScalar( + u8, + entry.name, + '.', + ) orelse continue; + if (index == 0) continue; + + // Name of the app and full path to the entrypoint. + const name = entry.name[0..index]; + + const is_zig = std.mem.eql(u8, entry.name[index + 1 ..], "zig"); + const exe: *Step.Compile = if (is_zig) exe: { + const exe = b.addExecutable(.{ + .name = name, + .root_module = b.createModule(.{ + .root_source_file = b.path(b.fmt( + "examples/{s}", + .{entry.name}, + )), + .target = target, + .optimize = optimize, + }), + }); + exe.root_module.addImport("xev", b.modules.get("xev").?); + break :exe exe; + } else exe: { + const c_lib = c_lib_ orelse return error.UnsupportedPlatform; + const exe = b.addExecutable(.{ + .name = name, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + exe.root_module.addIncludePath(b.path("include")); + exe.root_module.addCSourceFile(.{ + .file = b.path(b.fmt( + "examples/{s}", + .{entry.name}, + )), + .flags = &[_][]const u8{ + "-Wall", + "-Wextra", + "-pedantic", + "-std=c99", + "-D_POSIX_C_SOURCE=199309L", + }, + }); + exe.root_module.linkLibrary(c_lib); + break :exe exe; + }; + + // Store the mapping + try steps.append(alloc, exe); + } + + return try steps.toOwnedSlice(alloc); +} + +fn manPages(b: *std.Build) ![]const *Step { + const io = b.graph.io; + const alloc = b.allocator; + var steps: std.ArrayList(*Step) = .empty; + defer steps.deinit(alloc); + + var dir = try std.Io.Dir.cwd().openDir( + io, + try b.build_root.join(b.allocator, &.{"docs"}), + .{ .iterate = true }, + ); + defer dir.close(io); + + var it = dir.iterate(); + while (try it.next(io)) |*entry| { + // Filenames must end in "{section}.scd" and sections are + // single numerals. + const base = entry.name[0 .. entry.name.len - 4]; + const section = base[base.len - 1 ..]; + + const cmd = b.addSystemCommand(&.{"scdoc"}); + cmd.setStdIn(.{ .lazy_path = b.path( + b.fmt("docs/{s}", .{entry.name}), + ) }); + + try steps.append(alloc, &b.addInstallFile( + cmd.captureStdOut(.{}), + b.fmt("share/man/man{s}/{s}", .{ section, base }), + ).step); + } + + return try steps.toOwnedSlice(alloc); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig.zon b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig.zon new file mode 100644 index 0000000..cf53cc8 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig.zon @@ -0,0 +1,7 @@ +.{ + .name = .libxev, + .minimum_zig_version = "0.16.0", + .version = "0.0.0", + .fingerprint = 0x30f7363573edabf3, + .paths = .{""}, +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-c.7.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-c.7.scd new file mode 100644 index 0000000..011bb9c --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-c.7.scd @@ -0,0 +1,12 @@ +xev-c(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual" + +# NAME + +libxev C API + +# DESCRIPTION + +See xev(7) for a general libxev overview. This man page will give a more +specific overview of the C API for libxev. + +TODO -- This isn't written yet, sorry. diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-faq.7.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-faq.7.scd new file mode 100644 index 0000000..af3e803 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-faq.7.scd @@ -0,0 +1,48 @@ +xev-faq(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual" + +# NAME + +libxev Frequently Asked Questions (FAQ) + +# DESCRIPTION + +This manual page contains frequently asked questions around the design +and usage of libxev. The goal of this page is to collect various tips or +varios common challenges people run into. The information in this page +may duplicate documentation that is available for a specific API or +concept. + +# FAQ + +## HOW CAN I CHECK IF A COMPLETION, TIMER, SOCKET, ETC. IS ACTIVE? + +Some other event loops (libuv, libev) have a function that can check if +a given _thing_ (timer, socket, etc.) is "active." "Active" means that the +thing in question is part of an event loop and _running_ (the definition of +running is dependent on the type of the watcher). + +For libxev, the correct question to ask is: is this _completion_ active? +A completion represents a single operation and can be used to determine +if a corresponding watcher is active if the complation was used with +the watcher. + +Completion state can be checked using the `c.state()` function (Zig) or +the xev_completion_state(3) function (C). This will return an enum value +of "dead" or "alive". More enum values may be added in the future if +it is determined that finer grained state is useful. + +# SUGGEST A TOPIC + +There is still a lot of improvement to be made to the documentation. Please +suggest any topics to cover at . + +# SEE ALSO + +xev(7) + + + +# AUTHORS + +Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors. +See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-zig.7.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-zig.7.scd new file mode 100644 index 0000000..510ac77 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-zig.7.scd @@ -0,0 +1,136 @@ +xev-zig(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual" + +# NAME + +libxev Zig API + +# DESCRIPTION + +See xev(7) for a general libxev overview. This man page will give a more +specific overview of the Zig API for libxev. + +libxev is written in Zig and exports a native Zig API. The Zig API +takes advantage of first-class Zig concepts such as comptime parameters, +error sets, etc. in order to provide an idiomatic Zig experience. Beyond +basic idioms, these Zig features usually result in improved performance +over the C API. For example, all callbacks in the Zig API must be available +at comptime because the callback call is always inlined -- this results in +a noticable performance improvement over equivalent C consumption of libxev. + +The primary Zig API is visible in `src/main.zig` in the libxev source. + +# INSTALLATION + +libxev has no dependencies, making it easy to install into your Zig project +using any available Zig package manager or Git submodules. The `build.zig` +file exports a `module` constant that you can use with `addAnonymousModule`: + +``` +// build.zig + +const libxev = @import("submodules/libxev/build.zig"); + +pub fn build(b: *std.build.Builder) !void { + // Your other build options... + + my_exe.addAnonymousModule("xev", libxev.module); +} +``` + +The package is then available in your source code as "xev": + +``` +// Your main.zig + +const xev = @import("xev"); +``` + +# QUICK START + +## INITIALIZING A LOOP + +After importing xev, the first thing you'll need is an event loop: + +``` +var loop = try xev.Loop.init(.{}); +defer loop.deinit(); // or wherever your cleanup is +``` + +This initializes the resources associated with an event loop. An event loop +_can be copied_ until the first `run` is called. Once `run` is called, +the loop *must be at a stable memory location* (pointer won't change). + +## ADDING A COMPLETION + +An empty event loop does nothing. You must add one or more _completions_ +to request work to be done asynchronously. A completion is represented with +the `xev.Completion` structure. A completion *must have a stable memory +location when it is added to the loop* until the completion is finished. +xev(7) talks more about completion lifetimes and memory allocation requirements. + +The example below adds a timer to the previously initialized event loop: + +``` +var c_timer: xev.Completion = undefined; +const timer = try xev.Timer.init(); +timer.run(&loop, &c_timer, 5_000, void, null, timerCallback); +``` + +This uses the `xev.Timer` _high-level abstraction_. This is an abstraction +that provides a common API on top of multiple operating system async APIs. +You can also use the low-level API to manage completions with the loop directly, +but these are not portable. + +**Important concept to notice:** The completion allocation is up to the +program author. It can be stack allocated (such as in this case) or heap +allocated. The pointer must remain stable until it is complete. This gives +program authors substantial power over optimizing the performance of libxev. +In fact, libxev doesn't perform _any_ dynamic memory allocation. + +## RUNNING THE LOOP + +An added completion is a request for future work. The work does not start +until the completion is submitted. A completion is submitted only during +an event loop tick. To tick the event loop, use the `run` method: + +``` +try loop.run(.until_done); +``` + +The enum value is an `xev.RunMode` described in xev(7). This call will run +until the loop has no more active completions (`until_done`). At +some point (in about 5 seconds), this will call the registered `timerCallback`. + +# MEMORY ALLOCATIONS + +You'll notice that none of the Zig APIs take an allocator as an argument. +libxev does not perform any dynamic memory allocations. All memory must be +provided by the caller. This section will outline specific memory management +rules you should be aware of when using libxev. + +*xev.Loop.* This can be copied and moved until `submit`, `tick`, or `run` +is called. Once any of those loop functions are called, the loop memory +must remain at a stable address. It is not safe to copy, move, or reuse the +memory that loop is occupying until `deinit` completes. + +*xev.Completion.* This can be copied and moved until the completion is +added to a loop. A completion is often added to a loop with any function +call on a high-level abstraction that takes a completion pointer as a function +argument such as `xev.Timer.run`. The completion memory can be reclaimed +only when the callback associated with it is fired or the loop is deinitialized. + +# SUGGEST A TOPIC + +There is still a lot of improvement to be made to the documentation. Please +suggest any topics to cover at . + +# SEE ALSO + +xev(7) + + + +# AUTHORS + +Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors. +See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev.7.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev.7.scd new file mode 100644 index 0000000..cab21d9 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev.7.scd @@ -0,0 +1,338 @@ +xev(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual" + +# NAME + +libxev - high-performance, cross-platform event loop + +# DESCRIPTION + +*libxev* is a high-performance, cross-platform event loop. libxev provides a +unified event loop abstraction for non-blocking IO, timers, signals, events, +and more that works on macOS, Windows, Linux, and WebAssembly (browser and WASI). +It is written in Zig but exports a C-compatible API (which makes it compatible +with any language out there that can communicate with C APIs). + +This manual will focus on general libxev concepts. For details specific +to the C API see xev-c(7) and for details specific to the Zig API see +xev-zig(7). + +# FEATURES + +*Cross-platform.* Linux (io_uring(7) and epoll(7)), macOS (kqueue(2)), +WebAssembly + WASI (poll_oneoff(2), threaded and non-threaded runtimes). +(Windows support is planned and coming soon) + +*Proactor API.* Work is submitted to the libxev event loop and the caller +is notified of work _completion_, as opposed to work _readiness_. + +*Zero runtime allocations.* This helps make runtime performance more +predictable and makes libxev well suited for embedded environments. + +*Timers, TCP, UDP.* High-level platform-agnostic APIs for interacting +with timers, TCP/UDP sockets, and more. + +*Generic Thread Pool (Optional).* You can create a generic thread pool, +configure its resource utilization, and use this to perform custom background +tasks. The thread pool is used by some backends to do non-blocking tasks that +don't have reliable non-blocking APIs (such as local file operations with +kqueue(7)). The thread pool can be shared across multiple threads and event +loops to optimize resource utilization. + +*Low-level and High-Level API.* The high-level API is platform-agnostic +but has some opinionated behavior and limited flexibility. The high-level +API is recommended but the low-level API is always an available escape hatch. +The low-level API is platform-specific and provides a mechanism for libxev +users to squeeze out maximum performance. The low-level API is _just enough +abstraction_ above the OS interface to make it easier to use without +sacrificing noticable performance. + +*Tree Shaking (Zig).* This is a feature of Zig, but substantially benefits +libraries such as libxev. Zig will only include function calls and features +that you actually use. If you don't use a particular kind of high-level +watcher (such as UDP sockets), then the functionality related to that +abstraction is not compiled into your final binary at all. This lets libxev +support optional "nice-to-have" functionality that may be considered +"bloat" in some cases, but the end user doesn't have to pay for it. + +*Dependency-free.* libxev has no dependencies other than the built-in +OS APIs at runtime. The C library depends on libc. This makes it very +easy to cross-compile. + +# EVENT LOOP PROGRAMMING + +Event loop programming is a programming design pattern where a program +registers multiple events and is notified when those events occur. +Concretely, event loop programming is typically used as a foundational +component of asynchronous programs. It is the core mechanism used to +for asynchronous network IO, disk IO, timers, signals, and more. + +There are two popular event loop styles: _proactor_ and _reactor_. +The reactor pattern notifies the event loop user of task _readiness_, +whereas the proactor pattern notifies the event loop user of task _completion_. +Examples of reactor APIs: POSIX poll(2), BSD kqueue(2). Examples of proactor +APIs: Linux io_uring(7), Windows IOCP, and JavaScript IO callbacks. +*libxev is a proactor event loop.* + +# LIBXEV GENERAL CONCEPTS + +## TERMINOLOGY + +- *Loop*: An instance of an event loop. + +- *Completion*: A request to perform some work. A completion is _queued_ + in an event loop, and an associated callback is invoked when the work + is completed. + +- *Watcher*: A slightly higher level abstraction to make it easier to + work with common capabilities in a cross-platform way. For example, + files, sockets, async/notify patterns, etc. These are just opinionated + logic and sugar on top of completions. + +- *Disarm/Rearm*: A completion that is actively being worked on by the + event loop is considered _armed_. When a completion is complete, the + program can choose to _disarm_ or _rearm_ the completion. If a completion + is disarmed, it is no longer in use. If a completion is rearmed, it will + repeat its work and fire the associated callback again. + +## MEMORY ALLOCATION + +libxev doesn't do any runtime memory allocation. The caller is expected +to allocate memory and provide libxev with pointers. The caller is then +free to allocate on the stack or heap in the way that is best suited for +their program and lifecycle. + +The lifecycles of various resources in libxev are documented in their +resource-specific documentation sections. libxev _never_ takes ownership +of a programmer-provided memory location. + +## LOOPS + +The `xev.Loop` (Zig) and `xev_loop` (C) types represent a single event +loop. A program may have multiple event loops, but most typically there +is at most one per thread. If you are just getting started, just use a +single event loop per thread until you're more comfortable with the API. + +Completions are _added_ to the event loop. Completions are explained +in more detail in the next section but at a high level represent a request +to perform some work. The event loop does NOT begin performing any work +until the loop is run. This is important, so to repeat it in another way: +an _added_ completion does nothing until the next time a loop is run. + +An event loop is _run_ with `loop.run` (Zig) or xev_loop_run(3) (C). +The first thing the event loop does is _submit_ any _added_ completions. +Submitted completions begin their requested work in the background. If a +completion doesn't complete and the loop run returns (such as with a "no wait" +run mode -- covered later), the work will continue in the background and +may be checked for completion with another loop tick. + +A loop can be run in multiple _run modes_: + +- *No Wait.* This runs through the loop without blocking on any completions. + If a completion is ready, the callbacks will be fired, but otherwise + the loop will return. + +- *Once.* This runs the loop and waits for at least one completion to become + ready before returning. + +- *Until Done.* This runs the loop and waits until there are no more + completions in the event loop. This can potentially run forever as + completion callbacks rearm or register new completions. This is the + most common run mode and is usually used to start up the "main" loop + of a program. The loop can be stopped from the main thread using the + `stop` API call. + +An event loop has to be allocated to a stable memory address (stable +pointer) _once you have called `run`_ once. Prior to calling run, you +can copy the loop value. However, once the loop has run any times +(even a no wait run once), the loop pointer must remain stable. + +## COMPLETIONS + +The `xev.Completion` (Zig) and `xev_completion` (C) types represent a +single request to do some work, such as read or write a file, accept +a network connection, sleep on a timer, etc. + +Completions do nothing until they are _added_ to an event loop (and +even then, do nothing until the next event loop tick). Completions must +only be added to one event loop at a time. After a completion is dead +(see states below), it can be used with another event loop or the memory +can be reclaimed. + +Completions have multiple states that are managed by libxev: + +- *Dead.* The completion can be configured for new work and added to + an event loop. The completion is not actively being used by the loop + so you can also free memory associated with it. This is its initial state. + +- *Added.* The completion is queued to be submitted to an event loop. + The completion must no longer be modified. + +- *Active.* The completion is submitted as part of an event loop and actively + performing some work. The completion must no longer be modified. + +The memory associated with a completion is always owned by the program +author. libxev never takes ownership of memory and never dynamically +allocates or free memory on its own. The memory associated with a completion +cannot be freed unless the the completion is dead. + +A completion is dead only in the following scenarios: + +- The completion has never been added to an event loop. + +- The completion callback has been fired and the callback return action + was "disarm." The completion memory is safe to free _during the callback_, + in this case, too. + +- The event loop that a completion was added to has been deinitialized. + Even if the completion never fired, all system resources registered by + libxev for the completion are no longer in use. + +## CALLBACKS + +When the work associated with a completion has been completed, libxev +will invoke the registered callback. At a low-level, all callbacks have +the same function signature. The Zig signature is shown below. + +``` +pub const xev.Callback = *const fn ( + userdata: ?*anyopaque, + loop: *xev.Loop, + completion: *xev.Completion, + result: xev.Result, +) xev.CallbackAction; +``` + +*NOTE:* The "low-level" word above is important. In general, program authors +will be using the _high-level_ APIs which have varied and more +programmer-friendly callback signatures depending on the feature. For example, +TCP connection accept will provide the new connection in the callback. +Underneath these abstractions, however, this regular completion callback +signature is used. + +Every callback gets access to some optional programmer-provided userdata, +the loop where the completion was added, the completion itself, and a +result union. +The result of the callback is the action to take and is either "disarm" +or "rearm" (covered in TERMINOLOGY above). + +Some callback tips for common behaviors: + +- You can reuse the completion for a different operation now as long as + you return the disarm action. For example, after a TCP connection callback, + you can reuse the same completion now to begin writing data. The "loop" + parameter is specifically provided to make this easy. + +- You can free the memory associated with a completion from the callback + if you no longer intend to use it. In fact, its unsafe to free memory + for an active completion except after the callback is fired (or the + event loop is deinitialized). + +# EXAMPLE (C) + +The example below shows how the C API can be used to initialize an event +loop and run a 5 second timer. To learn more about the C API, see +xev-c(7). + +``` +#include +#include +#include + +xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) { + return XEV_DISARM; +} + +int main(void) { + // Initialize the loop state. Notice we can use a stack-allocated + // value here. We can even pass around the loop by value! The loop + // will contain all of our "completions" (watches). + xev_loop loop; + if (xev_loop_init(&loop, 128) != 0) { + printf("xev_loop_init failure\n"); + return 1; + } + + // Initialize a completion and a watcher. A completion is the actual + // thing a loop does for us, and a watcher is a high-level structure + // to help make it easier to use completions. + xev_completion c; + xev_watcher w; + + // In this case, we initialize a timer watcher. + if (xev_timer_init(&w) != 0) { + printf("xev_timer_init failure\n"); + return 1; + } + + // Configure the timer to run in 5s. This requires the completion that + // we actually configure to become a timer, and the loop that the + // completion should be registered with. + xev_timer_run(&w, &loop, &c, 5000, NULL, &timerCallback); + + // Run the loop until there are no more completions. + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); + + xev_timer_deinit(&w); + xev_loop_deinit(&loop); + return 0; +} +``` + +# EXAMPLE (ZIG) + +The example below shows how the Zig API can be used to initialize an event +loop and run a 5 second timer. To learn more about the Zig API, see +xev-zig(7). + +``` +const xev = @import("xev"); + +pub fn main() !void { + // Initialize the loop state. Notice we can use a stack-allocated + // value here. We can even pass around the loop by value! The loop + // will contain all of our "completions" (watches). + var loop = try xev.Loop.init(.{ .entries = 128 }); + defer loop.deinit(); + + // Initialize a completion and a watcher. A completion is the actual + // thing a loop does for us, and a watcher is a high-level structure + // to help make it easier to use completions. + var c: xev.Completion = undefined; + + // In this case, we initialize a timer watcher. + const w = try xev.Timer.init(); + defer w.deinit(); + + // Configure the timer to run in 5s. This requires the completion that + // we actually configure to become a timer, and the loop that the + // completion should be registered with. + w.run(&loop, &c, 5000, void, null, &timerCallback); + + // Run the loop until there are no more completions. + try loop.run(.until_done); +} + +fn timerCallback( + userdata: ?*void, + loop: *xev.Loop, + c: *xev.Completion, + result: xev.Timer.RunError!void, +) xev.CallbackAction { + _ = userdata; + _ = loop; + _ = c; + _ = result catch unreachable; + return .disarm; +} +``` + +# SEE ALSO + +xev-c(7), xev-zig(7) + + + +# AUTHORS + +Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors. +See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_state.3.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_state.3.scd new file mode 100644 index 0000000..8e23337 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_state.3.scd @@ -0,0 +1,48 @@ +xev_completion_state(3) "github.com/mitchellh/libxev" "Library Functions Manual" + +# NAME + +xev_completion_state - check the state of a completion + +# LIBRARY + +libxev (_-lxev_) + +# SYNOPSIS + +``` +#include ; + +xev_completion_state_t state = xev_completion_state(&c); +``` + +# DESCRIPTION + +*xev_completion_state* returns the current state of a completion: dead +if the completion is not currently used or active if the completion is +part of an event loop. + +The state is sometimes useful to determine if an operation has already +started or not. For example, the completion state can be used to check if +a timer is running or not. + +The completion must be initialized if there is any chance the program author +may call this function prior to the completion being used. To initialize +a completion use xev_completion_zero(3). + +This function can only be called from the main thread. + +# RETURN VALUES + +The return value is always a valid xev_completion_state_t enum value. + +# SEE ALSO + +xev_completion_state(3), xev(7), xev-c(7) + + + +# AUTHORS + +Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors. +See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_zero.3.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_zero.3.scd new file mode 100644 index 0000000..72e0915 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_zero.3.scd @@ -0,0 +1,44 @@ +xev_completion_zero(3) "github.com/mitchellh/libxev" "Library Functions Manual" + +# NAME + +xev_completion_zero - set a completion to the zero value + +# LIBRARY + +libxev (_-lxev_) + +# SYNOPSIS + +``` +#include ; + +xev_completion_zero(&c); +``` + +# DESCRIPTION + +*xev_completion_zero* sets default values for the given completion, rather +than uninitialized memory state. This is particularly useful if you're using +xev_completion_state(3). Otherwise, it isn't necessary. + +_You typically do NOT need to call this!_ You are allowed to pass uninitialized +completions to almost all functions that start an operation. However, you +_do need to call this_ if you are inspecting completion state (i.e. +xev_completion_state(3)) before the completion is started. + +You do NOT need to call this after you are done using a completion. Once +a completion has been used, the value of the completion is always initialized. +The ONLY time you need to call this is if you're inspecting completion state +prior to the completion ever being used. + +# SEE ALSO + +xev_completion_state(3), xev(7), xev-c(7) + + + +# AUTHORS + +Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors. +See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_threadpool.3.scd b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_threadpool.3.scd new file mode 100644 index 0000000..27fefc4 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_threadpool.3.scd @@ -0,0 +1,167 @@ +xev_threadpool(3) "github.com/mitchellh/libxev" "Library Functions Manual" + +# NAME + +xev_threadpool - generic thread pool for scheduling work + +# LIBRARY + +libxev (_-lxev_) + +# SYNOPSIS + +``` +#include ; + +xev_threadpool pool; +``` + +# DESCRIPTION + +*xev_threadpool* is a generic thread pool that is lock-free, allocation-free +(except spawning threads), supports batch scheduling, dynamically spawns +threads, and handles thread spawn failure. It can be used to queue work that +should be executed on other threads as resources permit. + +This man page focuses on the C API for the thread pool. The Zig API +can be discovered by reading the `src/ThreadPool.zig` file. This page will +provide a broad overview of using the thread pool API but won't cover +specific details such as an exhaustive list of error return codes. + +There are multiple types that are important when working with thread pools. + +- *xev_threadpool* is the pool itself. +- *xev_threadpool_config* is used to configure a new pool. +- *xev_threadpool_task* is a single task to execute. +- *xev_threadpool_batch* is a batch of zero or more tasks. + +All the types are expected to be allocated by the program author. They +can be allocated on the stack or heap, but their pointer lifetimes must +remain valid as documented throughout this manual. + +# POOL CREATION AND MANAGEMENT + +To start, a pool must be initialized. The easiest way to initialize a pool +is with xev_threadpool_init(3) and no configuration. The default configuration +will choose reasonable defaults. + +``` +xev_threadpool pool; +assert(xev_threadpool_init(&pool, null)); +``` + +The pool is now ready to have task batches scheduled to it. To shutdown +a pool, you must call xev_shutdown(3) and xev_deinit(3). + +``` +xev_shutdown(&pool); +xev_deinit(&pool); +``` + +The xev_shutdown(3) call notifies all threads that the pool is in a shutdown +state. They complete their most recent tasks, shut down, and accept no new +work. This function returns _immediately_. The xev_deinit(3) call waits for +all the threads in the pool to exit, then cleans up any additional state. +xev_deinit(3) and xev_shutdown(3) can be called in any order. + +The xev_threadpool(3) value must be pointer-stable until after +xev_deinit(3) returns. After this point, no other xev_threadpool API +calls can be called using the pointer. You may reinitialize and reuse the +value. + +# TASK SCHEDULING + +A task is a single unit of work. A task is inserted into a _batch_. +A batch is scheduled with a _pool_. The first step is to define a task: + +``` +void do_expensive_work(xev_threadpool_task* t) {} + +xev_threadpool_task t; +xev_threadpool_task_init(&t, &do_expensive_work); +``` + +The task then must be added to a batch. The code below creates a batch +with a single item "t". You can use xev_threadpool_batch_push_batch(3) to merge +multiple batches. + +``` +xev_threadpool_batch b; +xev_threadpool_batch_init(&b); +xev_threadpool_batch_push_task(&b, &t); +``` + +Finally, the batch must be scheduled with the pool with +xev_threadpool_schedule(3): + +``` +xev_threadpool_schedule(&pool, &b); +``` + +The scheduled work can be picked up immediately. The work is executed +on a separate thread so if resources are available, the work may begin +immediately. Otherwise, it is queued for execution later. + +You can call xev_threadpool_schedule(3) from multiple threads against +the same pool to schedule work concurrently. You MUST NOT read or write +batches concurrently; for concurrent scheduling each thread should build +up its own batch. + +## MEMORY LIFETIMES + +- The task "t" must be pointer-stable until the task has completed execution. + +- The task "t" can only be scheduled in one pool exactly one time until + it has completed execution. You CAN NOT initialize a task and add it to + multiple batches. + +- The batch "b" can be copied and doesn't need to be pointer-stable. The + batch can be freed at anytime. + +## TASK STATE + +The callback type only gives access to the `xev_threadpool_task` pointer. +To associate state or userdata with a task, make the task a member of +a struct and use the `container_of` macro (shown below) to access the +parent container. + +An example is shown below: + +``` +typedef struct { + xev_threadpool_task task; + bool state; + + // other state can be here, too. +} work_t; + + +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + +void do_expensive_work(xev_threadpool_task* t) { + work_t *work = container_of(t, work_t, task); + work->state = true; +} + +work_t work; +xev_threadpool_task_init(&work.task, &do_expensive_work); +``` + +*IMPORTANT:* The `xev_threadpool_task` must be aligned to a power-of-2 +memory address. When using it in a struct, be careful that it is properly +aligned. + +# SEE ALSO + +xev(7), xev-c(7) + + + + + +# AUTHORS + +King Protty (https://github.com/kprotty) is the author of the thread pool. +Mitchell Hashimoto (xmitchx@gmail.com) is the author of the C API and +documentation. Plus any open source contributors. See . diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.c b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.c new file mode 100644 index 0000000..718fe50 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.c @@ -0,0 +1,42 @@ +#include +#include +#include + +xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) { + return XEV_DISARM; +} + +int main(void) { + // Initialize the loop state. Notice we can use a stack-allocated + // value here. We can even pass around the loop by value! The loop + // will contain all of our "completions" (watches). + xev_loop loop; + if (xev_loop_init(&loop) != 0) { + printf("xev_loop_init failure\n"); + return 1; + } + + // Initialize a completion and a watcher. A completion is the actual + // thing a loop does for us, and a watcher is a high-level structure + // to help make it easier to use completions. + xev_completion c; + xev_watcher w; + + // In this case, we initialize a timer watcher. + if (xev_timer_init(&w) != 0) { + printf("xev_timer_init failure\n"); + return 1; + } + + // Configure the timer to run in 1ms. This requires the completion that + // we actually configure to become a timer, and the loop that the + // completion should be registered with. + xev_timer_run(&w, &loop, &c, 1, NULL, &timerCallback); + + // Run the loop until there are no more completions. + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); + + xev_timer_deinit(&w); + xev_loop_deinit(&loop); + return 0; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.zig new file mode 100644 index 0000000..2398ac4 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const xev = @import("xev"); + +pub fn main() !void { + // Initialize the loop state. Notice we can use a stack-allocated + // value here. We can even pass around the loop by value! The loop + // will contain all of our "completions" (watches). + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + // Initialize a completion and a watcher. A completion is the actual + // thing a loop does for us, and a watcher is a high-level structure + // to help make it easier to use completions. + var c: xev.Completion = undefined; + const timer = try xev.Timer.init(); + timer.run(&loop, &c, 1, void, null, timerCallback); + + // Run the loop until there are no more completions. + try loop.run(.until_done); +} + +fn timerCallback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + result: xev.Timer.RunError!void, +) xev.CallbackAction { + _ = result catch unreachable; + return .disarm; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/async.c b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/async.c new file mode 100644 index 0000000..5cbe33a --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/async.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#define UNUSED(v) ((void)v); + +xev_cb_action timer_callback(xev_loop* loop, xev_completion* c, int result, void *userdata) { + UNUSED(loop); UNUSED(c); UNUSED(result); + + // Send the notification to our async which will wake up the loop and + // call the waiter callback. + xev_async_notify((xev_watcher *)userdata); + return XEV_DISARM; +} + +xev_cb_action async_callback(xev_loop* loop, xev_completion* c, int result, void *userdata) { + UNUSED(loop); UNUSED(c); UNUSED(result); + + bool *notified = (bool *)userdata; + *notified = true; + return XEV_DISARM; +} + +int main(void) { + xev_loop loop; + if (xev_loop_init(&loop) != 0) { + printf("xev_loop_init failure\n"); + return 1; + } + + // Initialize an async watcher. An async watcher can be used to wake up + // the event loop from any thread. + xev_completion async_c; + xev_watcher async; + if (xev_async_init(&async) != 0) { + printf("xev_async_init failure\n"); + return 1; + } + + // We start a "waiter" for the async watcher. Only one waiter can + // ever be set at a time. This callback will be called when the async + // is notified (via xev_async_notify). + bool notified = false; + xev_async_wait(&async, &loop, &async_c, ¬ified, &async_callback); + + // Initialize a timer. The timer will fire our async. + xev_completion timer_c; + xev_watcher timer; + if (xev_timer_init(&timer) != 0) { + printf("xev_timer_init failure\n"); + return 1; + } + xev_timer_run(&timer, &loop, &timer_c, 1, &async, &timer_callback); + + // Run the loop until there are no more completions. This means + // that both the async watcher AND the timer have to complete. + // Notice that if you comment out `xev_async_notify` in the timer + // callback this blocks forever (because the async watcher is waiting). + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); + + if (!notified) { + printf("FAIL! async should've been notified!"); + return 1; + } + + xev_timer_deinit(&timer); + xev_async_deinit(&async); + xev_loop_deinit(&loop); + return 0; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/million-timers.c b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/million-timers.c new file mode 100644 index 0000000..8927bc6 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/million-timers.c @@ -0,0 +1,91 @@ +// libuv million-timers benchmark ported to libxev +#include +#include +#include +#include +#include + +#define NUM_TIMERS (10 * 1000 * 1000) + +static int timer_cb_called; + +xev_cb_action timer_cb(xev_loop* loop, xev_completion* c, int result, void *userdata) { + timer_cb_called++; + return XEV_DISARM; +} + +#ifdef _WIN32 +#include +uint64_t hrtime(void) { + static int initialized = 0; + static LARGE_INTEGER start_timestamp; + static uint64_t qpc_tick_duration; + + if (!initialized) { + initialized = 1; + + LARGE_INTEGER qpc_freq; + QueryPerformanceFrequency(&qpc_freq); + qpc_tick_duration = 1e9 / qpc_freq.QuadPart; + + QueryPerformanceCounter(&start_timestamp); + } + + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + t.QuadPart -= start_timestamp.QuadPart; + + return (uint64_t)t.QuadPart * qpc_tick_duration; +} +#else +uint64_t hrtime(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_nsec + (ts.tv_sec * 1e9); +} +#endif + +int main(void) { + xev_watcher* timers; + xev_completion* completions; + xev_loop loop; + uint64_t before_all; + uint64_t before_run; + uint64_t after_run; + uint64_t after_all; + int timeout; + int i; + int err; + + timers = malloc(NUM_TIMERS * sizeof(timers[0])); + completions = malloc(NUM_TIMERS * sizeof(completions[0])); + + if ((err = xev_loop_init(&loop)) != 0) { + fprintf(stderr, "xev_loop_init failure\n"); + return 1; + } + timeout = 1; + + before_all = hrtime(); + for (i = 0; i < NUM_TIMERS; i++) { + if (i % 1000 == 0) timeout++; + xev_timer_init(timers + i); + xev_timer_run(timers + i, &loop, completions + i, timeout, NULL, &timer_cb); + } + + before_run = hrtime(); + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); + after_run = hrtime(); + after_all = hrtime(); + + if (timer_cb_called != NUM_TIMERS) return 1; + free(timers); + free(completions); + + fprintf(stderr, "%.2f seconds total\n", (after_all - before_all) / 1e9); + fprintf(stderr, "%.2f seconds init\n", (before_run - before_all) / 1e9); + fprintf(stderr, "%.2f seconds dispatch\n", (after_run - before_run) / 1e9); + fprintf(stderr, "%.2f seconds cleanup\n", (after_all - after_run) / 1e9); + fflush(stderr); + return 0; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/threadpool.c b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/threadpool.c new file mode 100644 index 0000000..d99c573 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/threadpool.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include + +// We create a job struct that can contain additional state that we use +// for our threadpool tasks. In this case, we only have a boolean to note +// we're done but you can imagine any sort of user data here! +typedef struct { + xev_threadpool_task pool_task; + + bool done; +} job_t; + +// We use the container_of trick to access our job_t from a threadpool task. +// See task_callback. +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + +// This is the callback that is invoked when the task is being worked on. +void task_callback(xev_threadpool_task* t) { + job_t *job = container_of(t, job_t, pool_task); + job->done = true; +} + +int main(void) { + xev_threadpool pool; + if (xev_threadpool_init(&pool, NULL) != 0) { + printf("xev_threadpool_init failure\n"); + return 1; + } + + // A "batch" is used to group together multiple tasks that we schedule + // atomically into the thread pool. We initialize an empty batch that + // we'll add our tasks to. + xev_threadpool_batch batch; + xev_threadpool_batch_init(&batch); + + // Create all our tasks we want to submit. The number here can be changed + // to anything! + const int TASK_COUNT = 128; + job_t jobs[TASK_COUNT]; + for (int i = 0; i < TASK_COUNT; i++) { + jobs[i].done = false; + xev_threadpool_task_init(&jobs[i].pool_task, &task_callback); + xev_threadpool_batch_push_task(&batch, &jobs[i].pool_task); + } + + // Schedule our batch. This will immediately queue and start the tasks + // if there are available threads. This will also automatically start + // threads as needed. After this, you can reclaim the memory associated + // with "batch". + xev_threadpool_schedule(&pool, &batch); + + // We need a way to detect that our work is done. Normally here you'd + // use some sort of waitgroup or signal the libxev loop or something. + // Since this example is showing ONLY the threadpool, we just do a + // somewhat unsafe thing and just race on done booleans... + while (true) { + bool done = true; + for (int i = 0; i < TASK_COUNT; i++) { + if (!jobs[i].done) { + done = false; + break; + } + } + if (done) break; + } + + // Shutdown notifies the threadpool to notify the threads it has launched + // to start shutting down. This MUST be called. + xev_threadpool_shutdown(&pool); + + // Deinit reclaims memory. + xev_threadpool_deinit(&pool); + + printf("%d tasks completed!\n", TASK_COUNT); + return 0; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.lock b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.lock new file mode 100644 index 0000000..31c5406 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.lock @@ -0,0 +1,126 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1759205709, + "narHash": "sha256-oIwLpsAOGaGbotzQ5B+sZZqVsR0yZALER3tkGL3pH44=", + "rev": "5ed4e25ab58fd4c028b59d5611e14ea64de51d23", + "type": "tarball", + "url": "https://releases.nixos.org/nixos/25.05/nixos-25.05.810541.5ed4e25ab58f/nixexprs.tar.xz" + }, + "original": { + "type": "tarball", + "url": "https://channels.nixos.org/nixos-25.05/nixexprs.tar.xz" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1771043024, + "narHash": "sha256-O1XDr7EWbRp+kHrNNgLWgIrB0/US5wvw9K6RERWAj6I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3aadb7ca9eac2891d52a9dec199d9580a6e2bf44", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "zig": "zig" + } + }, + "systems": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-compat": "flake-compat_2", + "nixpkgs": "nixpkgs_2", + "systems": "systems" + }, + "locked": { + "lastModified": 1776398705, + "narHash": "sha256-/RYhj2fmbFWjfNORGjI18rJzLICNahxPSy0epCdyVRk=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "e2956eb5a19f92fef466f1af78f1fb8c207767af", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.nix b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.nix new file mode 100644 index 0000000..c5eb136 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.nix @@ -0,0 +1,64 @@ +{ + description = "libxev is a high performance, cross-platform event loop."; + + inputs = { + nixpkgs.url = "https://channels.nixos.org/nixos-25.05/nixexprs.tar.xz"; + flake-utils.url = "github:numtide/flake-utils"; + zig.url = "github:mitchellh/zig-overlay"; + + # Used for shell.nix + flake-compat = { + url = github:edolstra/flake-compat; + flake = false; + }; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + ... + } @ inputs: let + overlays = [ + # Other overlays + (final: prev: rec { + zigpkgs = inputs.zig.packages.${prev.system}; + zig = inputs.zig.packages.${prev.system}."0.16.0"; + + # Our package + libxev = prev.callPackage ./nix/package.nix {}; + }) + ]; + + # Our supported systems are the same supported systems as the Zig binaries + systems = builtins.attrNames inputs.zig.packages; + in + flake-utils.lib.eachSystem systems ( + system: let + pkgs = import nixpkgs {inherit overlays system;}; + in rec { + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + mandoc + scdoc + zig + + # Wasm + wabt + wasmtime + wasmer + + # Website + nodejs + ]; + }; + + # For compatibility with older versions of the `nix` binary + devShell = self.devShells.${system}.default; + + # Our package + packages.libxev = pkgs.libxev; + defaultPackage = packages.libxev; + } + ); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/include/xev.h b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/include/xev.h new file mode 100644 index 0000000..7089efe --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/include/xev.h @@ -0,0 +1,103 @@ +#ifndef XEV_H +#define XEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* TODO(mitchellh): we should use platform detection to set the correct + * byte sizes here. We choose some overly large values for now so that + * we can retain ABI compatibility. */ +const size_t XEV_SIZEOF_LOOP = 512; +const size_t XEV_SIZEOF_COMPLETION = 320; +const size_t XEV_SIZEOF_WATCHER = 256; +const size_t XEV_SIZEOF_THREADPOOL = 64; +const size_t XEV_SIZEOF_THREADPOOL_BATCH = 24; +const size_t XEV_SIZEOF_THREADPOOL_TASK = 24; +const size_t XEV_SIZEOF_THREADPOOL_CONFIG = 64; + +#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L +typedef max_align_t XEV_ALIGN_T; +#else +// max_align_t is usually synonymous with the largest scalar type, which is long double on most platforms, and its alignment requirement is either 8 or 16. +typedef long double XEV_ALIGN_T; +#endif + +/* There's a ton of preprocessor directives missing here for real cross-platform + * compatibility. I'm going to defer to the community or future issues to help + * plug those holes. For now, we get some stuff working we can test! */ + +/* Opaque types. These types have a size defined so that they can be + * statically allocated but they are not to be accessed. */ +// todo: give struct individual alignment, instead of max alignment +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_LOOP - sizeof(XEV_ALIGN_T)]; } xev_loop; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_COMPLETION - sizeof(XEV_ALIGN_T)]; } xev_completion; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_WATCHER - sizeof(XEV_ALIGN_T)]; } xev_watcher; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL - sizeof(XEV_ALIGN_T)]; } xev_threadpool; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_BATCH - sizeof(XEV_ALIGN_T)]; } xev_threadpool_batch; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_TASK - sizeof(XEV_ALIGN_T)]; } xev_threadpool_task; +typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_CONFIG - sizeof(XEV_ALIGN_T)]; } xev_threadpool_config; + +/* Callback types. */ +typedef enum { XEV_DISARM = 0, XEV_REARM = 1 } xev_cb_action; +typedef void (*xev_task_cb)(xev_threadpool_task* t); +typedef xev_cb_action (*xev_timer_cb)(xev_loop* l, xev_completion* c, int result, void* userdata); +typedef xev_cb_action (*xev_async_cb)(xev_loop* l, xev_completion* c, int result, void* userdata); + +typedef enum { + XEV_RUN_NO_WAIT = 0, + XEV_RUN_ONCE = 1, + XEV_RUN_UNTIL_DONE = 2, +} xev_run_mode_t; + +typedef enum { + XEV_COMPLETION_DEAD = 0, + XEV_COMPLETION_ACTIVE = 1, +} xev_completion_state_t; + + +/* Documentation for functions can be found in man pages or online. I + * purposely do not add docs to the header so that you can quickly scan + * all exported functions. */ +int xev_loop_init(xev_loop* loop); +void xev_loop_deinit(xev_loop* loop); +int xev_loop_run(xev_loop* loop, xev_run_mode_t mode); +int64_t xev_loop_now(xev_loop* loop); +void xev_loop_update_now(xev_loop* loop); + +void xev_completion_zero(xev_completion* c); +xev_completion_state_t xev_completion_state(xev_completion* c); + +void xev_threadpool_config_init(xev_threadpool_config* config); +void xev_threadpool_config_set_stack_size(xev_threadpool_config* config, uint32_t v); +void xev_threadpool_config_set_max_threads(xev_threadpool_config* config, uint32_t v); + +int xev_threadpool_init(xev_threadpool* pool, xev_threadpool_config* config); +void xev_threadpool_deinit(xev_threadpool* pool); +void xev_threadpool_shutdown(xev_threadpool* pool); +void xev_threadpool_schedule(xev_threadpool* pool, xev_threadpool_batch *batch); + +void xev_threadpool_task_init(xev_threadpool_task* t, xev_task_cb cb); +void xev_threadpool_batch_init(xev_threadpool_batch* b); +void xev_threadpool_batch_push_task(xev_threadpool_batch* b, xev_threadpool_task *t); +void xev_threadpool_batch_push_batch(xev_threadpool_batch* b, xev_threadpool_batch *other); + +int xev_timer_init(xev_watcher *w); +void xev_timer_deinit(xev_watcher *w); +void xev_timer_run(xev_watcher *w, xev_loop* loop, xev_completion* c, uint64_t next_ms, void* userdata, xev_timer_cb cb); +void xev_timer_reset(xev_watcher *w, xev_loop* loop, xev_completion* c, xev_completion *c_cancel, uint64_t next_ms, void* userdata, xev_timer_cb cb); +void xev_timer_cancel(xev_watcher *w, xev_loop* loop, xev_completion* c, xev_completion* c_cancel, void* userdata, xev_timer_cb cb); + +int xev_async_init(xev_watcher *w); +void xev_async_deinit(xev_watcher *w); +int xev_async_notify(xev_watcher *w); +void xev_async_wait(xev_watcher *w, xev_loop* loop, xev_completion* c, void* userdata, xev_async_cb cb); + +#ifdef __cplusplus +} +#endif + +#endif /* XEV_H */ diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/nix/package.nix b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/nix/package.nix new file mode 100644 index 0000000..4817154 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/nix/package.nix @@ -0,0 +1,39 @@ +{ stdenv +, lib +, zig +, pkg-config +, scdoc +}: + +stdenv.mkDerivation rec { + pname = "libxev"; + version = "0.1.0"; + + src = ./..; + + nativeBuildInputs = [ zig scdoc pkg-config ]; + + buildInputs = []; + + dontConfigure = true; + + preBuild = '' + # Necessary for zig cache to work + export HOME=$TMPDIR + ''; + + installPhase = '' + runHook preInstall + zig build -Doptimize=ReleaseFast -Demit-man-pages --prefix $out install + runHook postInstall + ''; + + outputs = [ "out" "dev" "man" ]; + + meta = with lib; { + description = "A high performance, cross-platform event loop."; + homepage = "https://github.com/mitchellh/libxev"; + license = licenses.mit; + platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/shell.nix b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/shell.nix new file mode 100644 index 0000000..a15057a --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/shell.nix @@ -0,0 +1,12 @@ +(import + ( + let + flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat; + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz"; + sha256 = flake-compat.locked.narHash; + } + ) + {src = ./.;}) +.shellNix diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/ThreadPool.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/ThreadPool.zig new file mode 100644 index 0000000..ce7e740 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/ThreadPool.zig @@ -0,0 +1,833 @@ +//! Thread pool copied almost directly from Zap[1]. In @kprotty's own words: +//! lock-free, allocation-free* (excluding spawning threads), supports batch +//! scheduling, and dynamically spawns threads while handling thread spawn +//! failure. I highly recommend reading @kprotty's incredible blog post[2] on +//! this topic. +//! +//! The original file in Zap is licensed under the MIT license, and the +//! license and copyright is reproduced below. The libxev project is also +//! MIT licensed so the entire project (including this file) are equally +//! licensed. This is just a convenience note for any OSS users, contributors, +//! etc. +//! +//! MIT License +//! +//! Copyright (c) 2021 kprotty +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in all +//! copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! SOFTWARE. +//! +//! [1]: https://github.com/kprotty/zap +//! [2]: https://zig.news/kprotty/resource-efficient-thread-pools-with-zig-3291 +const ThreadPool = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const Atomic = std.atomic.Value; +const Io = std.Io; + +// I know we shouldn't use a global io here, but we are doing this +// during the transition period so libxev is at least usable with Zig +// 0.16 until we do a cleaner transition. +const io = Io.Threaded.global_single_threaded.io(); + +stack_size: u32, +max_threads: u32, +sync: Atomic(u32) = Atomic(u32).init(@bitCast(Sync{})), +idle_event: Event = .{}, +join_event: Event = .{}, +run_queue: Node.Queue = .{}, +threads: Atomic(?*Thread) = Atomic(?*Thread).init(null), + +const Sync = packed struct { + /// Tracks the number of threads not searching for Tasks + idle: u14 = 0, + /// Tracks the number of threads spawned + spawned: u14 = 0, + /// What you see is what you get + unused: bool = false, + /// Used to not miss notifications while state = waking + notified: bool = false, + /// The current state of the thread pool + state: enum(u2) { + /// A notification can be issued to wake up a sleeping as the "waking thread". + pending = 0, + /// The state was notifiied with a signal. A thread is woken up. + /// The first thread to transition to `waking` becomes the "waking thread". + signaled, + /// There is a "waking thread" among us. + /// No other thread should be woken up until the waking thread transitions the state. + waking, + /// The thread pool was terminated. Start decremented `spawned` so that it can be joined. + shutdown, + } = .pending, +}; + +/// Configuration options for the thread pool. +/// TODO: add CPU core affinity? +pub const Config = struct { + stack_size: u32 = (std.Thread.SpawnConfig{}).stack_size, + max_threads: u32 = 0, +}; + +/// Statically initialize the thread pool using the configuration. +pub fn init(config: Config) ThreadPool { + return .{ + .stack_size = @max(1, config.stack_size), + .max_threads = if (config.max_threads > 0) + config.max_threads + else + @intCast(std.Thread.getCpuCount() catch 1), + }; +} + +/// Wait for a thread to call shutdown() on the thread pool and kill the worker threads. +pub fn deinit(self: *ThreadPool) void { + self.join(); + self.* = undefined; +} + +/// A Task represents the unit of Work / Job / Execution that the ThreadPool schedules. +/// The user provides a `callback` which is invoked when the *Task can run on a thread. +pub const Task = struct { + node: Node = .{}, + callback: *const fn (*Task) void, +}; + +/// An unordered collection of Tasks which can be submitted for scheduling as a group. +pub const Batch = struct { + len: usize = 0, + head: ?*Task = null, + tail: ?*Task = null, + + /// Create a batch from a single task. + pub fn from(task: *Task) Batch { + return Batch{ + .len = 1, + .head = task, + .tail = task, + }; + } + + /// Another batch into this one, taking ownership of its tasks. + pub fn push(self: *Batch, batch: Batch) void { + if (batch.len == 0) return; + if (self.len == 0) { + self.* = batch; + } else { + self.tail.?.node.next = if (batch.head) |h| &h.node else null; + self.tail = batch.tail; + self.len += batch.len; + } + } +}; + +/// Schedule a batch of tasks to be executed by some thread on the thread pool. +pub fn schedule(self: *ThreadPool, batch: Batch) void { + // Sanity check + if (batch.len == 0) { + return; + } + + // Extract out the Node's from the Tasks + var list = Node.List{ + .head = &batch.head.?.node, + .tail = &batch.tail.?.node, + }; + + // Push the task Nodes to the most approriate queue + if (Thread.current) |thread| { + thread.run_buffer.push(&list) catch thread.run_queue.push(list); + } else { + self.run_queue.push(list); + } + + // Try to notify a thread + const is_waking = false; + return self.notify(is_waking); +} + +inline fn notify(self: *ThreadPool, is_waking: bool) void { + // Fast path to check the Sync state to avoid calling into notifySlow(). + // If we're waking, then we need to update the state regardless + if (!is_waking) { + const sync: Sync = @bitCast(self.sync.load(.monotonic)); + if (sync.notified) { + return; + } + } + + return self.notifySlow(is_waking); +} + +noinline fn notifySlow(self: *ThreadPool, is_waking: bool) void { + var sync: Sync = @bitCast(self.sync.load(.monotonic)); + while (sync.state != .shutdown) { + const can_wake = is_waking or (sync.state == .pending); + if (is_waking) { + assert(sync.state == .waking); + } + + var new_sync = sync; + new_sync.notified = true; + if (can_wake and sync.idle > 0) { // wake up an idle thread + new_sync.state = .signaled; + } else if (can_wake and sync.spawned < self.max_threads) { // spawn a new thread + new_sync.state = .signaled; + new_sync.spawned += 1; + } else if (is_waking) { // no other thread to pass on "waking" status + new_sync.state = .pending; + } else if (sync.notified) { // nothing to update + return; + } + + // Release barrier synchronizes with Acquire in wait() + // to ensure pushes to run queues happen before observing a posted notification. + sync = @bitCast(self.sync.cmpxchgWeak( + @bitCast(sync), + @bitCast(new_sync), + .release, + .monotonic, + ) orelse { + // We signaled to notify an idle thread + if (can_wake and sync.idle > 0) { + return self.idle_event.notify(); + } + + // We signaled to spawn a new thread + if (can_wake and sync.spawned < self.max_threads) { + const spawn_config = std.Thread.SpawnConfig{ .stack_size = self.stack_size }; + const thread = std.Thread.spawn(spawn_config, Thread.run, .{self}) catch return self.unregister(null); + return thread.detach(); + } + + return; + }); + } +} + +noinline fn wait(self: *ThreadPool, _is_waking: bool) error{Shutdown}!bool { + var is_idle = false; + var is_waking = _is_waking; + var sync: Sync = @bitCast(self.sync.load(.monotonic)); + + while (true) { + if (sync.state == .shutdown) return error.Shutdown; + if (is_waking) assert(sync.state == .waking); + + // Consume a notification made by notify(). + if (sync.notified) { + var new_sync = sync; + new_sync.notified = false; + if (is_idle) + new_sync.idle -= 1; + if (sync.state == .signaled) + new_sync.state = .waking; + + // Acquire barrier synchronizes with notify() + // to ensure that pushes to run queue are observed after wait() returns. + sync = @bitCast(self.sync.cmpxchgWeak( + @bitCast(sync), + @bitCast(new_sync), + .acquire, + .monotonic, + ) orelse { + return is_waking or (sync.state == .signaled); + }); + + // No notification to consume. + // Mark this thread as idle before sleeping on the idle_event. + } else if (!is_idle) { + var new_sync = sync; + new_sync.idle += 1; + if (is_waking) + new_sync.state = .pending; + + sync = @bitCast(self.sync.cmpxchgWeak( + @bitCast(sync), + @bitCast(new_sync), + .monotonic, + .monotonic, + ) orelse { + is_waking = false; + is_idle = true; + continue; + }); + + // Wait for a signal by either notify() or shutdown() without wasting cpu cycles. + // TODO: Add I/O polling here. + } else { + self.idle_event.wait(); + sync = @bitCast(self.sync.load(.monotonic)); + } + } +} + +/// Marks the thread pool as shutdown +pub noinline fn shutdown(self: *ThreadPool) void { + var sync: Sync = @bitCast(self.sync.load(.monotonic)); + while (sync.state != .shutdown) { + var new_sync = sync; + new_sync.notified = true; + new_sync.state = .shutdown; + new_sync.idle = 0; + + // Full barrier to synchronize with both wait() and notify() + sync = @bitCast(self.sync.cmpxchgWeak( + @bitCast(sync), + @bitCast(new_sync), + .acq_rel, + .monotonic, + ) orelse { + // Wake up any threads sleeping on the idle_event. + // TODO: I/O polling notification here. + if (sync.idle > 0) self.idle_event.shutdown(); + if (sync.spawned == 0) self.join_event.notify(); + return; + }); + } +} + +fn register(noalias self: *ThreadPool, noalias thread: *Thread) void { + // Push the thread onto the threads stack in a lock-free manner. + var threads = self.threads.load(.monotonic); + while (true) { + thread.next = threads; + threads = self.threads.cmpxchgWeak( + threads, + thread, + .release, + .monotonic, + ) orelse break; + } +} + +fn unregister(noalias self: *ThreadPool, noalias maybe_thread: ?*Thread) void { + // Un-spawn one thread, either due to a failed OS thread spawning or the thread is exitting. + const one_spawned: u32 = @bitCast(Sync{ .spawned = 1 }); + const sync: Sync = @bitCast(self.sync.fetchSub(one_spawned, .release)); + assert(sync.spawned > 0); + + // The last thread to exit must wake up the thread pool join()er + // who will start the chain to shutdown all the threads. + if (sync.state == .shutdown and sync.spawned == 1) { + self.join_event.notify(); + } + + // If this is a thread pool thread, wait for a shutdown signal by the thread pool join()er. + const thread = maybe_thread orelse return; + thread.join_event.wait(); + + // After receiving the shutdown signal, shutdown the next thread in the pool. + // We have to do that without touching the thread pool itself since it's memory is invalidated by now. + // So just follow our .next link. + const next_thread = thread.next orelse return; + next_thread.join_event.notify(); +} + +fn join(self: *ThreadPool) void { + // Wait for the thread pool to be shutdown() then for all threads to enter a joinable state + self.join_event.wait(); + const sync: Sync = @bitCast(self.sync.load(.monotonic)); + + assert(sync.state == .shutdown); + assert(sync.spawned == 0); + + // If there are threads, start off the chain sending it the shutdown signal. + // The thread receives the shutdown signal and sends it to the next thread, and the next.. + const thread = self.threads.load(.acquire) orelse return; + thread.join_event.notify(); +} + +const Thread = struct { + next: ?*Thread = null, + target: ?*Thread = null, + join_event: Event = .{}, + run_queue: Node.Queue = .{}, + run_buffer: Node.Buffer = .{}, + + threadlocal var current: ?*Thread = null; + + /// Thread entry point which runs a worker for the ThreadPool + fn run(thread_pool: *ThreadPool) void { + var self = Thread{}; + current = &self; + + thread_pool.register(&self); + defer thread_pool.unregister(&self); + + var is_waking = false; + while (true) { + is_waking = thread_pool.wait(is_waking) catch return; + + while (self.pop(thread_pool)) |result| { + if (result.pushed or is_waking) + thread_pool.notify(is_waking); + is_waking = false; + + const task: *Task = @fieldParentPtr("node", result.node); + (task.callback)(task); + } + } + } + + /// Try to dequeue a Node/Task from the ThreadPool. + /// Spurious reports of dequeue() returning empty are allowed. + fn pop(noalias self: *Thread, noalias thread_pool: *ThreadPool) ?Node.Buffer.Stole { + // Check our local buffer first + if (self.run_buffer.pop()) |node| { + return Node.Buffer.Stole{ + .node = node, + .pushed = false, + }; + } + + // Then check our local queue + if (self.run_buffer.consume(&self.run_queue)) |stole| { + return stole; + } + + // Then the global queue + if (self.run_buffer.consume(&thread_pool.run_queue)) |stole| { + return stole; + } + + // TODO: add optimistic I/O polling here + + // Then try work stealing from other threads + var num_threads: u32 = @as(Sync, @bitCast(thread_pool.sync.load(.monotonic))).spawned; + while (num_threads > 0) : (num_threads -= 1) { + // Traverse the stack of registered threads on the thread pool + const target = self.target orelse thread_pool.threads.load(.acquire) orelse unreachable; + self.target = target.next; + + // Try to steal from their queue first to avoid contention (the target steal's from queue last). + if (self.run_buffer.consume(&target.run_queue)) |stole| { + return stole; + } + + // Skip stealing from the buffer if we're the target. + // We still steal from our own queue above given it may have just been locked the first time we tried. + if (target == self) { + continue; + } + + // Steal from the buffer of a remote thread as a last resort + if (self.run_buffer.steal(&target.run_buffer)) |stole| { + return stole; + } + } + + return null; + } +}; + +/// An event which stores 1 semaphore token and is multi-threaded safe. +/// The event can be shutdown(), waking up all wait()ing threads and +/// making subsequent wait()'s return immediately. +const Event = struct { + state: Atomic(u32) = Atomic(u32).init(EMPTY), + + const EMPTY = 0; + const WAITING = 1; + const NOTIFIED = 2; + const SHUTDOWN = 3; + + /// Wait for and consume a notification + /// or wait for the event to be shutdown entirely + noinline fn wait(self: *Event) void { + var acquire_with: u32 = EMPTY; + var state = self.state.load(.monotonic); + + while (true) { + // If we're shutdown then exit early. + // Acquire barrier to ensure operations before the shutdown() are seen after the wait(). + // Shutdown is rare so it's better to have an Acquire barrier here instead of on CAS failure + load which are common. + if (state == SHUTDOWN) { + _ = self.state.load(.acquire); + return; + } + + // Consume a notification when it pops up. + // Acquire barrier to ensure operations before the notify() appear after the wait(). + if (state == NOTIFIED) { + state = self.state.cmpxchgWeak( + state, + acquire_with, + .acquire, + .monotonic, + ) orelse return; + continue; + } + + // There is no notification to consume, we should wait on the event by ensuring its WAITING. + if (state != WAITING) blk: { + state = self.state.cmpxchgWeak( + state, + WAITING, + .monotonic, + .monotonic, + ) orelse break :blk; + continue; + } + + // Wait on the event until a notify() or shutdown(). + // If we wake up to a notification, we must acquire it with WAITING instead of EMPTY + // since there may be other threads sleeping on the Futex who haven't been woken up yet. + // + // Acquiring to WAITING will make the next notify() or shutdown() wake a sleeping futex thread + // who will either exit on SHUTDOWN or acquire with WAITING again, ensuring all threads are awoken. + // This unfortunately results in the last notify() or shutdown() doing an extra futex wake but that's fine. + io.futexWaitUncancelable(u32, &self.state.raw, WAITING); + state = self.state.load(.monotonic); + acquire_with = WAITING; + } + } + + /// Post a notification to the event if it doesn't have one already + /// then wake up a waiting thread if there is one as well. + fn notify(self: *Event) void { + return self.wake(NOTIFIED, 1); + } + + /// Marks the event as shutdown, making all future wait()'s return immediately. + /// Then wakes up any threads currently waiting on the Event. + fn shutdown(self: *Event) void { + return self.wake(SHUTDOWN, std.math.maxInt(u32)); + } + + fn wake(self: *Event, release_with: u32, wake_threads: u32) void { + // Update the Event to notifty it with the new `release_with` state (either NOTIFIED or SHUTDOWN). + // Release barrier to ensure any operations before this are this to happen before the wait() in the other threads. + const state = self.state.swap(release_with, .release); + + // Only wake threads sleeping in futex if the state is WAITING. + // Avoids unnecessary wake ups. + if (state == WAITING) { + io.futexWake(u32, &self.state.raw, wake_threads); + } + } +}; + +/// Linked list intrusive memory node and lock-free data structures to operate with it +const Node = struct { + next: ?*Node = null, + + /// A linked list of Nodes + const List = struct { + head: *Node, + tail: *Node, + }; + + /// An unbounded multi-producer-(non blocking)-multi-consumer queue of Node pointers. + const Queue = struct { + stack: Atomic(usize) = Atomic(usize).init(0), + cache: ?*Node = null, + + const HAS_CACHE: usize = 0b01; + const IS_CONSUMING: usize = 0b10; + const PTR_MASK: usize = ~(HAS_CACHE | IS_CONSUMING); + + comptime { + assert(@alignOf(Node) >= ((IS_CONSUMING | HAS_CACHE) + 1)); + } + + fn push(noalias self: *Queue, list: List) void { + var stack = self.stack.load(.monotonic); + while (true) { + // Attach the list to the stack (pt. 1) + list.tail.next = @ptrFromInt(stack & PTR_MASK); + + // Update the stack with the list (pt. 2). + // Don't change the HAS_CACHE and IS_CONSUMING bits of the consumer. + var new_stack = @intFromPtr(list.head); + assert(new_stack & ~PTR_MASK == 0); + new_stack |= (stack & ~PTR_MASK); + + // Push to the stack with a release barrier for the consumer to see the proper list links. + stack = self.stack.cmpxchgWeak( + stack, + new_stack, + .release, + .monotonic, + ) orelse break; + } + } + + fn tryAcquireConsumer(self: *Queue) error{ Empty, Contended }!?*Node { + var stack = self.stack.load(.monotonic); + while (true) { + if (stack & IS_CONSUMING != 0) + return error.Contended; // The queue already has a consumer. + if (stack & (HAS_CACHE | PTR_MASK) == 0) + return error.Empty; // The queue is empty when there's nothing cached and nothing in the stack. + + // When we acquire the consumer, also consume the pushed stack if the cache is empty. + var new_stack = stack | HAS_CACHE | IS_CONSUMING; + if (stack & HAS_CACHE == 0) { + assert(stack & PTR_MASK != 0); + new_stack &= ~PTR_MASK; + } + + // Acquire barrier on getting the consumer to see cache/Node updates done by previous consumers + // and to ensure our cache/Node updates in pop() happen after that of previous consumers. + stack = self.stack.cmpxchgWeak( + stack, + new_stack, + .acquire, + .monotonic, + ) orelse return self.cache orelse @ptrFromInt(stack & PTR_MASK); + } + } + + fn releaseConsumer(noalias self: *Queue, noalias consumer: ?*Node) void { + // Stop consuming and remove the HAS_CACHE bit as well if the consumer's cache is empty. + // When HAS_CACHE bit is zeroed, the next consumer will acquire the pushed stack nodes. + var remove = IS_CONSUMING; + if (consumer == null) + remove |= HAS_CACHE; + + // Release the consumer with a release barrier to ensure cache/node accesses + // happen before the consumer was released and before the next consumer starts using the cache. + self.cache = consumer; + const stack = self.stack.fetchSub(remove, .release); + assert(stack & remove != 0); + } + + fn pop(noalias self: *Queue, noalias consumer_ref: *?*Node) ?*Node { + // Check the consumer cache (fast path) + if (consumer_ref.*) |node| { + consumer_ref.* = node.next; + return node; + } + + // Load the stack to see if there was anything pushed that we could grab. + var stack = self.stack.load(.monotonic); + assert(stack & IS_CONSUMING != 0); + if (stack & PTR_MASK == 0) { + return null; + } + + // Nodes have been pushed to the stack, grab then with an Acquire barrier to see the Node links. + stack = self.stack.swap(HAS_CACHE | IS_CONSUMING, .acquire); + assert(stack & IS_CONSUMING != 0); + assert(stack & PTR_MASK != 0); + + const node: *Node = @ptrFromInt(stack & PTR_MASK); + consumer_ref.* = node.next; + return node; + } + }; + + /// A bounded single-producer, multi-consumer ring buffer for node pointers. + const Buffer = struct { + head: Atomic(Index) = Atomic(Index).init(0), + tail: Atomic(Index) = Atomic(Index).init(0), + array: [capacity]Atomic(*Node) = undefined, + + const Index = u32; + const capacity = 256; // Appears to be a pretty good trade-off in space vs contended throughput + comptime { + assert(std.math.maxInt(Index) >= capacity); + assert(std.math.isPowerOfTwo(capacity)); + } + + fn push(noalias self: *Buffer, noalias list: *List) error{Overflow}!void { + var head = self.head.load(.monotonic); + var tail = self.tail.raw; // we're the only thread that can change this + + while (true) { + var size = tail -% head; + assert(size <= capacity); + + // Push nodes from the list to the buffer if it's not empty.. + if (size < capacity) { + var nodes: ?*Node = list.head; + while (size < capacity) : (size += 1) { + const node = nodes orelse break; + nodes = node.next; + + // Array written atomically with weakest ordering since it could be getting atomically read by steal(). + self.array[tail % capacity].store(node, .unordered); + tail +%= 1; + } + + // Release barrier synchronizes with Acquire loads for steal()ers to see the array writes. + self.tail.store(tail, .release); + + // Update the list with the nodes we pushed to the buffer and try again if there's more. + list.head = nodes orelse return; + std.atomic.spinLoopHint(); + head = self.head.load(.monotonic); + continue; + } + + // Try to steal/overflow half of the tasks in the buffer to make room for future push()es. + // Migrating half amortizes the cost of stealing while requiring future pops to still use the buffer. + // Acquire barrier to ensure the linked list creation after the steal only happens after we succesfully steal. + var migrate = size / 2; + head = self.head.cmpxchgWeak( + head, + head +% migrate, + .acquire, + .monotonic, + ) orelse { + // Link the migrated Nodes together + const first = self.array[head % capacity].raw; + while (migrate > 0) : (migrate -= 1) { + const prev = self.array[head % capacity].raw; + head +%= 1; + prev.next = self.array[head % capacity].raw; + } + + // Append the list that was supposed to be pushed to the end of the migrated Nodes + const last = self.array[(head -% 1) % capacity].raw; + last.next = list.head; + list.tail.next = null; + + // Return the migrated nodes + the original list as overflowed + list.head = first; + return error.Overflow; + }; + } + } + + fn pop(self: *Buffer) ?*Node { + var head = self.head.load(.monotonic); + const tail = self.tail.raw; // we're the only thread that can change this + + while (true) { + // Quick sanity check and return null when not empty + const size = tail -% head; + assert(size <= capacity); + if (size == 0) { + return null; + } + + // Dequeue with an acquire barrier to ensure any writes done to the Node + // only happen after we succesfully claim it from the array. + head = self.head.cmpxchgWeak( + head, + head +% 1, + .acquire, + .monotonic, + ) orelse return self.array[head % capacity].raw; + } + } + + const Stole = struct { + node: *Node, + pushed: bool, + }; + + fn consume(noalias self: *Buffer, noalias queue: *Queue) ?Stole { + var consumer = queue.tryAcquireConsumer() catch return null; + defer queue.releaseConsumer(consumer); + + const head = self.head.load(.monotonic); + const tail = self.tail.raw; // we're the only thread that can change this + + const size = tail -% head; + assert(size <= capacity); + assert(size == 0); // we should only be consuming if our array is empty + + // Pop nodes from the queue and push them to our array. + // Atomic stores to the array as steal() threads may be atomically reading from it. + var pushed: Index = 0; + while (pushed < capacity) : (pushed += 1) { + const node = queue.pop(&consumer) orelse break; + self.array[(tail +% pushed) % capacity].store(node, .unordered); + } + + // We will be returning one node that we stole from the queue. + // Get an extra, and if that's not possible, take one from our array. + const node = queue.pop(&consumer) orelse blk: { + if (pushed == 0) return null; + pushed -= 1; + break :blk self.array[(tail +% pushed) % capacity].raw; + }; + + // Update the array tail with the nodes we pushed to it. + // Release barrier to synchronize with Acquire barrier in steal()'s to see the written array Nodes. + if (pushed > 0) self.tail.store(tail +% pushed, .release); + return Stole{ + .node = node, + .pushed = pushed > 0, + }; + } + + fn steal(noalias self: *Buffer, noalias buffer: *Buffer) ?Stole { + const head = self.head.load(.monotonic); + const tail = self.tail.raw; // we're the only thread that can change this + + const size = tail -% head; + assert(size <= capacity); + assert(size == 0); // we should only be stealing if our array is empty + + while (true) : (std.atomic.spinLoopHint()) { + const buffer_head = buffer.head.load(.acquire); + const buffer_tail = buffer.tail.load(.acquire); + + // Overly large size indicates the the tail was updated a lot after the head was loaded. + // Reload both and try again. + const buffer_size = buffer_tail -% buffer_head; + if (buffer_size > capacity) { + continue; + } + + // Try to steal half (divCeil) to amortize the cost of stealing from other threads. + const steal_size = buffer_size - (buffer_size / 2); + if (steal_size == 0) { + return null; + } + + // Copy the nodes we will steal from the target's array to our own. + // Atomically load from the target buffer array as it may be pushing and atomically storing to it. + // Atomic store to our array as other steal() threads may be atomically loading from it as above. + var i: Index = 0; + while (i < steal_size) : (i += 1) { + const node = buffer.array[(buffer_head +% i) % capacity].load(.unordered); + self.array[(tail +% i) % capacity].store(node, .unordered); + } + + // Try to commit the steal from the target buffer using: + // - an Acquire barrier to ensure that we only interact with the stolen Nodes after the steal was committed. + // - a Release barrier to ensure that the Nodes are copied above prior to the committing of the steal + // because if they're copied after the steal, the could be getting rewritten by the target's push(). + _ = buffer.head.cmpxchgStrong( + buffer_head, + buffer_head +% steal_size, + .acq_rel, + .monotonic, + ) orelse { + // Pop one from the nodes we stole as we'll be returning it + const pushed = steal_size - 1; + const node = self.array[(tail +% pushed) % capacity].raw; + + // Update the array tail with the nodes we pushed to it. + // Release barrier to synchronize with Acquire barrier in steal()'s to see the written array Nodes. + if (pushed > 0) self.tail.store(tail +% pushed, .release); + return Stole{ + .node = node, + .pushed = pushed > 0, + }; + }; + } + } + }; +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/api.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/api.zig new file mode 100644 index 0000000..e0c4c0f --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/api.zig @@ -0,0 +1,88 @@ +const builtin = @import("builtin"); +const stream = @import("watcher/stream.zig"); +const Backend = @import("backend.zig").Backend; + +/// Creates the Xev API based on a backend type. +/// +/// For the default backend type for your system (i.e. io_uring on Linux), +/// this is the main API you interact with. It is forwarded into the main +/// the "xev" package so you'd use types such as `xev.Loop`, `xev.Completion`, +/// etc. +/// +/// Unless you're using a custom or specific backend type, you do NOT ever +/// need to call the Xev function itself. +pub fn Xev(comptime be: Backend, comptime T: type) type { + return struct { + const Self = @This(); + const loop = @import("loop.zig"); + + /// This is used to detect a static vs dynamic API at comptime. + pub const dynamic = false; + + /// The backend that this is. This is supplied at comptime so + /// it is up to the caller to say the right thing. This lets custom + /// implementations also "quack" like an implementation. + pub const backend = be; + + /// A function to test if this API is available on the + /// current system. + pub const available = T.available; + + /// The core loop APIs. + pub const Loop = T.Loop; + pub const Completion = T.Completion; + pub const Result = T.Result; + pub const ReadBuffer = T.ReadBuffer; + pub const WriteBuffer = T.WriteBuffer; + pub const Options = loop.Options; + pub const RunMode = loop.RunMode; + pub const CallbackAction = loop.CallbackAction; + pub const CompletionState = loop.CompletionState; + + /// Error types + pub const AcceptError = T.AcceptError; + pub const CancelError = T.CancelError; + pub const CloseError = T.CloseError; + pub const ConnectError = T.ConnectError; + pub const ShutdownError = T.ShutdownError; + pub const WriteError = T.WriteError; + pub const ReadError = T.ReadError; + + /// Shared stream types + const SharedStream = stream.Shared(Self); + pub const PollError = SharedStream.PollError; + pub const PollEvent = SharedStream.PollEvent; + pub const WriteQueue = SharedStream.WriteQueue; + pub const WriteRequest = SharedStream.WriteRequest; + + /// The high-level helper interfaces that make it easier to perform + /// common tasks. These may not work with all possible Loop implementations. + pub const Async = @import("watcher/async.zig").Async(Self); + pub const File = @import("watcher/file.zig").File(Self); + pub const Process = @import("watcher/process.zig").Process(Self); + pub const Stream = stream.GenericStream(Self); + pub const Timer = @import("watcher/timer.zig").Timer(Self); + pub const TCP = @import("watcher/tcp.zig").TCP(Self); + pub const UDP = @import("watcher/udp.zig").UDP(Self); + + /// The callback of the main Loop operations. Higher level interfaces may + /// use a different callback mechanism. + pub const Callback = loop.Callback(T); + + /// A callback that does nothing and immediately disarms. This + /// implements xev.Callback and is the default value for completions. + pub const noopCallback = loop.NoopCallback(T); + + /// A way to access the raw type. + pub const Sys = T; + + test { + @import("std").testing.refAllDecls(@This()); + } + + test "completion is zero-able" { + const c: Self.Completion = .{}; + _ = c; + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend.zig new file mode 100644 index 0000000..9de7d3a --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend.zig @@ -0,0 +1,58 @@ +const builtin = @import("builtin"); +const stream = @import("watcher/stream.zig"); + +/// The backend types. +pub const Backend = enum { + io_uring, + epoll, + kqueue, + wasi_poll, + iocp, + + /// Returns a recommend default backend from inspecting the system. + pub fn default() Backend { + return switch (builtin.os.tag) { + .linux => if (builtin.abi.isAndroid()) + .epoll + else + .io_uring, + .ios, .macos, .visionos => .kqueue, + .freebsd => .kqueue, + .wasi => .wasi_poll, + .windows => .iocp, + else => { + @compileLog(builtin.os); + @compileError("no default backend for this target"); + }, + }; + } + + /// Candidate backends for this platform in priority order. + pub fn candidates() []const Backend { + return switch (builtin.os.tag) { + .linux => if (builtin.abi.isAndroid()) + &.{.epoll} + else + &.{ .io_uring, .epoll }, + .ios, .macos, .visionos => &.{.kqueue}, + .freebsd => &.{.kqueue}, + .wasi => &.{.wasi_poll}, + .windows => &.{.iocp}, + else => { + @compileLog(builtin.os); + @compileError("no candidate backends for this target"); + }, + }; + } + + /// Returns the Api (return value of Xev) for the given backend type. + pub fn Api(comptime self: Backend) type { + return switch (self) { + .io_uring => @import("main.zig").IO_Uring, + .epoll => @import("main.zig").Epoll, + .kqueue => @import("main.zig").Kqueue, + .wasi_poll => @import("main.zig").WasiPoll, + .iocp => @import("main.zig").IOCP, + }; + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/epoll.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/epoll.zig new file mode 100644 index 0000000..25c0a93 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/epoll.zig @@ -0,0 +1,2134 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const linux = std.os.linux; +const posix = std.posix; +const xev_posix = @import("../posix.zig"); +const net = xev_posix.net; +const queue = @import("../queue.zig"); +const queue_mpsc = @import("../queue_mpsc.zig"); +const heap = @import("../heap.zig"); +const ThreadPool = @import("../ThreadPool.zig"); +const Async = @import("../main.zig").Epoll.Async; + +/// In Zig 0.16, `std.posix.epoll_create1`, `epoll_ctl`, and `epoll_wait` +/// were removed. These helpers replicate the old wrappers using the raw +/// Linux syscall layer (`std.os.linux`). +const epoll_helper = struct { + const EpollCreateError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + SystemResources, + Unexpected, + }; + + fn epoll_create1(flags: u32) EpollCreateError!i32 { + const rc = linux.epoll_create1(flags); + switch (linux.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + else => return error.Unexpected, + } + } + + fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*linux.epoll_event) EpollCtlError!void { + const rc = linux.epoll_ctl(epfd, op, fd, event); + switch (linux.errno(rc)) { + .SUCCESS => return, + .EXIST => return error.FileDescriptorAlreadyPresentInSet, + .LOOP => return error.OperationCausesCircularLoop, + .NOENT => return error.FileDescriptorNotRegistered, + .NOMEM => return error.SystemResources, + .NOSPC => return error.UserResourceLimitReached, + .PERM => return error.FileDescriptorIncompatibleWithEpoll, + else => return error.Unexpected, + } + } + + fn epoll_wait(epfd: i32, events: []linux.epoll_event, timeout: i32) usize { + while (true) { + const rc = linux.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout); + switch (linux.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + else => unreachable, + } + } + } +}; + +const looppkg = @import("../loop.zig"); +const Options = looppkg.Options; +const RunMode = looppkg.RunMode; +const Callback = looppkg.Callback(@This()); +const CallbackAction = looppkg.CallbackAction; +const CompletionState = looppkg.CompletionState; +const noopCallback = looppkg.NoopCallback(@This()); + +/// True if epoll is available on this platform. +pub fn available() bool { + return builtin.os.tag == .linux; +} + +/// Epoll backend. +/// +/// WARNING: this backend is a bit of a mess. It is missing features and in +/// general is in much poorer quality than all of the other backends. It +/// isn't meant to really be used yet. We should remodel this in the style of +/// the kqueue backend. +pub const Loop = struct { + const TimerHeap = heap.Intrusive(Operation.Timer, void, Operation.Timer.less); + const TaskCompletionQueue = queue_mpsc.Intrusive(Completion); + + fd: posix.fd_t, + + /// The eventfd that this epoll queue always has a filter for. Writing + /// an empty message to this eventfd can be used to wake up the loop + /// at any time. Waking up the loop via this eventfd won't trigger any + /// particular completion, it just forces tick to cycle. + eventfd: Async, + + /// The number of active completions. This DOES NOT include completions that + /// are queued in the submissions queue. + active: usize = 0, + + /// Our queue of submissions that we want to enqueue on the next tick. + submissions: queue.Intrusive(Completion) = .{}, + + /// The queue for completions to delete from the epoll fd. + deletions: queue.Intrusive(Completion) = .{}, + + /// Heap of timers. + timers: TimerHeap = .{ .context = {} }, + + /// The thread pool to use for blocking operations that epoll can't do. + thread_pool: ?*ThreadPool, + + /// The MPSC queue for completed completions from the thread pool. + thread_pool_completions: TaskCompletionQueue, + + /// Cached time + cached_now: posix.timespec, + + /// Some internal fields we can pack for better space. + flags: packed struct { + /// True once it is initialized. + init: bool = false, + + /// Whether we're in a run or not (to prevent nested runs). + in_run: bool = false, + + /// Whether our loop is in a stopped state or not. + stopped: bool = false, + } = .{}, + + pub fn init(options: Options) !Loop { + var eventfd = try Async.init(); + errdefer eventfd.deinit(); + + var res: Loop = .{ + .fd = try epoll_helper.epoll_create1(std.os.linux.EPOLL.CLOEXEC), + .eventfd = eventfd, + .thread_pool = options.thread_pool, + .thread_pool_completions = undefined, + .cached_now = undefined, + }; + res.update_now(); + return res; + } + + pub fn deinit(self: *Loop) void { + xev_posix.close(self.fd); + self.eventfd.deinit(); + } + + /// Run the event loop. See RunMode documentation for details on modes. + /// Once the loop is run, the pointer MUST remain stable. + pub fn run(self: *Loop, mode: RunMode) !void { + switch (mode) { + .no_wait => try self.tick(0), + .once => try self.tick(1), + .until_done => while (!self.done()) try self.tick(1), + } + } + + fn done(self: *Loop) bool { + return self.flags.stopped or (self.active == 0 and + self.submissions.empty()); + } + + /// Stop the loop. This can only be called from the main thread. + /// This will stop the loop forever. Future ticks will do nothing. + /// + /// This does NOT stop any completions that are queued to be executed + /// in the thread pool. If you are using a thread pool, completions + /// are not safe to recover until the thread pool is shut down. If + /// you're not using a thread pool, all completions are safe to + /// read/write once any outstanding `run` or `tick` calls are returned. + pub fn stop(self: *Loop) void { + self.flags.stopped = true; + } + + /// Returns true if the loop is stopped. This may mean there + /// are still pending completions to be processed. + pub fn stopped(self: *Loop) bool { + return self.flags.stopped; + } + + /// Add a completion to the loop. + pub fn add(self: *Loop, completion: *Completion) void { + switch (completion.flags.state) { + // Already adding, forget about it. + .adding => return, + + // If it is dead we're good. If we're deleting we'll ignore it + // while we're processing. + .dead, + .deleting, + => {}, + + .active => unreachable, + } + completion.flags.state = .adding; + + // We just add the completion to the queue. Failures can happen + // at tick time... + self.submissions.push(completion); + } + + /// Delete a completion from the loop. + pub fn delete(self: *Loop, completion: *Completion) void { + switch (completion.flags.state) { + // Already deleted + .deleting => return, + + // If we're active then we will stop it and remove from epoll. + // If we're adding then we'll ignore it when adding. + .dead, .active, .adding => {}, + } + completion.flags.state = .deleting; + + self.deletions.push(completion); + } + + /// Returns the "loop" time in milliseconds. The loop time is updated + /// once per loop tick, before IO polling occurs. It remains constant + /// throughout callback execution. + /// + /// You can force an update of the "now" value by calling update_now() + /// at any time from the main thread. + /// + /// The clock that is used is not guaranteed. In general, a monotonic + /// clock source is always used if available. This value should typically + /// just be used for relative time calculations within the loop, such as + /// answering the question "did this happen ms ago?". + pub fn now(self: *Loop) i64 { + // If anything overflows we just return the max value. + const max = std.math.maxInt(i64); + + // Calculate all the values, being careful about overflows in order + // to just return the maximum value. + const sec = std.math.mul(isize, self.cached_now.sec, std.time.ms_per_s) catch return max; + const nsec = @divFloor(self.cached_now.nsec, std.time.ns_per_ms); + return std.math.lossyCast(i64, sec +| nsec); + } + + /// Update the cached time. + pub fn update_now(self: *Loop) void { + var ts: std.os.linux.timespec = undefined; + const rc = std.os.linux.clock_gettime(std.os.linux.CLOCK.MONOTONIC, &ts); + if (linux.errno(rc) == .SUCCESS) { + self.cached_now = ts; + } + } + + /// Add a timer to the loop. The timer will execute in "next_ms". This + /// is oneshot: the timer will not repeat. To repeat a timer, either + /// schedule another in your callback or return rearm from the callback. + pub fn timer( + self: *Loop, + c: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + c.* = .{ + .op = .{ + .timer = .{ + .next = self.timer_next(next_ms), + }, + }, + .userdata = userdata, + .callback = cb, + }; + + self.add(c); + } + + /// See io_uring.timer_reset for docs. + pub fn timer_reset( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + switch (c.flags.state) { + .dead, .deleting => { + self.timer(c, next_ms, userdata, cb); + return; + }, + + // Adding state we can just modify the metadata and return + // since the timer isn't in the heap yet. + .adding => { + c.op.timer.next = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + return; + }, + + .active => { + // Update the reset time for the timer to the desired time + // along with all the callbacks. + c.op.timer.reset = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + + // If the cancellation is active, we assume its for this timer + // and do nothing. + if (c_cancel.state() == .active) return; + assert(c_cancel.state() == .dead and c.state() == .active); + c_cancel.* = .{ .op = .{ .cancel = .{ .c = c } } }; + self.add(c_cancel); + }, + } + } + + fn timer_next(self: *Loop, next_ms: u64) posix.timespec { + // Get the timestamp of the absolute time that we'll execute this timer. + // There are lots of failure scenarios here in math. If we see any + // of them we just use the maximum value. + const max: posix.timespec = .{ + .sec = std.math.maxInt(isize), + .nsec = std.math.maxInt(isize), + }; + + const next_s = std.math.cast(isize, next_ms / std.time.ms_per_s) orelse + return max; + const next_ns = std.math.cast( + isize, + (next_ms % std.time.ms_per_s) * std.time.ns_per_ms, + ) orelse return max; + + return .{ + .sec = std.math.add(isize, self.cached_now.sec, next_s) catch + return max, + .nsec = std.math.add(isize, self.cached_now.nsec, next_ns) catch + return max, + }; + } + + /// Tick through the event loop once, waiting for at least "wait" completions + /// to be processed by the loop itself. + pub fn tick(self: *Loop, wait: u32) !void { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We can't nest runs. + if (self.flags.in_run) return error.NestedRunsNotAllowed; + self.flags.in_run = true; + defer self.flags.in_run = false; + + // Initialize + if (!self.flags.init) { + self.flags.init = true; + + if (self.thread_pool != null) { + self.thread_pool_completions.init(); + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, + .data = .{ .fd = self.eventfd.fd }, + }; + epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + self.eventfd.fd, + &ev, + ) catch |err| { + // We reset initialization because we can't do anything + // safely unless we get this mach port registered! + self.flags.init = false; + return err; + }; + } + + // Submit all the submissions. We copy the submission queue so that + // any resubmits don't cause an infinite loop. + var wait_rem: usize = @intCast(wait); + var queued = self.submissions; + self.submissions = .{}; + while (queued.pop()) |c| { + // We ignore any completions that aren't in the adding state. + // This usually means that we switched them to be deleted or + // something. + if (c.flags.state != .adding) continue; + + // These operations happen synchronously. Ensure they are + // decremented from wait_rem. + switch (c.op) { + .cancel, + // should noop be counted? + // .noop, + .shutdown, + .timer, + => wait_rem -|= 1, + else => {}, + } + + self.start(c); + } + + // Handle all deletions so we don't wait for them. + while (self.deletions.pop()) |c| { + if (c.flags.state != .deleting) continue; + self.stop_completion(c); + } + + // If we have no active handles then we return no matter what. + if (self.active == 0) { + // We still have to update our concept of "now". + self.update_now(); + return; + } + + // Wait and process events. We only do this if we have any active. + var events: [1024]linux.epoll_event = undefined; + while (self.active > 0 and (wait == 0 or wait_rem > 0)) { + self.update_now(); + const now_timer: Operation.Timer = .{ .next = self.cached_now }; + + // Run our expired timers + while (self.timers.peek()) |t| { + if (!Operation.Timer.less({}, t, &now_timer)) break; + + // Remove the timer + assert(self.timers.deleteMin().? == t); + + // Mark completion as done + const c = t.c; + c.flags.state = .dead; + self.active -= 1; + + // Lower our remaining count + wait_rem -|= 1; + + // Invoke + const action = c.callback(c.userdata, self, c, .{ + .timer = .expiration, + }); + switch (action) { + .disarm => {}, + .rearm => self.start(c), + } + } + + // Run our completed thread pool work + if (self.thread_pool != null) { + while (self.thread_pool_completions.pop()) |c| { + // Mark completion as done + c.flags.state = .dead; + self.active -= 1; + + // Lower our remaining count + wait_rem -|= 1; + + // Invoke + const action = c.callback(c.userdata, self, c, c.task_result); + switch (action) { + .disarm => {}, + .rearm => self.start(c), + } + } + } + + // Determine our next timeout based on the timers + const timeout: i32 = if (wait_rem == 0) 0 else timeout: { + // If we have a timer, we want to set the timeout to our next + // timer value. If we have no timer, we wait forever. + const t = self.timers.peek() orelse break :timeout -1; + + // Determine the time in milliseconds. + const ms_now = @as(u64, @intCast(self.cached_now.sec)) * std.time.ms_per_s + + @as(u64, @intCast(self.cached_now.nsec)) / std.time.ns_per_ms; + const ms_next = @as(u64, @intCast(t.next.sec)) * std.time.ms_per_s + + @as(u64, @intCast(t.next.nsec)) / std.time.ns_per_ms; + break :timeout @as(i32, @intCast(ms_next -| ms_now)); + }; + + const n = epoll_helper.epoll_wait(self.fd, &events, timeout); + + // Process all our events and invoke their completion handlers + for (events[0..n]) |ev| { + // Handle wakeup eventfd + if (ev.data.fd == self.eventfd.fd) { + var buffer: u64 = undefined; + _ = xev_posix.read(self.eventfd.fd, std.mem.asBytes(&buffer)) catch {}; + continue; + } + + const c: *Completion = @ptrFromInt(@as(usize, @intCast(ev.data.ptr))); + + // We get the fd and mark this as in progress we can properly + // clean this up late.r + const fd = if (c.flags.dup) c.flags.dup_fd else c.fd(); + const close_dup = c.flags.dup; + c.flags.state = .dead; + + const res = c.perform(); + const action = c.callback(c.userdata, self, c, res); + switch (action) { + .disarm => { + // We can't use self.stop because we can't trust + // that c is still a valid pointer. + if (fd) |v| { + epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_DEL, + v, + null, + ) catch unreachable; + + if (close_dup) { + xev_posix.close(v); + } + } + + self.active -= 1; + }, + + // For epoll, epoll remains armed by default. We have to + // reset the state, that is all. + .rearm => c.flags.state = .active, + } + } + + if (wait == 0) break; + wait_rem -|= n; + } + } + + /// Shedule a completion to run on a thread. + fn thread_schedule(self: *Loop, c: *Completion) !void { + const pool = self.thread_pool orelse return error.ThreadPoolRequired; + + // Setup our completion state so that thread_perform can do stuff + c.task_loop = self; + c.task_completions = &self.thread_pool_completions; + c.task = .{ .callback = Loop.thread_perform }; + + // We need to mark this completion as active before we schedule. + c.flags.state = .active; + self.active += 1; + + // Schedule it, from this point forward its not safe to touch c. + pool.schedule(ThreadPool.Batch.from(&c.task)); + } + + /// This is the main callback for the threadpool to perform work + /// on completions for the loop. + fn thread_perform(t: *ThreadPool.Task) void { + const c: *Completion = @fieldParentPtr("task", t); + + // Do our task + c.task_result = c.perform(); + + // Add to our completion queue + c.task_completions.push(c); + + // Wake up our main loop + c.task_loop.wakeup() catch {}; + } + + /// Sends an empty message to this loop's eventfd so that it wakes up. + fn wakeup(self: *Loop) !void { + try self.eventfd.notify(); + } + + fn start(self: *Loop, completion: *Completion) void { + const res_: ?Result = switch (completion.op) { + .noop => { + completion.flags.state = .dead; + return; + }, + + .cancel => |v| res: { + if (completion.flags.threadpool) { + break :res .{ .cancel = error.ThreadPoolUnsupported }; + } + + // We stop immediately. We only stop if we are in the + // "adding" state because cancellation or any other action + // means we're complete already. + if (completion.flags.state == .adding) { + if (v.c.op == .cancel) @panic("cannot cancel a cancellation"); + self.stop_completion(v.c); + } + + // We always run timers + break :res .{ .cancel = {} }; + }, + + .accept => res: { + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .accept = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .accept = err }; + }, + + .connect => |*v| res: { + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .connect = err }; + + if (xev_posix.connect(fd, &v.addr.any, v.addr.getOsSockLen())) { + break :res .{ .connect = {} }; + } else |err| switch (err) { + // If we would block then we register with epoll + error.WouldBlock => {}, + + // Any other error we just return immediately + else => break :res .{ .connect = err }, + } + + // If connect returns WouldBlock then we register for OUT events + // and are notified of connection completion that way. + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.OUT, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .connect = err }; + }, + + .read => res: { + if (completion.flags.threadpool) { + if (self.thread_schedule(completion)) |_| + return + else |err| + break :res .{ .read = err }; + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .read = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .read = err }; + }, + + .pread => res: { + if (completion.flags.threadpool) { + if (self.thread_schedule(completion)) |_| + return + else |err| + break :res .{ .read = err }; + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .read = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .read = err }; + }, + + .write => res: { + if (completion.flags.threadpool) { + if (self.thread_schedule(completion)) |_| + return + else |err| + break :res .{ .write = err }; + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.OUT, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .write = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .write = err }; + }, + + .pwrite => res: { + if (completion.flags.threadpool) { + if (self.thread_schedule(completion)) |_| + return + else |err| + break :res .{ .write = err }; + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.OUT, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .write = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .write = err }; + }, + + .send => res: { + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.OUT, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .send = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .send = err }; + }, + + .recv => res: { + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .recv = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .recv = err }; + }, + + .sendmsg => |*v| res: { + if (v.buffer) |_| { + @panic("TODO: sendmsg with buffer"); + } + + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.OUT, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .sendmsg = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .sendmsg = err }; + }, + + .recvmsg => res: { + var ev: linux.epoll_event = .{ + .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .recvmsg = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .recvmsg = err }; + }, + + .close => |v| res: { + if (completion.flags.threadpool) { + if (self.thread_schedule(completion)) |_| + return + else |err| + break :res .{ .close = err }; + } + + xev_posix.close(v.fd); + break :res .{ .close = {} }; + }, + + .shutdown => |v| res: { + const how_int: i32 = switch (v.how) { + .recv => linux.SHUT.RD, + .send => linux.SHUT.WR, + .both => linux.SHUT.RDWR, + }; + const rc = linux.shutdown(v.socket, how_int); + break :res .{ .shutdown = switch (linux.errno(rc)) { + .SUCCESS => {}, + .NOTCONN => error.SocketNotConnected, + .NOBUFS => error.SystemResources, + else => |err| posix.unexpectedErrno(err), + } }; + }, + + .timer => |*v| res: { + // Point back to completion since we need this. In the future + // we want to use @fieldParentPtr but https://github.com/ziglang/zig/issues/6611 + v.c = completion; + + // Insert the timer into our heap. + self.timers.insert(v); + + // We always run timers + break :res null; + }, + + .poll => |v| res: { + var ev: linux.epoll_event = .{ + .events = v.events, + .data = .{ .ptr = @intFromPtr(completion) }, + }; + + const fd = completion.fd_maybe_dup() catch |err| break :res .{ .poll = err }; + break :res if (epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_ADD, + fd, + &ev, + )) null else |err| .{ .poll = err }; + }, + }; + + // If we failed to add the completion then we call the callback + // immediately and mark the error. + if (res_) |res| { + completion.flags.state = .dead; + + switch (completion.callback( + completion.userdata, + self, + completion, + res, + )) { + .disarm => {}, + + // If we rearm then we requeue this. Due to the way that tick works, + // this won't try to re-add immediately it won't happen until the + // next tick. + .rearm => self.add(completion), + } + + return; + } + + // If the completion was requested on a threadpool we should + // never reach her. + assert(!completion.flags.threadpool); + + // Mark the completion as active if we reached this point + completion.flags.state = .active; + + // Increase our active count + self.active += 1; + } + + fn stop_completion(self: *Loop, completion: *Completion) void { + // Delete. This should never fail. + const maybe_fd = if (completion.flags.dup) completion.flags.dup_fd else completion.fd(); + if (maybe_fd) |fd| { + epoll_helper.epoll_ctl( + self.fd, + linux.EPOLL.CTL_DEL, + fd, + null, + ) catch unreachable; + } else switch (completion.op) { + .timer => |*v| { + const c = v.c; + + if (c.flags.state == .active) { + // Timers needs to be removed from the timer heap. + self.timers.remove(v); + } + + // If the timer was never fired, we need to fire it with + // the cancellation notice. + if (c.flags.state != .dead) { + // If we have reset set AND we got a cancellation result, + // that means that we were canceled so that we can update + // our expiration time. + if (v.reset) |r| { + v.next = r; + v.reset = null; + self.active -= 1; + self.start(c); + return; + } + + const action = c.callback(c.userdata, self, c, .{ .timer = .cancel }); + switch (action) { + .disarm => {}, + .rearm => { + self.active -= 1; + self.start(c); + return; + }, + } + } + }, + + else => unreachable, + } + + // Decrement the active count so we know how many are running for + // .until_done run semantics. + if (completion.flags.state == .active) self.active -= 1; + + // Mark the completion as done + completion.flags.state = .dead; + } +}; + +pub const Completion = struct { + /// Operation to execute. This is only safe to read BEFORE the completion + /// is queued. After being queued (with "add"), the operation may change. + op: Operation = .{ .noop = {} }, + + /// Userdata and callback for when the completion is finished. + userdata: ?*anyopaque = null, + callback: Callback = noopCallback, + + //--------------------------------------------------------------- + // Internal fields + + /// If scheduled on a thread pool, this will be set. This is NOT a + /// reliable way to get access to the loop and shouldn't be used + /// except internally. + task: ThreadPool.Task = undefined, + task_loop: *Loop = undefined, + task_completions: *Loop.TaskCompletionQueue = undefined, + task_result: Result = undefined, + + flags: packed struct { + /// Watch state of this completion. We use this to determine whether + /// we're active, adding, deleting, etc. This lets us add and delete + /// multiple times before a loop tick and handle the state properly. + state: State = .dead, + + /// Schedule this onto the threadpool rather than epoll. Not all + /// operations support this. + threadpool: bool = false, + + /// Set to true to dup the file descriptor for the operation prior + /// to setting it up with epoll. This is a hack to make it so that + /// a completion can represent a single op per fd, since epoll requires + /// a single fd for multiple ops. We don't want to track that esp + /// since epoll isn't the primary Linux interface. + dup: bool = false, + dup_fd: posix.fd_t = 0, + } = .{}, + + /// Intrusive queue field + next: ?*Completion = null, + + const State = enum(u3) { + /// completion is not part of any loop + dead = 0, + + /// completion is in the submission queue + adding = 1, + + /// completion is in the deletion queue + deleting = 2, + + /// completion is registered with epoll + active = 3, + }; + + /// Returns the state of this completion. There are some things to + /// be caution about when calling this function. + /// + /// First, this is only safe to call from the main thread. This cannot + /// be called from any other thread. + /// + /// Second, if you are using default "undefined" completions, this will + /// NOT return a valid value if you access it. You must zero your + /// completion using ".{}". You only need to zero the completion once. + /// Once the completion is in use, it will always be valid. + /// + /// Third, if you stop the loop (loop.stop()), the completions registered + /// with the loop will NOT be reset to a dead state. + pub fn state(self: Completion) CompletionState { + return switch (self.flags.state) { + .dead => .dead, + .adding, .deleting, .active => .active, + }; + } + + /// Perform the operation associated with this completion. This will + /// perform the full blocking operation for the completion. + fn perform(self: *Completion) Result { + return switch (self.op) { + // This should never happen because we always do these synchronously + // or in another location. + .cancel, + .noop, + .shutdown, + .timer, + => unreachable, + + .accept => |*op| .{ + .accept = if (xev_posix.accept( + op.socket, + &op.addr, + &op.addr_size, + op.flags, + )) |v| + v + else |_| + error.Unknown, + }, + + .connect => |*op| .{ + .connect = if (xev_posix.getsockoptError(op.socket)) {} else |err| err, + }, + + .poll => .{ .poll = {} }, + + .read => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| xev_posix.read(op.fd, v), + .array => |*v| xev_posix.read(op.fd, v), + }; + + break :res .{ + .read = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .pread => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| xev_posix.pread(op.fd, v, op.offset), + .array => |*v| xev_posix.pread(op.fd, v, op.offset), + }; + + break :res .{ + .pread = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .write => |*op| .{ + .write = switch (op.buffer) { + .slice => |v| xev_posix.write(op.fd, v), + .array => |*v| xev_posix.write(op.fd, v.array[0..v.len]), + }, + }, + + .pwrite => |*op| .{ + .pwrite = switch (op.buffer) { + .slice => |v| xev_posix.pwrite(op.fd, v, op.offset), + .array => |*v| xev_posix.pwrite(op.fd, v.array[0..v.len], op.offset), + }, + }, + + .send => |*op| .{ + .send = switch (op.buffer) { + .slice => |v| xev_posix.send(op.fd, v, 0), + .array => |*v| xev_posix.send(op.fd, v.array[0..v.len], 0), + }, + }, + + .sendmsg => |*op| .{ + .sendmsg = if (xev_posix.sendmsg(op.fd, op.msghdr, 0)) |v| + v + else |err| + err, + }, + + .recvmsg => |*op| res: { + const res = std.os.linux.recvmsg(op.fd, op.msghdr, 0); + break :res .{ + .recvmsg = if (res == 0) + error.EOF + else if (res > 0) + res + else switch (linux.errno(res)) { + else => |err| posix.unexpectedErrno(err), + }, + }; + }, + + .recv => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| xev_posix.recv(op.fd, v, 0), + .array => |*v| xev_posix.recv(op.fd, v, 0), + }; + + break :res .{ + .recv = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .close => |*op| res: { + xev_posix.close(op.fd); + break :res .{ .close = {} }; + }, + }; + } + + /// Return the fd for the completion. This will perform a dup(2) if + /// requested. + fn fd_maybe_dup(self: *Completion) error{DupFailed}!posix.fd_t { + const old_fd = self.fd().?; + if (!self.flags.dup) return old_fd; + if (self.flags.dup_fd > 0) return self.flags.dup_fd; + + self.flags.dup_fd = xev_posix.dup(old_fd) catch return error.DupFailed; + return self.flags.dup_fd; + } + + /// Returns the fd associated with the completion (if any). + fn fd(self: *Completion) ?posix.fd_t { + return switch (self.op) { + .accept => |v| v.socket, + .connect => |v| v.socket, + .poll => |v| v.fd, + .read => |v| v.fd, + .pread => |v| v.fd, + .recv => |v| v.fd, + .write => |v| v.fd, + .pwrite => |v| v.fd, + .send => |v| v.fd, + .sendmsg => |v| v.fd, + .recvmsg => |v| v.fd, + .close => |v| v.fd, + .shutdown => |v| v.socket, + + .cancel, + .timer, + => null, + + .noop => unreachable, + }; + } +}; + +pub const OperationType = enum { + noop, + cancel, + accept, + connect, + poll, + read, + pread, + write, + pwrite, + send, + recv, + sendmsg, + recvmsg, + close, + shutdown, + timer, +}; + +/// The result type based on the operation type. For a callback, the +/// result tag will ALWAYS match the operation tag. +pub const Result = union(OperationType) { + noop: void, + cancel: CancelError!void, + accept: AcceptError!posix.socket_t, + connect: ConnectError!void, + poll: PollError!void, + read: ReadError!usize, + pread: ReadError!usize, + write: WriteError!usize, + pwrite: WriteError!usize, + send: WriteError!usize, + recv: ReadError!usize, + sendmsg: WriteError!usize, + recvmsg: ReadError!usize, + close: CloseError!void, + shutdown: ShutdownError!void, + timer: TimerError!TimerTrigger, +}; + +/// All the supported operations of this event loop. These are always +/// backend-specific and therefore the structure and types change depending +/// on the underlying system in use. The high level operations are +/// done by initializing the request handles. +pub const Operation = union(OperationType) { + noop: void, + + cancel: struct { + c: *Completion, + }, + + accept: struct { + socket: posix.socket_t, + addr: posix.sockaddr = undefined, + addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), + flags: u32 = posix.SOCK.CLOEXEC, + }, + + connect: struct { + socket: posix.socket_t, + addr: net.Address, + }, + + /// Poll for events but do not perform any operations on them being + /// ready. The "events" field are a OR-ed list of EPOLL events. + poll: struct { + fd: posix.fd_t, + events: u32, + }, + + read: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + pread: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + offset: u64, + }, + + write: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + pwrite: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + offset: u64, + }, + + send: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + recv: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + sendmsg: struct { + fd: posix.fd_t, + msghdr: *linux.msghdr_const, + + /// Optionally, a write buffer can be specified and the given + /// msghdr will be populated with information about this buffer. + buffer: ?WriteBuffer = null, + + /// Do not use this, it is only used internally. + iov: [1]posix.iovec_const = undefined, + }, + + recvmsg: struct { + fd: posix.fd_t, + msghdr: *linux.msghdr, + }, + + close: struct { + fd: posix.fd_t, + }, + + shutdown: struct { + socket: posix.socket_t, + how: ShutdownHow = .both, + }, + + timer: Timer, + + const Timer = struct { + /// The absolute time to fire this timer next. + next: std.os.linux.timespec, + + /// Only used internally. If this is non-null and timer is + /// CANCELLED, then the timer is rearmed automatically with this + /// as the next time. The callback will not be called on the + /// cancellation. + reset: ?std.os.linux.timespec = null, + + /// Internal heap fields. + heap: heap.IntrusiveField(Timer) = .{}, + + /// We point back to completion for now. When issue[1] is fixed, + /// we can juse use that from our heap fields. + /// [1]: https://github.com/ziglang/zig/issues/6611 + c: *Completion = undefined, + + fn less(_: void, a: *const Timer, b: *const Timer) bool { + return a.ns() < b.ns(); + } + + /// Returns the nanoseconds of this timer. Note that maxInt(u64) ns is + /// 584 years so if we get any overflows we just use maxInt(u64). If + /// any software is running in 584 years waiting on this timer... + /// shame on me I guess... but I'll be dead. + fn ns(self: *const Timer) u64 { + assert(self.next.sec >= 0); + assert(self.next.nsec >= 0); + + const max = std.math.maxInt(u64); + const s_ns = std.math.mul( + u64, + @as(u64, @intCast(self.next.sec)), + std.time.ns_per_s, + ) catch return max; + return std.math.add(u64, s_ns, @as(u64, @intCast(self.next.nsec))) catch + return max; + } + }; +}; + +/// ReadBuffer are the various options for reading. +pub const ReadBuffer = union(enum) { + /// Read into this slice. + slice: []u8, + + /// Read into this array, just set this to undefined and it will + /// be populated up to the size of the array. This is an option because + /// the other union members force a specific size anyways so this lets us + /// use the other size in the union to support small reads without worrying + /// about buffer allocation. + /// + /// To know the size read you have to use the return value of the + /// read operations (i.e. recv). + /// + /// Note that the union at the time of this writing could accomodate a + /// much larger fixed size array here but we want to retain flexiblity + /// for future fields. + array: [32]u8, + + // TODO: future will have vectors +}; + +/// WriteBuffer are the various options for writing. +pub const WriteBuffer = union(enum) { + /// Write from this buffer. + slice: []const u8, + + /// Write from this array. See ReadBuffer.array for why we support this. + array: struct { + array: [32]u8, + len: usize, + }, + + // TODO: future will have vectors +}; + +const ThreadPoolError = error{ + ThreadPoolRequired, + ThreadPoolUnsupported, +}; + +pub const CancelError = ThreadPoolError || error{ + NotFound, +}; + +pub const ShutdownHow = std.Io.net.ShutdownHow; + +pub const EpollCtlError = error{ + FileDescriptorAlreadyPresentInSet, + OperationCausesCircularLoop, + FileDescriptorNotRegistered, + SystemResources, + UserResourceLimitReached, + FileDescriptorIncompatibleWithEpoll, +} || posix.UnexpectedError; + +pub const PosixShutdownError = error{ + ConnectionAborted, + ConnectionResetByPeer, + BlockingOperationInProgress, + NetworkSubsystemFailed, + SocketNotConnected, + SystemResources, +} || posix.UnexpectedError; + +pub const AcceptError = EpollCtlError || error{ + DupFailed, + Unknown, +}; + +pub const CloseError = EpollCtlError || ThreadPoolError || error{ + Unknown, +}; + +pub const PollError = EpollCtlError || error{ + DupFailed, + Unknown, +}; + +pub const ShutdownError = EpollCtlError || PosixShutdownError || error{ + Unknown, +}; + +pub const ConnectError = EpollCtlError || std.Io.net.IpAddress.ConnectError || xev_posix.ConnectError || error{ + DupFailed, + Unknown, +}; + +pub const ReadError = ThreadPoolError || EpollCtlError || + xev_posix.ReadError || + xev_posix.PReadError || + xev_posix.RecvFromError || + error{ + DupFailed, + EOF, + Unknown, + }; + +pub const WriteError = ThreadPoolError || EpollCtlError || + xev_posix.WriteError || + xev_posix.PWriteError || + xev_posix.SendError || + error{ + DupFailed, + Unknown, + }; + +pub const TimerError = error{ + Unexpected, +}; + +pub const TimerTrigger = enum { + /// Unused with epoll + request, + + /// Timer expired. + expiration, + + /// Timer was canceled. + cancel, +}; + +test "Completion size" { + const testing = std.testing; + + // Just so we are aware when we change the size + try testing.expectEqual(@as(usize, 184), @sizeOf(Completion)); +} + +test "epoll: default completion" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + var c: Completion = .{}; + loop.add(&c); + + // Tick + try loop.run(.until_done); + + // Completion should be dead. + try testing.expect(c.state() == .dead); +} + +test "epoll: loop time" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // should never init zero + const now = loop.now(); + try testing.expect(now > 0); + + // should update on a loop tick + while (now == loop.now()) try loop.run(.no_wait); +} + +test "epoll: stop" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1_000_000, &called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Tick + try loop.run(.no_wait); + try testing.expect(!called); + + // Stop + loop.stop(); + try loop.run(.until_done); + try testing.expect(!called); +} + +test "epoll: timer" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1, &called, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Add another timer + var called2 = false; + var c2: Completion = undefined; + loop.timer(&c2, 100_000, &called2, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // State checking + try testing.expect(c1.state() == .active); + try testing.expect(c2.state() == .active); + + // Tick + while (!called) try loop.run(.no_wait); + try testing.expect(called); + try testing.expect(!called2); + + // State checking + try testing.expect(c1.state() == .dead); + try testing.expect(c2.state() == .active); +} + +test "epoll: timer reset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // We know timer won't be called from the timer test previously. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .active); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "epoll: timer reset before tick" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "epoll: timer reset after trigger" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, cb); + + // Run the timer + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + trigger = null; + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "epoll: timerfd" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // We'll try with a simple timerfd + const Timerfd = @import("../linux/timerfd.zig").Timerfd; + var t = try Timerfd.init(.MONOTONIC, .{}); + defer t.deinit(); + try t.set(.{}, &.{ .value = .{ .nanoseconds = 1 } }, null); + + // Add the timer + var called = false; + var c: Completion = .{ + .op = .{ + .read = .{ + .fd = t.fd, + .buffer = .{ .array = undefined }, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = r.read catch unreachable; + _ = c; + _ = l; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c); + + // Tick + try loop.run(.until_done); + try testing.expect(called); +} + +test "epoll: socket accept/connect/send/recv/close" { + const mem = std.mem; + const os = posix; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + var ln = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); + try os.setsockopt(ln, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); + + // Create a TCP client socket + var client_conn = try xev_posix.socket( + address.any.family, + os.SOCK.NONBLOCK | os.SOCK.STREAM | os.SOCK.CLOEXEC, + 0, + ); + errdefer xev_posix.close(client_conn); + + // Accept + var server_conn: os.socket_t = 0; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const conn = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + conn.* = r.accept catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + + // Connect + var connected = false; + var c_connect: Completion = .{ + .op = .{ + .connect = .{ + .socket = client_conn, + .addr = address, + }, + }, + + .userdata = &connected, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.connect catch unreachable; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_connect); + + // Wait for the connection to be established + try loop.run(.until_done); + try testing.expect(server_conn > 0); + try testing.expect(connected); + + // Send + var c_send: Completion = .{ + .op = .{ + .send = .{ + .fd = client_conn, + .buffer = .{ .slice = &[_]u8{ 1, 1, 2, 3, 5, 8, 13 } }, + }, + }, + + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.send catch unreachable; + _ = ud; + return .disarm; + } + }).callback, + }; + loop.add(&c_send); + + // Receive + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + var c_recv: Completion = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &recv_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.recv catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, c_send.op.send.buffer.slice, recv_buf[0..recv_len]); + + // Shutdown + var shutdown = false; + var c_client_shutdown: Completion = .{ + .op = .{ + .shutdown = .{ + .socket = client_conn, + }, + }, + + .userdata = &shutdown, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.shutdown catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_shutdown); + try loop.run(.until_done); + try testing.expect(shutdown); + + // Read should be EOF + var eof: ?bool = null; + c_recv = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &eof, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*?bool, @ptrCast(@alignCast(ud.?))); + ptr.* = if (r.recv) |_| false else |err| switch (err) { + error.EOF => true, + else => false, + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + try loop.run(.until_done); + try testing.expect(eof.? == true); + + // Close + var c_client_close: Completion = .{ + .op = .{ + .close = .{ + .fd = client_conn, + }, + }, + + .userdata = &client_conn, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_close); + + var c_server_close: Completion = .{ + .op = .{ + .close = .{ + .fd = ln, + }, + }, + + .userdata = &ln, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_server_close); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(ln == 0); + try testing.expect(client_conn == 0); +} + +test "epoll: timer cancellation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr = @as(*?TimerTrigger, @ptrCast(@alignCast(ud.?))); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .cancel); +} + +test "epoll: canceling a completed operation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr = @as(*?TimerTrigger, @ptrCast(@alignCast(ud.?))); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .expiration); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/io_uring.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/io_uring.zig new file mode 100644 index 0000000..6891bc3 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/io_uring.zig @@ -0,0 +1,1792 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const linux = std.os.linux; +const posix = std.posix; +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); +const queue = @import("../queue.zig"); +const looppkg = @import("../loop.zig"); +const Callback = looppkg.Callback(@This()); +const CallbackAction = looppkg.CallbackAction; +const CompletionState = looppkg.CompletionState; +const noopCallback = looppkg.NoopCallback(@This()); + +pub const ShutdownHow = std.Io.net.ShutdownHow; + +/// True if this backend is available on this platform. +pub fn available() bool { + if (comptime builtin.os.tag != .linux) return false; + + // Perform a harmless syscall to determine if we have io_uring support. + // We should really only be checking for a subset of possible errors + // to determine if io_uring is available but there isn't really a strong + // course of action for any other error so we just mark it as unavailable. + // + // Also, this is a bit expensive -- we could get away with a single + // io_uring_register syscall to check for availability but that involves + // manually handling errors vs. the tried and true Zig stdlib here. + var ring = linux.IoUring.init(256, 0) catch return false; + ring.deinit(); + + return true; +} + +pub const Loop = struct { + ring: linux.IoUring, + + /// The number of active completions. This DOES NOT include completions that + /// are queued in the submissions queue. + active: usize = 0, + + /// Our queue of submissions that failed to enqueue. + submissions: queue.Intrusive(Completion) = .{}, + + /// Cached time + cached_now: posix.timespec = undefined, + + flags: packed struct { + /// True if the "now" field is outdated and should be updated + /// when it is used. + now_outdated: bool = true, + + /// Whether the loop is stopped or not. + stopped: bool = false, + + /// Whether we're in a run or not (to prevent nested runs). + in_run: bool = false, + } = .{}, + + /// Initialize the event loop. "entries" is the maximum number of + /// submissions that can be queued at one time. The number of completions + /// always matches the number of entries so the memory allocated will be + /// 2x entries (plus the basic loop overhead). + pub fn init(options: looppkg.Options) !Loop { + const entries = std.math.cast(u13, options.entries) orelse + return error.TooManyEntries; + + var result: Loop = .{ + // TODO(mitchellh): add an init_advanced function or something + // for people using the io_uring API directly to be able to set + // the flags for this. + .ring = try linux.IoUring.init(entries, 0), + }; + result.update_now(); + + return result; + } + + pub fn deinit(self: *Loop) void { + self.ring.deinit(); + } + + /// Run the event loop. See RunMode documentation for details on modes. + pub fn run(self: *Loop, mode: looppkg.RunMode) !void { + switch (mode) { + .no_wait => try self.tick_(.no_wait), + .once => try self.tick_(.once), + .until_done => try self.tick_(.until_done), + } + } + + /// Stop the loop. This can only be called from the main thread. + /// This will stop the loop forever. Future ticks will do nothing. + /// All completions are safe to read/write once any outstanding + /// `run` or `tick` calls are returned. + pub fn stop(self: *Loop) void { + self.flags.stopped = true; + } + + /// Returns true if the loop is stopped. This may mean there + /// are still pending completions to be processed. + pub fn stopped(self: *Loop) bool { + return self.flags.stopped; + } + + /// Returns the "loop" time in milliseconds. The loop time is updated + /// once per loop tick, before IO polling occurs. It remains constant + /// throughout callback execution. + /// + /// You can force an update of the "now" value by calling update_now() + /// at any time from the main thread. + /// + /// The clock that is used is not guaranteed. In general, a monotonic + /// clock source is always used if available. This value should typically + /// just be used for relative time calculations within the loop, such as + /// answering the question "did this happen ms ago?". + pub fn now(self: *Loop) i64 { + if (self.flags.now_outdated) self.update_now(); + + // If anything overflows we just return the max value. + const max = std.math.maxInt(i64); + + // Calculate all the values, being careful about overflows in order + // to just return the maximum value. + const sec = std.math.mul(isize, self.cached_now.sec, std.time.ms_per_s) catch return max; + const nsec = @divFloor(self.cached_now.nsec, std.time.ns_per_ms); + return std.math.lossyCast(i64, sec +| nsec); + } + + /// Update the cached time. + pub fn update_now(self: *Loop) void { + var ts: linux.timespec = undefined; + const rc = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts); + if (posix.errno(rc) == .SUCCESS) { + self.cached_now = ts; + self.flags.now_outdated = false; + } + } + + /// Tick the loop. The mode is comptime so we can do some tricks to + /// avoid function calls and runtime branching. + fn tick_(self: *Loop, comptime mode: looppkg.RunMode) !void { + // We can't nest runs. + if (self.flags.in_run) return error.NestedRunsNotAllowed; + self.flags.in_run = true; + defer self.flags.in_run = false; + + // The total number of events we're waiting for. + const wait = switch (mode) { + // until_done is one because we need to wait for at least one + // (if we have any). + .until_done => 1, + .once => 1, + .no_wait => 0, + }; + + var cqes: [128]linux.io_uring_cqe = undefined; + while (true) { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) break; + + // We always note that our "now" value is outdated even if we have + // no completions to execute. + self.flags.now_outdated = true; + + if (self.active == 0 and self.submissions.empty()) break; + + // If we have no queued submissions then we do the wait as part + // of the submit call, because then we can do exactly once syscall + // to get all our events. + if (self.submissions.empty()) { + _ = self.ring.submit_and_wait(wait) catch |err| switch (err) { + error.SignalInterrupt => continue, + else => return err, + }; + } else { + // We have submissions, meaning we have to do multiple submissions + // anyways so we always just do non-waiting ones. + _ = self.submit() catch |err| switch (err) { + error.SignalInterrupt => continue, + else => return err, + }; + } + + // Wait for completions... + const count = self.ring.copy_cqes(&cqes, wait) catch |err| switch (err) { + // EINTR means our blocking syscall was interrupted by some + // process signal. We just retry when we can. See signal(7). + error.SignalInterrupt => continue, + else => return err, + }; + + for (cqes[0..count]) |cqe| { + const c = @as(?*Completion, @ptrFromInt(@as(usize, @intCast(cqe.user_data)))) orelse continue; + self.active -= 1; + c.flags.state = .dead; + switch (c.invoke(self, cqe.res)) { + .disarm => {}, + .rearm => self.add(c), + } + } + + // Subtract our waiters. If we reached zero then we're done. + switch (mode) { + .no_wait => break, + .once => if (count > 0) break, + .until_done => {}, + } + } + } + + /// The errors that can come from submit. This has to be explicit + /// for now since Zig 0.11 has issues with the inferred error set. + /// We should try again someday. + pub const SubmitError = error{ + Unexpected, + SystemResources, + FileDescriptorInvalid, + FileDescriptorInBadState, + CompletionQueueOvercommitted, + SubmissionQueueEntryInvalid, + BufferInvalid, + RingShuttingDown, + OpcodeNotSupported, + SignalInterrupt, + InvalidThread, + }; + + /// Submit all queued operations. This never does an io_uring submit + /// and wait operation. + pub fn submit(self: *Loop) SubmitError!void { + _ = try self.ring.submit(); + + // If we have any submissions that failed to submit, we try to + // send those now. We have to make a copy so that any failures are + // resubmitted without an infinite loop. + var queued = self.submissions; + self.submissions = .{}; + while (queued.pop()) |c| self.add_(c, true); + } + + /// Add a timer to the loop. The timer will initially execute in "next_ms" + /// from now and will repeat every "repeat_ms" thereafter. If "next_ms" is + /// zero then the timer will invoke on the next loop tick. + /// + /// If next_ms is too large of a value that it doesn't fit into the + /// kernel time structure, the maximum sleep value will be used for your + /// system (this is at least 45 days on a 32-bit system, and thousands of + /// years on a 64-bit system). + pub fn timer( + self: *Loop, + c: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + c.* = .{ + .op = .{ + .timer = .{ + .next = self.timer_next(next_ms), + }, + }, + .userdata = userdata, + .callback = cb, + }; + + self.add(c); + } + + /// Reset a timer to trigger in next_ms. + /// + /// Important: c and c_cancel must NEVER be undefined. If you are using + /// timer_reset you should always initial c to the zero value ".{}". + /// + /// If the timer completion "c" is dead, this is equivalent to calling + /// "timer" and "c_cancel" is not used. You can verify this scenario by + /// checking "c_cancel.state()" after this call. + /// + /// If the timer completion "c" is active, and "c_cancel" is also active, + /// it is assumed that c_cancel is already resetting the timer. The + /// "next_ms" time is updated but otherwise the cancellation continues + /// and reset works. + /// + /// If the timer completion "c" is active, and "c_cancel" is dead, + /// "c" is cancelled and restarted to trigger in next_ms. + /// + /// There is no guarantee that c_cancel will ever be used. Check the + /// resulting state to know if you can reclaim its memory or not. + pub fn timer_reset( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + if (c.state() == .dead) { + self.timer(c, next_ms, userdata, cb); + return; + } + + // Update the reset time for the timer to the desired time + // along with all the callbacks. + c.op.timer.reset = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + + // If the cancellation is active, we assume its for this timer + // and do nothing. + if (c_cancel.state() == .active) return; + + assert(c_cancel.state() == .dead and c.state() == .active); + + // Note: we COULD use IORING_TIMEOUT_UPDATE here with the + // IORING_TIMEOUT_REMOVE op. We don't right now because it complicates + // our logic and ups our required kernel version to 5.11 while this + // works good enough. If there is a compelling reason to use + // IORING_TIMEOUT_UPDATE, I'm open to hearing it. + + c_cancel.* = .{ + .op = .{ .timer_remove = .{ .timer = c } }, + }; + self.add(c_cancel); + } + + fn timer_next(self: *Loop, next_ms: u64) linux.kernel_timespec { + // Get the timestamp of the absolute time that we'll execute this timer. + // There are lots of failure scenarios here in math. If we see any + // of them we just use the maximum value. + const max: linux.kernel_timespec = .{ + .sec = std.math.maxInt(isize), + .nsec = std.math.maxInt(isize), + }; + + const next_s = std.math.cast(isize, next_ms / std.time.ms_per_s) orelse + return max; + const next_ns = std.math.cast( + isize, + (next_ms % std.time.ms_per_s) * std.time.ns_per_ms, + ) orelse return max; + + if (self.flags.now_outdated) self.update_now(); + + return .{ + .sec = std.math.add(isize, self.cached_now.sec, next_s) catch + return max, + .nsec = std.math.add(isize, self.cached_now.nsec, next_ns) catch + return max, + }; + } + + /// Add a completion to the loop. This does NOT start the operation! + /// You must call "submit" at some point to submit all of the queued + /// work. + pub fn add(self: *Loop, completion: *Completion) void { + self.add_(completion, false); + } + + /// Internal add function. The only difference is try_submit. If try_submit + /// is true, then this function will attempt to submit the queue to the + /// ring if the submission queue is full rather than filling up our FIFO. + inline fn add_( + self: *Loop, + completion: *Completion, + try_submit: bool, + ) void { + // The completion at this point is active no matter what because + // it is going to be queued. + completion.flags.state = .active; + + const sqe = self.ring.get_sqe() catch |err| switch (err) { + error.SubmissionQueueFull => retry: { + // If the queue is full and we're in try_submit mode then we + // attempt to submit. This is used during submission flushing. + if (try_submit) { + if (self.submit()) { + // Submission succeeded but we may still fail (unlikely) + // to get an SQE... + if (self.ring.get_sqe()) |sqe| { + break :retry sqe; + } else |retry_err| switch (retry_err) { + error.SubmissionQueueFull => {}, + } + } else |_| {} + } + + // Add the completion to our submissions to try to flush later. + self.submissions.push(completion); + return; + }, + }; + + // Increase active to the amount in the ring. + self.active += 1; + + // Setup the submission depending on the operation + switch (completion.op) { + // Do nothing with noop completions. + .noop => { + completion.flags.state = .dead; + self.active -= 1; + return; + }, + + .accept => |*v| sqe.prep_accept( + v.socket, + &v.addr, + &v.addr_size, + v.flags, + ), + + .close => |v| sqe.prep_close(v.fd), + + .connect => |*v| sqe.prep_connect( + v.socket, + &v.addr.any, + v.addr.getOsSockLen(), + ), + + .poll => |v| sqe.prep_poll_add(v.fd, v.events), + + .read => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_read( + v.fd, + buf, + + // offset is a u64 but if the value is -1 then it uses + // the offset in the fd. + @bitCast(@as(i64, -1)), + ), + + .slice => |buf| sqe.prep_read( + v.fd, + buf, + + // offset is a u64 but if the value is -1 then it uses + // the offset in the fd. + @bitCast(@as(i64, -1)), + ), + }, + + .pread => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_read( + v.fd, + buf, + v.offset, + ), + + .slice => |buf| sqe.prep_read( + v.fd, + buf, + v.offset, + ), + }, + + .recv => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_recv( + v.fd, + buf, + 0, + ), + + .slice => |buf| sqe.prep_recv( + v.fd, + buf, + 0, + ), + }, + + .recvmsg => |*v| { + sqe.prep_recvmsg( + v.fd, + v.msghdr, + 0, + ); + }, + + .send => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_send( + v.fd, + buf.array[0..buf.len], + 0, + ), + + .slice => |buf| sqe.prep_send( + v.fd, + buf, + 0, + ), + }, + + .sendmsg => |*v| { + if (v.buffer) |_| { + @panic("TODO: sendmsg with buffer"); + } + + sqe.prep_sendmsg( + v.fd, + v.msghdr, + 0, + ); + }, + + .shutdown => |v| sqe.prep_shutdown( + v.socket, + switch (v.how) { + .both => linux.SHUT.RDWR, + .send => linux.SHUT.WR, + .recv => linux.SHUT.RD, + }, + ), + + .timer => |*v| sqe.prep_timeout( + &v.next, + 0, + linux.IORING_TIMEOUT_ABS, + ), + + .timer_remove => |v| sqe.prep_timeout_remove( + @intFromPtr(v.timer), + 0, + ), + + .write => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_write( + v.fd, + buf.array[0..buf.len], + + // offset is a u64 but if the value is -1 then it uses + // the offset in the fd. + @bitCast(@as(i64, -1)), + ), + + .slice => |buf| sqe.prep_write( + v.fd, + buf, + + // offset is a u64 but if the value is -1 then it uses + // the offset in the fd. + @bitCast(@as(i64, -1)), + ), + }, + + .pwrite => |*v| switch (v.buffer) { + .array => |*buf| sqe.prep_write( + v.fd, + buf.array[0..buf.len], + v.offset, + ), + + .slice => |buf| sqe.prep_write( + v.fd, + buf, + v.offset, + ), + }, + + .cancel => |v| sqe.prep_cancel(@intCast(@intFromPtr(v.c)), 0), + } + + // Our sqe user data always points back to the completion. + // The prep functions above reset the user data so we have to do this + // here. + sqe.user_data = @intFromPtr(completion); + } + + /// Submits a completion cancelation request. + /// Results in error.NotFound if the completion couldn't be located because + /// it was already completed or an invalid Completion was provided. + pub fn cancel( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *Loop, + c: *Completion, + r: CancelError!void, + ) CallbackAction, + ) void { + c_cancel.* = .{ + .op = .{ + .cancel = .{ + .c = c, + }, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *Loop, + c_inner: *Completion, + r: Result, + ) CallbackAction { + return @call(.always_inline, cb, .{ + @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), + l_inner, + c_inner, + if (r.cancel) |_| {} else |err| err, + }); + } + }).callback, + }; + self.add(c_cancel); + } +}; + +/// A completion represents a single queued request in the ring. +/// Completions must have stable pointers. +/// +/// For the lowest overhead, these can be created manually and queued +/// directly. The API over the individual fields isn't the most user-friendly +/// since it is tune for performance. For user-friendly operations, +/// use the higher-level functions on this structure or the even +/// higher-level abstractions like the Timer struct. +pub const Completion = struct { + /// Operation to execute. This is only safe to read BEFORE the completion + /// is queued. After being queued (with "add"), the operation may change. + op: Operation = .{ .noop = {} }, + + /// Userdata and callback for when the completion is finished. + userdata: ?*anyopaque = null, + callback: Callback = noopCallback, + + /// Internally set + next: ?*Completion = null, + flags: packed struct { + state: State = .dead, + } = .{}, + + const State = enum(u1) { + /// completion is not part of any loop + dead = 0, + + /// completion is registered with epoll + active = 1, + }; + + /// Returns the state of this completion. There are some things to + /// be caution about when calling this function. + /// + /// First, this is only safe to call from the main thread. This cannot + /// be called from any other thread. + /// + /// Second, if you are using default "undefined" completions, this will + /// NOT return a valid value if you access it. You must zero your + /// completion using ".{}". You only need to zero the completion once. + /// Once the completion is in use, it will always be valid. + /// + /// Third, if you stop the loop (loop.stop()), the completions registered + /// with the loop will NOT be reset to a dead state. + pub fn state(self: Completion) CompletionState { + return switch (self.flags.state) { + .dead => .dead, + .active => .active, + }; + } + + /// Invokes the callback for this completion after properly constructing + /// the Result based on the res code. + fn invoke(self: *Completion, loop: *Loop, res: i32) CallbackAction { + const result: Result = switch (self.op) { + .noop => unreachable, + + .accept => .{ + .accept = if (res >= 0) + @intCast(res) + else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .AGAIN => error.Again, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .close => .{ + .close = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .connect => .{ + .connect = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .CONNREFUSED => error.ConnectionRefused, + .TIMEDOUT => error.TimedOut, + .HOSTUNREACH => error.HostUnreachable, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .poll => .{ + .poll = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .read => .{ + .read = self.readResult(.read, res), + }, + + .pread => .{ + .pread = self.readResult(.pread, res), + }, + + .recv => .{ + .recv = self.readResult(.recv, res), + }, + + .recvmsg => .{ + .recvmsg = self.readResult(.recvmsg, res), + }, + + .send => .{ + .send = if (res >= 0) + @intCast(res) + else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .PIPE => error.BrokenPipe, + .CONNRESET => error.ConnectionResetByPeer, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .sendmsg => .{ + .sendmsg = if (res >= 0) + @intCast(res) + else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .PIPE => error.BrokenPipe, + .CONNRESET => error.ConnectionResetByPeer, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .shutdown => .{ + .shutdown = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .NOTCONN => error.SocketNotConnected, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .timer => |*op| timer: { + const e = @as(posix.E, @enumFromInt(-res)); + + // If we have reset set, that means that we were canceled so + // that we can update our expiration time. + if (op.reset) |r| { + op.next = r; + op.reset = null; + return .rearm; + } + + break :timer .{ + .timer = if (res >= 0) .request else switch (e) { + .TIME => .expiration, + .CANCELED => .cancel, + else => |errno| posix.unexpectedErrno(errno), + }, + }; + }, + + .timer_remove => .{ + .timer_remove = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + .NOENT => error.NotFound, + .BUSY => error.ExpirationInProgress, + + // Okay, I don't know why this might happen. It appears to + // happen when the timer you're attempting to cancel has + // already expired, but I can't reproduce this in a unit + // test. If this isn't safe to ignore or someone can find + // the meaning of this, I'd be curious. + .TIME => {}, + + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .write => .{ + .write = if (res >= 0) + @intCast(res) + else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + // If a write is interrupted, we retry it automatically. + .INTR => return .rearm, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .pwrite => .{ + .pwrite = if (res >= 0) + @intCast(res) + else switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + // If a write is interrupted, we retry it automatically. + .INTR => return .rearm, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + + .cancel => .{ + .cancel = if (res >= 0) {} else switch (@as(posix.E, @enumFromInt(-res))) { + .NOENT => error.NotFound, + .ALREADY => error.ExpirationInProgress, + else => |errno| posix.unexpectedErrno(errno), + }, + }, + }; + + return self.callback(self.userdata, loop, self, result); + } + + fn readResult(self: *Completion, comptime op: OperationType, res: i32) ReadError!usize { + if (res > 0) { + return @intCast(res); + } + + if (res == 0) { + const active = @field(self.op, @tagName(op)); + if (!@hasField(@TypeOf(active), "buffer")) return ReadError.EOF; + + // If we receieve a zero byte read, it is an EOF _unless_ + // the requestesd buffer size was zero (weird). + return switch (active.buffer) { + .slice => |b| if (b.len == 0) 0 else ReadError.EOF, + .array => ReadError.EOF, + }; + } + + return switch (@as(posix.E, @enumFromInt(-res))) { + .CANCELED => error.Canceled, + .CONNRESET => error.ConnectionResetByPeer, + else => |errno| posix.unexpectedErrno(errno), + }; + } +}; + +pub const OperationType = enum { + /// Do nothing. This operation will not be queued and will never + /// have its callback fired. This is NOT equivalent to the io_uring + /// "nop" operation. + noop, + + /// Accept a connection on a socket. + accept, + + /// Close a file descriptor. + close, + + /// Initiate a connection on a socket. + connect, + + /// Poll a fd. Only oneshot mode is supported today. + poll, + + /// Read + read, + + /// PRead + pread, + + /// Receive a message from a socket. + recv, + + /// Send a message on a socket. + send, + + /// Send a message on a socket using sendmsg (i.e. UDP). + sendmsg, + + /// Recieve a message on a socket using recvmsg (i.e. UDP). + recvmsg, + + /// Shutdown all or part of a full-duplex connection. + shutdown, + + /// PWrite + pwrite, + + /// Write + write, + + /// A oneshot or repeating timer. For io_uring, this is implemented + /// using the timeout mechanism. + timer, + + /// Cancel an existing timer. + timer_remove, + + /// Cancel an existing operation. + cancel, +}; + +/// The result type based on the operation type. For a callback, the +/// result tag will ALWAYS match the operation tag. +pub const Result = union(OperationType) { + noop: void, + accept: AcceptError!posix.socket_t, + close: CloseError!void, + connect: ConnectError!void, + poll: PollError!void, + read: ReadError!usize, + pread: ReadError!usize, + recv: ReadError!usize, + send: WriteError!usize, + sendmsg: WriteError!usize, + recvmsg: ReadError!usize, + shutdown: ShutdownError!void, + pwrite: WriteError!usize, + write: WriteError!usize, + timer: TimerError!TimerTrigger, + timer_remove: TimerRemoveError!void, + cancel: CancelError!void, +}; + +/// All the supported operations of this event loop. These are always +/// backend-specific and therefore the structure and types change depending +/// on the underlying system in use. The high level operations are +/// done by initializing the request handles. +pub const Operation = union(OperationType) { + noop: void, + + accept: struct { + socket: posix.socket_t, + addr: posix.sockaddr = undefined, + addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), + flags: u32 = posix.SOCK.CLOEXEC, + }, + + close: struct { + fd: posix.fd_t, + }, + + connect: struct { + socket: posix.socket_t, + addr: net.Address, + }, + + poll: struct { + fd: posix.fd_t, + events: u32 = posix.POLL.IN, + }, + + read: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + pread: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + offset: u64, + }, + + recv: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + send: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + sendmsg: struct { + fd: posix.fd_t, + msghdr: *linux.msghdr_const, + + /// Optionally, a write buffer can be specified and the given + /// msghdr will be populated with information about this buffer. + buffer: ?WriteBuffer = null, + + /// Do not use this, it is only used internally. + iov: [1]posix.iovec_const = undefined, + }, + + recvmsg: struct { + fd: posix.fd_t, + msghdr: *linux.msghdr, + }, + + shutdown: struct { + socket: posix.socket_t, + how: ShutdownHow = .both, + }, + + pwrite: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + offset: u64, + }, + + write: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + timer: struct { + next: linux.kernel_timespec, + + /// Only used internally. If this is non-null and timer is + /// CANCELLED, then the timer is rearmed automatically with this + /// as the next time. The callback will not be called on the + /// cancellation. + reset: ?linux.kernel_timespec = null, + }, + + timer_remove: struct { + timer: *Completion, + }, + + cancel: struct { + c: *Completion, + }, +}; + +/// ReadBuffer are the various options for reading. +pub const ReadBuffer = union(enum) { + /// Read into this slice. + slice: []u8, + + /// Read into this array, just set this to undefined and it will + /// be populated up to the size of the array. This is an option because + /// the other union members force a specific size anyways so this lets us + /// use the other size in the union to support small reads without worrying + /// about buffer allocation. + /// + /// To know the size read you have to use the return value of the + /// read operations (i.e. recv). + /// + /// Note that the union at the time of this writing could accomodate a + /// much larger fixed size array here but we want to retain flexiblity + /// for future fields. + array: [32]u8, + + // TODO: future will have vectors +}; + +/// WriteBuffer are the various options for writing. +pub const WriteBuffer = union(enum) { + /// Write from this buffer. + slice: []const u8, + + /// Write from this array. See ReadBuffer.array for why we support this. + array: struct { + array: [32]u8, + len: usize, + }, + + // TODO: future will have vectors +}; + +pub const AcceptError = error{ + Canceled, + Again, + Unexpected, +}; + +pub const CancelError = error{ + NotFound, + ExpirationInProgress, + Unexpected, +}; + +pub const CloseError = error{ + Canceled, + Unexpected, +}; + +pub const ConnectError = error{ + Canceled, + Unexpected, + ConnectionRefused, + HostUnreachable, + TimedOut, +}; + +pub const PollError = error{ + Canceled, + Unexpected, +}; + +pub const ReadError = error{ + EOF, + Canceled, + Unexpected, + ConnectionResetByPeer, +}; + +pub const ShutdownError = error{ + Canceled, + SocketNotConnected, + Unexpected, +}; + +pub const WriteError = error{ + Canceled, + BrokenPipe, + ConnectionResetByPeer, + Unexpected, +}; + +pub const TimerError = error{ + Unexpected, +}; + +pub const TimerRemoveError = error{ + NotFound, + ExpirationInProgress, + Unexpected, +}; + +pub const TimerTrigger = enum { + /// Timer completed due to linked request completing in time. + request, + + /// Timer expired. + expiration, + + /// Timer was canceled. + cancel, +}; + +test "Completion size" { + const testing = std.testing; + + // Just so we are aware when we change the size + try testing.expectEqual(@as(usize, 128), @sizeOf(Completion)); +} + +test "io_uring: available" { + try std.testing.expect(available()); +} + +test "io_uring: overflow entries count" { + const testing = std.testing; + + { + var loop = try Loop.init(.{}); + defer loop.deinit(); + } + + { + try testing.expectError(error.TooManyEntries, Loop.init(.{ + .entries = std.math.pow(u14, 2, 13), + })); + } +} + +test "io_uring: default completion" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Due to implementation details, we need to have an ongoing completion to test that the noop operation works properly + var timer_called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1, &timer_called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + var c: Completion = .{}; + loop.add(&c); + + // Tick + try loop.run(.until_done); + + // Completion should have been called and be dead. + try testing.expect(c.state() == .dead); + try testing.expect(timer_called); +} + +test "io_uring: timerfd" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // We'll try with a simple timerfd + const Timerfd = @import("../linux/timerfd.zig").Timerfd; + var t = try Timerfd.init(.MONOTONIC, .{}); + defer t.deinit(); + try t.set(.{}, &.{ .value = .{ .nanoseconds = 1 } }, null); + + // Add the timer + var called = false; + var c: Completion = .{ + .op = .{ + // Note: we should be able to use `read` here but on + // Kernel 6.15.4 there is a bug that prevents the read + // from ever firing with io_uring. I don't know why. I changed + // this to a poll so tests pass, which should also be fine! + .poll = .{ + .fd = t.fd, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = c; + _ = r; + _ = l; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c); + + // Verify states + try testing.expect(c.state() == .active); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(c.state() == .dead); +} + +test "io_uring: timer" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1, &called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Add another timer + var called2 = false; + var c2: Completion = undefined; + loop.timer(&c2, 100_000, &called2, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // State checking + try testing.expect(c1.state() == .active); + try testing.expect(c2.state() == .active); + + // Tick + while (!called) try loop.run(.no_wait); + try testing.expect(called); + try testing.expect(!called2); + + // State checking + try testing.expect(c1.state() == .dead); + try testing.expect(c2.state() == .active); +} + +test "io_uring: timer reset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // We know timer won't be called from the timer test previously. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .active); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "io_uring: stop" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1_000_000, &called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Tick + try loop.run(.no_wait); + try testing.expect(!called); + + // Stop + loop.stop(); + try loop.run(.until_done); + try testing.expect(!called); +} + +test "io_uring: loop time" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // should never init zero + const now = loop.now(); + try testing.expect(now > 0); + + // should update on a loop tick + while (now == loop.now()) try loop.run(.no_wait); +} + +test "io_uring: timer remove" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + const b = @as(*?TimerTrigger, @ptrCast(ud.?)); + b.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Remove it + var c_remove: Completion = .{ + .op = .{ + .timer_remove = .{ + .timer = &c1, + }, + }, + + .userdata = null, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = ud; + _ = r.timer_remove catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_remove); + + // Tick + try loop.run(.until_done); + try testing.expect(trigger.? == .cancel); +} + +test "io_uring: socket accept/connect/send/recv/close" { + const mem = std.mem; + const os = posix; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + var ln = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); + try os.setsockopt(ln, os.SOL.SOCKET, os.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); + + // Create a TCP client socket + var client_conn = try xev_posix.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(client_conn); + + // Accept + var server_conn: os.socket_t = 0; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + const conn = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + conn.* = r.accept catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + + // Connect + var connected = false; + var c_connect: Completion = .{ + .op = .{ + .connect = .{ + .socket = client_conn, + .addr = address, + }, + }, + + .userdata = &connected, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = r.connect catch unreachable; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_connect); + + // Wait for the connection to be established + try loop.run(.until_done); + try testing.expect(server_conn > 0); + try testing.expect(connected); + + // Send + var c_send: Completion = .{ + .op = .{ + .send = .{ + .fd = client_conn, + .buffer = .{ .slice = &[_]u8{ 1, 1, 2, 3, 5, 8, 13 } }, + }, + }, + + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = r.send catch unreachable; + _ = ud; + return .disarm; + } + }).callback, + }; + loop.add(&c_send); + + // Receive + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + var c_recv: Completion = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &recv_len, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.recv catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, c_send.op.send.buffer.slice, recv_buf[0..recv_len]); + + // Shutdown + var shutdown = false; + var c_client_shutdown: Completion = .{ + .op = .{ + .shutdown = .{ + .socket = client_conn, + }, + }, + + .userdata = &shutdown, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = r.shutdown catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_shutdown); + try loop.run(.until_done); + try testing.expect(shutdown); + + // Read should be EOF + var eof: ?bool = null; + c_recv = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &eof, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*?bool, @ptrCast(@alignCast(ud.?))); + ptr.* = if (r.recv) |_| false else |err| switch (err) { + error.EOF => true, + else => false, + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + try loop.run(.until_done); + try testing.expect(eof.? == true); + + // Close + var c_client_close: Completion = .{ + .op = .{ + .close = .{ + .fd = client_conn, + }, + }, + + .userdata = &client_conn, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_close); + + var c_server_close: Completion = .{ + .op = .{ + .close = .{ + .fd = ln, + }, + }, + + .userdata = &ln, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*os.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_server_close); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(ln == 0); + try testing.expect(client_conn == 0); +} + +test "io_uring: sendmsg/recvmsg" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const server = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM, 0); + defer xev_posix.close(server); + try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1))); + try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(server, &address.any, address.getOsSockLen()); + + const client = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM, 0); + defer xev_posix.close(client); + + // Send + const buffer_send = [_]u8{42} ** 128; + const iovecs_send = [_]posix.iovec_const{ + posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len }, + }; + var msg_send = linux.msghdr_const{ + .name = &address.any, + .namelen = address.getOsSockLen(), + .iov = &iovecs_send, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }; + var c_sendmsg: Completion = .{ + .op = .{ + .sendmsg = .{ + .fd = client, + .msghdr = &msg_send, + }, + }, + + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = ud; + _ = l; + _ = c; + _ = r.sendmsg catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_sendmsg); + + // Recv + + var buffer_recv = [_]u8{0} ** 128; + var iovecs_recv = [_]posix.iovec{ + posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len }, + }; + const addr = [_]u8{0} ** 4; + var address_recv = net.Address.initIp4(addr, 0); + var msg_recv: linux.msghdr = linux.msghdr{ + .name = &address_recv.any, + .namelen = address_recv.getOsSockLen(), + .iov = &iovecs_recv, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }; + var recv_size: usize = 0; + var c_recvmsg: Completion = .{ + .op = .{ + .recvmsg = .{ + .fd = server, + .msghdr = &msg_recv, + }, + }, + + .userdata = &recv_size, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.recvmsg catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_recvmsg); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(recv_size == buffer_recv.len); + try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]); +} + +test "io_uring: socket read cancellation" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a UDP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const socket = try xev_posix.socket(address.any.family, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(socket); + try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(socket, &address.any, address.getOsSockLen()); + + // Read + var read_result: Result = undefined; + var c_read: Completion = .{ + .op = .{ + .read = .{ + .fd = socket, + .buffer = .{ + .array = undefined, + }, + }, + }, + .userdata = &read_result, + .callback = (struct { + fn callback(ud: ?*anyopaque, l: *Loop, c: *Completion, r: Result) CallbackAction { + const ptr = @as(*Result, @ptrCast(@alignCast(ud))); + ptr.* = r; + _ = c; + _ = l; + return .disarm; + } + }).callback, + }; + loop.add(&c_read); + + // Cancellation + var c_read_cancel = Completion{}; + loop.cancel( + &c_read, + &c_read_cancel, + void, + null, + (struct { + fn callback(ud: ?*void, l: *Loop, c: *Completion, r: CancelError!void) CallbackAction { + r catch unreachable; + _ = c; + _ = l; + _ = ud; + return .disarm; + } + }).callback, + ); + loop.add(&c_read_cancel); + + // Wait for the read to be cancelled + try loop.run(.until_done); + try testing.expectEqual(OperationType.read, @as(OperationType, read_result)); + try testing.expectError(error.Canceled, read_result.read); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/iocp.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/iocp.zig new file mode 100644 index 0000000..cc8506e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/iocp.zig @@ -0,0 +1,2421 @@ +//! Backend to use win32 IOCP. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const windows = @import("../windows.zig"); +const queue = @import("../queue.zig"); +const heap = @import("../heap.zig"); +const posix = std.posix; +const net = @import("../posix.zig").net; + +pub const ShutdownHow = std.Io.net.ShutdownHow; + +const looppkg = @import("../loop.zig"); +const Options = looppkg.Options; +const RunMode = looppkg.RunMode; +const Callback = looppkg.Callback(@This()); +const CallbackAction = looppkg.CallbackAction; +const CompletionState = looppkg.CompletionState; +const noopCallback = looppkg.NoopCallback(@This()); + +const log = std.log.scoped(.libxev_iocp); + +/// True if this backend is available on this platform. +pub fn available() bool { + return switch (builtin.os.tag) { + .windows => true, + else => false, + }; +} + +pub const Loop = struct { + const TimerHeap = heap.Intrusive(Timer, void, Timer.less); + + /// The handle to the IO completion port. + iocp_handle: windows.HANDLE = windows.INVALID_HANDLE_VALUE, + + /// The number of active completions. This DOES NOT include completions that are queued in the + /// submissions queue. + active: usize = 0, + + /// Our queue of submissions that we want to enqueue on the next tick. + /// These are NOT started. + submissions: queue.Intrusive(Completion) = .{}, + + /// The queue of cancellation requests. These will point to the completion that we need to + /// cancel. We don't enqueue the exact completion to cancel because it may be in another queue. + cancellations: queue.Intrusive(Completion) = .{}, + + /// Our queue of completed completions where the callback hasn't been called yet, but the + /// "result" field should be set on every completion. This is used to delay completion callbacks + /// until the next tick. + completions: queue.Intrusive(Completion) = .{}, + + /// Our queue of waiting completions + asyncs: queue.Intrusive(Completion) = .{}, + + /// Heap of timers. + timers: TimerHeap = .{ .context = {} }, + + /// Cached time + cached_now: u64, + + /// Duration of a tick of Windows QueryPerformanceCounter. + qpc_duration: u64, + + /// Some internal fields we can pack for better space. + flags: packed struct { + /// Whether we're in a run of not (to prevent nested runs). + in_run: bool = false, + + /// Whether our loop is in a stopped state or not. + stopped: bool = false, + } = .{}, + + /// Initialize a new IOCP-backed event loop. See the Options docs + /// for what options matter for IOCP. + pub fn init(options: Options) !Loop { + _ = options; + + // Get the duration of the QueryPerformanceCounter. + // We should check if the division is lossless, but it returns 10_000_000 on my machine so + // we'll handle that later. + const qpc_duration = 1_000_000_000 / windows.QueryPerformanceFrequency(); + + // This creates a new Completion Port + const handle = try windows.CreateIoCompletionPort(windows.INVALID_HANDLE_VALUE, null, 0, 1); + var res: Loop = .{ + .iocp_handle = handle, + .qpc_duration = qpc_duration, + .cached_now = undefined, + }; + + res.update_now(); + return res; + } + + /// Deinitialize the loop, this closes the handle to the Completion Port. Any events that were + /// unprocessed are lost -- their callbacks will never be called. + pub fn deinit(self: *Loop) void { + windows.CloseHandle(self.iocp_handle); + } + + /// Stop the loop. This can only be called from the main thread. + /// This will stop the loop forever. Future ticks will do nothing. + /// + /// This does NOT stop any completions associated to operations that are in-flight. + pub fn stop(self: *Loop) void { + self.flags.stopped = true; + } + + /// Returns true if the loop is stopped. This may mean there + /// are still pending completions to be processed. + pub fn stopped(self: *Loop) bool { + return self.flags.stopped; + } + + /// Add a completion to the loop. The completion is not started until the loop is run (`run`) or + /// an explicit submission request is made (`submit`). + pub fn add(self: *Loop, completion: *Completion) void { + // If the completion is a cancel operation, we start it immediately as it will be put in the + // cancellations queue. + if (completion.op == .cancel) { + self.start_completion(completion); + return; + } + + switch (completion.flags.state) { + // The completion is in an adding state already, nothing needs to be done. + .adding => return, + + // The completion is dead, probably because it was canceled. + .dead => {}, + + // If we reach this point, we have a problem... + .active => unreachable, + } + + // We just add the completion to the queue. Failures can happen + // at submission or tick time. + completion.flags.state = .adding; + self.submissions.push(completion); + } + + /// Submit any enqueued completions. This does not fire any callbacks for completed events + /// (success or error). Callbacks are only fired on the next tick. + pub fn submit(self: *Loop) !void { + // Submit all the submissions. We copy the submission queue so that any resubmits don't + // cause an infinite loop. + var queued = self.submissions; + self.submissions = .{}; + + // On error, we have to restore the queue because we may be batching. + errdefer self.submissions = queued; + + while (queued.pop()) |c| { + switch (c.flags.state) { + .adding => self.start_completion(c), + .dead => self.stop_completion(c, null), + .active => std.log.err( + "invalid state in submission queue state={}", + .{c.flags.state}, + ), + } + } + } + + /// Process the cancellations queue. This doesn't call any callbacks but can potentially make + /// system call to cancel active IO. + fn process_cancellations(self: *Loop) void { + while (self.cancellations.pop()) |c| { + const target = c.op.cancel.c; + var cancel_result: CancelError!void = {}; + switch (target.flags.state) { + // If the target is dead already we do nothing. + .dead => {}, + + // If it is in the submission queue, mark them as dead so they will never be + // submitted. + .adding => target.flags.state = .dead, + + // If it is active we need to schedule the deletion. + .active => self.stop_completion(target, &cancel_result), + } + + // We completed the cancellation. + c.result = .{ .cancel = cancel_result }; + self.completions.push(c); + } + } + + /// Run the event loop. See RunMode documentation for details on modes. + /// Once the loop is run, the pointer MUST remain stable. + pub fn run(self: *Loop, mode: RunMode) !void { + switch (mode) { + .no_wait => try self.tick(0), + .once => try self.tick(1), + .until_done => while (!self.done()) try self.tick(1), + } + } + + /// Tick through the event loop once, waiting for at least "wait" completions to be processed by + /// the loop itself. + pub fn tick(self: *Loop, wait: u32) !void { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We can't nest runs. + if (self.flags.in_run) return error.NestedRunsNotAllowed; + self.flags.in_run = true; + defer self.flags.in_run = false; + + // The list of entry that will be filled with a call to GetQueuedCompletionStatusEx. + var entries: [128]windows.OVERLAPPED_ENTRY = undefined; + + var wait_rem = @as(usize, @intCast(wait)); + + // Handle all of our cancellations first because we may be able to stop submissions from + // even happening if its still queued. Plus, cancellations sometimes add more to the + // submission queue. + self.process_cancellations(); + + // Submit pending completions. + try self.submit(); + + // Loop condition is inspired from the kqueue backend. See its documentation for details. + while (true) { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We must update our time no matter what. + self.update_now(); + + const should_continue = (self.active > 0 and (wait == 0 or wait_rem > 0)) or !self.completions.empty(); + if (!should_continue) break; + + // Run our expired timers. + const now_timer: Timer = .{ .next = self.cached_now }; + while (self.timers.peek()) |t| { + if (!Timer.less({}, t, &now_timer)) break; + + // Remove the timer + assert(self.timers.deleteMin().? == t); + + // Mark completion as done + const c = t.c; + c.flags.state = .dead; + + // We mark it as inactive here because if we rearm below the start() function will + // reincrement this. + self.active -= 1; + + // Lower our remaining count since we have processed something. + wait_rem -|= 1; + + // Invoke + const action = c.callback(c.userdata, self, c, .{ .timer = .expiration }); + switch (action) { + .disarm => {}, + .rearm => self.start_completion(c), + } + } + + // Process the completions we already have completed. + while (self.completions.pop()) |c| { + // We store whether this completion was active so we can decrement the active count + // later. + const c_active = c.flags.state == .active; + c.flags.state = .dead; + + // Decrease our waiters because we are definitely processing one. + wait_rem -|= 1; + + // Completion queue items MUST have a result set. + const action = c.callback(c.userdata, self, c, c.result.?); + switch (action) { + .disarm => { + // If we were active, decrement the number of active completions. + if (c_active) self.active -= 1; + }, + + // Only resubmit if we aren't already active + .rearm => if (!c_active) self.submissions.push(c), + } + } + + // Process asyncs + if (!self.asyncs.empty()) { + var asyncs = self.asyncs; + self.asyncs = .{}; + + while (asyncs.pop()) |c| { + const c_wakeup = c.op.async_wait.wakeup.swap(false, .seq_cst); + + // If we aren't waking this one up, requeue + if (!c_wakeup) { + self.asyncs.push(c); + continue; + } + + // We are waking up, mark this as dead and call it. + c.flags.state = .dead; + self.active -= 1; + + // Lower our waiters + wait_rem -|= 1; + + const action = c.callback(c.userdata, self, c, .{ .async_wait = {} }); + switch (action) { + .disarm => {}, + .rearm => self.start_completion(c), + } + } + } + + // If we have processed enough event, we break out of the loop. + if (wait_rem == 0) break; + + // Determine our next timeout based on the timers. + const timeout: ?windows.DWORD = timeout: { + // If we have a timer, we want to set the timeout to our next timer value. If we + // have no timer, we wait forever. + const t = self.timers.peek() orelse break :timeout null; + + // Determin the time in milliseconds. If the cast fails, we fallback to the maximum + // acceptable value. + const ms_now = self.cached_now / std.time.ns_per_ms; + const ms_next = t.next / std.time.ns_per_ms; + const ms = ms_next -| ms_now; + break :timeout std.math.cast(windows.DWORD, ms) orelse windows.INFINITE - 1; + }; + + // Wait for changes IO completions. + const count: u32 = windows.GetQueuedCompletionStatusEx(self.iocp_handle, &entries, timeout, false) catch |err| switch (err) { + // A timeout means that nothing was completed. + error.Timeout => 0, + + else => return err, + }; + + // Go through the entries and perform completions callbacks. + for (entries[0..count]) |entry| { + const completion: *Completion = if (entry.lpCompletionKey == 0) completion: { + // We retrieve the Completion from the OVERLAPPED pointer as we know it's a part of + // the Completion struct. + const overlapped_ptr: ?*windows.OVERLAPPED = @as(?*windows.OVERLAPPED, @ptrCast(entry.lpOverlapped)); + if (overlapped_ptr == null) { + // Probably an async wakeup + continue; + } + + break :completion @alignCast(@fieldParentPtr("overlapped", overlapped_ptr.?)); + } else completion: { + // JobObjects are a special case where the OVERLAPPED_ENTRY fields are interpreted differently. + // When JOBOBJECT_ASSOCIATE_COMPLETION_PORT is used, lpOverlapped actually contains the message + // value, and not the address of the overlapped structure. The Completion pointer is passed + // as the completion key instead. + const completion: *Completion = @ptrFromInt(entry.lpCompletionKey); + completion.result = .{ .job_object = .{ + .message = .{ + .type = @enumFromInt(entry.dwNumberOfBytesTransferred), + .value = @intFromPtr(entry.lpOverlapped), + }, + } }; + break :completion completion; + }; + + wait_rem -|= 1; + + self.active -= 1; + completion.flags.state = .dead; + + const result = completion.perform(); + const action = completion.callback(completion.userdata, self, completion, result); + switch (action) { + .disarm => {}, + .rearm => { + completion.reset(); + self.start_completion(completion); + }, + } + } + + // If we ran through the loop once we break if we don't care. + if (wait == 0) break; + } + } + + /// Returns the "loop" time in milliseconds. The loop time is updated once per loop tick, before + /// IO polling occurs. It remains constant throughout callback execution. + /// + /// You can force an update of the "now" value by calling update_now() at any time from the main + /// thread. + /// + /// QueryPerformanceCounter is used to get the current timestamp. + pub fn now(self: *Loop) i64 { + return @as(i64, @intCast(self.cached_now)); + } + + /// Update the cached time. + pub fn update_now(self: *Loop) void { + // Compute the current timestamp in ms by multiplying the QueryPerfomanceCounter value in + // ticks by the duration of a tick. + self.cached_now = windows.QueryPerformanceCounter() * self.qpc_duration; + } + + /// Add a timer to the loop. The timer will execute in "next_ms". This is oneshot: the timer + /// will not repeat. To repeat a timer, either schedule another in your callback or return rearm + /// from the callback. + pub fn timer( + self: *Loop, + c: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + c.* = .{ + .op = .{ + .timer = .{ + .next = self.timer_next(next_ms), + }, + }, + .userdata = userdata, + .callback = cb, + }; + + self.add(c); + } + + /// see io_uring.timer_reset for docs. + pub fn timer_reset( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + switch (c.flags.state) { + .dead => { + self.timer(c, next_ms, userdata, cb); + return; + }, + + // Adding state we can just modify the metadata and return since the timer isn't in the + // heap yet. + .adding => { + c.op.timer.next = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + }, + + .active => { + // Update the reset time for the timer to the desired time along with all the + // callbacks. + c.op.timer.reset = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + + // If the cancellation is active, we assume its for this timer. + if (c_cancel.state() == .active) return; + assert(c_cancel.state() == .dead and c.state() == .active); + c_cancel.* = .{ .op = .{ .cancel = .{ .c = c } } }; + self.add(c_cancel); + }, + } + } + + // Get the absolute timestamp corresponding to the given "next_ms". + pub fn timer_next(self: *Loop, next_ms: u64) u64 { + return self.cached_now + next_ms * std.time.ns_per_ms; + } + + pub fn done(self: *Loop) bool { + return self.flags.stopped or (self.active == 0 and + self.submissions.empty() and + self.completions.empty()); + } + + // Start the completion. + fn start_completion(self: *Loop, completion: *Completion) void { + const StartAction = union(enum) { + // We successfully submitted the operation. + submitted: void, + + // We are a timer. + timer: void, + + // We are a cancellation. + cancel: void, + + // We are an async wait + async_wait: void, + + // We have a result code from making a system call now. + result: Result, + }; + + const action: StartAction = switch (completion.op) { + .noop => { + completion.flags.state = .dead; + return; + }, + + .accept => |*v| action: { + if (v.internal_accept_socket == null) { + var addr: posix.sockaddr.storage = undefined; + var addr_len: i32 = @sizeOf(posix.sockaddr.storage); + + std.debug.assert(windows.ws2_32.getsockname(asSocket(v.socket), @as(*posix.sockaddr, @ptrCast(&addr)), &addr_len) == 0); + + var socket_type: i32 = 0; + const socket_type_bytes = std.mem.asBytes(&socket_type); + var opt_len: i32 = @as(i32, @intCast(socket_type_bytes.len)); + std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.TYPE, socket_type_bytes, &opt_len) == 0); + + v.internal_accept_socket = windows.WSASocketW(addr.family, socket_type, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED) catch |err| { + break :action .{ .result = .{ .accept = err } }; + }; + } + + self.associate_fd(completion.handle().?) catch unreachable; + + var discard: u32 = undefined; + const result = windows.ws2_32.AcceptEx( + asSocket(v.socket), + asSocket(v.internal_accept_socket.?), + &v.storage, + 0, + 0, + @as(u32, @intCast(@sizeOf(posix.sockaddr.storage))), + &discard, + &completion.overlapped, + ); + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + switch (err) { + windows.ws2_32.WinsockError.WSA_IO_PENDING => break :action .{ .submitted = {} }, + else => { + windows.CloseHandle(v.internal_accept_socket.?); + break :action .{ .result = .{ .accept = windows.unexpectedWSAError(err) } }; + }, + } + } + + break :action .{ .submitted = {} }; + }, + + .close => |v| .{ .result = .{ .close = windows.CloseHandle(v.fd) } }, + + .connect => |*v| action: { + const result = windows.ws2_32.connect(asSocket(v.socket), &v.addr.any, @as(i32, @intCast(v.addr.getOsSockLen()))); + if (result != 0) { + const err = windows.ws2_32.WSAGetLastError(); + break :action switch (err) { + else => .{ .result = .{ .connect = windows.unexpectedWSAError(err) } }, + }; + } + break :action .{ .result = .{ .connect = {} } }; + }, + + .read => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []u8 = if (v.buffer == .slice) v.buffer.slice else &v.buffer.array; + break :action if (windows.exp.ReadFile(v.fd, buffer, &completion.overlapped)) |_| + .{ + .submitted = {}, + } + else |err| + .{ + .result = .{ .read = err }, + }; + }, + + .pread => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []u8 = if (v.buffer == .slice) v.buffer.slice else &v.buffer.array; + completion.overlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Offset = @intCast(v.offset & 0xFFFF_FFFF_FFFF_FFFF); + completion.overlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME.OffsetHigh = @intCast(v.offset >> 32); + break :action if (windows.exp.ReadFile(v.fd, buffer, &completion.overlapped)) |_| + .{ + .submitted = {}, + } + else |err| + .{ + .result = .{ .pread = err }, + }; + }, + + .shutdown => |*v| .{ .result = .{ .shutdown = iocpShutdown(asSocket(v.socket), v.how) } }, + + .write => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []const u8 = if (v.buffer == .slice) v.buffer.slice else v.buffer.array.array[0..v.buffer.array.len]; + break :action if (windows.exp.WriteFile(v.fd, buffer, &completion.overlapped)) |_| + .{ + .submitted = {}, + } + else |err| + .{ + .result = .{ .write = err }, + }; + }, + + .pwrite => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []const u8 = if (v.buffer == .slice) v.buffer.slice else v.buffer.array.array[0..v.buffer.array.len]; + completion.overlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Offset = @intCast(v.offset & 0xFFFF_FFFF_FFFF_FFFF); + completion.overlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME.OffsetHigh = @intCast(v.offset >> 32); + break :action if (windows.exp.WriteFile(v.fd, buffer, &completion.overlapped)) |_| + .{ + .submitted = {}, + } + else |err| + .{ + .result = .{ .pwrite = err }, + }; + }, + + .send => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []const u8 = if (v.buffer == .slice) v.buffer.slice else v.buffer.array.array[0..v.buffer.array.len]; + v.wsa_buffer = .{ .buf = @constCast(buffer.ptr), .len = @as(u32, @intCast(buffer.len)) }; + const result = windows.ws2_32.WSASend( + asSocket(v.fd), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), + 1, + null, + 0, + &completion.overlapped, + null, + ); + if (result != 0) { + const err = windows.ws2_32.WSAGetLastError(); + break :action switch (err) { + windows.ws2_32.WinsockError.WSA_IO_PENDING => .{ .submitted = {} }, + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => .{ .result = .{ .send = error.Canceled } }, + .WSAECONNRESET, .WSAENETRESET => .{ .result = .{ .send = error.ConnectionResetByPeer } }, + else => .{ .result = .{ .send = windows.unexpectedWSAError(err) } }, + }; + } + break :action .{ .submitted = {} }; + }, + + .recv => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []u8 = if (v.buffer == .slice) v.buffer.slice else &v.buffer.array; + v.wsa_buffer = .{ .buf = buffer.ptr, .len = @as(u32, @intCast(buffer.len)) }; + + var flags: u32 = 0; + + const result = windows.ws2_32.WSARecv( + asSocket(v.fd), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), + 1, + null, + &flags, + &completion.overlapped, + null, + ); + if (result != 0) { + const err = windows.ws2_32.WSAGetLastError(); + break :action switch (err) { + windows.ws2_32.WinsockError.WSA_IO_PENDING => .{ .submitted = {} }, + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => .{ .result = .{ .recv = error.Canceled } }, + .WSAECONNRESET, .WSAENETRESET => .{ .result = .{ .recv = error.ConnectionResetByPeer } }, + else => .{ .result = .{ .recv = windows.unexpectedWSAError(err) } }, + }; + } + break :action .{ .submitted = {} }; + }, + + .sendto => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []const u8 = if (v.buffer == .slice) v.buffer.slice else v.buffer.array.array[0..v.buffer.array.len]; + v.wsa_buffer = .{ .buf = @constCast(buffer.ptr), .len = @as(u32, @intCast(buffer.len)) }; + const result = windows.ws2_32.WSASendTo( + asSocket(v.fd), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), + 1, + null, + 0, + &v.addr.any, + @as(i32, @intCast(v.addr.getOsSockLen())), + &completion.overlapped, + null, + ); + if (result != 0) { + const err = windows.ws2_32.WSAGetLastError(); + break :action switch (err) { + windows.ws2_32.WinsockError.WSA_IO_PENDING => .{ .submitted = {} }, + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => .{ .result = .{ .sendto = error.Canceled } }, + .WSAECONNRESET, .WSAENETRESET => .{ .result = .{ .sendto = error.ConnectionResetByPeer } }, + else => .{ .result = .{ .sendto = windows.unexpectedWSAError(err) } }, + }; + } + break :action .{ .submitted = {} }; + }, + + .recvfrom => |*v| action: { + self.associate_fd(completion.handle().?) catch unreachable; + const buffer: []u8 = if (v.buffer == .slice) v.buffer.slice else &v.buffer.array; + v.wsa_buffer = .{ .buf = buffer.ptr, .len = @as(u32, @intCast(buffer.len)) }; + + var flags: u32 = 0; + + const result = windows.ws2_32.WSARecvFrom( + asSocket(v.fd), + @as([*]windows.WSABUF, @ptrCast(&v.wsa_buffer)), + 1, + null, + &flags, + &v.addr, + @as(*i32, @ptrCast(&v.addr_size)), + &completion.overlapped, + null, + ); + if (result != 0) { + const err = windows.ws2_32.WSAGetLastError(); + break :action switch (err) { + windows.ws2_32.WinsockError.WSA_IO_PENDING => .{ .submitted = {} }, + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => .{ .result = .{ .recvfrom = error.Canceled } }, + .WSAECONNRESET, .WSAENETRESET => .{ .result = .{ .recvfrom = error.ConnectionResetByPeer } }, + else => .{ .result = .{ .recvfrom = windows.unexpectedWSAError(err) } }, + }; + } + break :action .{ .submitted = {} }; + }, + + .timer => |*v| action: { + v.c = completion; + self.timers.insert(v); + break :action .{ .timer = {} }; + }, + + .cancel => action: { + self.cancellations.push(completion); + break :action .{ .cancel = {} }; + }, + + .async_wait => action: { + self.asyncs.push(completion); + break :action .{ .async_wait = {} }; + }, + + .job_object => |*v| action: { + if (!v.associated) { + var port = windows.exp.JOBOBJECT_ASSOCIATE_COMPLETION_PORT{ + .CompletionKey = @intFromPtr(completion), + .CompletionPort = self.iocp_handle, + }; + + windows.exp.SetInformationJobObject( + v.job, + .JobObjectAssociateCompletionPortInformation, + &port, + @sizeOf(windows.exp.JOBOBJECT_ASSOCIATE_COMPLETION_PORT), + ) catch |err| break :action .{ .result = .{ .job_object = err } }; + + v.associated = true; + const action = completion.callback(completion.userdata, self, completion, .{ .job_object = .{ .associated = {} } }); + switch (action) { + .disarm => { + completion.flags.state = .dead; + return; + }, + .rearm => break :action .{ .submitted = {} }, + } + } + + break :action .{ .submitted = {} }; + }, + }; + + switch (action) { + .timer, .submitted, .cancel => { + // Increase our active count so we now wait for this. We assume it'll successfully + // queue. If it doesn't we handle that later (see submit). + self.active += 1; + completion.flags.state = .active; + }, + + .async_wait => { + // We are considered an active completion. + self.active += 1; + completion.flags.state = .active; + }, + + // A result is immediately available. Queue the completion to be invoked. + .result => |r| { + completion.result = r; + self.completions.push(completion); + }, + } + } + + /// Stop the completion. Fill `cancel_result` if it is non-null. + fn stop_completion(self: *Loop, completion: *Completion, cancel_result: ?*CancelError!void) void { + if (completion.flags.state == .active and completion.result != null) return; + + // Inspect other operations. WARNING: the state can be anything here so per op be sure to + // check the state flag. + switch (completion.op) { + .timer => |*v| { + if (completion.flags.state == .active) { + // Remove from the heap so it never fires... + self.timers.remove(v); + + // If we have reset AND we got cancellation result, that means that we were + // canceled so that we can update our expiration time. + if (v.reset) |r| { + v.next = r; + v.reset = null; + completion.flags.state = .dead; + self.active -= 1; + self.add(completion); + return; + } + } + + // Add to our completion so we trigger the callback. + completion.result = .{ .timer = .cancel }; + self.completions.push(completion); + + // Note the timers state purposely remains ACTIVE so that + // when we process the completion we decrement the + // active count. + }, + + .accept => |*v| { + if (completion.flags.state == .active) { + const result = windows.kernel32.CancelIoEx(asSocket(v.socket), &completion.overlapped); + cancel_result.?.* = if (result == windows.FALSE) + windows.unexpectedError(windows.kernel32.GetLastError()) + else {}; + } + }, + + inline .read, .pread, .write, .pwrite, .recv, .send, .sendto, .recvfrom => |*v| { + if (completion.flags.state == .active) { + const result = windows.kernel32.CancelIoEx(asSocket(v.fd), &completion.overlapped); + cancel_result.?.* = if (result == windows.FALSE) + windows.unexpectedError(windows.kernel32.GetLastError()) + else {}; + } + }, + + else => @panic("Not implemented"), + } + } + + // Sens an empty Completion token so that the loop wakes up if it is waiting for a completion + // event. + pub fn async_notify(self: *Loop, completion: *Completion) void { + // The completion must be in a waiting state. + assert(completion.op == .async_wait); + + // The completion has been wakeup, this is used to see which completion in the async queue + // needs to be removed. + completion.op.async_wait.wakeup.store(true, .seq_cst); + + // NOTE: This call can fail but errors are not documented, so we log the error here. + windows.PostQueuedCompletionStatus(self.iocp_handle, 0, 0, null) catch |err| { + log.warn("unexpected async_notify error={}", .{err}); + }; + } + + /// Associate a handler to the internal completion port. + /// This has to be done only once per handle so we delegate the responsibility to the caller. + pub fn associate_fd(self: Loop, fd: windows.HANDLE) !void { + if (fd == windows.INVALID_HANDLE_VALUE or self.iocp_handle == windows.INVALID_HANDLE_VALUE) return error.InvalidParameter; + // We ignore the error here because multiple calls to CreateIoCompletionPort with a HANDLE + // already registered triggers an INVALID_PARAMETER error and we have no way to see the cause + // of it. Call the raw extern directly to avoid the wrapper's unreachable on INVALID_PARAMETER. + _ = windows.kernel32.CreateIoCompletionPort(fd, self.iocp_handle, 0, 0); + } +}; + +/// Convenience to convert from windows.HANDLE to windows.ws2_32.SOCKET (which are the same thing). +inline fn asSocket(h: windows.HANDLE) windows.ws2_32.SOCKET { + return @as(windows.ws2_32.SOCKET, @ptrCast(h)); +} + +fn iocpShutdown(sock: windows.ws2_32.SOCKET, how: ShutdownHow) ShutdownError!void { + const result = windows.ws2_32.shutdown(sock, switch (how) { + .recv => windows.ws2_32.SD_RECEIVE, + .send => windows.ws2_32.SD_SEND, + .both => windows.ws2_32.SD_BOTH, + }); + if (result != 0) switch (windows.ws2_32.WSAGetLastError()) { + .WSAECONNABORTED => return error.ConnectionAborted, + .WSAECONNRESET => return error.ConnectionResetByPeer, + .WSAEINPROGRESS => return error.BlockingOperationInProgress, + .WSAEINVAL => unreachable, + .WSAENETDOWN => return error.NetworkSubsystemFailed, + .WSAENOTCONN => return error.SocketNotConnected, + .WSAENOTSOCK => unreachable, + .WSANOTINITIALISED => unreachable, + else => |err| return windows.unexpectedWSAError(err), + }; +} + +fn iocpClose(h: windows.HANDLE) void { + _ = windows.ws2_32.closesocket(asSocket(h)); +} + +fn iocpSetsockopt(sock: windows.ws2_32.SOCKET, level: i32, optname: i32, optval: []const u8) !void { + const result = windows.ws2_32.setsockopt(sock, level, optname, optval.ptr, @as(i32, @intCast(optval.len))); + if (result != 0) return error.Unexpected; +} + +fn iocpBind(sock: windows.ws2_32.SOCKET, addr: *const posix.sockaddr, len: posix.socklen_t) !void { + const result = windows.ws2_32.bind(sock, addr, @as(i32, @intCast(len))); + if (result != 0) return error.Unexpected; +} + +fn iocpListen(sock: windows.ws2_32.SOCKET, backlog: u31) !void { + const result = windows.ws2_32.listen(sock, @as(i32, backlog)); + if (result != 0) return error.Unexpected; +} + +/// A completion is a request to perform some work with the loop. +pub const Completion = struct { + /// Operation to execute. + op: Operation = .{ .noop = {} }, + + /// Userdata and callback for when the completion is finished. + userdata: ?*anyopaque = null, + callback: Callback = noopCallback, + + //--------------------------------------------------------------- + // Internal fields + + /// Intrusive queue field. + next: ?*Completion = null, + + /// Result code of the syscall. Only used internally in certain scenarios, should not be relied + /// upon by program authors. + result: ?Result = null, + + flags: packed struct { + /// Watch state of this completion. We use this to determine whether we're active, adding or + /// dead. This lets us add and abd delete multiple times before a loop tick and handle the + /// state properly. + state: State = .dead, + } = .{}, + + /// Win32 OVERLAPPED struct used for asynchronous IO. Only used internally in certain scenarios. + /// It needs to be there as we rely on @fieldParentPtr to get the completion using a pointer to + /// that field. + overlapped: windows.OVERLAPPED = .{ + .Internal = 0, + .InternalHigh = 0, + .DUMMYUNIONNAME = .{ .Pointer = null }, + .hEvent = null, + }, + + /// Loop associated with this completion. HANDLE are required to be associated with an I/O + /// Completion Port to work properly. + loop: ?*const Loop = null, + + const State = enum(u2) { + /// completion is not part of any loop + dead = 0, + + /// completion is in the submission queue + adding = 1, + + /// completion is submitted successfully + active = 2, + }; + + /// Returns the state of this completion. There are some things to be cautious about when + /// calling this function. + /// + /// First, this is only safe to call from the main thread. This cannot be called from any other + /// thread. + /// + /// Second, if you are using default "undefined" completions, this will NOT return a valid value + /// if you access it. You must zero your completion using ".{}". You only need to zero the + /// completion once. Once the completion is in use, it will always be valid. + /// + /// Third, if you stop the loop (loop.stop()), the completions registered with the loop will NOT + /// be reset to a dead state. + pub fn state(self: Completion) CompletionState { + return switch (self.flags.state) { + .dead => .dead, + .adding, .active => .active, + }; + } + + /// Returns a handle for the current operation if it makes sense. + fn handle(self: Completion) ?windows.HANDLE { + return switch (self.op) { + inline .accept => |*v| v.socket, + inline .read, .pread, .write, .pwrite, .recv, .send, .recvfrom, .sendto => |*v| v.fd, + else => null, + }; + } + + /// Perform the operation associated with this completion. This will perform the full blocking + /// operation for the completion. + pub fn perform(self: *Completion) Result { + return switch (self.op) { + .noop, .close, .connect, .shutdown, .timer, .cancel => { + std.log.warn("perform op={s}", .{@tagName(self.op)}); + unreachable; + }, + + .accept => |*v| { + var bytes_transferred: u32 = 0; + var flags: u32 = 0; + const result = windows.ws2_32.WSAGetOverlappedResult(asSocket(v.socket), &self.overlapped, &bytes_transferred, windows.FALSE, &flags); + + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + const r: Result = .{ + .accept = switch (err) { + windows.ws2_32.WinsockError.WSA_OPERATION_ABORTED => error.Canceled, + else => windows.unexpectedWSAError(err), + }, + }; + windows.CloseHandle(v.internal_accept_socket.?); + return r; + } + + return .{ .accept = self.op.accept.internal_accept_socket.? }; + }, + + .read => |*v| { + var bytes_transferred: windows.DWORD = 0; + const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); + if (result == windows.FALSE) { + const err = windows.kernel32.GetLastError(); + return .{ .read = switch (err) { + windows.Win32Error.OPERATION_ABORTED => error.Canceled, + else => error.Unexpected, + } }; + } + return .{ .read = @as(usize, @intCast(bytes_transferred)) }; + }, + + .pread => |*v| { + var bytes_transferred: windows.DWORD = 0; + const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); + if (result == windows.FALSE) { + const err = windows.kernel32.GetLastError(); + return .{ + .read = switch (err) { + windows.Win32Error.OPERATION_ABORTED => error.Canceled, + else => error.Unexpected, + }, + }; + } + return .{ .pread = @as(usize, @intCast(bytes_transferred)) }; + }, + + .write => |*v| { + var bytes_transferred: windows.DWORD = 0; + const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); + if (result == windows.FALSE) { + const err = windows.kernel32.GetLastError(); + return .{ + .write = switch (err) { + windows.Win32Error.OPERATION_ABORTED => error.Canceled, + else => error.Unexpected, + }, + }; + } + return .{ .write = @as(usize, @intCast(bytes_transferred)) }; + }, + + .pwrite => |*v| { + var bytes_transferred: windows.DWORD = 0; + const result = windows.kernel32.GetOverlappedResult(v.fd, &self.overlapped, &bytes_transferred, windows.FALSE); + if (result == windows.FALSE) { + const err = windows.kernel32.GetLastError(); + return .{ + .write = switch (err) { + windows.Win32Error.OPERATION_ABORTED => error.Canceled, + else => error.Unexpected, + }, + }; + } + return .{ .pwrite = @as(usize, @intCast(bytes_transferred)) }; + }, + + .send => |*v| { + var bytes_transferred: u32 = 0; + var flags: u32 = 0; + + const result = windows.ws2_32.WSAGetOverlappedResult(asSocket(v.fd), &self.overlapped, &bytes_transferred, windows.FALSE, &flags); + + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + return .{ + .send = switch (err) { + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => error.Canceled, + .WSAECONNRESET, .WSAENETRESET => error.ConnectionResetByPeer, + else => windows.unexpectedWSAError(err), + }, + }; + } + return .{ .send = @as(usize, @intCast(bytes_transferred)) }; + }, + + .recv => |*v| { + var bytes_transferred: u32 = 0; + var flags: u32 = 0; + + const result = windows.ws2_32.WSAGetOverlappedResult(asSocket(v.fd), &self.overlapped, &bytes_transferred, windows.FALSE, &flags); + + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + return .{ + .recv = switch (err) { + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => error.Canceled, + .WSAECONNRESET, .WSAENETRESET => error.ConnectionResetByPeer, + else => windows.unexpectedWSAError(err), + }, + }; + } + + // NOTE(Corendos): according to Win32 documentation, EOF has to be detected using the socket type. + const socket_type = t: { + var socket_type: windows.DWORD = 0; + const socket_type_bytes = std.mem.asBytes(&socket_type); + var opt_len: i32 = @as(i32, @intCast(socket_type_bytes.len)); + + // Here we assume the call will succeed because the socket should be valid. + std.debug.assert(windows.ws2_32.getsockopt(asSocket(v.fd), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.TYPE, socket_type_bytes, &opt_len) == 0); + break :t socket_type; + }; + + if (socket_type == windows.ws2_32.SOCK.STREAM and bytes_transferred == 0) { + return .{ .recv = error.EOF }; + } + + return .{ .recv = @as(usize, @intCast(bytes_transferred)) }; + }, + + .sendto => |*v| { + var bytes_transferred: u32 = 0; + var flags: u32 = 0; + + const result = windows.ws2_32.WSAGetOverlappedResult(asSocket(v.fd), &self.overlapped, &bytes_transferred, windows.FALSE, &flags); + + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + return .{ + .sendto = switch (err) { + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => error.Canceled, + .WSAECONNRESET, .WSAENETRESET => error.ConnectionResetByPeer, + else => windows.unexpectedWSAError(err), + }, + }; + } + return .{ .sendto = @as(usize, @intCast(bytes_transferred)) }; + }, + + .recvfrom => |*v| { + var bytes_transferred: u32 = 0; + var flags: u32 = 0; + + const result = windows.ws2_32.WSAGetOverlappedResult(asSocket(v.fd), &self.overlapped, &bytes_transferred, windows.FALSE, &flags); + + if (result != windows.TRUE) { + const err = windows.ws2_32.WSAGetLastError(); + return .{ + .recvfrom = switch (err) { + .WSA_OPERATION_ABORTED, .WSAECONNABORTED => error.Canceled, + .WSAECONNRESET, .WSAENETRESET => error.ConnectionResetByPeer, + else => windows.unexpectedWSAError(err), + }, + }; + } + return .{ .recvfrom = @as(usize, @intCast(bytes_transferred)) }; + }, + + .async_wait => .{ .async_wait = {} }, + + .job_object => self.result.?, + }; + } + + /// Reset the completion so it can be reused (in case of rearming for example). + pub fn reset(self: *Completion) void { + self.overlapped = .{ + .Internal = 0, + .InternalHigh = 0, + .DUMMYUNIONNAME = .{ .Pointer = null }, + .hEvent = null, + }; + self.result = null; + } +}; + +pub const OperationType = enum { + /// Do nothing. This operation will not be queued and will never + /// have its callback fired. This is NOT equivalent to the io_uring + /// "nop" operation. + noop, + + /// Accept a connection on a socket. + accept, + + /// Close a file descriptor. + close, + + /// Initiate a connection on a socket. + connect, + + /// Read + read, + + /// Pread + pread, + + /// Shutdown all or part of a full-duplex connection. + shutdown, + + /// Write + write, + + /// Pwrite + pwrite, + + /// Send + send, + + /// Recv + recv, + + /// Sendto + sendto, + + /// Recvfrom + recvfrom, + + /// A oneshot or repeating timer. For io_uring, this is implemented + /// using the timeout mechanism. + timer, + + /// Cancel an existing operation. + cancel, + + /// Wait for an async event to be posted. + async_wait, + + /// Receive a notification from a job object associated with a completion port + job_object, +}; + +/// All the supported operations of this event loop. These are always +/// backend-specific and therefore the structure and types change depending +/// on the underlying system in use. The high level operations are +/// done by initializing the request handles. +pub const Operation = union(OperationType) { + noop: void, + + accept: struct { + socket: windows.HANDLE, + storage: [@sizeOf(posix.sockaddr.storage)]u8 = undefined, + + internal_accept_socket: ?windows.HANDLE = null, + }, + + close: struct { + fd: windows.HANDLE, + }, + + connect: struct { + socket: windows.HANDLE, + addr: net.Address, + }, + + read: struct { + fd: windows.HANDLE, + buffer: ReadBuffer, + }, + + pread: struct { + fd: windows.HANDLE, + buffer: ReadBuffer, + offset: u64, + }, + + shutdown: struct { + socket: windows.HANDLE, + how: ShutdownHow = .both, + }, + + write: struct { + fd: windows.HANDLE, + buffer: WriteBuffer, + }, + + pwrite: struct { + fd: windows.HANDLE, + buffer: WriteBuffer, + offset: u64, + }, + + send: struct { + fd: windows.HANDLE, + buffer: WriteBuffer, + wsa_buffer: windows.WSABUF = undefined, + }, + + recv: struct { + fd: windows.HANDLE, + buffer: ReadBuffer, + wsa_buffer: windows.WSABUF = undefined, + }, + + sendto: struct { + fd: windows.HANDLE, + buffer: WriteBuffer, + addr: net.Address, + wsa_buffer: windows.WSABUF = undefined, + }, + + recvfrom: struct { + fd: windows.HANDLE, + buffer: ReadBuffer, + addr: posix.sockaddr = undefined, + addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), + wsa_buffer: windows.WSABUF = undefined, + }, + + timer: Timer, + + cancel: struct { + c: *Completion, + }, + + async_wait: struct { + wakeup: std.atomic.Value(bool) = .{ .raw = false }, + }, + + job_object: struct { + job: windows.HANDLE, + userdata: ?*anyopaque, + + /// Tracks if the job has been associated with the completion port. + /// Do not use this, it is used internally. + associated: bool = false, + }, +}; + +/// The result type based on the operation type. For a callback, the +/// result tag will ALWAYS match the operation tag. +pub const Result = union(OperationType) { + noop: void, + accept: AcceptError!windows.HANDLE, + close: CloseError!void, + connect: ConnectError!void, + read: ReadError!usize, + pread: ReadError!usize, + shutdown: ShutdownError!void, + write: WriteError!usize, + pwrite: WriteError!usize, + send: WriteError!usize, + recv: ReadError!usize, + sendto: WriteError!usize, + recvfrom: ReadError!usize, + timer: TimerError!TimerTrigger, + cancel: CancelError!void, + async_wait: AsyncError!void, + job_object: JobObjectError!JobObjectResult, +}; + +pub const CancelError = error{ + Unexpected, +}; + +pub const AcceptError = error{ + AddressFamilyNotSupported, + ProcessFdQuotaExceeded, + SystemResources, + ProtocolNotSupported, + Canceled, + Unexpected, +}; + +pub const CloseError = error{ + Unexpected, +}; + +pub const ConnectError = error{ + Canceled, + Unexpected, +}; + +pub const ShutdownError = error{ + ConnectionAborted, + ConnectionResetByPeer, + BlockingOperationInProgress, + NetworkSubsystemFailed, + SocketNotConnected, + SystemResources, +} || posix.UnexpectedError || error{ + Unexpected, +}; + +pub const WriteError = windows.WriteFileError || error{ + Canceled, + ConnectionResetByPeer, + Unexpected, +}; + +pub const ReadError = windows.ReadFileError || error{ + EOF, + Canceled, + ConnectionResetByPeer, + Unexpected, +}; + +pub const AsyncError = error{ + Unexpected, +}; + +pub const TimerError = error{ + Unexpected, +}; + +pub const TimerTrigger = enum { + /// Unused with IOCP + request, + + /// Timer expired. + expiration, + + /// Timer was canceled. + cancel, +}; + +pub const JobObjectError = error{ + Unexpected, +}; + +pub const JobObjectResult = union(enum) { + // The job object was associated with the completion port + associated: void, + + /// A message was recived on the completion port for this job object + message: struct { + type: windows.exp.JOB_OBJECT_MSG_TYPE, + value: usize, + }, +}; + +/// ReadBuffer are the various options for reading. +pub const ReadBuffer = union(enum) { + /// Read into this slice. + slice: []u8, + + /// Read into this array, just set this to undefined and it will + /// be populated up to the size of the array. This is an option because + /// the other union members force a specific size anyways so this lets us + /// use the other size in the union to support small reads without worrying + /// about buffer allocation. + /// + /// To know the size read you have to use the return value of the + /// read operations (i.e. recv). + /// + /// Note that the union at the time of this writing could accomodate a + /// much larger fixed size array here but we want to retain flexiblity + /// for future fields. + array: [32]u8, + + // TODO: future will have vectors +}; + +/// WriteBuffer are the various options for writing. +pub const WriteBuffer = union(enum) { + /// Write from this buffer. + slice: []const u8, + + /// Write from this array. See ReadBuffer.array for why we support this. + array: struct { + array: [32]u8, + len: usize, + }, + + // TODO: future will have vectors +}; + +/// Timer that is inserted into the heap. +pub const Timer = struct { + /// The absolute time to fire this timer next. + next: u64, + + /// Only used internally. If this is non-null and timer is + /// CANCELLED, then the timer is rearmed automatically with this + /// as the next time. The callback will not be called on the + /// cancellation. + reset: ?u64 = null, + + /// Internal heap field. + heap: heap.IntrusiveField(Timer) = .{}, + + /// We point back to completion for now. When issue[1] is fixed, + /// we can juse use that from our heap fields. + /// [1]: https://github.com/ziglang/zig/issues/6611 + c: *Completion = undefined, + + fn less(_: void, a: *const Timer, b: *const Timer) bool { + return a.next < b.next; + } +}; + +test "iocp: loop time" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // should never init zero + const now = loop.now(); + try testing.expect(now > 0); + + while (now == loop.now()) try loop.run(.no_wait); +} +test "iocp: stop" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1_000_000, &called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Tick + try loop.run(.no_wait); + try testing.expect(!called); + + // Stop + loop.stop(); + try loop.run(.until_done); + try testing.expect(!called); +} + +test "iocp: timer" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1, &called, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Add another timer + var called2 = false; + var c2: Completion = undefined; + loop.timer(&c2, 100_000, &called2, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // State checking + try testing.expect(c1.state() == .active); + try testing.expect(c2.state() == .active); + + // Tick + while (!called) try loop.run(.no_wait); + try testing.expect(called); + try testing.expect(!called2); + + // State checking + try testing.expect(c1.state() == .dead); + try testing.expect(c2.state() == .active); +} + +test "iocp: timer reset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // We know timer won't be called from the timer test previously. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .active); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "iocp: timer reset before tick" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "iocp: timer reset after trigger" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, cb); + + // Run the timer + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + trigger = null; + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "iocp: timer cancellation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr: *?TimerTrigger = @ptrCast(@alignCast(ud.?)); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .cancel); +} + +test "iocp: canceling a completed operation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr: *?TimerTrigger = @ptrCast(@alignCast(ud.?)); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .expiration); +} + +test "iocp: noop" { + var loop = try Loop.init(.{}); + defer loop.deinit(); + + var c: Completion = .{}; + loop.add(&c); + + try loop.run(.once); +} + +test "iocp: file IO" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const utf16_file_name = (try windows.sliceToPrefixedFileW(null, "test_watcher_file")).span(); + + const f_handle = try windows.exp.CreateFile(utf16_file_name, windows.GENERIC_READ | windows.GENERIC_WRITE, 0, null, windows.OPEN_ALWAYS, windows.FILE_FLAG_OVERLAPPED, null); + defer windows.exp.DeleteFile(utf16_file_name) catch {}; + defer windows.CloseHandle(f_handle); + + // Perform a write and then a read + var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: Completion = .{ + .op = .{ + .write = .{ + .fd = f_handle, + .buffer = .{ .slice = &write_buf }, + }, + }, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = ud; + _ = l; + _ = c; + _ = r.write catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_write); + + // Wait for the write + try loop.run(.until_done); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + var c_read: Completion = .{ + .op = .{ + .read = .{ + .fd = f_handle, + .buffer = .{ .slice = &read_buf }, + }, + }, + .userdata = &read_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr: *usize = @ptrCast(@alignCast(ud.?)); + ptr.* = r.read catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_read); + + // Wait for the read + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &write_buf, read_buf[0..read_len]); +} + +test "iocp: file IO with offset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const utf16_file_name = (try windows.sliceToPrefixedFileW(null, "test_watcher_file")).span(); + + const f_handle = try windows.exp.CreateFile(utf16_file_name, windows.GENERIC_READ | windows.GENERIC_WRITE, 0, null, windows.OPEN_ALWAYS, windows.FILE_FLAG_OVERLAPPED, null); + defer windows.exp.DeleteFile(utf16_file_name) catch {}; + defer windows.CloseHandle(f_handle); + + // Perform a write and then a read + var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: Completion = .{ + .op = .{ + .pwrite = .{ + .fd = f_handle, + .buffer = .{ .slice = &write_buf }, + .offset = 1, + }, + }, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = ud; + _ = l; + _ = c; + _ = r.pwrite catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_write); + + // Wait for the write + try loop.run(.until_done); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + var c_read: Completion = .{ + .op = .{ + .pread = .{ + .fd = f_handle, + .buffer = .{ .slice = &read_buf }, + .offset = 1, + }, + }, + .userdata = &read_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr: *usize = @ptrCast(@alignCast(ud.?)); + ptr.* = r.pread catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_read); + + // Wait for the read + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &write_buf, read_buf[0..read_len]); +} + +test "iocp: socket accept/connect/send/recv/close" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + const ln = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(ln); + + try iocpSetsockopt(asSocket(ln), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(ln), &address.any, address.getOsSockLen()); + try iocpListen(asSocket(ln), kernel_backlog); + + // Create a TCP client socket + const client_conn = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(client_conn); + + var server_conn_result: Result = undefined; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn_result, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const conn: *Result = @ptrCast(@alignCast(ud.?)); + conn.* = r; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + + // Connect + var connected = false; + var c_connect: Completion = .{ + .op = .{ + .connect = .{ + .socket = client_conn, + .addr = address, + }, + }, + + .userdata = &connected, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.connect catch unreachable; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_connect); + + // Wait for the connection to be established + try loop.run(.until_done); + //try testing.expect(server_conn > 0); + try testing.expect(connected); + const server_conn = try server_conn_result.accept; + + // Send + var c_send: Completion = .{ + .op = .{ + .send = .{ + .fd = client_conn, + .buffer = .{ .slice = &[_]u8{ 1, 1, 2, 3, 5, 8, 13 } }, + }, + }, + + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.send catch unreachable; + _ = ud; + return .disarm; + } + }).callback, + }; + loop.add(&c_send); + + // Receive + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + var c_recv: Completion = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &recv_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr: *usize = @ptrCast(@alignCast(ud.?)); + ptr.* = r.recv catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, c_send.op.send.buffer.slice, recv_buf[0..recv_len]); + + // Shutdown + var shutdown = false; + var c_client_shutdown: Completion = .{ + .op = .{ + .shutdown = .{ + .socket = client_conn, + }, + }, + + .userdata = &shutdown, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.shutdown catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_shutdown); + try loop.run(.until_done); + try testing.expect(shutdown); + + // Read should be EOF + var eof: ?bool = null; + c_recv = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &eof, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr: *?bool = @ptrCast(@alignCast(ud.?)); + ptr.* = if (r.recv) |_| false else |err| switch (err) { + error.EOF => true, + else => false, + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + try loop.run(.until_done); + try testing.expect(eof.? == true); + + // Close + var client_conn_closed: bool = false; + var c_client_close: Completion = .{ + .op = .{ + .close = .{ + .fd = client_conn, + }, + }, + + .userdata = &client_conn_closed, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_close); + + var ln_closed: bool = false; + var c_server_close: Completion = .{ + .op = .{ + .close = .{ + .fd = ln, + }, + }, + + .userdata = &ln_closed, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_server_close); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(ln_closed); + try testing.expect(client_conn_closed); +} + +test "iocp: recv cancellation" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const socket = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.DGRAM, windows.ws2_32.IPPROTO.UDP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(socket); + + try iocpSetsockopt(asSocket(socket), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(socket), &address.any, address.getOsSockLen()); + + var recv_buf: [128]u8 = undefined; + var recv_result: Result = undefined; + var c_recv: Completion = .{ + .op = .{ + .recv = .{ + .fd = socket, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &recv_result, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr: *Result = @ptrCast(@alignCast(ud.?)); + ptr.* = r; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + try loop.submit(); + + var c_cancel_recv: Completion = .{ + .op = .{ .cancel = .{ .c = &c_recv } }, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = r.cancel catch unreachable; + _ = ud; + _ = l; + _ = c; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel_recv); + + // Wait for the send/receive + try loop.run(.until_done); + + try testing.expect(recv_result == .recv); + try testing.expectError(error.Canceled, recv_result.recv); +} + +test "iocp: accept cancellation" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + const ln = try windows.WSASocketW(windows.ws2_32.AF.INET, windows.ws2_32.SOCK.STREAM, windows.ws2_32.IPPROTO.TCP, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + errdefer iocpClose(ln); + + try iocpSetsockopt(asSocket(ln), windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try iocpBind(asSocket(ln), &address.any, address.getOsSockLen()); + try iocpListen(asSocket(ln), kernel_backlog); + + var server_conn_result: Result = undefined; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn_result, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const conn: *Result = @ptrCast(@alignCast(ud.?)); + conn.* = r; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + + try loop.submit(); + + var c_cancel_accept: Completion = .{ + .op = .{ .cancel = .{ .c = &c_accept } }, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = r.cancel catch unreachable; + _ = ud; + _ = l; + _ = c; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel_accept); + + // Wait for the send/receive + try loop.run(.until_done); + + try testing.expect(server_conn_result == .accept); + try testing.expectError(error.Canceled, server_conn_result.accept); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/kqueue.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/kqueue.zig new file mode 100644 index 0000000..a78249d --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/kqueue.zig @@ -0,0 +1,2972 @@ +//! Backend to use kqueue. This is currently only tested on macOS but +//! support for BSDs is planned (if it doesn't already work). +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const posix = std.posix; +const darwin = @import("../darwin.zig"); +const xev_posix = @import("../posix.zig"); +const net = xev_posix.net; +const queue = @import("../queue.zig"); +const queue_mpsc = @import("../queue_mpsc.zig"); +const heap = @import("../heap.zig"); +const ThreadPool = @import("../ThreadPool.zig"); +const Async = @import("../main.zig").Kqueue.Async; +const KEventError = std.Io.Kqueue.KEventError; + +const looppkg = @import("../loop.zig"); +const Options = looppkg.Options; +const RunMode = looppkg.RunMode; +const Callback = looppkg.Callback(@This()); +const CallbackAction = looppkg.CallbackAction; +const CompletionState = looppkg.CompletionState; +const noopCallback = looppkg.NoopCallback(@This()); + +const log = std.log.scoped(.libxev_kqueue); + +/// True if this backend is available on this platform. +pub fn available() bool { + return switch (builtin.os.tag) { + // macOS uses kqueue + .ios, .macos, .visionos => true, + + // BSDs use kqueue, but we only test on FreeBSD for now. + // kqueue isn't exactly the same here as it is on Apple platforms. + .freebsd => true, + + // Technically other BSDs support kqueue but our implementation + // below hard requires mach ports currently. That's not a fundamental + // requirement but until someone makes this implementation work + // on other BSDs we'll just say it isn't available. + else => false, + }; +} + +pub const NOTE_EXIT_FLAGS = switch (builtin.os.tag) { + .ios, .macos, .visionos => std.c.NOTE.EXIT | std.c.NOTE.EXITSTATUS, + .freebsd => std.c.NOTE.EXIT, + else => @compileError("kqueue not supported yet for target OS"), +}; + +pub const Loop = struct { + const TimerHeap = heap.Intrusive(Timer, void, Timer.less); + const TaskCompletionQueue = queue_mpsc.Intrusive(Completion); + + /// The fd of the kqueue. + kqueue_fd: posix.fd_t, + + /// The wakeup mechanism (mach ports on Apple, eventfd on BSD). + wakeup_state: Wakeup, + + /// The number of active completions. This DOES NOT include completions that + /// are queued in the submissions queue. + active: usize = 0, + + /// Our queue of submissions that we want to enqueue on the next tick. + /// These are NOT started, they are NOT submitted to kqueue. They are + /// pending. + submissions: queue.Intrusive(Completion) = .{}, + + /// The queue of cancellation requests. These will point to the + /// completion that we need to cancel. We don't queue the exact completion + /// to cancel because it may be in another queue. + cancellations: queue.Intrusive(Completion) = .{}, + + /// Our queue of completed completions where the callback hasn't been + /// called yet, but the "result" field should be set on every completion. + /// This is used to delay completion callbacks until the next tick. + /// Values in the completion queue must not be in the kqueue. + completions: queue.Intrusive(Completion) = .{}, + + /// Heap of timers. We use heaps instead of the EVFILT_TIMER because + /// it avoids a lot of syscalls in the case where there are a LOT of + /// timers. + timers: TimerHeap = .{ .context = {} }, + + /// The thread pool to use for blocking operations that kqueue can't do. + thread_pool: ?*ThreadPool, + + /// The MPSC queue for completed completions from the thread pool. + thread_pool_completions: TaskCompletionQueue, + + /// Cached time + cached_now: posix.timespec, + + /// Some internal fields we can pack for better space. + flags: packed struct { + /// True once it is initialized. + init: bool = false, + + /// Whether we're in a run or not (to prevent nested runs). + in_run: bool = false, + + /// Whether our loop is in a stopped state or not. + stopped: bool = false, + } = .{}, + + /// Initialize a new kqueue-backed event loop. See the Options docs + /// for what options matter for kqueue. + pub fn init(options: Options) !Loop { + // This creates a new kqueue fd + const fd = try createKqueueFd(); + errdefer xev_posix.close(fd); + + const wakeup_state: Wakeup = try .init(); + errdefer wakeup_state.deinit(); + + var res: Loop = .{ + .kqueue_fd = fd, + .wakeup_state = wakeup_state, + .thread_pool = options.thread_pool, + .thread_pool_completions = undefined, + .cached_now = undefined, + }; + + res.update_now(); + return res; + } + + /// Deinitialize the loop, this closes the kqueue. Any events that + /// were unprocessed are lost -- their callbacks will never be called. + pub fn deinit(self: *Loop) void { + xev_posix.close(self.kqueue_fd); + self.wakeup_state.deinit(); + } + + /// Stop the loop. This can only be called from the main thread. + /// This will stop the loop forever. Future ticks will do nothing. + /// + /// This does NOT stop any completions that are queued to be executed + /// in the thread pool. If you are using a thread pool, completions + /// are not safe to recover until the thread pool is shut down. If + /// you're not using a thread pool, all completions are safe to + /// read/write once any outstanding `run` or `tick` calls are returned. + pub fn stop(self: *Loop) void { + self.flags.stopped = true; + } + + /// Returns true if the loop is stopped. This may mean there + /// are still pending completions to be processed. + pub fn stopped(self: *Loop) bool { + return self.flags.stopped; + } + + /// Add a completion to the loop. The completion is not started until + /// the loop is run (`run`, `tick`) or an explicit submission request + /// is made (`submit`). + pub fn add(self: *Loop, completion: *Completion) void { + // If this is a cancellation, we special case it and add it to + // a separate queue so we can handle them first. + if (completion.op == .cancel) { + assert(!self.start(completion, undefined)); + return; + } + + // We just add the completion to the queue. Failures can happen + // at submission or tick time. + completion.flags.state = .adding; + self.submissions.push(completion); + } + + /// Submit any enqueue completions. This does not fire any callbacks + /// for completed events (success or error). Callbacks are only fired + /// on the next tick. + /// + /// If an error is returned, some events might be lost. Errors are + /// exceptional and should generally not happen. If we could recover + /// which completions were not submitted and restore them we would, + /// but the kqueue API doesn't provide that level of clarity. + pub fn submit(self: *Loop) !void { + // We try to submit as many events at once as we can. + var events: [256]Kevent = undefined; + var events_len: usize = 0; + + // Submit all the submissions. We copy the submission queue so that + // any resubmits don't cause an infinite loop. + var queued = self.submissions; + self.submissions = .{}; + + // On error, we have to restore the queue because we may be batching. + errdefer self.submissions = queued; + + while (true) { + queue_pop: while (queued.pop()) |c| { + switch (c.flags.state) { + // If we're adding then we start the event. + .adding => if (self.start(c, &events[events_len])) { + events_len += 1; + if (events_len >= events.len) break :queue_pop; + }, + + // If we're deleting then we create a deletion event and + // queue the completion to notify cancellation. + .deleting => if (c.kevent()) |ev| { + const ecanceled = errno_to_result(.CANCELED); + c.result = c.syscall_result(ecanceled); + c.flags.state = .dead; + self.completions.push(c); + + events[events_len] = ev; + events[events_len].flags = std.c.EV.DELETE; + events_len += 1; + if (events_len >= events.len) break :queue_pop; + }, + + // This is set if the completion was canceled while in the + // submission queue. This is a special case where we still + // want to call the callback to tell it it was canceled. + .dead => self.stop_completion(c), + + // Shouldn't happen if our logic is all correct. + .active => log.err( + "invalid state in submission queue state={}", + .{c.flags.state}, + ), + } + } + + // If we have no events then we have to have gone through the entire + // submission queue and we're done. + if (events_len == 0) break; + + // Zero timeout so that kevent returns immediately. + var timeout = std.mem.zeroes(posix.timespec); + const completed = try kevent_syscall( + self.kqueue_fd, + events[0..events_len], + events[0..events.len], + &timeout, + ); + events_len = 0; + + // Go through the completed events and queue them. + // NOTE: we currently never process completions (we set + // event list to zero length) because it was leading to + // memory corruption we need to investigate. + for (events[0..completed]) |ev| { + // Zero udata values are internal events that we do nothing + // on such as the mach port wakeup. + if (ev.udata == 0) continue; + + // We handle deletions separately. + if (ev.flags & std.c.EV.DELETE != 0) continue; + + const c: *Completion = @ptrFromInt(@as(usize, @intCast(ev.udata))); + + // If EV_ERROR is set, then submission failed for this + // completion. We get the syscall errorcode from data and + // store it. + if (ev.flags & std.c.EV.ERROR != 0) { + c.result = c.syscall_result(-@as(i32, @intCast(ev.data))); + } else { + // No error, means that this completion is ready to work. + c.result = c.perform(&ev); + } + + assert(c.result != null); + self.completions.push(c); + } + } + } + + /// Process the cancellations queue. This doesn't call any callbacks + /// or perform any syscalls. This just shuffles state around and sets + /// things up for cancellation to occur. + fn process_cancellations(self: *Loop) void { + while (self.cancellations.pop()) |c| { + const target = c.op.cancel.c; + switch (target.flags.state) { + // If the target is dead already we do nothing. + .dead => {}, + + // If the targeting is in the process of being removed + // from the kqueue we do nothing because its already done. + .deleting => {}, + + // If they are in the submission queue, mark them as dead + // so they will never be submitted. + .adding => target.flags.state = .dead, + + // If it is active we need to schedule the deletion. + .active => self.stop_completion(target), + } + + // We completed the cancellation. + c.result = .{ .cancel = {} }; + self.completions.push(c); + } + } + + /// Run the event loop. See RunMode documentation for details on modes. + /// Once the loop is run, the pointer MUST remain stable. + pub fn run(self: *Loop, mode: RunMode) !void { + switch (mode) { + .no_wait => try self.tick(0), + .once => try self.tick(1), + .until_done => while (!self.done()) try self.tick(1), + } + } + + /// Tick through the event loop once, waiting for at least "wait" completions + /// to be processed by the loop itself. + pub fn tick(self: *Loop, wait: u32) !void { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We can't nest runs. + if (self.flags.in_run) return error.NestedRunsNotAllowed; + self.flags.in_run = true; + defer self.flags.in_run = false; + + // Initialize + if (!self.flags.init) { + self.flags.init = true; + + if (self.thread_pool != null) { + self.thread_pool_completions.init(); + } + + self.wakeup_state.setup(self.kqueue_fd) catch |err| { + // We reset initialization because we can't do anything + // safely unless we get this mach port registered! + self.flags.init = false; + return err; + }; + } + + // The list of events, used as both a changelist and eventlist. + var events: [256]Kevent = undefined; + + // The number of events in the events array to submit as changes + // on repeat ticks. Used mostly for efficient disarm. + var changes: usize = 0; + + var wait_rem = @as(usize, @intCast(wait)); + + // Handle all of our cancellations first because we may be able + // to stop submissions from even happening if its still queued. + // Plus, cancellations sometimes add more to the submission queue + // (to remove from kqueue) + self.process_cancellations(); + + // TODO(mitchellh): an optimization in the future is for the last + // batch of submissions to return the changelist, because we can + // reuse that for the kevent call later... + try self.submit(); + + // Explaining the loop condition: we want to loop only if we have + // active handles (because it means we have something to do) + // and we have stuff we want to wait for still (wait_rem > 0) or + // we requested just a nowait tick (because we have to loop at least + // once). + // + // We also loop if there are any requested changes. Requested + // changes are only ever deletions currently, so we just process + // those until we have no more. + while (true) { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We must update our time no matter what + self.update_now(); + + // NOTE(mitchellh): This is a hideous boolean statement we should + // clean it up. + if (!((self.active > 0 and (wait == 0 or wait_rem > 0)) or + changes > 0 or + !self.completions.empty())) break; + + // Run our expired timers + const now_timer: Timer = .{ .next = self.cached_now }; + while (self.timers.peek()) |t| { + if (!Timer.less({}, t, &now_timer)) break; + + // Remove the timer + assert(self.timers.deleteMin().? == t); + + // Mark completion as done + const c = t.c; + c.flags.state = .dead; + + // We mark it as inactive here because if we rearm below + // the start() function will reincrement this. + self.active -= 1; + + // Lower our remaining count since we have processed something. + wait_rem -|= 1; + + // Invoke + const action = c.callback(c.userdata, self, c, .{ .timer = .expiration }); + switch (action) { + .disarm => {}, + + // We use undefined as the second param because timers + // never set a kevent, and we assert false for the same + // reason. + .rearm => assert(!self.start(c, undefined)), + } + } + + // Migrate our completions from the thread pool MPSC queue to our + // completion queue. + // TODO: unify the queues + if (self.thread_pool != null) { + while (self.thread_pool_completions.pop()) |c| { + self.completions.push(c); + } + } + + // Process the completions we already have completed. + while (self.completions.pop()) |c| { + // disarm_ev is the Kevent to use for disarming if the + // completion wants to disarm. We have to calculate this up + // front because c can be reused in callback. + const disarm_ev: ?Kevent = ev: { + // If we're not active then we were never part of the kqueue. + // If we are part of a threadpool we also never were part + // of the kqueue. + if (c.flags.state != .active or + c.flags.threadpool) break :ev null; + + break :ev c.kevent(); + }; + + // We store whether this completion was active so we can decrement + // the active count later + const c_active = c.flags.state == .active; + c.flags.state = .dead; + + // Decrease our waiters because we are definitely processing one. + wait_rem -|= 1; + + // Completion queue items MUST have a result set. + const action = c.callback(c.userdata, self, c, c.result.?); + switch (action) { + // If we're active we have to schedule a delete. Otherwise + // we do nothing because we were never part of the kqueue. + .disarm => { + if (disarm_ev) |ev| { + events[changes] = ev; + events[changes].flags = std.c.EV.DELETE; + events[changes].udata = 0; + changes += 1; + assert(changes <= events.len); + } + + if (c_active) self.active -= 1; + }, + + // Only resubmit if we aren't already active (in the queue) + .rearm => if (!c_active) self.submissions.push(c), + } + + // If we filled the events slice, we break to avoid overflow. + if (changes == events.len) break; + } + + // Determine our next timeout based on the timers + const timeout: ?posix.timespec = timeout: { + if (wait_rem == 0) break :timeout std.mem.zeroes(posix.timespec); + + // If we have a timer, we want to set the timeout to our next + // timer value. If we have no timer, we wait forever. + const t = self.timers.peek() orelse break :timeout null; + + // Determine the time in milliseconds. + const ms_now = @as(u64, @intCast(self.cached_now.sec)) * std.time.ms_per_s + + @as(u64, @intCast(self.cached_now.nsec)) / std.time.ns_per_ms; + const ms_next = @as(u64, @intCast(t.next.sec)) * std.time.ms_per_s + + @as(u64, @intCast(t.next.nsec)) / std.time.ns_per_ms; + const ms = ms_next -| ms_now; + + // Convert to s/ns for the timespec + const sec = ms / std.time.ms_per_s; + const nsec = (ms % std.time.ms_per_s) * std.time.ns_per_ms; + break :timeout .{ .sec = @intCast(sec), .nsec = @intCast(nsec) }; + }; + + // Wait for changes. Note that we ALWAYS attempt to get completions + // back even if are done waiting (wait_rem == 0) because if we have + // to make a syscall to submit changes, we might as well also check + // for done events too. + const completed = completed: while (true) { + break :completed kevent_syscall( + self.kqueue_fd, + events[0..changes], + events[0..events.len], + if (timeout) |*t| t else null, + ) catch |err| switch (err) { + // This should never happen because we always have + // space in our event list. If I'm reading the BSD source + // right (and Apple does something similar...) then ENOENT + // is always put into the eventlist if there is space: + // https://github.com/freebsd/freebsd-src/blob/5a4a83fd0e67a0d7787d2f3e09ef0e5552a1ffb6/sys/kern/kern_event.c#L1668 + error.EventNotFound => unreachable, + + // Any other error is fatal + else => return err, + }; + }; + + // Reset changes since they're not submitted + changes = 0; + + // Go through the completed events and queue them. + for (events[0..completed]) |ev| { + // Zero udata values are internal events that we do nothing + // on such as the mach port wakeup. + if (ev.udata == 0) continue; + + // Ignore any successful deletions. This can only happen + // from disarms below and in that case we already processed + // their callback. + if (ev.flags & std.c.EV.DELETE != 0) continue; + + // This can only be set during changelist processing so + // that means that this event was never actually active. + // Therefore, we only decrement the waiters by 1 if we + // processed an active change. + if (ev.flags & std.c.EV.ERROR != 0) { + // We cannot use c here because c is already dead + // at this point for this event. + continue; + } + wait_rem -|= 1; + + const c: *Completion = @ptrFromInt(@as(usize, @intCast(ev.udata))); + + // c is ready to be reused rigt away if we're dearming + // so we mark it as dead. + c.flags.state = .dead; + + const result = c.perform(&ev); + const action = c.callback(c.userdata, self, c, result); + switch (action) { + .disarm => { + // Mark this event for deletion, it'll happen + // on the next tick. + events[changes] = ev; + events[changes].flags = std.c.EV.DELETE; + events[changes].udata = 0; + changes += 1; + assert(changes <= events.len); + + self.active -= 1; + }, + + // We rearm by default with kqueue so we just have to make + // sure that the state is correct. + .rearm => { + c.flags.state = .active; + }, + } + } + + // If we ran through the loop once we break if we don't care. + if (wait == 0) break; + } + } + + /// Returns the "loop" time in milliseconds. The loop time is updated + /// once per loop tick, before IO polling occurs. It remains constant + /// throughout callback execution. + /// + /// You can force an update of the "now" value by calling update_now() + /// at any time from the main thread. + /// + /// The clock that is used is not guaranteed. In general, a monotonic + /// clock source is always used if available. This value should typically + /// just be used for relative time calculations within the loop, such as + /// answering the question "did this happen ms ago?". + pub fn now(self: *Loop) i64 { + // If anything overflows we just return the max value. + const max = std.math.maxInt(i64); + + // Calculate all the values, being careful about overflows in order + // to just return the maximum value. + const sec = std.math.mul(isize, self.cached_now.sec, std.time.ms_per_s) catch return max; + const nsec = @divFloor(self.cached_now.nsec, std.time.ns_per_ms); + return std.math.lossyCast(i64, sec +| nsec); + } + + /// Update the cached time. + pub fn update_now(self: *Loop) void { + switch (posix.errno(posix.system.clock_gettime(posix.CLOCK.MONOTONIC, &self.cached_now))) { + .SUCCESS => {}, + else => {}, + } + } + + /// Add a timer to the loop. The timer will execute in "next_ms". This + /// is oneshot: the timer will not repeat. To repeat a timer, either + /// schedule another in your callback or return rearm from the callback. + pub fn timer( + self: *Loop, + c: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + c.* = .{ + .op = .{ + .timer = .{ + .next = self.timer_next(next_ms), + }, + }, + .userdata = userdata, + .callback = cb, + }; + + self.add(c); + } + + /// See io_uring.timer_reset for docs. + pub fn timer_reset( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: Callback, + ) void { + switch (c.flags.state) { + .dead, .deleting => { + self.timer(c, next_ms, userdata, cb); + return; + }, + + // Adding state we can just modify the metadata and return + // since the timer isn't in the heap yet. + .adding => { + c.op.timer.next = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + return; + }, + + .active => { + // Update the reset time for the timer to the desired time + // along with all the callbacks. + c.op.timer.reset = self.timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + + // If the cancellation is active, we assume its for this timer + // and do nothing. + if (c_cancel.state() == .active) return; + assert(c_cancel.state() == .dead and c.state() == .active); + c_cancel.* = .{ .op = .{ .cancel = .{ .c = c } } }; + self.add(c_cancel); + }, + } + } + + fn timer_next(self: *Loop, next_ms: u64) posix.timespec { + // Get the timestamp of the absolute time that we'll execute this timer. + // There are lots of failure scenarios here in math. If we see any + // of them we just use the maximum value. + const max: posix.timespec = .{ + .sec = std.math.maxInt(isize), + .nsec = std.math.maxInt(isize), + }; + + const next_s = std.math.cast(isize, next_ms / std.time.ms_per_s) orelse + return max; + const next_ns = std.math.cast( + isize, + (next_ms % std.time.ms_per_s) * std.time.ns_per_ms, + ) orelse return max; + + self.update_now(); + + return .{ + .sec = std.math.add(isize, self.cached_now.sec, next_s) catch + return max, + .nsec = std.math.add(isize, self.cached_now.nsec, next_ns) catch + return max, + }; + } + + fn done(self: *Loop) bool { + return self.flags.stopped or (self.active == 0 and + self.submissions.empty() and + self.completions.empty()); + } + + /// Start the completion. This returns true if the Kevent was set + /// and should be queued. + fn start(self: *Loop, c: *Completion, ev: *Kevent) bool { + const StartAction = union(enum) { + /// We have set the kevent out parameter + kevent: void, + + // We are a timer, + timer: void, + + // We are a cancellation + cancel: void, + + // We want to run on the threadpool + threadpool: void, + + /// We have a result code from making a system call now. + result: i32, + }; + + const action: StartAction = if (c.flags.threadpool) .{ + .threadpool = {}, + } else switch (c.op) { + .noop => { + c.flags.state = .dead; + return false; + }, + + .cancel => action: { + // Queue the cancel + break :action .{ .cancel = {} }; + }, + + .accept => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .connect => |*v| action: { + while (true) { + const result = posix.system.connect(v.socket, &v.addr.any, v.addr.getOsSockLen()); + switch (posix.errno(result)) { + // Interrupt, try again + .INTR => continue, + + // This means the connect is blocked and in progress. + // We register for the write event which will let us know + // when it is complete. + .AGAIN, .INPROGRESS => { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + // Any other error we report + else => |errno| break :action .{ .result = errno_to_result(errno) }, + } + } + }, + + .write => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .pwrite => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .read => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .pread => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .send => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .recv => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .sendto => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .recvfrom => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .machport => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .proc => action: { + ev.* = c.kevent().?; + break :action .{ .kevent = {} }; + }, + + .shutdown => |v| action: { + const result = posix.system.shutdown(v.socket, switch (v.how) { + .recv => posix.SHUT.RD, + .send => posix.SHUT.WR, + .both => posix.SHUT.RDWR, + }); + + if (result >= 0) { + break :action .{ .result = result }; + } else { + break :action .{ .result = errno_to_result(posix.errno(result)) }; + } + }, + + .close => |v| action: { + xev_posix.close(v.fd); + break :action .{ .result = 0 }; + }, + + .timer => |*v| action: { + // Point back to completion since we need this. In the future + // we want to use @fieldParentPtr but https://github.com/ziglang/zig/issues/6611 + v.c = c; + + // Insert the timer into our heap. + self.timers.insert(v); + + // We always run timers + break :action .{ .timer = {} }; + }, + }; + + switch (action) { + .kevent, + .timer, + => { + // Increase our active count so we now wait for this. We + // assume it'll successfully queue. If it doesn't we handle + // that later (see submit) + self.active += 1; + c.flags.state = .active; + + // We only return true if this is a kevent, since other + // actions can come in here. + return action == .kevent; + }, + + .cancel => { + // We are considered an active completion. + self.active += 1; + c.flags.state = .active; + + self.cancellations.push(c); + return false; + }, + + .threadpool => { + // We need to mark this completion as active no matter + // what happens below so that we mark is inactive with + // completion handling. + self.active += 1; + c.flags.state = .active; + + // We need a thread pool otherwise we set an error on + // our result and queue the completion. + const pool = self.thread_pool orelse { + // We use EPERM as a way to note there is no thread + // pool. We can change this in the future if there is + // a better choice. + const eperm = errno_to_result(.PERM); + c.result = c.syscall_result(eperm); + self.completions.push(c); + return false; + }; + + // Setup our completion state so that the thread can + // communicate back to our main thread. + c.task_loop = self; + c.task = .{ .callback = thread_perform }; + + // Schedule it, from this point forward its not safe to touch c. + pool.schedule(ThreadPool.Batch.from(&c.task)); + + return false; + }, + + // A result is immediately available. Queue the completion to + // be invoked. + .result => |result| { + c.result = c.syscall_result(result); + self.completions.push(c); + + return false; + }, + } + } + + fn stop_completion(self: *Loop, c: *Completion) void { + if (c.flags.state == .active) { + // If there is a result already, then we're already in the + // completion queue and we can be done. Items in the completion + // queue can NOT be in the kqueue too. + if (c.result != null) return; + + // If this completion has a kevent associated with it, then + // we must remove the kevent. We remove the kevent by adding it + // to the submission queue (because its the same syscall) but + // setting the state to deleting. + if (c.kevent() != null) { + self.active -= 1; + c.flags.state = .deleting; + self.submissions.push(c); + return; + } + } + + // Inspect other operations. WARNING: the state can be ANYTHING + // here so per op be sure to check the state flag. + switch (c.op) { + .timer => |*v| { + if (c.flags.state == .active) { + // Remove from the heap so it never fires... + self.timers.remove(v); + + // If we have reset set AND we got a cancellation result, + // that means that we were canceled so that we can update + // our expiration time. + if (v.reset) |r| { + v.next = r; + v.reset = null; + self.active -= 1; + self.add(c); + return; + } + } + + // Add to our completions so we trigger the callback. + c.result = .{ .timer = .cancel }; + self.completions.push(c); + + // Note the timers state purposely remains ACTIVE so that + // when we process the completion we decrement the + // active count. + }, + + else => {}, + } + } + + /// This is the main callback for the threadpool to perform work + /// on completions for the loop. + fn thread_perform(t: *ThreadPool.Task) void { + const c: *Completion = @fieldParentPtr("task", t); + + // Do our task + c.result = c.perform(null); + + // Add to our completion queue + c.task_loop.thread_pool_completions.push(c); + + if (comptime builtin.target.os.tag.isDarwin()) { + // Wake up our main loop + c.task_loop.wakeup() catch {}; + } + } + + /// Sends an empty message to this loop's mach port so that it wakes + /// up if it is blocking on kevent(). + fn wakeup(self: *Loop) !void { + try self.wakeup_state.wakeup(); + } +}; + +/// A completion is a request to perform some work with the loop. +pub const Completion = struct { + /// Operation to execute. + op: Operation = .{ .noop = {} }, + + /// Userdata and callback for when the completion is finished. + userdata: ?*anyopaque = null, + callback: Callback = noopCallback, + + //--------------------------------------------------------------- + // Internal fields + + /// Intrusive queue field + next: ?*Completion = null, + + /// Result code of the syscall. Only used internally in certain + /// scenarios, should not be relied upon by program authors. + result: ?Result = null, + + flags: packed struct { + /// Watch state of this completion. We use this to determine whether + /// we're active, adding, deleting, etc. This lets us add and delete + /// multiple times before a loop tick and handle the state properly. + state: State = .dead, + + /// Set this to true to schedule this operation on the thread pool. + /// This can be set by anyone. If the operation is scheduled on + /// the thread pool then it will NOT be registered with kqueue even + /// if it is supported. + threadpool: bool = false, + } = .{}, + + /// If scheduled on a thread pool, this will be set. This is NOT a + /// reliable way to get access to the loop and shouldn't be used + /// except internally. + task: ThreadPool.Task = undefined, + task_loop: *Loop = undefined, + + const State = enum(u3) { + /// completion is not part of any loop + dead = 0, + + /// completion is in the submission queue + adding = 1, + + /// completion is in the deletion queue + deleting = 2, + + /// completion is submitted with kqueue successfully + active = 3, + }; + + /// Returns the state of this completion. There are some things to + /// be caution about when calling this function. + /// + /// First, this is only safe to call from the main thread. This cannot + /// be called from any other thread. + /// + /// Second, if you are using default "undefined" completions, this will + /// NOT return a valid value if you access it. You must zero your + /// completion using ".{}". You only need to zero the completion once. + /// Once the completion is in use, it will always be valid. + /// + /// Third, if you stop the loop (loop.stop()), the completions registered + /// with the loop will NOT be reset to a dead state. + pub fn state(self: Completion) CompletionState { + return switch (self.flags.state) { + .dead => .dead, + .adding, .deleting, .active => .active, + }; + } + + /// Returns a kevent for this completion, if any. Note that the + /// kevent isn't immediately useful for all event types. For example, + /// "connect" requires you to initiate the connection first. + fn kevent(self: *Completion) ?Kevent { + return switch (self.op) { + .noop => unreachable, + + .cancel, + .close, + .timer, + .shutdown, + => null, + + .accept => |v| kevent_init(.{ + .ident = @intCast(v.socket), + .filter = std.c.EVFILT.READ, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = 0, + .data = 0, + .udata = @intFromPtr(self), + }), + + .connect => |v| kevent_init(.{ + .ident = @intCast(v.socket), + .filter = std.c.EVFILT.WRITE, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = 0, + .data = 0, + .udata = @intFromPtr(self), + }), + + .machport => if (comptime !builtin.os.tag.isDarwin()) return null else kevent: { + // We can't use |*v| above because it crahses the Zig + // compiler (as of 0.11.0-dev.1413). We can retry another time. + const v = &self.op.machport; + const slice: []u8 = switch (v.buffer) { + .slice => |slice| slice, + .array => |*arr| arr, + }; + + // The kevent below waits for a machport to have a message + // available AND automatically reads the message into the + // buffer since MACH_RCV_MSG is set. + break :kevent .{ + .ident = @as(c_uint, v.port), + .filter = std.c.EVFILT.MACHPORT, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = darwin.MACH_RCV_MSG, + .data = 0, + .udata = @intFromPtr(self), + .ext = .{ @intFromPtr(slice.ptr), slice.len }, + }; + }, + + .proc => |v| kevent_init(.{ + .ident = @intCast(v.pid), + .filter = std.c.EVFILT.PROC, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = v.flags, + .data = 0, + .udata = @intFromPtr(self), + }), + + inline .write, .pwrite, .send, .sendto => |v| kevent_init(.{ + .ident = @intCast(v.fd), + .filter = std.c.EVFILT.WRITE, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = 0, + .data = 0, + .udata = @intFromPtr(self), + }), + + inline .read, .pread, .recv, .recvfrom => |v| kevent_init(.{ + .ident = @intCast(v.fd), + .filter = std.c.EVFILT.READ, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = 0, + .data = 0, + .udata = @intFromPtr(self), + }), + }; + } + + /// Perform the operation associated with this completion. This will + /// perform the full blocking operation for the completion. + fn perform(self: *Completion, ev_: ?*const Kevent) Result { + return switch (self.op) { + .cancel, + .noop, + .timer, + .shutdown, + => { + log.warn("perform op={s}", .{@tagName(self.op)}); + unreachable; + }, + + .accept => |*op| .{ + .accept = if (xev_posix.accept( + op.socket, + &op.addr, + &op.addr_size, + op.flags, + )) |v| + v + else |err| + err, + }, + + .connect => |*op| .{ + .connect = if (getsockoptError(op.socket)) {} else |err| err, + }, + + .write => |*op| .{ + .write = switch (op.buffer) { + .slice => |v| xev_posix.write(op.fd, v) catch |err| mapWriteError(err), + .array => |*v| xev_posix.write(op.fd, v.array[0..v.len]) catch |err| mapWriteError(err), + }, + }, + + .pwrite => |*op| .{ + .pwrite = switch (op.buffer) { + .slice => |v| xev_posix.pwrite(op.fd, v, op.offset) catch |err| mapWriteError(err), + .array => |*v| xev_posix.pwrite(op.fd, v.array[0..v.len], op.offset) catch |err| mapWriteError(err), + }, + }, + + .send => |*op| .{ + .send = switch (op.buffer) { + .slice => |v| xev_posix.send(op.fd, v, 0) catch |err| mapWriteError(err), + .array => |*v| xev_posix.send(op.fd, v.array[0..v.len], 0) catch |err| mapWriteError(err), + }, + }, + + .sendto => |*op| .{ + .sendto = switch (op.buffer) { + .slice => |v| xev_posix.sendto(op.fd, v, 0, &op.addr.any, op.addr.getOsSockLen()) catch |err| mapWriteError(err), + .array => |*v| xev_posix.sendto(op.fd, v.array[0..v.len], 0, &op.addr.any, op.addr.getOsSockLen()) catch |err| mapWriteError(err), + }, + }, + + .read => |*op| res: { + const n_: ReadError!usize = switch (op.buffer) { + .slice => |v| if (v.len == 0) empty: { + const ev = ev_ orelse + break :res .{ .read = error.MissingKevent }; + break :empty @intCast(ev.data); + } else xev_posix.read(op.fd, v) catch |err| mapReadError(err), + .array => |*v| xev_posix.read(op.fd, v) catch |err| mapReadError(err), + }; + + break :res .{ + .read = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .pread => |*op| res: { + const n_: ReadError!usize = switch (op.buffer) { + .slice => |v| if (v.len == 0) empty: { + const ev = ev_ orelse + break :res .{ .read = error.MissingKevent }; + break :empty @intCast(ev.data); + } else xev_posix.pread(op.fd, v, op.offset) catch |err| mapReadError(err), + .array => |*v| xev_posix.pread(op.fd, v, op.offset) catch |err| mapReadError(err), + }; + + break :res .{ + .pread = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .recv => |*op| res: { + const n_: ReadError!usize = switch (op.buffer) { + .slice => |v| if (v.len == 0) empty: { + const ev = ev_ orelse + break :res .{ .read = error.MissingKevent }; + break :empty @intCast(ev.data); + } else xev_posix.recv(op.fd, v, 0) catch |err| mapReadError(err), + .array => |*v| xev_posix.recv(op.fd, v, 0) catch |err| mapReadError(err), + }; + + break :res .{ + .recv = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .recvfrom => |*op| res: { + const n_: ReadError!usize = switch (op.buffer) { + .slice => |v| if (v.len == 0) empty: { + const ev = ev_ orelse + break :res .{ .read = error.MissingKevent }; + break :empty @intCast(ev.data); + } else xev_posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size) catch |err| mapReadError(err), + .array => |*v| xev_posix.recvfrom(op.fd, v, 0, &op.addr, &op.addr_size) catch |err| mapReadError(err), + }; + + break :res .{ + .recvfrom = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + // Our machport operation ALWAYS has MACH_RCV set so there + // is no operation to perform. kqueue automatically reads in + // the mach message into the read buffer. + .machport => .{ + .machport = {}, + }, + + // For proc watching, it is identical to the syscall result. + .proc => res: { + const ev = ev_ orelse break :res .{ .proc = ProcError.MissingKevent }; + + // If we have the exit status, we read it. + if (ev.fflags & NOTE_EXIT_FLAGS > 0) { + const data: u32 = @intCast(ev.data); + if (posix.W.IFEXITED(data)) break :res .{ + .proc = posix.W.EXITSTATUS(data), + }; + } + + break :res .{ .proc = 0 }; + }, + + .close => |*op| res: { + xev_posix.close(op.fd); + break :res .{ .close = {} }; + }, + }; + } + + /// Returns the error result for the given result code. This is called + /// in the situation that kqueue fails to enqueue the completion or + /// a raw syscall fails. + fn syscall_result(c: *Completion, r: i32) Result { + const errno: posix.E = if (r >= 0) .SUCCESS else @enumFromInt(-r); + return switch (c.op) { + .noop => unreachable, + + .accept => .{ + .accept = switch (errno) { + .SUCCESS => r, + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .connect => .{ + .connect = switch (errno) { + .SUCCESS => {}, + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .write => .{ + .write = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + .PERM => error.PermissionDenied, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .pwrite => .{ + .pwrite = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .read => .{ + .read = switch (errno) { + .SUCCESS => if (r == 0) error.EOF else @intCast(r), + .CANCELED => error.Canceled, + .PERM => error.PermissionDenied, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .pread => .{ + .pread = switch (errno) { + .SUCCESS => if (r == 0) error.EOF else @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .send => .{ + .send = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .recv => .{ + .recv = switch (errno) { + .SUCCESS => if (r == 0) error.EOF else @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .sendto => .{ + .sendto = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .recvfrom => .{ + .recvfrom = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .machport => .{ + .machport = switch (errno) { + .SUCCESS => {}, + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .proc => .{ + .proc = switch (errno) { + .SUCCESS => @intCast(r), + .CANCELED => error.Canceled, + .SRCH => ProcError.NoSuchProcess, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .shutdown => .{ + .shutdown = switch (errno) { + .SUCCESS => {}, + .CANCELED => error.Canceled, + .NOTCONN => error.SocketUnconnected, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .close => .{ + .close = switch (errno) { + .SUCCESS => {}, + .CANCELED => error.Canceled, + .PERM => error.ThreadPoolRequired, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .timer => .{ + .timer = switch (errno) { + // Success is impossible because timers don't execute syscalls. + .SUCCESS => unreachable, + .CANCELED => error.Canceled, + else => |err| posix.unexpectedErrno(err), + }, + }, + + .cancel => .{ + .cancel = switch (errno) { + .SUCCESS => {}, + .CANCELED => error.Canceled, + + // Syscall errors should not be possible since cancel + // doesn't run any syscalls. + else => |err| { + posix.unexpectedErrno(err) catch {}; + unreachable; + }, + }, + }, + }; + } +}; + +/// The struct used for loop wakeup. This is only internal state. +const Wakeup = if (builtin.os.tag.isDarwin()) struct { + const Self = @This(); + + /// The mach port that this kqueue always has a filter for. Writing + /// an empty message to this port can be used to wake up the loop + /// at any time. Waking up the loop via this port won't trigger any + /// particular completion, it just forces tick to cycle. + mach_port: Async, + mach_port_buffer: [32]u8 = undefined, + + fn init() !Self { + const mach_port = try Async.init(); + errdefer mach_port.deinit(); + return .{ .mach_port = mach_port }; + } + + fn deinit(self: *Self) void { + self.mach_port.deinit(); + } + + fn setup(self: *Self, kqueue_fd: posix.fd_t) !void { + const events = [_]Kevent{.{ + .ident = @as(usize, @intCast(self.mach_port.port)), + .filter = std.c.EVFILT.MACHPORT, + .flags = std.c.EV.ADD | std.c.EV.ENABLE, + .fflags = darwin.MACH_RCV_MSG, + .data = 0, + .udata = 0, + .ext = .{ + @intFromPtr(&self.mach_port_buffer), + self.mach_port_buffer.len, + }, + }}; + const n = try kevent_syscall( + kqueue_fd, + &events, + events[0..0], + null, + ); + assert(n == 0); + } + + fn wakeup(self: *Self) !void { + try self.mach_port.notify(); + } +} else struct { + // TODO: We should use eventfd for FreeBSD. Until this is + // implemented, loop wakeup will crash on BSD. + const Self = @This(); + + fn init() !Self { + return .{}; + } + + fn deinit(self: *Self) void { + _ = self; + } + + fn setup(self: *Self, kqueue_fd: posix.fd_t) !void { + _ = self; + _ = kqueue_fd; + } + + fn wakeup(self: *Self) !void { + _ = self; + @panic("wakeup not implemented on this platform"); + } +}; + +fn createKqueueFd() !posix.fd_t { + const rc = posix.system.kqueue(); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + else => |err| return posix.unexpectedErrno(err), + } +} + +fn mapReadError(err: anyerror) ReadError { + return switch (err) { + error.AccessDenied, error.PermissionDenied => error.PermissionDenied, + error.WouldBlock => error.MissingKevent, + else => error.Unexpected, + }; +} + +fn mapWriteError(err: anyerror) WriteError { + return switch (err) { + error.AccessDenied, error.PermissionDenied => error.PermissionDenied, + else => error.Unexpected, + }; +} + +fn monotonicNanos() i128 { + var ts: posix.timespec = undefined; + switch (posix.errno(posix.system.clock_gettime(posix.CLOCK.MONOTONIC, &ts))) { + .SUCCESS => {}, + else => unreachable, + } + return @as(i128, ts.sec) * std.time.ns_per_s + ts.nsec; +} + +fn getsockoptError(socket: posix.socket_t) ConnectError!void { + var err_code: c_int = 0; + var err_len: posix.socklen_t = @sizeOf(c_int); + const rc = std.c.getsockopt(socket, posix.SOL.SOCKET, posix.SO.ERROR, &err_code, &err_len); + switch (posix.errno(rc)) { + .SUCCESS => {}, + else => |err| return posix.unexpectedErrno(err), + } + + if (err_code == 0) return; + return switch (@as(posix.E, @enumFromInt(@as(u16, @intCast(err_code))))) { + .ADDRNOTAVAIL => error.AddressUnavailable, + .AFNOSUPPORT => error.AddressFamilyUnsupported, + .ACCES, .PERM => error.AccessDenied, + .ALREADY, .INPROGRESS => error.ConnectionPending, + .CONNREFUSED => error.ConnectionRefused, + .CONNRESET => error.ConnectionResetByPeer, + .HOSTUNREACH => error.HostUnreachable, + .NETUNREACH => error.NetworkUnreachable, + .TIMEDOUT => error.Timeout, + .NETDOWN => error.NetworkDown, + .NOBUFS, .NOMEM => error.SystemResources, + else => error.Unexpected, + }; +} + +pub const OperationType = enum { + noop, + accept, + connect, + read, + write, + pread, + pwrite, + send, + recv, + sendto, + recvfrom, + close, + shutdown, + timer, + cancel, + machport, + proc, +}; + +/// All the supported operations of this event loop. These are always +/// backend-specific and therefore the structure and types change depending +/// on the underlying system in use. The high level operations are +/// done by initializing the request handles. +pub const Operation = union(OperationType) { + noop: void, + + accept: struct { + socket: posix.socket_t, + addr: posix.sockaddr = undefined, + addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), + flags: u32 = posix.SOCK.CLOEXEC, + }, + + connect: struct { + socket: posix.socket_t, + addr: net.Address, + }, + + read: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + write: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + pread: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + offset: u64, + }, + + pwrite: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + offset: u64, + }, + + send: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + recv: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + // Note: this is making our Completion quite large. We can follow + // the pattern of io_uring and require another user-provided pointer + // here for state to move all this stuff out to a pointer. + sendto: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + addr: net.Address, + }, + + recvfrom: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + addr: posix.sockaddr = undefined, + addr_size: posix.socklen_t = @sizeOf(posix.sockaddr), + }, + + close: struct { + fd: posix.fd_t, + }, + + shutdown: struct { + socket: posix.socket_t, + how: std.Io.net.ShutdownHow = .both, + }, + + timer: Timer, + + cancel: struct { + c: *Completion, + }, + + machport: if (!builtin.os.tag.isDarwin()) void else struct { + port: posix.system.mach_port_name_t, + buffer: ReadBuffer, + }, + + proc: struct { + pid: posix.pid_t, + flags: u32 = NOTE_EXIT_FLAGS, + }, +}; + +pub const Result = union(OperationType) { + noop: void, + accept: AcceptError!posix.socket_t, + connect: ConnectError!void, + read: ReadError!usize, + write: WriteError!usize, + pread: ReadError!usize, + pwrite: WriteError!usize, + send: WriteError!usize, + recv: ReadError!usize, + sendto: WriteError!usize, + recvfrom: ReadError!usize, + close: CloseError!void, + shutdown: ShutdownError!void, + timer: TimerError!TimerTrigger, + cancel: CancelError!void, + machport: MachPortError!void, + proc: ProcError!u32, +}; + +const ThreadPoolError = error{ + ThreadPoolRequired, + ThreadPoolUnsupported, +}; + +pub const CancelError = error{ + Canceled, +}; + +pub const AcceptError = KEventError || std.Io.net.Server.AcceptError || error{ + Canceled, + Unexpected, +}; + +pub const ConnectError = KEventError || std.Io.net.IpAddress.ConnectError || error{ + Canceled, + Unexpected, +}; + +pub const ReadError = KEventError || error{ + EOF, + Canceled, + MissingKevent, + PermissionDenied, + Unexpected, +}; + +pub const WriteError = KEventError || error{ + Canceled, + PermissionDenied, + Unexpected, +}; + +pub const MachPortError = KEventError || error{ + Canceled, + Unexpected, +}; + +pub const ProcError = KEventError || error{ + Canceled, + MissingKevent, + Unexpected, + NoSuchProcess, +}; + +pub const ShutdownError = std.Io.net.ShutdownError || error{ + Canceled, + Unexpected, +}; + +pub const CloseError = ThreadPoolError || error{ + Canceled, + Unexpected, +}; + +pub const TimerError = error{ + Canceled, + Unexpected, +}; + +pub const TimerTrigger = enum { + /// Unused with epoll + request, + + /// Timer expired. + expiration, + + /// Timer was canceled. + cancel, +}; + +/// ReadBuffer are the various options for reading. +pub const ReadBuffer = union(enum) { + /// Read into this slice. + /// + /// For zero-length slices, the event will notify of read readiness + /// (similar to poll) rather than performing an explicit zero-length + /// read which will always succeed regardless of if the fd is ready + /// or not. + slice: []u8, + + /// Read into this array, just set this to undefined and it will + /// be populated up to the size of the array. This is an option because + /// the other union members force a specific size anyways so this lets us + /// use the other size in the union to support small reads without worrying + /// about buffer allocation. + /// + /// To know the size read you have to use the return value of the + /// read operations (i.e. recv). + /// + /// Note that the union at the time of this writing could accomodate a + /// much larger fixed size array here but we want to retain flexiblity + /// for future fields. + array: [32]u8, + + // TODO: future will have vectors +}; + +/// WriteBuffer are the various options for writing. +pub const WriteBuffer = union(enum) { + /// Write from this buffer. + slice: []const u8, + + /// Write from this array. See ReadBuffer.array for why we support this. + array: struct { + array: [32]u8, + len: usize, + }, + + // TODO: future will have vectors +}; + +/// Timer that is inserted into the heap. +const Timer = struct { + /// The absolute time to fire this timer next. + next: posix.timespec, + + /// Only used internally. If this is non-null and timer is + /// CANCELLED, then the timer is rearmed automatically with this + /// as the next time. The callback will not be called on the + /// cancellation. + reset: ?posix.timespec = null, + + /// Internal heap fields. + heap: heap.IntrusiveField(Timer) = .{}, + + /// We point back to completion for now. When issue[1] is fixed, + /// we can juse use that from our heap fields. + /// [1]: https://github.com/ziglang/zig/issues/6611 + c: *Completion = undefined, + + fn less(_: void, a: *const Timer, b: *const Timer) bool { + return a.ns() < b.ns(); + } + + /// Returns the nanoseconds of this timer. Note that maxInt(u64) ns is + /// 584 years so if we get any overflows we just use maxInt(u64). If + /// any software is running in 584 years waiting on this timer... + /// shame on me I guess... but I'll be dead. + fn ns(self: *const Timer) u64 { + assert(self.next.sec >= 0); + assert(self.next.nsec >= 0); + + const max = std.math.maxInt(u64); + const s_ns = std.math.mul( + u64, + @as(u64, @intCast(self.next.sec)), + std.time.ns_per_s, + ) catch return max; + return std.math.add(u64, s_ns, @as(u64, @intCast(self.next.nsec))) catch + return max; + } +}; + +/// Kevent is either kevent_s or kevent64_s depending on the target platform. +/// This lets us support both Mac and non-Mac platforms. +const Kevent = switch (builtin.os.tag) { + .ios, .macos, .visionos => posix.system.kevent64_s, + .freebsd => std.c.Kevent, + else => @compileError("kqueue not supported yet for target OS"), +}; + +/// kevent calls either kevent or kevent64 depending on the +/// target platform. +fn kevent_syscall( + kq: i32, + changelist: []const Kevent, + eventlist: []Kevent, + timeout: ?*const posix.timespec, +) KEventError!usize { + // Normal Kevent? Just use the normal kevent syscall. + if (Kevent == std.c.Kevent) { + while (true) { + const rc = posix.system.kevent( + kq, + changelist.ptr, + std.math.cast(c_int, changelist.len) orelse return error.Overflow, + eventlist.ptr, + std.math.cast(c_int, eventlist.len) orelse return error.Overflow, + timeout, + ); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .BADF => unreachable, // Always a race condition. + .INTR => continue, + .INVAL => unreachable, + .NOENT => return error.EventNotFound, + .NOMEM => return error.SystemResources, + .SRCH => return error.ProcessNotFound, + else => unreachable, + } + } + } + + // Otherwise, we have to call the kevent64 variant. + while (true) { + const rc = posix.system.kevent64( + kq, + changelist.ptr, + std.math.cast(c_int, changelist.len) orelse return error.Overflow, + eventlist.ptr, + std.math.cast(c_int, eventlist.len) orelse return error.Overflow, + .{}, + timeout, + ); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .FAULT => unreachable, + .BADF => unreachable, // Always a race condition. + .INTR => continue, + .INVAL => unreachable, + .NOENT => return error.EventNotFound, + .NOMEM => return error.SystemResources, + .SRCH => return error.ProcessNotFound, + else => unreachable, + } + } +} + +/// Converts a posix errno to the negative i32 format expected by syscall_result. +inline fn errno_to_result(errno: posix.E) i32 { + return -@as(i32, @intCast(@intFromEnum(errno))); +} + +/// kevent_init initializes a Kevent from a std.c.Kevent. This is used when +/// the "ext" fields are zero. +inline fn kevent_init(ev: std.c.Kevent) Kevent { + if (Kevent == std.c.Kevent) return ev; + + return .{ + .ident = ev.ident, + .filter = ev.filter, + .flags = ev.flags, + .fflags = ev.fflags, + .data = ev.data, + .udata = ev.udata, + .ext = .{ 0, 0 }, + }; +} + +comptime { + if (@sizeOf(Completion) > 256) { + @compileLog(@sizeOf(Completion)); + unreachable; + } +} + +test "kqueue: loop time" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // should never init zero + const now = loop.now(); + try testing.expect(now > 0); + + // should update on a loop tick + while (now == loop.now()) try loop.run(.no_wait); +} + +test "kqueue: stop" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1_000_000, &called, (struct { + fn callback(ud: ?*anyopaque, l: *Loop, _: *Completion, r: Result) CallbackAction { + _ = l; + _ = r; + const b: *bool = @ptrCast(ud.?); + b.* = true; + return .disarm; + } + }).callback); + + // Tick + try loop.run(.no_wait); + try testing.expect(!called); + + // Stop + loop.stop(); + try loop.run(.until_done); + try testing.expect(!called); +} + +test "kqueue: timer" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: Completion = undefined; + loop.timer(&c1, 1, &called, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b: *bool = @ptrCast(ud.?); + b.* = true; + return .disarm; + } + }).callback); + + // Add another timer + var called2 = false; + var c2: Completion = undefined; + loop.timer(&c2, 100_000, &called2, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = r; + const b: *bool = @ptrCast(ud.?); + b.* = true; + return .disarm; + } + }).callback); + + // State checking + try testing.expect(c1.state() == .active); + try testing.expect(c2.state() == .active); + + // Tick + while (!called) try loop.run(.no_wait); + try testing.expect(called); + try testing.expect(!called2); + + // State checking + try testing.expect(c1.state() == .dead); + try testing.expect(c2.state() == .active); +} + +test "kqueue: timer reset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v: *?TimerTrigger = @ptrCast(ud.?); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // We know timer won't be called from the timer test previously. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .active); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "kqueue: timer reset before tick" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v: *?TimerTrigger = @ptrCast(ud.?); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "kqueue: timer reset after trigger" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const v: *?TimerTrigger = @ptrCast(ud.?); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, cb); + + // Run the timer + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + trigger = null; + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "kqueue: timer cancellation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr: *?TimerTrigger = @ptrCast(@alignCast(ud.?)); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .cancel); +} + +test "kqueue: canceling a completed operation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = l; + const ptr: *?TimerTrigger = @ptrCast(@alignCast(ud.?)); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + + // Cancel the timer + var called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr: *bool = @ptrCast(@alignCast(ud.?)); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .expiration); +} + +test "kqueue: socket accept/connect/send/recv/close" { + const mem = std.mem; + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + var ln = try xev_posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); + try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); + + // Create a TCP client socket + var client_conn = try xev_posix.socket( + address.any.family, + posix.SOCK.NONBLOCK | posix.SOCK.STREAM | posix.SOCK.CLOEXEC, + 0, + ); + errdefer xev_posix.close(client_conn); + + // Accept + var server_conn: posix.socket_t = 0; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const conn = @as(*posix.socket_t, @ptrCast(@alignCast(ud.?))); + conn.* = r.accept catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + + // Connect + var connected = false; + var c_connect: Completion = .{ + .op = .{ + .connect = .{ + .socket = client_conn, + .addr = address, + }, + }, + + .userdata = &connected, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.connect catch unreachable; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_connect); + + // Wait for the connection to be established + try loop.run(.until_done); + try testing.expect(server_conn > 0); + try testing.expect(connected); + + // Send + var c_send: Completion = .{ + .op = .{ + .send = .{ + .fd = client_conn, + .buffer = .{ .slice = &[_]u8{ 1, 1, 2, 3, 5, 8, 13 } }, + }, + }, + + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.send catch unreachable; + _ = ud; + return .disarm; + } + }).callback, + }; + loop.add(&c_send); + + // Receive + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + var c_recv: Completion = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &recv_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.recv catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, c_send.op.send.buffer.slice, recv_buf[0..recv_len]); + + // Shutdown + var shutdown = false; + var c_client_shutdown: Completion = .{ + .op = .{ + .shutdown = .{ + .socket = client_conn, + }, + }, + + .userdata = &shutdown, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.shutdown catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_shutdown); + try loop.run(.until_done); + try testing.expect(shutdown); + + // Read should be EOF + var eof: ?bool = null; + c_recv = .{ + .op = .{ + .recv = .{ + .fd = server_conn, + .buffer = .{ .slice = &recv_buf }, + }, + }, + + .userdata = &eof, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*?bool, @ptrCast(@alignCast(ud.?))); + ptr.* = if (r.recv) |_| false else |err| switch (err) { + error.EOF => true, + else => false, + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_recv); + + try loop.run(.until_done); + try testing.expect(eof.? == true); + + // Close + var c_client_close: Completion = .{ + .op = .{ + .close = .{ + .fd = client_conn, + }, + }, + + .userdata = &client_conn, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*posix.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_client_close); + + var c_server_close: Completion = .{ + .op = .{ + .close = .{ + .fd = ln, + }, + }, + + .userdata = &ln, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*posix.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_server_close); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(ln == 0); + try testing.expect(client_conn == 0); +} + +test "kqueue: file IO on thread pool" { + if (builtin.os.tag != .macos) return error.SkipZigTest; + const testing = std.testing; + const io = testing.io; + + var tpool = ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Create our file + const path = "test_watcher_file"; + const f = try std.Io.Dir.cwd().createFile(io, path, .{ + .read = true, + .truncate = true, + }); + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; + + // Perform a write and then a read + var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: Completion = .{ + .op = .{ + .write = .{ + .fd = f.handle, + .buffer = .{ .slice = &write_buf }, + }, + }, + + .flags = .{ .threadpool = true }, + + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = ud; + _ = l; + _ = c; + _ = r.write catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_write); + + // Wait for the write + try loop.run(.until_done); + + // Make sure the data is on disk + try f.sync(io); + + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + var c_read: Completion = .{ + .op = .{ + .read = .{ + .fd = f2.handle, + .buffer = .{ .slice = &read_buf }, + }, + }, + + .flags = .{ .threadpool = true }, + + .userdata = &read_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + const ptr = @as(*usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.read catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_read); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &write_buf, read_buf[0..read_len]); +} + +test "kqueue: mach port" { + if (builtin.os.tag != .macos) return error.SkipZigTest; + + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Allocate the port + const mach_self = posix.system.mach_task_self(); + var mach_port: posix.system.mach_port_name_t = undefined; + try testing.expectEqual( + darwin.KernE.SUCCESS, + darwin.getKernError(posix.system.mach_port_allocate( + mach_self, + posix.system.MACH.PORT.RIGHT.RECEIVE, + &mach_port, + )), + ); + defer _ = posix.system.mach_port_deallocate(mach_self, mach_port); + + // Add the waiter + var called = false; + var c_wait: Completion = .{ + .op = .{ + .machport = .{ + .port = mach_port, + .buffer = .{ .array = undefined }, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.machport catch unreachable; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_wait); + + // Tick so we submit... should not call since we never sent. + try loop.run(.no_wait); + try testing.expect(!called); + + // Send a message to the port + var msg: darwin.mach_msg_header_t = .{ + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.MAKE_SEND_ONCE), + .msgh_size = @sizeOf(darwin.mach_msg_header_t), + .msgh_remote_port = mach_port, + .msgh_local_port = darwin.MACH_PORT_NULL, + .msgh_voucher_port = undefined, + .msgh_id = undefined, + }; + try testing.expectEqual(darwin.MachMsgE.SUCCESS, darwin.getMachMsgError( + darwin.mach_msg( + &msg, + darwin.MACH_SEND_MSG, + msg.msgh_size, + 0, + darwin.MACH_PORT_NULL, + darwin.MACH_MSG_TIMEOUT_NONE, + darwin.MACH_PORT_NULL, + ), + )); + + // We should receive now! + try loop.run(.until_done); + try testing.expect(called); + + // We should not receive again + called = false; + loop.add(&c_wait); + + // Tick so we submit... should not call since we never sent. + try loop.run(.no_wait); + try testing.expect(!called); +} + +test "kqueue: timer armed from delayed callback must not fire early" { + if (builtin.os.tag != .macos) return error.SkipZigTest; + + const testing = std.testing; + + const send_delay_ms: u64 = 50; + const timer_delay_ms: u64 = 20; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Allocate the port used to wake the loop after a delayed send. + const mach_self = posix.system.mach_task_self(); + var mach_port: posix.system.mach_port_name_t = undefined; + try testing.expectEqual( + darwin.KernE.SUCCESS, + darwin.getKernError(posix.system.mach_port_allocate( + mach_self, + posix.system.MACH.PORT.RIGHT.RECEIVE, + &mach_port, + )), + ); + defer _ = posix.system.mach_port_deallocate(mach_self, mach_port); + + const State = struct { + timer_started_ns: i128 = 0, + timer_fired_ns: i128 = 0, + timer_trigger: ?TimerTrigger = null, + timer_completion: Completion = undefined, + }; + + var state: State = .{}; + + const timer_cb: Callback = (struct { + fn callback( + ud: ?*anyopaque, + _: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + const s: *State = @ptrCast(@alignCast(ud.?)); + s.timer_fired_ns = monotonicNanos(); + s.timer_trigger = r.timer catch unreachable; + return .disarm; + } + }).callback; + + var c_wait: Completion = .{ + .op = .{ + .machport = .{ + .port = mach_port, + .buffer = .{ .array = undefined }, + }, + }, + + .userdata = &state, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = r.machport catch unreachable; + const s: *State = @ptrCast(@alignCast(ud.?)); + s.timer_started_ns = monotonicNanos(); + l.timer(&s.timer_completion, timer_delay_ms, s, timer_cb); + return .disarm; + } + }).callback, + }; + loop.add(&c_wait); + + // Send to the mach port only after the loop has been blocked for a while. + const sender = try std.Thread.spawn(.{}, (struct { + fn run(port: posix.system.mach_port_name_t) void { + std.Io.sleep( + std.Io.Threaded.global_single_threaded.io(), + .fromMilliseconds(@intCast(send_delay_ms)), + .awake, + ) catch unreachable; + + var msg: darwin.mach_msg_header_t = .{ + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.MAKE_SEND_ONCE), + .msgh_size = @sizeOf(darwin.mach_msg_header_t), + .msgh_remote_port = port, + .msgh_local_port = darwin.MACH_PORT_NULL, + .msgh_voucher_port = undefined, + .msgh_id = undefined, + }; + + const rc = darwin.mach_msg( + &msg, + darwin.MACH_SEND_MSG, + msg.msgh_size, + 0, + darwin.MACH_PORT_NULL, + darwin.MACH_MSG_TIMEOUT_NONE, + darwin.MACH_PORT_NULL, + ); + assert(darwin.getMachMsgError(rc) == darwin.MachMsgE.SUCCESS); + } + }).run, .{mach_port}); + defer sender.join(); + + try loop.run(.until_done); + + try testing.expect(state.timer_trigger.? == .expiration); + + const elapsed_ns = state.timer_fired_ns - state.timer_started_ns; + const elapsed_ms: i128 = @divFloor(elapsed_ns, std.time.ns_per_ms); + try testing.expect(elapsed_ms >= @as(i128, @intCast(timer_delay_ms))); +} + +test "kqueue: socket accept/cancel cancellation should decrease active count" { + const mem = std.mem; + const testing = std.testing; + + //if (true) return error.SkipZigTest; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a TCP server socket + const address = try net.Address.parseIp4("127.0.0.1", 3131); + const kernel_backlog = 1; + var ln = try xev_posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); + errdefer xev_posix.close(ln); + try posix.setsockopt(ln, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(ln, &address.any, address.getOsSockLen()); + try xev_posix.listen(ln, kernel_backlog); + + // Accept + var server_conn: posix.socket_t = 0; + var c_accept: Completion = .{ + .op = .{ + .accept = .{ + .socket = ln, + }, + }, + + .userdata = &server_conn, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + _: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = ud; + _ = r.accept catch |err| switch (err) { + error.Canceled => {}, + else => @panic("wrong"), + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_accept); + try loop.run(.no_wait); + try testing.expectEqual(@as(usize, 1), loop.active); + + var cancel_called = false; + var c_cancel: Completion = .{ + .op = .{ + .cancel = .{ + .c = &c_accept, + }, + }, + + .userdata = &cancel_called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + _: *Loop, + _: *Completion, + r: Result, + ) CallbackAction { + _ = r.cancel catch unreachable; + const ptr = @as(*?bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + try testing.expectEqual(@as(usize, 2), loop.active); + try loop.run(.once); + try testing.expect(cancel_called); + + // Both callbacks are called active count should be 0 + try testing.expectEqual(@as(usize, 0), loop.active); + + var c_server_close: Completion = .{ + .op = .{ + .close = .{ + .fd = ln, + }, + }, + + .userdata = &ln, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *Loop, + c: *Completion, + r: Result, + ) CallbackAction { + _ = l; + _ = c; + _ = r.close catch unreachable; + const ptr = @as(*posix.socket_t, @ptrCast(@alignCast(ud.?))); + ptr.* = 0; + return .disarm; + } + }).callback, + }; + loop.add(&c_server_close); + + // Wait for the sockets to close + try loop.run(.until_done); + try testing.expect(ln == 0); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/wasi_poll.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/wasi_poll.zig new file mode 100644 index 0000000..5d979f7 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/wasi_poll.zig @@ -0,0 +1,1652 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const wasi = std.os.wasi; +const posix = std.posix; + +pub const ShutdownHow = std.Io.net.ShutdownHow; + +const xev_posix = @import("../posix.zig"); +const queue = @import("../queue.zig"); +const heap = @import("../heap.zig"); +const xev = @import("../main.zig").WasiPoll; + +/// True if this backend is available on this platform. +pub fn available() bool { + return builtin.os.tag == .wasi; +} + +pub const Loop = struct { + pub const threaded = std.Target.wasm.featureSetHas(builtin.cpu.features, .atomics); + const TimerHeap = heap.Intrusive(Timer, void, Timer.less); + const WakeupType = if (threaded) std.atomic.Atomic(bool) else bool; + const wakeup_init = if (threaded) .{ .value = false } else false; + + /// The number of active completions. This DOES NOT include completions that + /// are queued in the submissions queue. + active: usize = 0, + + /// Our queue of submissions that we want to enqueue on the next tick. + submissions: queue.Intrusive(Completion) = .{}, + + /// Our list of async waiters. + asyncs: queue.Intrusive(Completion) = .{}, + + /// Batch of subscriptions to send to poll. + batch: Batch = .{}, + + /// Heap of timers. + timers: TimerHeap = .{ .context = {} }, + + /// The wakeup signal variable for Async. If we have Wasm threads + /// enabled then we use an atomic for this, otherwise we just use a plain + /// bool because we know we're single threaded. + wakeup: WakeupType = wakeup_init, + + /// Cached time + cached_now: wasi.timestamp_t, + + /// Some internal fields we can pack for better space. + flags: packed struct { + /// Whether we're in a run or not (to prevent nested runs). + in_run: bool = false, + + /// Whether our loop is in a stopped state or not. + stopped: bool = false, + } = .{}, + + pub fn init(options: xev.Options) !Loop { + _ = options; + return .{ .cached_now = try get_now() }; + } + + pub fn deinit(self: *Loop) void { + _ = self; + } + + /// Run the event loop. See RunMode documentation for details on modes. + pub fn run(self: *Loop, mode: xev.RunMode) !void { + switch (mode) { + .no_wait => try self.tick(0), + .once => try self.tick(1), + .until_done => while (!self.done()) try self.tick(1), + } + } + + /// Stop the loop. This can only be called from the main thread. + /// This will stop the loop forever. Future ticks will do nothing. + /// + /// This does NOT stop any completions that are queued to be executed + /// in the thread pool. If you are using a thread pool, completions + /// are not safe to recover until the thread pool is shut down. If + /// you're not using a thread pool, all completions are safe to + /// read/write once any outstanding `run` or `tick` calls are returned. + pub fn stop(self: *Loop) void { + self.flags.stopped = true; + } + + /// Returns true if the loop is stopped. This may mean there + /// are still pending completions to be processed. + pub fn stopped(self: *Loop) bool { + return self.flags.stopped; + } + + /// Add a completion to the loop. This doesn't DO anything except queue + /// the completion. Any configuration errors will be exposed via the + /// callback on the next loop tick. + pub fn add(self: *Loop, c: *Completion) void { + c.flags.state = .adding; + self.submissions.push(c); + } + + fn done(self: *Loop) bool { + return self.flags.stopped or (self.active == 0 and + self.submissions.empty()); + } + + /// Wake up the event loop and force a tick. This only works if there + /// is a corresponding async_wait completion _already registered_ with + /// the event loop. If there isn't already a completion, it will still + /// work, but the async_wait will be triggered on the next loop tick + /// it is added, and the loop won't wake up until then. This is usually + /// pointless since completions can only be added from the main thread. + /// + /// The completion c doesn't yet have to be registered as a waiter, but + /// + /// + /// This function can be called from any thread. + pub fn async_notify(self: *Loop, c: *Completion) void { + assert(c.op == .async_wait); + + if (threaded) { + self.wakeup.store(true, .SeqCst); + c.op.async_wait.wakeup.store(true, .SeqCst); + } else { + self.wakeup = true; + c.op.async_wait.wakeup = true; + } + } + + /// Tick through the event loop once, waiting for at least "wait" completions + /// to be processed by the loop itself. + pub fn tick(self: *Loop, wait: u32) !void { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We can't nest runs. + if (self.flags.in_run) return error.NestedRunsNotAllowed; + self.flags.in_run = true; + defer self.flags.in_run = false; + + // Submit all the submissions. We copy the submission queue so that + // any resubmits don't cause an infinite loop. + var queued = self.submissions; + self.submissions = .{}; + while (queued.pop()) |c| { + // We ignore any completions that aren't in the adding state. + // This usually means that we switched them to be deleted or + // something. + if (c.flags.state != .adding) continue; + self.start(c); + } + + // Wait and process events. We only do this if we have any active. + var wait_rem = @as(usize, @intCast(wait)); + while (true) { + // If we're stopped then the loop is fully over. + if (self.flags.stopped) return; + + // We must always update the loop time even if we have no + // active completions. + self.update_now(); + + if (!(self.active > 0 and (wait == 0 or wait_rem > 0))) break; + + // Run our expired timers + const now_timer: Timer = .{ .next = self.cached_now }; + while (self.timers.peek()) |t| { + if (!Timer.less({}, t, &now_timer)) break; + + // Remove the timer + assert(self.timers.deleteMin().? == t); + + // Completion is now dead because it has been processed. + // Users can reuse it (unless they specify rearm in + // which case we readd it). + const c = t.c; + c.flags.state = .dead; + self.active -= 1; + + // Lower our remaining count + wait_rem -|= 1; + + // Invoke + const action = c.callback(c.userdata, self, c, .{ + .timer = .expiration, + }); + switch (action) { + .disarm => {}, + .rearm => self.start(c), + } + } + + // Run our async waiters + if (!self.asyncs.empty()) { + const wakeup = if (threaded) self.wakeup.load(.SeqCst) else self.wakeup; + if (wakeup) { + // Reset to false, we've "woken up" now. + if (threaded) + self.wakeup.store(false, .SeqCst) + else + self.wakeup = false; + + // There is at least one pending async. This isn't efficient + // AT ALL. We should improve this in the short term by + // using a queue here of asyncs we know should wake up + // (we know because we have access to it in async_notify). + // I didn't do that right away because we need a Wasm + // compatibile std.Thread.Mutex. + var asyncs = self.asyncs; + self.asyncs = .{}; + while (asyncs.pop()) |c| { + const c_wakeup = if (threaded) + c.op.async_wait.wakeup.load(.SeqCst) + else + c.op.async_wait.wakeup; + + // If we aren't waking this one up, requeue + if (!c_wakeup) { + self.asyncs.push(c); + continue; + } + + // We are waking up, mark this as dead and call it. + c.flags.state = .dead; + self.active -= 1; + + // Lower our waiters + wait_rem -|= 1; + + const action = c.callback(c.userdata, self, c, .{ .async_wait = {} }); + switch (action) { + // We disarm by default + .disarm => {}, + + // Rearm we just restart it. We use start instead of + // add because then it'll become immediately available + // if we loop again. + .rearm => self.start(c), + } + } + } + } + + // Setup our timeout. If we have nothing to wait for then + // we just set an expiring timer so that we still poll but it + // will return ASAP. + const timeout: wasi.timestamp_t = if (wait_rem == 0) self.cached_now else timeout: { + // If we have a timer use that value, otherwise we can afford + // to sleep for awhile since we're waiting for something to + // happen. We set this sleep to 60 seconds arbitrarily. On + // other backends we wait indefinitely. + const t: *const Timer = self.timers.peek() orelse + break :timeout self.cached_now + (60 * std.time.ns_per_s); + break :timeout t.next; + }; + self.batch.array[0] = .{ + .userdata = 0, + .u = .{ + .tag = wasi.EVENTTYPE_CLOCK, + .u = .{ + .clock = .{ + .id = @as(u32, @bitCast(posix.CLOCK.MONOTONIC)), + .timeout = timeout, + .precision = 1 * std.time.ns_per_ms, + .flags = wasi.SUBSCRIPTION_CLOCK_ABSTIME, + }, + }, + }, + }; + + // Build our batch of subscriptions and poll + var events: [Batch.capacity]wasi.event_t = undefined; + const subs = self.batch.array[0..self.batch.len]; + assert(events.len >= subs.len); + var n: usize = 0; + switch (wasi.poll_oneoff(&subs[0], &events[0], subs.len, &n)) { + .SUCCESS => {}, + else => |err| return posix.unexpectedErrno(err), + } + + // Poll! + for (events[0..n]) |ev| { + // A system event + if (ev.userdata == 0) continue; + + const c = @as(*Completion, @ptrFromInt(@as(usize, @intCast(ev.userdata)))); + + // We assume disarm since this is the safest time to access + // the completion. It makes rearms slightly more expensive + // but not by very much. + c.flags.state = .dead; + self.batch.put(c); + self.active -= 1; + + const res = c.perform(); + const action = c.callback(c.userdata, self, c, res); + switch (action) { + // We disarm by default + .disarm => {}, + + // Rearm we just restart it. We use start instead of + // add because then it'll become immediately available + // if we loop again. + .rearm => self.start(c), + } + } + + if (wait == 0) break; + wait_rem -|= n; + } + } + + fn start(self: *Loop, completion: *Completion) void { + const res_: ?Result = switch (completion.op) { + .noop => { + completion.flags.state = .dead; + return; + }, + + .cancel => |v| res: { + // We stop immediately. We only stop if we are in the + // "adding" state because cancellation or any other action + // means we're complete already. + // + // For example, if we're in the deleting state, it means + // someone is cancelling the cancel. So we do nothing. If + // we're in the dead state it means we ran already. + if (completion.flags.state == .adding) { + if (v.c.op == .cancel) break :res .{ .cancel = CancelError.InvalidOp }; + self.stop_completion(v.c); + } + + // We always run timers + break :res .{ .cancel = {} }; + }, + + .read => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .read = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .pread => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .pread = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .write => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .write = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .pwrite => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .pwrite = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .recv => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .recv = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .send => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .send = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .accept => res: { + const sub = self.batch.get(completion) catch |err| break :res .{ .accept = err }; + sub.* = completion.subscription(); + break :res null; + }, + + .shutdown => |v| res: { + const how: wasi.sdflags_t = switch (v.how) { + .both => wasi.SHUT.WR | wasi.SHUT.RD, + .recv => wasi.SHUT.RD, + .send => wasi.SHUT.WR, + }; + + break :res .{ + .shutdown = switch (wasi.sock_shutdown(v.socket, how)) { + .SUCCESS => {}, + else => |err| posix.unexpectedErrno(err), + }, + }; + }, + + .close => |v| res: { + posix.close(v.fd); + break :res .{ .close = {} }; + }, + + .async_wait => res: { + // Add our async to the list of asyncs + self.asyncs.push(completion); + break :res null; + }, + + .timer => |*v| res: { + // Point back to completion since we need this. In the future + // we want to use @fieldParentPtr but https://github.com/ziglang/zig/issues/6611 + v.c = completion; + + // Insert the timer into our heap. + self.timers.insert(v); + + // We always run timers + break :res null; + }, + }; + + // If we failed to add the completion then we call the callback + // immediately and mark the error. + if (res_) |res| { + completion.flags.state = .dead; + switch (completion.callback( + completion.userdata, + self, + completion, + res, + )) { + .disarm => {}, + + // If we rearm then we requeue this. Due to the way that tick works, + // this won't try to re-add immediately it won't happen until the + // next tick. + .rearm => self.add(completion), + } + + return; + } + + // The completion is now active since it is in our poll set. + completion.flags.state = .active; + + // Increase our active count + self.active += 1; + } + + fn stop_completion(self: *Loop, c: *Completion) void { + // We may modify the state below so we need to know now if this + // completion was active. + const active = c.flags.state == .active; + + const rearm: bool = switch (c.op) { + .timer => |*v| timer: { + assert(v.c == c); + + // Timers needs to be removed from the timer heap only if + // it has been inserted. + if (c.flags.state == .active) { + self.timers.remove(v); + } + + // If the timer was never fired, we need to fire it with + // the cancellation notice. + if (c.flags.state != .dead) { + // If we have reset set AND we got a cancellation result, + // that means that we were canceled so that we can update + // our expiration time. + if (v.reset) |r| { + v.next = r; + v.reset = null; + break :timer true; + } + + // We have to set the completion as dead here because + // it isn't safe to modify completions after a callback. + c.flags.state = .dead; + + const action = c.callback(c.userdata, self, c, .{ .timer = .cancel }); + switch (action) { + .disarm => {}, + .rearm => break :timer true, + } + } + + break :timer false; + }, + + else => unreachable, + }; + + // Decrement the active count so we know how many are running for + // .until_done run semantics. + if (active) self.active -= 1; + + // If we're rearming, add it again immediately + if (rearm) self.start(c); + } + + /// Add a timer to the loop. The timer will initially execute in "next_ms" + /// from now and will repeat every "repeat_ms" thereafter. If "repeat_ms" is + /// zero then the timer is oneshot. If "next_ms" is zero then the timer will + /// invoke immediately (the callback will be called immediately -- as part + /// of this function call -- to avoid any additional system calls). + pub fn timer( + self: *Loop, + c: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: xev.Callback, + ) void { + c.* = .{ + .op = .{ + .timer = .{ + .next = timer_next(next_ms), + }, + }, + .userdata = userdata, + .callback = cb, + }; + + self.add(c); + } + + /// See io_uring.timer_reset for docs. + pub fn timer_reset( + self: *Loop, + c: *Completion, + c_cancel: *Completion, + next_ms: u64, + userdata: ?*anyopaque, + comptime cb: xev.Callback, + ) void { + switch (c.flags.state) { + .dead, .deleting => { + self.timer(c, next_ms, userdata, cb); + return; + }, + + // Adding state we can just modify the metadata and return + // since the timer isn't in the heap yet. + .adding => { + c.op.timer.next = timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + return; + }, + + .active => { + // Update the reset time for the timer to the desired time + // along with all the callbacks. + c.op.timer.reset = timer_next(next_ms); + c.userdata = userdata; + c.callback = cb; + + // If the cancellation is active, we assume its for this timer + // and do nothing. + if (c_cancel.state() == .active) return; + assert(c_cancel.state() == .dead and c.state() == .active); + c_cancel.* = .{ .op = .{ .cancel = .{ .c = c } } }; + self.add(c_cancel); + }, + } + } + + /// Returns the "loop" time in milliseconds. The loop time is updated + /// once per loop tick, before IO polling occurs. It remains constant + /// throughout callback execution. + /// + /// You can force an update of the "now" value by calling update_now() + /// at any time from the main thread. + /// + /// The clock that is used is not guaranteed. In general, a monotonic + /// clock source is always used if available. This value should typically + /// just be used for relative time calculations within the loop, such as + /// answering the question "did this happen ms ago?". + pub fn now(self: *Loop) i64 { + return std.math.lossyCast(i64, @divFloor(self.cached_now, std.time.ns_per_ms)); + } + + /// Update the cached time. + pub fn update_now(self: *Loop) void { + if (get_now()) |t| self.cached_now = t else |_| {} + } + + fn timer_next(next_ms: u64) wasi.timestamp_t { + // Get the absolute time we'll execute this timer next. + var now_ts: wasi.timestamp_t = undefined; + switch (wasi.clock_time_get(@as(u32, @bitCast(posix.CLOCK.MONOTONIC)), 1, &now_ts)) { + .SUCCESS => {}, + .INVAL => unreachable, + else => unreachable, + } + + // TODO: overflow + now_ts += next_ms * std.time.ns_per_ms; + return now_ts; + } + + fn get_now() !wasi.timestamp_t { + var ts: wasi.timestamp_t = undefined; + return switch (wasi.clock_time_get(posix.CLOCK.MONOTONIC, 1, &ts)) { + .SUCCESS => ts, + .INVAL => error.UnsupportedClock, + else => |err| posix.unexpectedErrno(err), + }; + } +}; + +pub const Completion = struct { + /// Operation to execute. This is only safe to read BEFORE the completion + /// is queued. After being queued (with "add"), the operation may change. + op: Operation = .{ .noop = {} }, + + /// Userdata and callback for when the completion is finished. + userdata: ?*anyopaque = null, + callback: xev.Callback = xev.noopCallback, + + //--------------------------------------------------------------- + // Internal fields + + flags: packed struct { + /// Watch state of this completion. We use this to determine whether + /// we're active, adding, deleting, etc. This lets us add and delete + /// multiple times before a loop tick and handle the state properly. + state: State = .dead, + } = .{}, + + /// Intrusive queue field + next: ?*Completion = null, + + /// Index in the batch array. + batch_idx: usize = 0, + + const State = enum(u3) { + /// completion is not part of any loop + dead = 0, + + /// completion is in the submission queue + adding = 1, + + /// completion is in the deletion queue + deleting = 2, + + /// completion is actively being sent to poll + active = 3, + }; + + /// Returns the state of this completion. There are some things to + /// be caution about when calling this function. + /// + /// First, this is only safe to call from the main thread. This cannot + /// be called from any other thread. + /// + /// Second, if you are using default "undefined" completions, this will + /// NOT return a valid value if you access it. You must zero your + /// completion using ".{}". You only need to zero the completion once. + /// Once the completion is in use, it will always be valid. + /// + /// Third, if you stop the loop (loop.stop()), the completions registered + /// with the loop will NOT be reset to a dead state. + pub fn state(self: Completion) xev.CompletionState { + return switch (self.flags.state) { + .dead => .dead, + .adding, .deleting, .active => .active, + }; + } + + fn subscription(self: *Completion) wasi.subscription_t { + return switch (self.op) { + .read => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_READ, + .u = .{ + .fd_read = .{ + .fd = v.fd, + }, + }, + }, + }, + + .pread => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_READ, + .u = .{ + .fd_read = .{ + .fd = v.fd, + }, + }, + }, + }, + + .write => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_WRITE, + .u = .{ + .fd_write = .{ + .fd = v.fd, + }, + }, + }, + }, + + .pwrite => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_WRITE, + .u = .{ + .fd_write = .{ + .fd = v.fd, + }, + }, + }, + }, + + .accept => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_READ, + .u = .{ + .fd_read = .{ + .fd = v.socket, + }, + }, + }, + }, + + .recv => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_READ, + .u = .{ + .fd_read = .{ + .fd = v.fd, + }, + }, + }, + }, + + .send => |v| .{ + .userdata = @intFromPtr(self), + .u = .{ + .tag = wasi.EVENTTYPE_FD_WRITE, + .u = .{ + .fd_write = .{ + .fd = v.fd, + }, + }, + }, + }, + + .close, + .async_wait, + .noop, + .shutdown, + .cancel, + .timer, + => unreachable, + }; + } + + /// Perform the operation associated with this completion. This will + /// perform the full blocking operation for the completion. + fn perform(self: *Completion) Result { + return switch (self.op) { + // This should never happen because we always do these synchronously + // or in another location. + .close, + .async_wait, + .noop, + .shutdown, + .cancel, + .timer, + => unreachable, + + .accept => |*op| res: { + var out_fd: posix.fd_t = undefined; + break :res .{ + .accept = switch (wasi.sock_accept(op.socket, 0, &out_fd)) { + .SUCCESS => out_fd, + else => |err| posix.unexpectedErrno(err), + }, + }; + }, + + .read => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| posix.read(op.fd, v), + .array => |*v| posix.read(op.fd, v), + }; + + break :res .{ + .read = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .pread => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| posix.pread(op.fd, v, op.offset), + .array => |*v| posix.pread(op.fd, v, op.offset), + }; + + break :res .{ + .pread = if (n_) |n| + if (n == 0) error.EOF else n + else |err| + err, + }; + }, + + .write => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| posix.write(op.fd, v), + .array => |*v| posix.write(op.fd, v.array[0..v.len]), + }; + + break :res .{ + .write = if (n_) |n| n else |err| err, + }; + }, + + .pwrite => |*op| res: { + const n_ = switch (op.buffer) { + .slice => |v| posix.pwrite(op.fd, v, op.offset), + .array => |*v| posix.pwrite(op.fd, v.array[0..v.len], op.offset), + }; + + break :res .{ + .pwrite = if (n_) |n| n else |err| err, + }; + }, + + .recv => |*op| res: { + var n: usize = undefined; + var roflags: wasi.roflags_t = undefined; + const errno = switch (op.buffer) { + .slice => |v| slice: { + var iovs = [1]posix.iovec{posix.iovec{ + .base = v.ptr, + .len = v.len, + }}; + + break :slice wasi.sock_recv( + op.fd, + @ptrCast(&iovs[0]), + iovs.len, + 0, + &n, + &roflags, + ); + }, + + .array => |*v| array: { + var iovs = [1]posix.iovec{posix.iovec{ + .base = v, + .len = v.len, + }}; + + break :array wasi.sock_recv( + op.fd, + @ptrCast(&iovs[0]), + iovs.len, + 0, + &n, + &roflags, + ); + }, + }; + + break :res .{ + .recv = switch (errno) { + .SUCCESS => n, + else => |err| posix.unexpectedErrno(err), + }, + }; + }, + + .send => |*op| res: { + var n: usize = undefined; + const errno = switch (op.buffer) { + .slice => |v| slice: { + var iovs = [1]posix.iovec_const{posix.iovec_const{ + .base = v.ptr, + .len = v.len, + }}; + + break :slice wasi.sock_send( + op.fd, + @ptrCast(&iovs[0]), + iovs.len, + 0, + &n, + ); + }, + + .array => |*v| array: { + var iovs = [1]posix.iovec_const{posix.iovec_const{ + .base = &v.array, + .len = v.len, + }}; + + break :array wasi.sock_send( + op.fd, + @ptrCast(&iovs[0]), + iovs.len, + 0, + &n, + ); + }, + }; + + break :res .{ + .send = switch (errno) { + .SUCCESS => n, + else => |err| posix.unexpectedErrno(err), + }, + }; + }, + }; + } +}; + +pub const OperationType = enum { + noop, + cancel, + accept, + read, + pread, + write, + pwrite, + send, + recv, + shutdown, + close, + timer, + async_wait, +}; + +/// The result type based on the operation type. For a callback, the +/// result tag will ALWAYS match the operation tag. +pub const Result = union(OperationType) { + noop: void, + cancel: CancelError!void, + accept: AcceptError!posix.fd_t, + read: ReadError!usize, + pread: ReadError!usize, + write: WriteError!usize, + pwrite: WriteError!usize, + send: WriteError!usize, + recv: ReadError!usize, + shutdown: ShutdownError!void, + close: CloseError!void, + timer: TimerError!TimerTrigger, + async_wait: AsyncError!void, +}; + +/// All the supported operations of this event loop. These are always +/// backend-specific and therefore the structure and types change depending +/// on the underlying system in use. The high level operations are +/// done by initializing the request handles. +pub const Operation = union(OperationType) { + noop: void, + + cancel: struct { + c: *Completion, + }, + + accept: struct { + socket: posix.socket_t, + }, + + read: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + pread: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + offset: u64, + }, + + write: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + pwrite: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + offset: u64, + }, + + send: struct { + fd: posix.fd_t, + buffer: WriteBuffer, + }, + + recv: struct { + fd: posix.fd_t, + buffer: ReadBuffer, + }, + + shutdown: struct { + socket: posix.socket_t, + how: ShutdownHow = .both, + }, + + close: struct { + fd: posix.fd_t, + }, + + timer: Timer, + + async_wait: struct { + wakeup: Loop.WakeupType = Loop.wakeup_init, + }, +}; + +const Timer = struct { + /// The absolute time to fire this timer next. + next: wasi.timestamp_t, + + /// Only used internally. If this is non-null and timer is + /// CANCELLED, then the timer is rearmed automatically with this + /// as the next time. The callback will not be called on the + /// cancellation. + reset: ?wasi.timestamp_t = null, + + /// Internal heap fields. + heap: heap.IntrusiveField(Timer) = .{}, + + /// We point back to completion for now. When issue[1] is fixed, + /// we can juse use that from our heap fields. + /// [1]: https://github.com/ziglang/zig/issues/6611 + c: *Completion = undefined, + + fn less(_: void, a: *const Timer, b: *const Timer) bool { + return a.next < b.next; + } +}; + +pub const CancelError = error{ + /// Invalid operation to cancel. You cannot cancel a cancel operation. + InvalidOp, +}; + +pub const CloseError = error{ + Unknown, +}; + +pub const AcceptError = Batch.Error || error{ + Unexpected, +}; + +pub const ConnectError = error{}; + +pub const ShutdownError = error{ + Unexpected, +}; + +pub const ReadError = Batch.Error || xev_posix.ReadError || xev_posix.PReadError || + error{ + EOF, + Unknown, + }; + +pub const WriteError = Batch.Error || xev_posix.WriteError || xev_posix.PWriteError || + error{ + Unknown, + }; + +pub const AsyncError = error{ + Unknown, +}; + +pub const TimerError = error{ + Unexpected, +}; + +pub const TimerTrigger = enum { + /// Timer expired. + expiration, + + /// Timer was canceled. + cancel, + + /// Unused + request, +}; + +/// ReadBuffer are the various options for reading. +pub const ReadBuffer = union(enum) { + /// Read into this slice. + slice: []u8, + + /// Read into this array, just set this to undefined and it will + /// be populated up to the size of the array. This is an option because + /// the other union members force a specific size anyways so this lets us + /// use the other size in the union to support small reads without worrying + /// about buffer allocation. + /// + /// To know the size read you have to use the return value of the + /// read operations (i.e. recv). + /// + /// Note that the union at the time of this writing could accomodate a + /// much larger fixed size array here but we want to retain flexiblity + /// for future fields. + array: [32]u8, + + // TODO: future will have vectors +}; + +/// WriteBuffer are the various options for writing. +pub const WriteBuffer = union(enum) { + /// Write from this buffer. + slice: []const u8, + + /// Write from this array. See ReadBuffer.array for why we support this. + array: struct { + array: [32]u8, + len: usize, + }, + + // TODO: future will have vectors +}; + +/// A batch of subscriptions to send to poll_oneoff. +const Batch = struct { + pub const capacity = 1024; + + /// The array of subscriptions. Sub zero is ALWAYS our loop timeout + /// so the actual capacity of this for user completions is (len - 1). + array: [capacity]wasi.subscription_t = undefined, + + /// The length of the used slots in the array including our reserved slot. + len: usize = 1, + + pub const Error = error{BatchFull}; + + /// Initialize a batch entry for the given completion. This will + /// store the batch index on the completion. + pub fn get(self: *Batch, c: *Completion) Error!*wasi.subscription_t { + if (self.len >= self.array.len) return error.BatchFull; + c.batch_idx = self.len; + self.len += 1; + return &self.array[c.batch_idx]; + } + + /// Put an entry back. + pub fn put(self: *Batch, c: *Completion) void { + assert(c.batch_idx > 0); + assert(self.len > 1); + + const old_idx = c.batch_idx; + c.batch_idx = 0; + self.len -= 1; + + // If we're empty then we don't worry about swapping. + if (self.len == 0) return; + + // We're not empty so swap the value we just removed with the + // last one so our empty slot is always at the end. + self.array[old_idx] = self.array[self.len]; + const swapped = @as(*Completion, @ptrFromInt(@as(usize, @intCast(self.array[old_idx].userdata)))); + swapped.batch_idx = old_idx; + } + + test { + const testing = std.testing; + + var b: Batch = .{}; + var cs: [capacity - 1]Completion = undefined; + for (&cs, 0..) |*c, i| { + c.* = .{ .op = undefined, .callback = undefined }; + const sub = try b.get(c); + sub.* = .{ .userdata = @intFromPtr(c), .u = undefined }; + try testing.expectEqual(@as(usize, i + 1), c.batch_idx); + } + + var bad: Completion = .{ .op = undefined, .callback = undefined }; + try testing.expectError(error.BatchFull, b.get(&bad)); + + // Put one back + const old = cs[4].batch_idx; + const replace = &cs[cs.len - 1]; + b.put(&cs[4]); + try testing.expect(b.len == capacity - 1); + try testing.expect(b.array[old].userdata == @intFromPtr(replace)); + try testing.expect(replace.batch_idx == old); + + // Put it back in + const sub = try b.get(&cs[4]); + sub.* = .{ .userdata = @intFromPtr(&cs[4]), .u = undefined }; + try testing.expect(cs[4].batch_idx == capacity - 1); + } +}; + +test "wasi: loop time" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // should never init zero + const now = loop.now(); + try testing.expect(now > 0); + + // should update on a loop tick + while (now == loop.now()) try loop.run(.no_wait); +} + +test "wasi: timer" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var called = false; + var c1: xev.Completion = undefined; + loop.timer(&c1, 1, &called, (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // Add another timer + var called2 = false; + var c2: xev.Completion = undefined; + loop.timer(&c2, 100_000, &called2, (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = r; + const b = @as(*bool, @ptrCast(ud.?)); + b.* = true; + return .disarm; + } + }).callback); + + // State checking + try testing.expect(c1.state() == .active); + try testing.expect(c2.state() == .active); + + // Tick + while (!called) try loop.run(.no_wait); + try testing.expect(called); + try testing.expect(!called2); + + // State checking + try testing.expect(c1.state() == .dead); + try testing.expect(c2.state() == .active); +} + +test "wasi: timer reset" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: xev.Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // We know timer won't be called from the timer test previously. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .active); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "wasi: timer reset before tick" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: xev.Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 100_000, &trigger, cb); + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "wasi: timer reset after trigger" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + const cb: xev.Callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + const v = @as(*?TimerTrigger, @ptrCast(ud.?)); + v.* = r.timer catch unreachable; + return .disarm; + } + }).callback; + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: Completion = undefined; + loop.timer(&c1, 1, &trigger, cb); + + // Run the timer + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + trigger = null; + + // Reset the timer + var c_cancel: Completion = .{}; + loop.timer_reset(&c1, &c_cancel, 1, &trigger, cb); + try testing.expect(c1.state() == .active); + try testing.expect(c_cancel.state() == .dead); + + // Run + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + try testing.expect(c1.state() == .dead); + try testing.expect(c_cancel.state() == .dead); +} + +test "wasi: timer cancellation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: xev.Completion = undefined; + loop.timer(&c1, 100_000, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + const ptr = @as(*?TimerTrigger, @ptrCast(@alignCast(ud.?))); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.no_wait); + try testing.expect(trigger == null); + + // Cancel the timer + var called = false; + var c_cancel: xev.Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + c: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .cancel); +} + +test "wasi: canceling a completed operation" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Add the timer + var trigger: ?TimerTrigger = null; + var c1: xev.Completion = undefined; + loop.timer(&c1, 1, &trigger, (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + _: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + const ptr = @as(*?TimerTrigger, @ptrCast(@alignCast(ud.?))); + ptr.* = r.timer catch unreachable; + return .disarm; + } + }).callback); + + // Tick and verify we're not called. + try loop.run(.until_done); + try testing.expect(trigger.? == .expiration); + + // Cancel the timer + var called = false; + var c_cancel: xev.Completion = .{ + .op = .{ + .cancel = .{ + .c = &c1, + }, + }, + + .userdata = &called, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + c: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = c; + _ = r.cancel catch unreachable; + const ptr = @as(*bool, @ptrCast(@alignCast(ud.?))); + ptr.* = true; + return .disarm; + } + }).callback, + }; + loop.add(&c_cancel); + + // Tick + try loop.run(.until_done); + try testing.expect(called); + try testing.expect(trigger.? == .expiration); +} + +test "wasi: file" { + const testing = std.testing; + + var loop = try Loop.init(.{}); + defer loop.deinit(); + + // Create a file + const path = "zig-cache/wasi-test-file.txt"; + const dir = std.fs.cwd(); + // We can't use dir.createFile yet: https://github.com/ziglang/zig/issues/14324 + const f = f: { + const w = wasi; + const oflags = w.O.CREAT | w.O.TRUNC; + const base: w.rights_t = w.RIGHT.FD_WRITE | + w.RIGHT.FD_READ | + w.RIGHT.FD_DATASYNC | + w.RIGHT.FD_SEEK | + w.RIGHT.FD_TELL | + w.RIGHT.FD_FDSTAT_SET_FLAGS | + w.RIGHT.FD_SYNC | + w.RIGHT.FD_ALLOCATE | + w.RIGHT.FD_ADVISE | + w.RIGHT.FD_FILESTAT_SET_TIMES | + w.RIGHT.FD_FILESTAT_SET_SIZE | + w.RIGHT.FD_FILESTAT_GET | + w.RIGHT.POLL_FD_READWRITE; + const fdflags: w.fdflags_t = w.FDFLAG.SYNC | w.FDFLAG.RSYNC | w.FDFLAG.DSYNC; + const fd = try posix.openatWasi(dir.fd, path, 0x0, oflags, 0x0, base, fdflags); + break :f std.fs.File{ .handle = fd }; + }; + defer dir.deleteFile(path) catch unreachable; + defer f.close(); + + // Start a reader + var read_buf: [128]u8 = undefined; + var read_len: ?usize = null; + var c_read: xev.Completion = .{ + .op = .{ + .read = .{ + .fd = f.handle, + .buffer = .{ .slice = &read_buf }, + }, + }, + + .userdata = &read_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + c: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = c; + const ptr = @as(*?usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.read catch |err| switch (err) { + error.EOF => 0, + else => unreachable, + }; + return .disarm; + } + }).callback, + }; + loop.add(&c_read); + + // Tick. The reader should NOT read because we are blocked with no data. + try loop.run(.until_done); + try testing.expect(read_len.? == 0); + + // Start a writer + const write_buf = "hello!"; + var write_len: ?usize = null; + var c_write: xev.Completion = .{ + .op = .{ + .write = .{ + .fd = f.handle, + .buffer = .{ .slice = write_buf }, + }, + }, + + .userdata = &write_len, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + c: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = l; + _ = c; + const ptr = @as(*?usize, @ptrCast(@alignCast(ud.?))); + ptr.* = r.write catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_write); + + try loop.run(.until_done); + try testing.expect(write_len.? == write_buf.len); + + // Close + var c_close: xev.Completion = .{ + .op = .{ + .close = .{ + .fd = f.handle, + }, + }, + + .userdata = null, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l: *xev.Loop, + c: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + _ = ud; + _ = l; + _ = c; + _ = r.close catch unreachable; + return .disarm; + } + }).callback, + }; + loop.add(&c_close); + try loop.run(.until_done); + + // Read and verify we've written + const f_verify = try dir.openFile(path, .{}); + defer f_verify.close(); + read_len = try f_verify.readAll(&read_buf); + try testing.expectEqualStrings(write_buf, read_buf[0..read_len.?]); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async1.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async1.zig new file mode 100644 index 0000000..3e0e762 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async1.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +// Tune-ables +pub const NUM_PINGS = 1000 * 1000; + +pub fn main(init: std.process.Init) !void { + try run(1, init.io); +} + +pub fn run(comptime thread_count: comptime_int, io: std.Io) !void { + if (comptime xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + // Initialize all our threads + var contexts: [thread_count]Thread = undefined; + var threads: [contexts.len]std.Thread = undefined; + var comps: [contexts.len]xev.Completion = undefined; + for (&contexts, 0..) |*ctx, i| { + ctx.* = try Thread.init(); + ctx.main_async.wait(&loop, &comps[i], Thread, ctx, mainAsyncCallback); + threads[i] = try std.Thread.spawn(.{}, Thread.threadMain, .{ctx}); + } + + const start_time = std.Io.Clock.awake.now(io); + try loop.run(.until_done); + for (&threads) |thr| thr.join(); + const end_time = std.Io.Clock.awake.now(io); + + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); + std.log.info("async{d}: {d:.2} seconds ({d:.2}/sec)", .{ + thread_count, + elapsed / 1e9, + NUM_PINGS / (elapsed / 1e9), + }); +} + +fn mainAsyncCallback( + ud: ?*Thread, + _: *xev.Loop, + _: *xev.Completion, + r: xev.Async.WaitError!void, +) xev.CallbackAction { + _ = r catch unreachable; + + const self = ud.?; + self.worker_async.notify() catch unreachable; + self.main_sent += 1; + self.main_seen += 1; + + return if (self.main_sent >= NUM_PINGS) .disarm else .rearm; +} + +/// The thread state +const Thread = struct { + loop: xev.Loop, + worker_async: xev.Async, + main_async: xev.Async, + worker_sent: usize = 0, + worker_seen: usize = 0, + main_sent: usize = 0, + main_seen: usize = 0, + + pub fn init() !Thread { + return .{ + .loop = try xev.Loop.init(.{}), + .worker_async = try xev.Async.init(), + .main_async = try xev.Async.init(), + }; + } + + pub fn threadMain(self: *Thread) !void { + // Kick us off + try self.main_async.notify(); + + // Start our waiter + var c: xev.Completion = undefined; + self.worker_async.wait(&self.loop, &c, Thread, self, asyncCallback); + + // Run + try self.loop.run(.until_done); + if (self.worker_sent < NUM_PINGS) @panic("FAIL"); + } + + fn asyncCallback( + ud: ?*Thread, + _: *xev.Loop, + _: *xev.Completion, + r: xev.Async.WaitError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + const self = ud.?; + self.main_async.notify() catch unreachable; + self.worker_sent += 1; + self.worker_seen += 1; + return if (self.worker_sent >= NUM_PINGS) .disarm else .rearm; + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async2.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async2.zig new file mode 100644 index 0000000..65ffd50 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async2.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(2, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async4.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async4.zig new file mode 100644 index 0000000..e03cfd9 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async4.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(4, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async8.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async8.zig new file mode 100644 index 0000000..97716a2 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async8.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(8, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_1.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_1.zig new file mode 100644 index 0000000..9fabea8 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_1.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +// Tune-ables +pub const NUM_PINGS = 1000 * 1000; + +pub fn main(init: std.process.Init) !void { + try run(1, init.io); +} + +pub fn run(comptime thread_count: comptime_int, io: std.Io) !void { + var thread_pool = xev.ThreadPool.init(.{}); + defer thread_pool.deinit(); + defer thread_pool.shutdown(); + + if (xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer loop.deinit(); + + // Create our async + notifier = try xev.Async.init(); + defer notifier.deinit(); + + const userdata: ?*void = null; + var c: xev.Completion = undefined; + notifier.wait(&loop, &c, void, userdata, &asyncCallback); + + // Initialize all our threads + var threads: [thread_count]std.Thread = undefined; + for (&threads) |*thr| { + thr.* = try std.Thread.spawn(.{}, threadMain, .{}); + } + + const start_time = std.Io.Clock.awake.now(io); + try loop.run(.until_done); + for (&threads) |thr| thr.join(); + const end_time = std.Io.Clock.awake.now(io); + + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); + std.log.info("async_pummel_{d}: {d} callbacks in {d:.2} seconds ({d:.2}/sec)", .{ + thread_count, + callbacks, + elapsed / 1e9, + @as(f64, @floatFromInt(callbacks)) / (elapsed / 1e9), + }); +} + +var callbacks: usize = 0; +var notifier: xev.Async = undefined; +var state: enum { running, stop, stopped } = .running; + +fn asyncCallback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + r: xev.Async.WaitError!void, +) xev.CallbackAction { + _ = r catch unreachable; + + callbacks += 1; + if (callbacks < NUM_PINGS) return .rearm; + + // We're done + state = .stop; + while (state != .stopped) std.atomic.spinLoopHint(); + return .disarm; +} + +fn threadMain() !void { + while (state == .running) try notifier.notify(); + state = .stopped; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_2.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_2.zig new file mode 100644 index 0000000..13764b2 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_2.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async_pummel_1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(2, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_4.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_4.zig new file mode 100644 index 0000000..d2b1889 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_4.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async_pummel_1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(4, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_8.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_8.zig new file mode 100644 index 0000000..fa68592 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_8.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const run = @import("async_pummel_1.zig").run; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(8, init.io); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/million-timers.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/million-timers.zig new file mode 100644 index 0000000..2a539b0 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/million-timers.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + +pub const NUM_TIMERS: usize = 10 * 1000 * 1000; + +pub fn main(init: std.process.Init) !void { + const alloc = init.gpa; + const io = init.io; + + var thread_pool = xev.ThreadPool.init(.{}); + defer thread_pool.deinit(); + defer thread_pool.shutdown(); + + if (xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer loop.deinit(); + + var cs = try alloc.alloc(xev.Completion, NUM_TIMERS); + defer alloc.free(cs); + + const clock = std.Io.Clock.awake; + const before_all = clock.now(io); + var i: usize = 0; + var timeout: u64 = 1; + while (i < NUM_TIMERS) : (i += 1) { + if (i % 1000 == 0) timeout += 1; + const timer = try xev.Timer.init(); + timer.run(&loop, &cs[i], timeout, void, null, timerCallback); + } + + const before_run = clock.now(io); + try loop.run(.until_done); + const after_run = clock.now(io); + const after_all = clock.now(io); + + const dur = struct { + fn ns(from: std.Io.Timestamp, to: std.Io.Timestamp) f64 { + return @floatFromInt(from.durationTo(to).nanoseconds); + } + }.ns; + + std.log.info("{d:.2} seconds total", .{dur(before_all, after_all) / 1e9}); + std.log.info("{d:.2} seconds init", .{dur(before_all, before_run) / 1e9}); + std.log.info("{d:.2} seconds dispatch", .{dur(before_run, after_run) / 1e9}); + std.log.info("{d:.2} seconds cleanup", .{dur(after_run, after_all) / 1e9}); +} + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +var timer_callback_count: usize = 0; + +fn timerCallback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + result: xev.Timer.RunError!void, +) xev.CallbackAction { + _ = result catch unreachable; + timer_callback_count += 1; + return .disarm; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-pongs.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-pongs.zig new file mode 100644 index 0000000..3a8c4f8 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-pongs.zig @@ -0,0 +1,356 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + + + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + const alloc = init.gpa; + const io = init.io; + + var thread_pool = xev.ThreadPool.init(.{}); + defer thread_pool.deinit(); + defer thread_pool.shutdown(); + + if (xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer loop.deinit(); + + var server_loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer server_loop.deinit(); + + var server = try Server.init(alloc, &server_loop); + defer server.deinit(); + try server.start(); + + // Start our echo server + const server_thr = try std.Thread.spawn(.{}, Server.threadMain, .{&server}); + + // Start our client + var client_loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer client_loop.deinit(); + + var client = try Client.init(alloc, &client_loop); + defer client.deinit(); + try client.start(); + + const clock = std.Io.Clock.awake; + const start_time = clock.now(io); + try client_loop.run(.until_done); + server_thr.join(); + const end_time = clock.now(io); + + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); + std.log.info("{d:.2} roundtrips/s", .{@as(f64, @floatFromInt(client.pongs)) / (elapsed / 1e9)}); + std.log.info("{d:.2} seconds total", .{elapsed / 1e9}); +} + +/// Memory pools for things that need stable pointers +const BufferPool = std.heap.MemoryPool([4096]u8); +const CompletionPool = std.heap.MemoryPool(xev.Completion); +const TCPPool = std.heap.MemoryPool(xev.TCP); + +/// The client state +const Client = struct { + loop: *xev.Loop, + alloc: Allocator, + completion_pool: CompletionPool = .empty, + read_buf: [1024]u8 = undefined, + pongs: u64 = 0, + state: usize = 0, + stop: bool = false, + + pub const PING = "PING\n"; + + pub fn init(alloc: Allocator, loop: *xev.Loop) !Client { + return .{ + .loop = loop, + .alloc = alloc, + }; + } + + pub fn deinit(self: *Client) void { + self.completion_pool.deinit(self.alloc); + } + + /// Must be called with stable self pointer. + pub fn start(self: *Client) !void { + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); + const socket = try xev.TCP.init(addr); + + const c = try self.completion_pool.create(self.alloc); + socket.connect(self.loop, c, addr, Client, self, connectCallback); + } + + fn connectCallback( + self_: ?*Client, + l: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + r: xev.ConnectError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + + const self = self_.?; + + // Send message + socket.write(l, c, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback); + + // Read + const c_read = self.completion_pool.create(self.alloc) catch unreachable; + socket.read(l, c_read, .{ .slice = &self.read_buf }, Client, self, readCallback); + return .disarm; + } + + fn writeCallback( + self_: ?*Client, + l: *xev.Loop, + c: *xev.Completion, + s: xev.TCP, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + _ = l; + _ = s; + _ = b; + + // Put back the completion. + self_.?.completion_pool.destroy(c); + return .disarm; + } + + fn readCallback( + self_: ?*Client, + l: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + buf: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + const self = self_.?; + const n = r catch unreachable; + const data = buf.slice[0..n]; + + // Count the number of pings in our message + var i: usize = 0; + while (i < n) : (i += 1) { + assert(data[i] == PING[self.state]); + self.state = (self.state + 1) % (PING.len); + if (self.state == 0) { + self.pongs += 1; + + // If we're done then exit + if (self.pongs > 500_000) { + socket.shutdown(l, c, Client, self, shutdownCallback); + return .disarm; + } + + // Send another ping + const c_ping = self.completion_pool.create(self.alloc) catch unreachable; + socket.write(l, c_ping, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback); + } + } + + // Read again + return .rearm; + } + + fn shutdownCallback( + self_: ?*Client, + l: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + r: xev.ShutdownError!void, + ) xev.CallbackAction { + _ = r catch {}; + + const self = self_.?; + socket.close(l, c, Client, self, closeCallback); + return .disarm; + } + + fn closeCallback( + self_: ?*Client, + l: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = l; + _ = socket; + _ = r catch unreachable; + + const self = self_.?; + self.stop = true; + self.completion_pool.destroy(c); + return .disarm; + } +}; + +/// The server state +const Server = struct { + loop: *xev.Loop, + alloc: Allocator, + buffer_pool: BufferPool = .empty, + completion_pool: CompletionPool = .empty, + socket_pool: TCPPool = .empty, + stop: bool = false, + + pub fn init(alloc: Allocator, loop: *xev.Loop) !Server { + return .{ + .loop = loop, + .alloc = alloc, + }; + } + + pub fn deinit(self: *Server) void { + self.buffer_pool.deinit(self.alloc); + self.completion_pool.deinit(self.alloc); + self.socket_pool.deinit(self.alloc); + } + + /// Must be called with stable self pointer. + pub fn start(self: *Server) !void { + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); + var socket = try xev.TCP.init(addr); + + const c = try self.completion_pool.create(self.alloc); + try socket.bind(addr); + try socket.listen(128); + socket.accept(self.loop, c, Server, self, acceptCallback); + } + + pub fn threadMain(self: *Server) !void { + try self.loop.run(.until_done); + } + + fn destroyBuf(self: *Server, buf: []const u8) void { + self.buffer_pool.destroy( + @alignCast( + @as(*[4096]u8, @ptrFromInt(@intFromPtr(buf.ptr))), + ), + ); + } + + fn acceptCallback( + self_: ?*Server, + l: *xev.Loop, + c: *xev.Completion, + r: xev.AcceptError!xev.TCP, + ) xev.CallbackAction { + const self = self_.?; + + // Create our socket + const socket = self.socket_pool.create(self.alloc) catch unreachable; + socket.* = r catch unreachable; + + // Start reading -- we can reuse c here because its done. + const buf = self.buffer_pool.create(self.alloc) catch unreachable; + socket.read(l, c, .{ .slice = buf }, Server, self, readCallback); + return .disarm; + } + + fn readCallback( + self_: ?*Server, + loop: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + buf: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + const self = self_.?; + const n = r catch |err| switch (err) { + error.EOF => { + self.destroyBuf(buf.slice); + socket.shutdown(loop, c, Server, self, shutdownCallback); + return .disarm; + }, + + else => { + self.destroyBuf(buf.slice); + self.completion_pool.destroy(c); + std.log.warn("server read unexpected err={}", .{err}); + return .disarm; + }, + }; + + // Echo it back + const c_echo = self.completion_pool.create(self.alloc) catch unreachable; + const buf_write = self.buffer_pool.create(self.alloc) catch unreachable; + @memcpy(buf_write, buf.slice[0..n]); + socket.write(loop, c_echo, .{ .slice = buf_write[0..n] }, Server, self, writeCallback); + + // Read again + return .rearm; + } + + fn writeCallback( + self_: ?*Server, + l: *xev.Loop, + c: *xev.Completion, + s: xev.TCP, + buf: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = l; + _ = s; + _ = r catch unreachable; + + // We do nothing for write, just put back objects into the pool. + const self = self_.?; + self.completion_pool.destroy(c); + self.buffer_pool.destroy( + @alignCast( + @as(*[4096]u8, @ptrFromInt(@intFromPtr(buf.slice.ptr))), + ), + ); + return .disarm; + } + + fn shutdownCallback( + self_: ?*Server, + l: *xev.Loop, + c: *xev.Completion, + s: xev.TCP, + r: xev.ShutdownError!void, + ) xev.CallbackAction { + _ = r catch {}; + + const self = self_.?; + s.close(l, c, Server, self, closeCallback); + return .disarm; + } + + fn closeCallback( + self_: ?*Server, + l: *xev.Loop, + c: *xev.Completion, + socket: xev.TCP, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = l; + _ = r catch unreachable; + _ = socket; + + const self = self_.?; + self.stop = true; + self.completion_pool.destroy(c); + return .disarm; + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-udp1.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-udp1.zig new file mode 100644 index 0000000..7403187 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-udp1.zig @@ -0,0 +1,178 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(1, init.io); +} + +pub fn run(comptime count: comptime_int, io: std.Io) !void { + var thread_pool = xev.ThreadPool.init(.{}); + defer thread_pool.deinit(); + defer thread_pool.shutdown(); + + if (xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer loop.deinit(); + + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", 3131); + + var pingers: [count]Pinger = undefined; + for (&pingers) |*p| { + p.* = try Pinger.init(addr); + try p.start(&loop); + } + + const start_time = std.Io.Clock.awake.now(io); + try loop.run(.until_done); + const end_time = std.Io.Clock.awake.now(io); + + const total: usize = total: { + var total: usize = 0; + for (&pingers) |p| total += p.pongs; + break :total total; + }; + + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); + std.log.info("ping_pongs: {d} pingers, ~{d:.0} roundtrips/s", .{ + count, + @as(f64, @floatFromInt(total)) / (elapsed / 1e9), + }); +} + +const Pinger = struct { + udp: xev.UDP, + addr: std.Io.net.IpAddress, + state: usize = 0, + pongs: u64 = 0, + read_buf: [1024]u8 = undefined, + c_read: xev.Completion = undefined, + c_write: xev.Completion = undefined, + state_read: xev.UDP.State = undefined, + state_write: xev.UDP.State = undefined, + op_count: u8 = 0, + + pub const PING = "PING\n"; + + pub fn init(addr: std.Io.net.IpAddress) !Pinger { + return .{ + .udp = try xev.UDP.init(addr), + .state = 0, + .pongs = 0, + .addr = addr, + }; + } + + pub fn start(self: *Pinger, loop: *xev.Loop) !void { + try self.udp.bind(self.addr); + + self.udp.read( + loop, + &self.c_read, + &self.state_read, + .{ .slice = &self.read_buf }, + Pinger, + self, + Pinger.readCallback, + ); + + self.write(loop); + } + + pub fn write(self: *Pinger, loop: *xev.Loop) void { + self.udp.write( + loop, + &self.c_write, + &self.state_write, + self.addr, + .{ .slice = PING[0..PING.len] }, + Pinger, + self, + writeCallback, + ); + } + + pub fn readCallback( + self_: ?*Pinger, + loop: *xev.Loop, + c: *xev.Completion, + _: *xev.UDP.State, + _: std.Io.net.IpAddress, + socket: xev.UDP, + buf: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + _ = c; + _ = socket; + const self = self_.?; + const n = r catch unreachable; + const data = buf.slice[0..n]; + + var i: usize = 0; + while (i < n) : (i += 1) { + assert(data[i] == PING[self.state]); + self.state = (self.state + 1) % (PING.len); + if (self.state == 0) { + self.pongs += 1; + + // If we're done then exit + if (self.pongs > 500_000) { + self.udp.close(loop, &self.c_read, Pinger, self, closeCallback); + return .disarm; + } + + self.op_count += 1; + if (self.op_count == 2) { + self.op_count = 0; + // Send another ping + self.write(loop); + } + } + } + + return .rearm; + } + + pub fn writeCallback( + self_: ?*Pinger, + loop: *xev.Loop, + _: *xev.Completion, + _: *xev.UDP.State, + _: xev.UDP, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + const self = self_.?; + + self.op_count += 1; + if (self.op_count == 2) { + self.op_count = 0; + // Send another ping + self.write(loop); + } + + _ = r catch unreachable; + return .disarm; + } + + pub fn closeCallback( + _: ?*Pinger, + _: *xev.Loop, + _: *xev.Completion, + _: xev.UDP, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/udp_pummel_1v1.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/udp_pummel_1v1.zig new file mode 100644 index 0000000..cdaad33 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/udp_pummel_1v1.zig @@ -0,0 +1,148 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const xev = @import("xev"); +//const xev = @import("xev").Dynamic; + +const EXPECTED = "RANG TANG DING DONG I AM THE JAPANESE SANDMAN"; + +/// This is a global var decremented for the test without any locks. That's +/// how the original is written and that's how we're going to do it. +var packet_counter: usize = 1e6; +var send_cb_called: usize = 0; +var recv_cb_called: usize = 0; + +pub const std_options: std.Options = .{ + .log_level = .info, +}; + +pub fn main(init: std.process.Init) !void { + try run(1, 1, init.io); +} + +pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int, io: std.Io) !void { + const base_port = 12345; + + var thread_pool = xev.ThreadPool.init(.{}); + defer thread_pool.deinit(); + defer thread_pool.shutdown(); + + if (xev.dynamic) try xev.detect(); + var loop = try xev.Loop.init(.{ + .entries = std.math.pow(u13, 2, 12), + .thread_pool = &thread_pool, + }); + defer loop.deinit(); + + var receivers: [n_receivers]Receiver = undefined; + for (&receivers, 0..) |*r, i| { + const addr = try std.Io.net.IpAddress.parse("127.0.0.1", @as(u16, @intCast(base_port + i))); + r.* = .{ .udp = try xev.UDP.init(addr) }; + try r.udp.bind(addr); + r.udp.read( + &loop, + &r.c_recv, + &r.udp_state, + .{ .slice = &r.recv_buf }, + Receiver, + r, + Receiver.readCallback, + ); + } + + var senders: [n_senders]Sender = undefined; + for (&senders, 0..) |*s, i| { + const addr = try std.Io.net.IpAddress.parse( + "127.0.0.1", + @as(u16, @intCast(base_port + (i % n_receivers))), + ); + s.* = .{ .udp = try xev.UDP.init(addr) }; + s.udp.write( + &loop, + &s.c_send, + &s.udp_state, + addr, + .{ .slice = EXPECTED }, + Sender, + s, + Sender.writeCallback, + ); + } + + const start_time = std.Io.Clock.awake.now(io); + try loop.run(.until_done); + const end_time = std.Io.Clock.awake.now(io); + + const elapsed: f64 = @floatFromInt(start_time.durationTo(end_time).nanoseconds); + std.log.info("udp_pummel_{d}v{d}: {d:.0}f/s received, {d:.0}f/s sent, {d} received, {d} sent in {d:.1} seconds", .{ + n_senders, + n_receivers, + @as(f64, @floatFromInt(recv_cb_called)) / (elapsed / std.time.ns_per_s), + @as(f64, @floatFromInt(send_cb_called)) / (elapsed / std.time.ns_per_s), + recv_cb_called, + send_cb_called, + elapsed / std.time.ns_per_s, + }); +} + +const Sender = struct { + udp: xev.UDP, + udp_state: xev.UDP.State = undefined, + c_send: xev.Completion = undefined, + + fn writeCallback( + _: ?*Sender, + l: *xev.Loop, + _: *xev.Completion, + _: *xev.UDP.State, + _: xev.UDP, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + + if (packet_counter == 0) { + l.stop(); + return .disarm; + } + + packet_counter -|= 1; + send_cb_called += 1; + + return .rearm; + } +}; + +const Receiver = struct { + udp: xev.UDP, + udp_state: xev.UDP.State = undefined, + c_recv: xev.Completion = undefined, + recv_buf: [65536]u8 = undefined, + + fn readCallback( + _: ?*Receiver, + _: *xev.Loop, + _: *xev.Completion, + _: *xev.UDP.State, + _: std.Io.net.IpAddress, + _: xev.UDP, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + const n = r catch |err| { + switch (err) { + error.EOF => {}, + else => std.log.warn("err={}", .{err}), + } + + return .disarm; + }; + + if (!std.mem.eql(u8, b.slice[0..n], EXPECTED)) { + @panic("Unexpected data."); + } + + recv_cb_called += 1; + return .rearm; + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/c_api.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/c_api.zig new file mode 100644 index 0000000..dbeb364 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/c_api.zig @@ -0,0 +1,360 @@ +// This file contains the C bindings that are exported when building +// the system libraries. +// +// WHERE IS THE DOCUMENTATION? Note that all the documentation for the C +// interface is in the man pages. The header file xev.h purposely has no +// documentation so that its concise and easy to see the list of exported +// functions. + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const xev = @import("main.zig"); + +const func_callconv: std.builtin.CallingConvention = if (blk: { + const order = builtin.zig_version.order(.{ .major = 0, .minor = 14, .patch = 1 }); + break :blk order == .lt or order == .eq; +}) .C else .c; + +export fn xev_loop_init(loop: *xev.Loop) c_int { + // TODO: overflow + loop.* = xev.Loop.init(.{}) catch |err| return errorCode(err); + return 0; +} + +export fn xev_loop_deinit(loop: *xev.Loop) void { + loop.deinit(); +} + +export fn xev_loop_run(loop: *xev.Loop, mode: xev.RunMode) c_int { + loop.run(mode) catch |err| return errorCode(err); + return 0; +} + +export fn xev_loop_now(loop: *xev.Loop) i64 { + return loop.now(); +} + +export fn xev_loop_update_now(loop: *xev.Loop) void { + loop.update_now(); +} + +export fn xev_completion_zero(c: *xev.Completion) void { + c.* = .{}; +} + +export fn xev_completion_state(c: *xev.Completion) xev.CompletionState { + return c.state(); +} + +//------------------------------------------------------------------- +// ThreadPool + +export fn xev_threadpool_config_init(cfg: *xev.ThreadPool.Config) void { + cfg.* = .{}; +} + +export fn xev_threadpool_config_set_stack_size( + cfg: *xev.ThreadPool.Config, + v: u32, +) void { + cfg.stack_size = v; +} + +export fn xev_threadpool_config_set_max_threads( + cfg: *xev.ThreadPool.Config, + v: u32, +) void { + cfg.max_threads = v; +} + +export fn xev_threadpool_init( + threadpool: *xev.ThreadPool, + cfg_: ?*xev.ThreadPool.Config, +) c_int { + const cfg: xev.ThreadPool.Config = if (cfg_) |v| v.* else .{}; + threadpool.* = xev.ThreadPool.init(cfg); + return 0; +} + +export fn xev_threadpool_deinit(threadpool: *xev.ThreadPool) void { + threadpool.deinit(); +} + +export fn xev_threadpool_shutdown(threadpool: *xev.ThreadPool) void { + threadpool.shutdown(); +} + +export fn xev_threadpool_schedule( + pool: *xev.ThreadPool, + batch: *xev.ThreadPool.Batch, +) void { + pool.schedule(batch.*); +} + +export fn xev_threadpool_task_init( + t: *xev.ThreadPool.Task, + cb: *const fn (*xev.ThreadPool.Task) callconv(func_callconv) void, +) void { + const extern_t = @as(*Task, @ptrCast(@alignCast(t))); + extern_t.c_callback = cb; + + t.* = .{ + .callback = (struct { + fn callback(inner_t: *xev.ThreadPool.Task) void { + const outer_t: *Task = @alignCast(@fieldParentPtr( + "data", + @as(*Task.Data, @ptrCast(inner_t)), + )); + outer_t.c_callback(inner_t); + } + }).callback, + }; +} + +export fn xev_threadpool_batch_init(b: *xev.ThreadPool.Batch) void { + b.* = .{}; +} + +export fn xev_threadpool_batch_push_task( + b: *xev.ThreadPool.Batch, + t: *xev.ThreadPool.Task, +) void { + b.push(xev.ThreadPool.Batch.from(t)); +} + +export fn xev_threadpool_batch_push_batch( + b: *xev.ThreadPool.Batch, + other: *xev.ThreadPool.Batch, +) void { + b.push(other.*); +} + +//------------------------------------------------------------------- +// Timers + +export fn xev_timer_init(v: *xev.Timer) c_int { + v.* = xev.Timer.init() catch |err| return errorCode(err); + return 0; +} + +export fn xev_timer_deinit(v: *xev.Timer) void { + v.deinit(); +} + +export fn xev_timer_run( + v: *xev.Timer, + loop: *xev.Loop, + c: *xev.Completion, + next_ms: u64, + userdata: ?*anyopaque, + cb: *const fn ( + *xev.Loop, + *xev.Completion, + c_int, + ?*anyopaque, + ) callconv(func_callconv) xev.CallbackAction, +) void { + const Callback = @typeInfo(@TypeOf(cb)).pointer.child; + const extern_c = @as(*Completion, @ptrCast(@alignCast(c))); + extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb)); + + v.run(loop, c, next_ms, anyopaque, userdata, (struct { + fn callback( + ud: ?*anyopaque, + cb_loop: *xev.Loop, + cb_c: *xev.Completion, + r: xev.Timer.RunError!void, + ) xev.CallbackAction { + const cb_extern_c = @as(*Completion, @ptrCast(cb_c)); + const cb_c_callback = @as( + *const Callback, + @ptrCast(@alignCast(cb_extern_c.c_callback)), + ); + return @call(.auto, cb_c_callback, .{ + cb_loop, + cb_c, + if (r) |_| 0 else |err| errorCode(err), + ud, + }); + } + }).callback); +} + +export fn xev_timer_reset( + v: *xev.Timer, + loop: *xev.Loop, + c: *xev.Completion, + c_cancel: *xev.Completion, + next_ms: u64, + userdata: ?*anyopaque, + cb: *const fn ( + *xev.Loop, + *xev.Completion, + c_int, + ?*anyopaque, + ) callconv(func_callconv) xev.CallbackAction, +) void { + const Callback = @typeInfo(@TypeOf(cb)).pointer.child; + const extern_c = @as(*Completion, @ptrCast(@alignCast(c))); + extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb)); + + v.reset(loop, c, c_cancel, next_ms, anyopaque, userdata, (struct { + fn callback( + ud: ?*anyopaque, + cb_loop: *xev.Loop, + cb_c: *xev.Completion, + r: xev.Timer.RunError!void, + ) xev.CallbackAction { + const cb_extern_c = @as(*Completion, @ptrCast(cb_c)); + const cb_c_callback = @as( + *const Callback, + @ptrCast(@alignCast(cb_extern_c.c_callback)), + ); + return @call(.auto, cb_c_callback, .{ + cb_loop, + cb_c, + if (r) |_| 0 else |err| errorCode(err), + ud, + }); + } + }).callback); +} + +export fn xev_timer_cancel( + v: *xev.Timer, + loop: *xev.Loop, + c_timer: *xev.Completion, + c_cancel: *xev.Completion, + userdata: ?*anyopaque, + cb: *const fn ( + *xev.Loop, + *xev.Completion, + c_int, + ?*anyopaque, + ) callconv(func_callconv) xev.CallbackAction, +) void { + const Callback = @typeInfo(@TypeOf(cb)).pointer.child; + const extern_c = @as(*Completion, @ptrCast(@alignCast(c_cancel))); + extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb)); + + v.cancel(loop, c_timer, c_cancel, anyopaque, userdata, (struct { + fn callback( + ud: ?*anyopaque, + cb_loop: *xev.Loop, + cb_c: *xev.Completion, + r: xev.Timer.CancelError!void, + ) xev.CallbackAction { + const cb_extern_c = @as(*Completion, @ptrCast(cb_c)); + const cb_c_callback = @as( + *const Callback, + @ptrCast(@alignCast(cb_extern_c.c_callback)), + ); + return @call(.auto, cb_c_callback, .{ + cb_loop, + cb_c, + if (r) |_| 0 else |err| errorCode(err), + ud, + }); + } + }).callback); +} + +//------------------------------------------------------------------- +// Async + +export fn xev_async_init(v: *xev.Async) c_int { + v.* = xev.Async.init() catch |err| return errorCode(err); + return 0; +} + +export fn xev_async_deinit(v: *xev.Async) void { + v.deinit(); +} + +export fn xev_async_notify(v: *xev.Async) c_int { + v.notify() catch |err| return errorCode(err); + return 0; +} + +export fn xev_async_wait( + v: *xev.Async, + loop: *xev.Loop, + c: *xev.Completion, + userdata: ?*anyopaque, + cb: *const fn ( + *xev.Loop, + *xev.Completion, + c_int, + ?*anyopaque, + ) callconv(func_callconv) xev.CallbackAction, +) void { + const Callback = @typeInfo(@TypeOf(cb)).pointer.child; + const extern_c = @as(*Completion, @ptrCast(@alignCast(c))); + extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb)); + + v.wait(loop, c, anyopaque, userdata, (struct { + fn callback( + ud: ?*anyopaque, + cb_loop: *xev.Loop, + cb_c: *xev.Completion, + r: xev.Async.WaitError!void, + ) xev.CallbackAction { + const cb_extern_c = @as(*Completion, @ptrCast(cb_c)); + const cb_c_callback = @as( + *const Callback, + @ptrCast(@alignCast(cb_extern_c.c_callback)), + ); + return @call(.auto, cb_c_callback, .{ + cb_loop, + cb_c, + if (r) |_| 0 else |err| errorCode(err), + ud, + }); + } + }).callback); +} + +//------------------------------------------------------------------- +// Sync with xev.h + +/// Since we can't pass the callback at comptime with C, we have to +/// have an additional field on completions to store our callback pointer. +/// We just tack it onto the end of the memory chunk that C programs allocate +/// for completions. +const Completion = extern struct { + const Data = [@sizeOf(xev.Completion)]u8; + data: Data, + c_callback: *const anyopaque, +}; + +const Task = extern struct { + const Data = [@sizeOf(xev.ThreadPool.Task)]u8; + data: Data, + c_callback: *const fn (*xev.ThreadPool.Task) callconv(func_callconv) void, +}; + +/// Returns the unique error code for an error. +fn errorCode(err: anyerror) c_int { + // TODO(mitchellh): This is a bad idea because its not stable across + // code changes. For now we just document that error codes are not + // stable but that is not useful at all! + return @intFromError(err); +} + +test "c-api sizes" { + // This tests the sizes that are defined in the C API. We must ensure + // that our main structure sizes never exceed these so that the C ABI + // is maintained. + // + // THE MAGIC NUMBERS ARE KEPT IN SYNC WITH "include/xev.h" + const testing = std.testing; + try testing.expect(@sizeOf(xev.Loop) <= 512); + try testing.expect(@sizeOf(Completion) <= 320); + try testing.expect(@sizeOf(xev.Async) <= 256); + try testing.expect(@sizeOf(xev.Timer) <= 256); + try testing.expectEqual(@as(usize, 48), @sizeOf(xev.ThreadPool)); + try testing.expectEqual(@as(usize, 24), @sizeOf(xev.ThreadPool.Batch)); + try testing.expectEqual(@as(usize, 24), @sizeOf(Task)); + try testing.expectEqual(@as(usize, 8), @sizeOf(xev.ThreadPool.Config)); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/darwin.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/darwin.zig new file mode 100644 index 0000000..6f95c2e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/darwin.zig @@ -0,0 +1,376 @@ +//! These are copied from Zig's stdlib as of Zig 0.14 because +//! they are no longer exported. This is probably going to be +//! fixed in the future: https://github.com/ziglang/zig/pull/21218 +const std = @import("std"); + +pub const MACH_SEND_MSG = 0x00000001; +pub const MACH_RCV_MSG = 0x00000002; + +pub const MACH_SEND_TIMEOUT = 0x00000010; +pub const MACH_SEND_OVERRIDE = 0x00000020; +pub const MACH_SEND_INTERRUPT = 0x00000040; +pub const MACH_SEND_NOTIFY = 0x00000080; +pub const MACH_SEND_ALWAYS = 0x00010000; +pub const MACH_SEND_FILTER_NONFATAL = 0x00010000; +pub const MACH_SEND_TRAILER = 0x00020000; +pub const MACH_SEND_NOIMPORTANCE = 0x00040000; +pub const MACH_SEND_NODENAP = MACH_SEND_NOIMPORTANCE; +pub const MACH_SEND_IMPORTANCE = 0x00080000; +pub const MACH_SEND_SYNC_OVERRIDE = 0x00100000; +pub const MACH_SEND_PROPAGATE_QOS = 0x00200000; +pub const MACH_SEND_SYNC_USE_THRPRI = MACH_SEND_PROPAGATE_QOS; +pub const MACH_SEND_KERNEL = 0x00400000; +pub const MACH_SEND_SYNC_BOOTSTRAP_CHECKIN = 0x00800000; + +pub const MACH_RCV_TIMEOUT = 0x00000100; +pub const MACH_RCV_NOTIFY = 0x00000000; +pub const MACH_RCV_INTERRUPT = 0x00000400; +pub const MACH_RCV_VOUCHER = 0x00000800; +pub const MACH_RCV_OVERWRITE = 0x00000000; +pub const MACH_RCV_GUARDED_DESC = 0x00001000; +pub const MACH_RCV_SYNC_WAIT = 0x00004000; +pub const MACH_RCV_SYNC_PEEK = 0x00008000; + +pub const MACH_PORT_NULL: mach_port_t = 0; +pub const MACH_MSG_TIMEOUT_NONE: mach_msg_timeout_t = 0; + +pub const natural_t = c_uint; +pub const integer_t = c_int; +pub const mach_port_t = c_uint; +pub const mach_port_name_t = natural_t; +pub const mach_msg_bits_t = c_uint; +pub const mach_msg_id_t = integer_t; +pub const mach_msg_type_number_t = natural_t; +pub const mach_msg_type_name_t = c_uint; +pub const mach_msg_option_t = integer_t; +pub const mach_msg_size_t = natural_t; +pub const mach_msg_timeout_t = natural_t; +pub const mach_msg_return_t = std.c.kern_return_t; + +pub const mach_msg_header_t = extern struct { + msgh_bits: mach_msg_bits_t, + msgh_size: mach_msg_size_t, + msgh_remote_port: mach_port_t, + msgh_local_port: mach_port_t, + msgh_voucher_port: mach_port_name_t, + msgh_id: mach_msg_id_t, +}; + +pub extern "c" fn mach_msg( + msg: ?*mach_msg_header_t, + option: mach_msg_option_t, + send_size: mach_msg_size_t, + rcv_size: mach_msg_size_t, + rcv_name: mach_port_name_t, + timeout: mach_msg_timeout_t, + notify: mach_port_name_t, +) std.c.kern_return_t; + +pub fn getKernError(err: std.c.kern_return_t) KernE { + return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); +} + +/// Kernel return values +pub const KernE = enum(u32) { + SUCCESS = 0, + + /// Specified address is not currently valid + INVALID_ADDRESS = 1, + + /// Specified memory is valid, but does not permit the + /// required forms of access. + PROTECTION_FAILURE = 2, + + /// The address range specified is already in use, or + /// no address range of the size specified could be + /// found. + NO_SPACE = 3, + + /// The function requested was not applicable to this + /// type of argument, or an argument is invalid + INVALID_ARGUMENT = 4, + + /// The function could not be performed. A catch-all. + FAILURE = 5, + + /// A system resource could not be allocated to fulfill + /// this request. This failure may not be permanent. + RESOURCE_SHORTAGE = 6, + + /// The task in question does not hold receive rights + /// for the port argument. + NOT_RECEIVER = 7, + + /// Bogus access restriction. + NO_ACCESS = 8, + + /// During a page fault, the target address refers to a + /// memory object that has been destroyed. This + /// failure is permanent. + MEMORY_FAILURE = 9, + + /// During a page fault, the memory object indicated + /// that the data could not be returned. This failure + /// may be temporary; future attempts to access this + /// same data may succeed, as defined by the memory + /// object. + MEMORY_ERROR = 10, + + /// The receive right is already a member of the portset. + ALREADY_IN_SET = 11, + + /// The receive right is not a member of a port set. + NOT_IN_SET = 12, + + /// The name already denotes a right in the task. + NAME_EXISTS = 13, + + /// The operation was aborted. Ipc code will + /// catch this and reflect it as a message error. + ABORTED = 14, + + /// The name doesn't denote a right in the task. + INVALID_NAME = 15, + + /// Target task isn't an active task. + INVALID_TASK = 16, + + /// The name denotes a right, but not an appropriate right. + INVALID_RIGHT = 17, + + /// A blatant range error. + INVALID_VALUE = 18, + + /// Operation would overflow limit on user-references. + UREFS_OVERFLOW = 19, + + /// The supplied (port) capability is improper. + INVALID_CAPABILITY = 20, + + /// The task already has send or receive rights + /// for the port under another name. + RIGHT_EXISTS = 21, + + /// Target host isn't actually a host. + INVALID_HOST = 22, + + /// An attempt was made to supply "precious" data + /// for memory that is already present in a + /// memory object. + MEMORY_PRESENT = 23, + + /// A page was requested of a memory manager via + /// memory_object_data_request for an object using + /// a MEMORY_OBJECT_COPY_CALL strategy, with the + /// VM_PROT_WANTS_COPY flag being used to specify + /// that the page desired is for a copy of the + /// object, and the memory manager has detected + /// the page was pushed into a copy of the object + /// while the kernel was walking the shadow chain + /// from the copy to the object. This error code + /// is delivered via memory_object_data_error + /// and is handled by the kernel (it forces the + /// kernel to restart the fault). It will not be + /// seen by users. + MEMORY_DATA_MOVED = 24, + + /// A strategic copy was attempted of an object + /// upon which a quicker copy is now possible. + /// The caller should retry the copy using + /// vm_object_copy_quickly. This error code + /// is seen only by the kernel. + MEMORY_RESTART_COPY = 25, + + /// An argument applied to assert processor set privilege + /// was not a processor set control port. + INVALID_PROCESSOR_SET = 26, + + /// The specified scheduling attributes exceed the thread's + /// limits. + POLICY_LIMIT = 27, + + /// The specified scheduling policy is not currently + /// enabled for the processor set. + INVALID_POLICY = 28, + + /// The external memory manager failed to initialize the + /// memory object. + INVALID_OBJECT = 29, + + /// A thread is attempting to wait for an event for which + /// there is already a waiting thread. + ALREADY_WAITING = 30, + + /// An attempt was made to destroy the default processor + /// set. + DEFAULT_SET = 31, + + /// An attempt was made to fetch an exception port that is + /// protected, or to abort a thread while processing a + /// protected exception. + EXCEPTION_PROTECTED = 32, + + /// A ledger was required but not supplied. + INVALID_LEDGER = 33, + + /// The port was not a memory cache control port. + INVALID_MEMORY_CONTROL = 34, + + /// An argument supplied to assert security privilege + /// was not a host security port. + INVALID_SECURITY = 35, + + /// thread_depress_abort was called on a thread which + /// was not currently depressed. + NOT_DEPRESSED = 36, + + /// Object has been terminated and is no longer available + TERMINATED = 37, + + /// Lock set has been destroyed and is no longer available. + LOCK_SET_DESTROYED = 38, + + /// The thread holding the lock terminated before releasing + /// the lock + LOCK_UNSTABLE = 39, + + /// The lock is already owned by another thread + LOCK_OWNED = 40, + + /// The lock is already owned by the calling thread + LOCK_OWNED_SELF = 41, + + /// Semaphore has been destroyed and is no longer available. + SEMAPHORE_DESTROYED = 42, + + /// Return from RPC indicating the target server was + /// terminated before it successfully replied + RPC_SERVER_TERMINATED = 43, + + /// Terminate an orphaned activation. + RPC_TERMINATE_ORPHAN = 44, + + /// Allow an orphaned activation to continue executing. + RPC_CONTINUE_ORPHAN = 45, + + /// Empty thread activation (No thread linked to it) + NOT_SUPPORTED = 46, + + /// Remote node down or inaccessible. + NODE_DOWN = 47, + + /// A signalled thread was not actually waiting. + NOT_WAITING = 48, + + /// Some thread-oriented operation (semaphore_wait) timed out + OPERATION_TIMED_OUT = 49, + + /// During a page fault, indicates that the page was rejected + /// as a result of a signature check. + CODESIGN_ERROR = 50, + + /// The requested property cannot be changed at this time. + POLICY_STATIC = 51, + + /// The provided buffer is of insufficient size for the requested data. + INSUFFICIENT_BUFFER_SIZE = 52, + + /// Denied by security policy + DENIED = 53, + + /// The KC on which the function is operating is missing + MISSING_KC = 54, + + /// The KC on which the function is operating is invalid + INVALID_KC = 55, + + /// A search or query operation did not return a result + NOT_FOUND = 56, + + _, +}; + +pub fn getMachMsgError(err: mach_msg_return_t) MachMsgE { + return @as(MachMsgE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); +} + +/// Mach msg return values +pub const MachMsgE = enum(u32) { + SUCCESS = 0x00000000, + + /// Thread is waiting to send. (Internal use only.) + SEND_IN_PROGRESS = 0x10000001, + /// Bogus in-line data. + SEND_INVALID_DATA = 0x10000002, + /// Bogus destination port. + SEND_INVALID_DEST = 0x10000003, + /// Message not sent before timeout expired. + SEND_TIMED_OUT = 0x10000004, + /// Bogus voucher port. + SEND_INVALID_VOUCHER = 0x10000005, + /// Software interrupt. + SEND_INTERRUPTED = 0x10000007, + /// Data doesn't contain a complete message. + SEND_MSG_TOO_SMALL = 0x10000008, + /// Bogus reply port. + SEND_INVALID_REPLY = 0x10000009, + /// Bogus port rights in the message body. + SEND_INVALID_RIGHT = 0x1000000a, + /// Bogus notify port argument. + SEND_INVALID_NOTIFY = 0x1000000b, + /// Invalid out-of-line memory pointer. + SEND_INVALID_MEMORY = 0x1000000c, + /// No message buffer is available. + SEND_NO_BUFFER = 0x1000000d, + /// Send is too large for port + SEND_TOO_LARGE = 0x1000000e, + /// Invalid msg-type specification. + SEND_INVALID_TYPE = 0x1000000f, + /// A field in the header had a bad value. + SEND_INVALID_HEADER = 0x10000010, + /// The trailer to be sent does not match kernel format. + SEND_INVALID_TRAILER = 0x10000011, + /// The sending thread context did not match the context on the dest port + SEND_INVALID_CONTEXT = 0x10000012, + /// compatibility: no longer a returned error + SEND_INVALID_RT_OOL_SIZE = 0x10000015, + /// The destination port doesn't accept ports in body + SEND_NO_GRANT_DEST = 0x10000016, + /// Message send was rejected by message filter + SEND_MSG_FILTERED = 0x10000017, + + /// Thread is waiting for receive. (Internal use only.) + RCV_IN_PROGRESS = 0x10004001, + /// Bogus name for receive port/port-set. + RCV_INVALID_NAME = 0x10004002, + /// Didn't get a message within the timeout value. + RCV_TIMED_OUT = 0x10004003, + /// Message buffer is not large enough for inline data. + RCV_TOO_LARGE = 0x10004004, + /// Software interrupt. + RCV_INTERRUPTED = 0x10004005, + /// compatibility: no longer a returned error + RCV_PORT_CHANGED = 0x10004006, + /// Bogus notify port argument. + RCV_INVALID_NOTIFY = 0x10004007, + /// Bogus message buffer for inline data. + RCV_INVALID_DATA = 0x10004008, + /// Port/set was sent away/died during receive. + RCV_PORT_DIED = 0x10004009, + /// compatibility: no longer a returned error + RCV_IN_SET = 0x1000400a, + /// Error receiving message header. See special bits. + RCV_HEADER_ERROR = 0x1000400b, + /// Error receiving message body. See special bits. + RCV_BODY_ERROR = 0x1000400c, + /// Invalid msg-type specification in scatter list. + RCV_INVALID_TYPE = 0x1000400d, + /// Out-of-line overwrite region is not large enough + RCV_SCATTER_SMALL = 0x1000400e, + /// trailer type or number of trailer elements not supported + RCV_INVALID_TRAILER = 0x1000400f, + /// Waiting for receive with timeout. (Internal use only.) + RCV_IN_PROGRESS_TIMED = 0x10004011, + /// invalid reply port used in a STRICT_REPLY message + RCV_INVALID_REPLY = 0x10004012, +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/debug.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/debug.zig new file mode 100644 index 0000000..734e4ab --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/debug.zig @@ -0,0 +1,58 @@ +const std = @import("std"); + +inline fn indent(depth: usize, writer: anytype) !void { + for (0..depth) |_| try writer.writeByte(' '); +} + +pub fn describe(comptime T: type, writer: anytype, depth: usize) !void { + const type_info = @typeInfo(T); + switch (type_info) { + .Type, + .Void, + .Bool, + .NoReturn, + .Int, + .Float, + .Pointer, + .Array, + .ComptimeFloat, + .ComptimeInt, + .Undefined, + .Null, + .Optional, + .ErrorUnion, + .ErrorSet, + .Enum, + .Fn, + .Opaque, + .Frame, + .AnyFrame, + .Vector, + .EnumLiteral, + => { + try writer.print("{s} ({d} bytes)", .{ @typeName(T), @sizeOf(T) }); + }, + .Union => |s| { + try writer.print("{s} ({d} bytes) {{\n", .{ @typeName(T), @sizeOf(T) }); + inline for (s.fields) |f| { + try indent(depth + 4, writer); + try writer.print("{s}: ", .{f.name}); + try describe(f.type, writer, depth + 4); + try writer.writeByte('\n'); + } + try indent(depth, writer); + try writer.writeByte('}'); + }, + .Struct => |s| { + try writer.print("{s} ({d} bytes) {{\n", .{ @typeName(T), @sizeOf(T) }); + inline for (s.fields) |f| { + try indent(depth + 4, writer); + try writer.print("{s}: ", .{f.name}); + try describe(f.type, writer, depth + 4); + try writer.writeByte('\n'); + } + try indent(depth, writer); + try writer.writeByte('}'); + }, + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/dynamic.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/dynamic.zig new file mode 100644 index 0000000..51f438b --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/dynamic.zig @@ -0,0 +1,515 @@ +const dynamicpkg = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const posix = std.posix; +const main = @import("main.zig"); +const AllBackend = main.Backend; +const looppkg = @import("loop.zig"); + +/// Creates the Xev API based on a set of backend types that allows +/// for dynamic choice between the various candidate backends (assuming +/// they're available). For example, on Linux, this allows a consumer to +/// choose between io_uring and epoll, or for automatic fallback to epoll +/// to occur. +/// +/// If only one backend is given, the returned type is equivalent to +/// the static API with no runtime conditional logic, so there is no downside +/// to always using the dynamic choice variant if you wish to support +/// dynamic choice. +/// +/// The goal of this API is to match the static Xev() (in main.zig) +/// API as closely as possible. It can't be exact since this is an abstraction +/// and therefore must be limited to the least common denominator of all +/// backends. +/// +/// Since the static Xev API exposes types that can be initialized on their +/// own (i.e. xev.Async.init()), this API does the same. Since we need to +/// know the backend to use, this uses a global variable. The backend to use +/// defaults to the first candidate backend and DOES NOT test that it is +/// available. The API user should call `detect()` to set the backend to +/// the first available backend. +/// +/// Additionally, a caller can manually set the backend to use, but this +/// must be done before initializing any other types. +pub fn Xev(comptime bes: []const AllBackend) type { + if (bes.len == 0) @compileError("no backends provided"); + + // Ensure that if we only have one candidate that we have no + // overhead to use the dynamic API. + if (bes.len == 1) return bes[0].Api(); + + return struct { + const Dynamic = @This(); + + /// This is a flag that can be used to detect that you're using + /// a dynamic API and not the static API via `hasDecl`. + pub const dynamic = true; + + pub const candidates = bes; + + /// Backend becomes the subset of the full xev.Backend that + /// is available to this dynamic API. + pub const Backend = EnumSubset(AllBackend, bes); + + /// Forward some global types so that users can replace + /// @import("xev") with the dynamic package and everything + /// works. + pub const ThreadPool = main.ThreadPool; + + /// The shared structures + pub const Options = looppkg.Options; + pub const RunMode = looppkg.RunMode; + pub const CallbackAction = looppkg.CallbackAction; + pub const CompletionState = looppkg.CompletionState; + pub const Completion = DynamicCompletion(Dynamic); + pub const PollEvent = DynamicPollEvent(Dynamic); + pub const ReadBuffer = DynamicReadBuffer(Dynamic); + pub const WriteBuffer = DynamicWriteBuffer(Dynamic); + pub const WriteQueue = DynamicWriteQueue(Dynamic); + pub const WriteRequest = Dynamic.Union(&.{"WriteRequest"}); + + /// Error types + pub const AcceptError = Dynamic.ErrorSet(&.{"AcceptError"}); + pub const CancelError = Dynamic.ErrorSet(&.{"CancelError"}); + pub const CloseError = Dynamic.ErrorSet(&.{"CloseError"}); + pub const ConnectError = Dynamic.ErrorSet(&.{"ConnectError"}); + pub const PollError = Dynamic.ErrorSet(&.{"PollError"}); + pub const ShutdownError = Dynamic.ErrorSet(&.{"ShutdownError"}); + pub const WriteError = Dynamic.ErrorSet(&.{"WriteError"}); + pub const ReadError = Dynamic.ErrorSet(&.{"ReadError"}); + + /// Core types + pub const Async = @import("watcher/async.zig").Async(Dynamic); + pub const File = @import("watcher/file.zig").File(Dynamic); + pub const Process = @import("watcher/process.zig").Process(Dynamic); + pub const Stream = @import("watcher/stream.zig").GenericStream(Dynamic); + pub const Timer = @import("watcher/timer.zig").Timer(Dynamic); + pub const TCP = @import("watcher/tcp.zig").TCP(Dynamic); + pub const UDP = @import("watcher/udp.zig").UDP(Dynamic); + + /// The backend that is in use. + pub var backend: Backend = subset(bes[bes.len - 1]); + + /// Detect the preferred backend based on the system and set it + /// for use. "Preferred" is the first available (API exists) candidate + /// backend in the list. + pub fn detect() error{NoAvailableBackends}!void { + inline for (bes) |be| { + if (be.Api().available()) { + backend = subset(be); + return; + } + } + + return error.NoAvailableBackends; + } + + /// Manually set the backend to use, but if the backend is not + /// available, this will not change the backend in use. + pub fn prefer(be: AllBackend) bool { + inline for (bes) |candidate| { + if (candidate == be) { + const api = candidate.Api(); + if (!api.available()) return false; + backend = subset(candidate); + return true; + } + } + + return false; + } + + pub const Loop = struct { + backend: Loop.Union, + + pub const Union = Dynamic.Union(&.{"Loop"}); + + pub fn init(opts: Options) !Loop { + return .{ .backend = switch (backend) { + inline else => |tag| backend: { + const api = (comptime superset(tag)).Api(); + break :backend @unionInit( + Loop.Union, + @tagName(tag), + try api.Loop.init(opts), + ); + }, + } }; + } + + pub fn deinit(self: *Loop) void { + switch (backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).deinit(), + } + } + + pub fn stop(self: *Loop) void { + switch (backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).stop(), + } + } + + pub fn stopped(self: *Loop) bool { + return switch (backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).stopped(), + }; + } + + pub fn run(self: *Loop, mode: RunMode) !void { + switch (backend) { + inline else => |tag| try @field( + self.backend, + @tagName(tag), + ).run(mode), + } + } + }; + + /// Helpers to convert between the subset/superset of backends. + pub fn subset(comptime be: AllBackend) Backend { + return @enumFromInt(@intFromEnum(be)); + } + + pub fn superset(comptime be: Backend) AllBackend { + return @enumFromInt(@intFromEnum(be)); + } + + pub fn Union(comptime field: []const []const u8) type { + return dynamicpkg.Union(bes, field, false); + } + + pub fn TaggedUnion(comptime field: []const []const u8) type { + return dynamicpkg.Union(bes, field, true); + } + + pub fn ErrorSet(comptime field: []const []const u8) type { + return dynamicpkg.ErrorSet(bes, field); + } + + test { + @import("std").testing.refAllDecls(@This()); + } + + test "completion is zero-able" { + const c: Completion = .{}; + _ = c; + } + + test "detect" { + const testing = std.testing; + try detect(); + inline for (bes) |be| { + if (@intFromEnum(be) == @intFromEnum(backend)) { + try testing.expect(be.Api().available()); + break; + } + } else try testing.expect(false); + } + + test "prefer" { + const testing = std.testing; + try testing.expect(prefer(bes[0])); + inline for (bes) |be| { + if (@intFromEnum(be) == @intFromEnum(backend)) { + try testing.expect(be.Api().available()); + break; + } + } else try testing.expect(false); + } + + test "loop basics" { + try detect(); + var l = try Loop.init(.{}); + defer l.deinit(); + try l.run(.until_done); + l.stop(); + try std.testing.expect(l.stopped()); + } + }; +} + +fn DynamicCompletion(comptime dynamic: type) type { + return struct { + const Self = @This(); + + // Completions, unlike almost any other dynamic type in this + // file, are tagged unions. This adds a minimal overhead to + // the completion but is necessary to ensure that we have + // zero-initialized the correct type for where it matters + // (timers). + pub const Union = dynamic.TaggedUnion(&.{"Completion"}); + + value: Self.Union = @unionInit( + Self.Union, + @tagName(dynamic.candidates[dynamic.candidates.len - 1]), + .{}, + ), + + pub fn init() Self { + return .{ .value = switch (dynamic.backend) { + inline else => |tag| value: { + const api = (comptime dynamic.superset(tag)).Api(); + break :value @unionInit( + Self.Union, + @tagName(tag), + api.Completion.init(), + ); + }, + } }; + } + + pub fn state(self: *Self) dynamic.CompletionState { + return switch (self.value) { + inline else => |*v| v.state(), + }; + } + + /// This ensures that the tag is currently set for this + /// completion. If it is not, it will be set to the given + /// tag with a zero-initialized value. + /// + /// This lets users zero-intialize the completion and then + /// call any watcher API on it without having to worry about + /// the correct detected backend tag being set. + pub fn ensureTag(self: *Self, comptime tag: dynamic.Backend) void { + if (self.value == tag) return; + self.value = @unionInit( + Self.Union, + @tagName(tag), + .{}, + ); + } + }; +} + +fn DynamicReadBuffer(comptime dynamic: type) type { + // Our read buffer supports a least common denominator of + // read targets available by all backends. The direct backend + // may support more read targets but if you need that you should + // use the backend directly and not through the dynamic API. + return union(enum) { + const Self = @This(); + + slice: []u8, + array: [32]u8, + + /// Convert a backend-specific read buffer to this. + pub fn fromBackend( + comptime be: dynamic.Backend, + buf: dynamic.superset(be).Api().ReadBuffer, + ) Self { + return switch (buf) { + inline else => |data, tag| @unionInit( + Self, + @tagName(tag), + data, + ), + }; + } + + /// Convert this read buffer to the backend-specific + /// buffer. + pub fn toBackend( + self: Self, + comptime be: dynamic.Backend, + ) dynamic.superset(be).Api().ReadBuffer { + return switch (self) { + inline else => |data, tag| @unionInit( + dynamic.superset(be).Api().ReadBuffer, + @tagName(tag), + data, + ), + }; + } + }; +} + +fn DynamicWriteBuffer(comptime dynamic: type) type { + // Our write buffer supports a least common denominator of + // write targets available by all backends. The direct backend + // may support more write targets but if you need that you should + // use the backend directly and not through the dynamic API. + return union(enum) { + const Self = @This(); + + slice: []const u8, + array: struct { array: [32]u8, len: usize }, + + /// Convert a backend-specific write buffer to this. + pub fn fromBackend( + comptime be: dynamic.Backend, + buf: dynamic.superset(be).Api().WriteBuffer, + ) Self { + return switch (buf) { + .slice => |v| .{ .slice = v }, + .array => |v| .{ .array = .{ .array = v.array, .len = v.len } }, + }; + } + + /// Convert this write buffer to the backend-specific + /// buffer. + pub fn toBackend( + self: Self, + comptime be: dynamic.Backend, + ) dynamic.superset(be).Api().WriteBuffer { + return switch (self) { + .slice => |v| .{ .slice = v }, + .array => |v| .{ .array = .{ .array = v.array, .len = v.len } }, + }; + } + }; +} + +fn DynamicWriteQueue(comptime xev: type) type { + return struct { + const Self = @This(); + pub const Union = xev.TaggedUnion(&.{"WriteQueue"}); + + value: Self.Union = @unionInit( + Self.Union, + @tagName(xev.candidates[xev.candidates.len - 1]), + .{}, + ), + + pub fn ensureTag(self: *Self, comptime tag: xev.Backend) void { + if (self.value == tag) return; + self.value = @unionInit( + Self.Union, + @tagName(tag), + .{}, + ); + } + }; +} + +fn DynamicPollEvent(comptime xev: type) type { + return enum { + const Self = @This(); + + read, + + pub fn fromBackend( + comptime tag: xev.Backend, + event: xev.superset(tag).Api().PollEvent, + ) Self { + return switch (event) { + inline else => |event_tag| @field( + Self, + @tagName(event_tag), + ), + }; + } + + pub fn toBackend( + self: Self, + comptime tag: xev.Backend, + ) xev.superset(tag).Api().PollEvent { + return switch (self) { + inline else => |event_tag| @field( + (comptime xev.superset(tag)).Api().PollEvent, + @tagName(event_tag), + ), + }; + } + }; +} + +/// Create an exhaustive enum that is a subset of another enum. +/// Preserves the same backing type and integer values for the +/// subset making it easy to convert between the two. +fn EnumSubset(comptime T: type, comptime values: []const T) type { + const tag_type = @typeInfo(T).@"enum".tag_type; + var field_names: [values.len][:0]const u8 = undefined; + var field_values: [values.len]tag_type = undefined; + for (values, 0..) |value, i| { + field_names[i] = @tagName(value); + field_values[i] = @intFromEnum(value); + } + + return @Enum(tag_type, .exhaustive, &field_names, &field_values); +} + +/// Creates a union type that can hold the implementation of a given +/// backend by common field name. Example, for Async: Union(bes, "Async") +/// produces: +/// +/// union { +/// io_uring: IO_Uring.Async, +/// epoll: Epoll.Async, +/// ... +/// } +/// +/// The union is untagged to save an extra bit of memory per +/// instance since we have the active backend from the outer struct. +fn Union( + comptime bes: []const AllBackend, + comptime field: []const []const u8, + comptime tagged: bool, +) type { + // Keep track of the largest field size, see the conditional + // below using this variable for more info. + var largest: usize = 0; + + var field_names: [bes.len + 1][:0]const u8 = undefined; + var field_types: [bes.len + 1]type = undefined; + var field_attrs: [bes.len + 1]std.builtin.Type.UnionField.Attributes = @splat(.{}); + for (bes, 0..) |be, i| { + var T: type = be.Api(); + for (field) |f| T = @field(T, f); + largest = @max(largest, @sizeOf(T)); + field_names[i] = @tagName(be); + field_types[i] = T; + field_attrs[i] = .{ .@"align" = @alignOf(T) }; + } + + // If our union only has zero-sized types, we need to add some + // non-zero sized padding entry. This avoids a Zig 0.13 compiler + // crash when trying to create a zero-sized union using @unionInit + // from a switch of a comptime-generated enum. I wasn't able to + // minimize this. In future Zig versions we can remove this and if + // our examples can build with Dynamic then we're good. + var count: usize = bes.len; + if (largest == 0) { + field_names[count] = "_zig_bug_padding"; + field_types[count] = u8; + field_attrs[count] = .{ .@"align" = @alignOf(u8) }; + count += 1; + } + + return @Union( + .auto, + if (tagged) EnumSubset(AllBackend, bes) else null, + field_names[0..count], + field_types[0..count], + field_attrs[0..count], + ); +} + +/// Create a new error set from a list of error sets within +/// the given backends at the given field name. For example, +/// to merge all xev.Async.WaitErrors: +/// +/// ErrorSet(bes, &.{"Async", "WaitError"}); +/// +fn ErrorSet( + comptime bes: []const AllBackend, + comptime field: []const []const u8, +) type { + var Set: type = error{}; + for (bes) |be| { + var NextSet: type = be.Api(); + for (field) |f| NextSet = @field(NextSet, f); + Set = Set || NextSet; + } + + return Set; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/heap.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/heap.zig new file mode 100644 index 0000000..37d8d4d --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/heap.zig @@ -0,0 +1,379 @@ +const std = @import("std"); +const assert = std.debug.assert; + +/// An intrusive heap implementation backed by a pairing heap[1] implementation. +/// +/// Why? Intrusive data structures require the element type to hold the metadata +/// required for the structure, rather than an additional container structure. +/// There are numerous pros/cons that are documented well by Boost[2]. For Zig, +/// I think the primary benefits are making data structures allocation free +/// (rather, shifting allocation up to the consumer which can choose how they +/// want the memory to be available). There are various costs to this such as +/// the costs of pointer chasing, larger memory overhead, requiring the element +/// type to be aware of its container, etc. But for certain use cases an intrusive +/// data structure can yield much better performance. +/// +/// Usage notes: +/// - The element T is expected to have a field "heap" of type InstrusiveHeapField. +/// See the tests for a full example of how to set this. +/// - You can easily make this a min or max heap by inverting the result of +/// "less" below. +/// +/// [1]: https://en.wikipedia.org/wiki/Pairing_heap +/// [2]: https://www.boost.org/doc/libs/1_64_0/doc/html/intrusive/intrusive_vs_nontrusive.html +pub fn Intrusive( + comptime T: type, + comptime Context: type, + comptime less: *const fn (ctx: Context, a: *T, b: *T) bool, +) type { + return struct { + const Self = @This(); + + root: ?*T = null, + context: Context, + + /// Insert a new element v into the heap. An element v can only + /// be a member of a single heap at any given time. When compiled + /// with runtime-safety, assertions will help verify this property. + pub fn insert(self: *Self, v: *T) void { + self.root = if (self.root) |root| self.meld(v, root) else v; + } + + /// Look at the next minimum value but do not remove it. + pub fn peek(self: *Self) ?*T { + return self.root; + } + + /// Delete the minimum value from the heap and return it. + pub fn deleteMin(self: *Self) ?*T { + const root = self.root orelse return null; + self.root = if (root.heap.child) |child| + self.combine_siblings(child) + else + null; + + // Clear pointers with runtime safety so we can verify on + // insert that values aren't incorrectly being set multiple times. + root.heap = .{}; + + return root; + } + + /// Remove the value v from the heap. + pub fn remove(self: *Self, v: *T) void { + // If v doesn't have a previous value, this must be the root + // element. If it is NOT the root element, v can't be in this + // heap and we trigger an assertion failure. + const prev = v.heap.prev orelse { + assert(self.root.? == v); + _ = self.deleteMin(); + return; + }; + + // Detach "v" from the tree and clean up any links so it + // is as if this node never nexisted. The previous value + // must point to the proper next value and the pointers + // must all be cleaned up. + if (v.heap.next) |next| next.heap.prev = prev; + if (prev.heap.child == v) + prev.heap.child = v.heap.next + else + prev.heap.next = v.heap.next; + v.heap.prev = null; + v.heap.next = null; + + // If we have children, then we need to merge them back in. + const child = v.heap.child orelse return; + v.heap.child = null; + const x = self.combine_siblings(child); + self.root = self.meld(x, self.root.?); + } + + /// Meld (union) two heaps together. This isn't a generalized + /// union. It assumes that a.heap.next is null so this is only + /// meant in specific scenarios in the pairing heap where meld + /// is expected. + /// + /// For example, when melding a new value "v" with an existing + /// root "root", "v" must always be the first param. + fn meld(self: *Self, a: *T, b: *T) *T { + assert(a.heap.next == null); + + if (less(self.context, a, b)) { + // B points back to A + b.heap.prev = a; + + // If B has siblings, then A inherits B's siblings + // and B's immediate sibling must point back to A to + // maintain the doubly linked list. + if (b.heap.next) |b_next| { + a.heap.next = b_next; + b_next.heap.prev = a; + b.heap.next = null; + } + + // If A has a child, then B becomes the leftmost sibling + // of that child. + if (a.heap.child) |a_child| { + b.heap.next = a_child; + a_child.heap.prev = b; + } + + // B becomes the leftmost child of A + a.heap.child = b; + + return a; + } + + // Replace A with B in the tree. Any of B's children + // become siblings of A. A becomes the leftmost child of B. + // A points back to B + b.heap.prev = a.heap.prev; + a.heap.prev = b; + if (b.heap.child) |b_child| { + a.heap.next = b_child; + b_child.heap.prev = a; + } + b.heap.child = a; + return b; + } + + /// Combine the siblings of the leftmost value "left" into a single + /// new rooted with the minimum value. + fn combine_siblings(self: *Self, left: *T) *T { + left.heap.prev = null; + + // Merge pairs right + var root: *T = root: { + var a: *T = left; + while (true) { + var b = a.heap.next orelse break :root a; + a.heap.next = null; + b = self.meld(a, b); + a = b.heap.next orelse break :root b; + } + }; + + // Merge pairs left + while (true) { + var b = root.heap.prev orelse return root; + b.heap.next = null; + root = self.meld(b, root); + } + } + }; +} + +/// The state that is required for IntrusiveHeap element types. This +/// should be set as the "heap" field in the type T. +pub fn IntrusiveField(comptime T: type) type { + return struct { + child: ?*T = null, + prev: ?*T = null, + next: ?*T = null, + }; +} + +test "heap" { + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + var a: Elem = .{ .value = 12 }; + var b: Elem = .{ .value = 24 }; + var c: Elem = .{ .value = 7 }; + var d: Elem = .{ .value = 9 }; + + var h: Heap = .{ .context = {} }; + h.insert(&a); + h.insert(&b); + h.insert(&c); + h.insert(&d); + h.remove(&d); + + const testing = std.testing; + try testing.expect(h.deleteMin().?.value == 7); + try testing.expect(h.deleteMin().?.value == 12); + try testing.expect(h.deleteMin().?.value == 24); + try testing.expect(h.deleteMin() == null); +} + +test "heap remove root" { + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + var a: Elem = .{ .value = 12 }; + var b: Elem = .{ .value = 24 }; + + var h: Heap = .{ .context = {} }; + h.insert(&a); + h.insert(&b); + h.remove(&a); + + const testing = std.testing; + try testing.expect(h.deleteMin().?.value == 24); + try testing.expect(h.deleteMin() == null); +} + +test "heap remove with children" { + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + var a: Elem = .{ .value = 36 }; + var b: Elem = .{ .value = 24 }; + var c: Elem = .{ .value = 12 }; + + var h: Heap = .{ .context = {} }; + h.insert(&a); + h.insert(&b); + h.insert(&c); + h.remove(&b); + + const testing = std.testing; + try testing.expect(h.deleteMin().?.value == 12); + try testing.expect(h.deleteMin().?.value == 36); + try testing.expect(h.deleteMin() == null); +} + +test "heap equal values" { + const testing = std.testing; + + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + var a: Elem = .{ .value = 1 }; + var b: Elem = .{ .value = 2 }; + var c: Elem = .{ .value = 3 }; + var d: Elem = .{ .value = 4 }; + + var h: Heap = .{ .context = {} }; + h.insert(&a); + h.insert(&b); + h.insert(&c); + h.insert(&d); + + try testing.expect(h.deleteMin().?.value == 1); + try testing.expect(h.deleteMin().?.value == 2); + try testing.expect(h.deleteMin().?.value == 3); + try testing.expect(h.deleteMin().?.value == 4); + try testing.expect(h.deleteMin() == null); +} + +test "heap: million values" { + const testing = std.testing; + const alloc = testing.allocator; + + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + const NUM_TIMERS: usize = 1000 * 1000; + var elems = try alloc.alloc(Elem, NUM_TIMERS); + defer alloc.free(elems); + + var i: usize = 0; + var value: usize = 0; + while (i < NUM_TIMERS) : (i += 1) { + if (i % 100 == 0) value += 1; + elems[i] = .{ .value = value }; + } + + var h: Heap = .{ .context = {} }; + for (elems) |*elem| { + h.insert(elem); + } + + var count: usize = 0; + var last: usize = 0; + while (h.deleteMin()) |elem| { + count += 1; + try testing.expect(elem.value >= last); + last = elem.value; + } + try testing.expect(h.deleteMin() == null); + try testing.expect(count == NUM_TIMERS); +} + +test "heap: dangling next pointer" { + const testing = std.testing; + const Elem = struct { + const Self = @This(); + value: usize = 0, + heap: IntrusiveField(Self) = .{}, + }; + + const Heap = Intrusive(Elem, void, (struct { + fn less(ctx: void, a: *Elem, b: *Elem) bool { + _ = ctx; + return a.value < b.value; + } + }).less); + + var a: Elem = .{ .value = 2 }; + var b: Elem = .{ .value = 4 }; + var c: Elem = .{ .value = 5 }; + var d: Elem = .{ .value = 1 }; + var e: Elem = .{ .value = 3 }; + + var h: Heap = .{ .context = {} }; + h.insert(&a); + h.insert(&b); + h.insert(&c); + h.insert(&d); + h.insert(&e); + + try testing.expect(h.deleteMin().?.value == 1); + try testing.expect(h.deleteMin().?.value == 2); + try testing.expect(h.deleteMin().?.value == 3); + try testing.expect(h.deleteMin().?.value == 4); + try testing.expect(h.deleteMin().?.value == 5); + try testing.expect(h.deleteMin() == null); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/linux/timerfd.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/linux/timerfd.zig new file mode 100644 index 0000000..92879c4 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/linux/timerfd.zig @@ -0,0 +1,90 @@ +const std = @import("std"); +const linux = std.os.linux; +const posix = std.posix; +const xev_posix = @import("../posix.zig"); + +/// Timerfd is a wrapper around the timerfd system calls. See the +/// timerfd_create man page for information on timerfd and associated +/// system calls. +/// +/// This is a small wrapper around timerfd to make it slightly more +/// pleasant to use, but may not expose all available functionality. +/// For maximum control you should use the syscalls directly. +pub const Timerfd = struct { + /// The timerfd file descriptor for use with poll, etc. + fd: i32, + + /// timerfd_create + pub fn init( + clock: linux.timerfd_clockid_t, + flags: linux.TFD, + ) !Timerfd { + const res = linux.timerfd_create(clock, flags); + return switch (posix.errno(res)) { + .SUCCESS => .{ .fd = @as(i32, @intCast(res)) }, + else => error.UnknownError, + }; + } + + pub fn deinit(self: *const Timerfd) void { + xev_posix.close(self.fd); + } + + /// timerfd_settime + pub fn set( + self: *const Timerfd, + flags: linux.TFD.TIMER, + new_value: *const Spec, + old_value: ?*Spec, + ) !void { + const res = linux.timerfd_settime( + self.fd, + flags, + @as(*const linux.itimerspec, @ptrCast(new_value)), + @as(?*linux.itimerspec, @ptrCast(old_value)), + ); + + return switch (posix.errno(res)) { + .SUCCESS => {}, + else => error.UnknownError, + }; + } + + /// timerfd_gettime + pub fn get(self: *const Timerfd) !Spec { + var out: Spec = undefined; + const res = linux.timerfd_gettime(self.fd, @as(*linux.itimerspec, @ptrCast(&out))); + return switch (posix.errno(res)) { + .SUCCESS => out, + else => error.UnknownError, + }; + } + + /// itimerspec + pub const Spec = extern struct { + interval: TimeSpec = .{}, + value: TimeSpec = .{}, + }; + + /// timespec + pub const TimeSpec = extern struct { + seconds: isize = 0, + nanoseconds: isize = 0, + }; +}; + +test Timerfd { + const testing = std.testing; + + var t = try Timerfd.init(.MONOTONIC, .{}); + defer t.deinit(); + + // Set + try t.set(.{}, &.{ .value = .{ .seconds = 60 } }, null); + try testing.expect((try t.get()).value.seconds > 0); + + // Disarm + var old: Timerfd.Spec = undefined; + try t.set(.{}, &.{ .value = .{ .seconds = 0 } }, &old); + try testing.expect(old.value.seconds > 0); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/loop.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/loop.zig new file mode 100644 index 0000000..5f80998 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/loop.zig @@ -0,0 +1,103 @@ +//! Common loop structures. The actual loop implementation is in backend-specific +//! files such as linux/io_uring.zig. + +const std = @import("std"); +const assert = std.debug.assert; +const xev = @import("main.zig"); + +/// Common options across backends. Not all options apply to all backends. +/// Read the doc comment for individual fields to learn what backends they +/// apply to. +pub const Options = struct { + /// The number of queued completions that can be in flight before + /// requiring interaction with the kernel. + /// + /// Backends: io_uring + entries: u32 = 256, + + /// A thread pool to use for blocking operations. If the backend doesn't + /// need to perform any blocking operations then no threads will ever + /// be spawned. If the backend does need to perform blocking operations + /// on a thread and no thread pool is provided, the operations will simply + /// fail. Unless you're trying to really optimize for space, it is + /// recommended you provide a thread pool. + /// + /// Backends: epoll, kqueue + thread_pool: ?*xev.ThreadPool = null, +}; + +/// The loop run mode -- all backends are required to support this in some way. +/// Backends may provide backend-specific APIs that behave slightly differently +/// or in a more configurable way. +pub const RunMode = enum(c_int) { + /// Run the event loop once. If there are no blocking operations ready, + /// return immediately. + no_wait = 0, + + /// Run the event loop once, waiting for at least one blocking operation + /// to complete. + once = 1, + + /// Run the event loop until it is "done". "Doneness" is defined as + /// there being no more completions that are active. + until_done = 2, +}; + +/// The callback of the main Loop operations. Higher level interfaces may +/// use a different callback mechanism. +pub fn Callback(comptime T: type) type { + return *const fn ( + userdata: ?*anyopaque, + loop: *T.Loop, + completion: *T.Completion, + result: T.Result, + ) CallbackAction; +} + +/// A callback that does nothing and immediately disarms. This +/// implements xev.Callback and is the default value for completions. +pub fn NoopCallback(comptime T: type) Callback(T) { + return (struct { + pub fn noopCallback( + _: ?*anyopaque, + _: *T.Loop, + _: *T.Completion, + _: T.Result, + ) CallbackAction { + return .disarm; + } + }).noopCallback; +} + +/// The result type for callbacks. This should be used by all loop +/// implementations and higher level abstractions in order to control +/// what to do after the loop completes. +pub const CallbackAction = enum(c_int) { + /// The request is complete and is not repeated. For example, a read + /// callback only fires once and is no longer watched for reads. You + /// can always free memory associated with the completion prior to + /// returning this. + disarm = 0, + + /// Requeue the same operation request with the same parameters + /// with the event loop. This makes it easy to repeat a read, timer, + /// etc. This rearms the request EXACTLY as-is. For example, the + /// low-level timer interface for io_uring uses an absolute timeout. + /// If you rearm the timer, it will fire immediately because the absolute + /// timeout will be in the past. + /// + /// The completion is reused so it is not safe to use the same completion + /// for anything else. + rearm = 1, +}; + +/// The state that a completion can be in. +pub const CompletionState = enum(c_int) { + /// The completion is not being used and is ready to be configured + /// for new work. + dead = 0, + + /// The completion is part of an event loop. This may be already waited + /// on or in the process of being registered. + active = 1, +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/main.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/main.zig new file mode 100644 index 0000000..39119a6 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/main.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +/// The low-level IO interfaces using the recommended compile-time +/// interface for the target system. We forward these as the default +/// API of this package. +const xev = Backend.default().Api(); +pub const dynamic = xev.dynamic; +pub const backend = xev.backend; +pub const available = xev.available; +pub const noopCallback = xev.noopCallback; +pub const Sys = xev.Sys; +pub const Loop = xev.Loop; +pub const Completion = xev.Completion; +pub const Result = xev.Result; +pub const ReadBuffer = xev.ReadBuffer; +pub const WriteBuffer = xev.WriteBuffer; +pub const Options = xev.Options; +pub const RunMode = xev.RunMode; +pub const Callback = xev.Callback; +pub const CallbackAction = xev.CallbackAction; +pub const CompletionState = xev.CompletionState; +pub const AcceptError = xev.AcceptError; +pub const CancelError = xev.CancelError; +pub const CloseError = xev.CloseError; +pub const ConnectError = xev.ConnectError; +pub const ShutdownError = xev.ShutdownError; +pub const WriteError = xev.WriteError; +pub const ReadError = xev.ReadError; +pub const PollError = xev.PollError; +pub const PollEvent = xev.PollEvent; +pub const WriteQueue = xev.WriteQueue; +pub const WriteRequest = xev.WriteRequest; +pub const Async = xev.Async; +pub const File = xev.File; +pub const Process = xev.Process; +pub const Stream = xev.Stream; +pub const Timer = xev.Timer; +pub const TCP = xev.TCP; +pub const UDP = xev.UDP; + +comptime { + // This ensures that all the public decls from the API are forwarded + // from the main struct. + const main = @This(); + const default = Backend.default().Api(); + for (@typeInfo(default).@"struct".decls) |decl| { + const Decl = @TypeOf(@field(default, decl.name)); + if (Decl == void) continue; + if (!@hasDecl(main, decl.name)) { + @compileError("missing decl: " ++ decl.name); + } + if (Decl != @TypeOf(@field(main, decl.name))) { + @compileError("decl has wrong type: " ++ decl.name); + } + } +} + +/// The dynamic interface that allows for runtime selection of the +/// backend to use. This is useful if you want to support multiple +/// backends and have a fallback mechanism. +/// +/// There is a very small overhead to using this API compared to the +/// static API, but it is generally negligible. +/// +/// The API for this isn't _exactly_ the same as the static API +/// since it requires initialization of the main struct to detect +/// a backend and then this needs to be passed to every high-level +/// type such as Async, File, etc. so that they can use the correct +/// backend that is coherent with the loop. +pub const Dynamic = DynamicXev(Backend.candidates()); +pub const DynamicXev = @import("dynamic.zig").Xev; + +/// System-specific interfaces. Note that they are always pub for +/// all systems but if you reference them and force them to be analyzed +/// the proper system APIs must exist. Due to Zig's lazy analysis, if you +/// don't use any interface it will NOT be compiled (yay!). +const Xev = @import("api.zig").Xev; +pub const IO_Uring = Xev(.io_uring, @import("backend/io_uring.zig")); +pub const Epoll = Xev(.epoll, @import("backend/epoll.zig")); +pub const Kqueue = Xev(.kqueue, @import("backend/kqueue.zig")); +pub const WasiPoll = Xev(.wasi_poll, @import("backend/wasi_poll.zig")); +pub const IOCP = Xev(.iocp, @import("backend/iocp.zig")); + +/// Generic thread pool implementation. +pub const ThreadPool = @import("ThreadPool.zig"); + +/// This stream (lowercase s) can be used as a namespace to access +/// Closeable, Writeable, Readable, etc. so that custom streams +/// can be constructed. +pub const stream = @import("watcher/stream.zig"); + +/// The backend types. +pub const Backend = @import("backend.zig").Backend; + +test { + // Tested on all platforms + _ = @import("heap.zig"); + _ = @import("queue.zig"); + _ = @import("queue_mpsc.zig"); + _ = ThreadPool; + _ = Dynamic; + + // Test the C API + if (builtin.os.tag != .wasi) _ = @import("c_api.zig"); + + // OS-specific tests + switch (builtin.os.tag) { + .linux => { + _ = Epoll; + _ = IO_Uring; + _ = @import("linux/timerfd.zig"); + }, + + .freebsd => { + _ = Kqueue; + }, + + .wasi => { + //_ = WasiPoll; + _ = @import("backend/wasi_poll.zig"); + }, + + .windows => { + _ = @import("backend/iocp.zig"); + }, + + else => {}, + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/posix.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/posix.zig new file mode 100644 index 0000000..81e3210 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/posix.zig @@ -0,0 +1,694 @@ +//! A lot of compatibility shims over the years of Zig updates +//! for removed functions from Zig 0.15, 0.16, etc. + +const std = @import("std"); +const builtin = @import("builtin"); +const posix = std.posix; +const system = posix.system; +const maxInt = std.math.maxInt; + +pub const net = struct { + pub const Address = extern union { + any: posix.sockaddr, + in: posix.sockaddr.in, + in6: posix.sockaddr.in6, + + pub fn parseIp4(text: []const u8, port: u16) !Address { + return initIp4((try std.Io.net.Ip4Address.parse(text, port)).bytes, port); + } + + pub fn parseIp6(text: []const u8, port: u16) !Address { + const ip6 = try std.Io.net.Ip6Address.parse(text, port); + var result: Address = undefined; + result.in6 = .{ + .port = std.mem.nativeToBig(u16, ip6.port), + .flowinfo = ip6.flow, + .addr = ip6.bytes, + .scope_id = ip6.interface.index, + }; + return result; + } + + pub fn initIp4(bytes: [4]u8, port: u16) Address { + var result: Address = undefined; + result.in = .{ + .port = std.mem.nativeToBig(u16, port), + .addr = @bitCast(bytes), + }; + return result; + } + + pub fn initPosix(addr: *const posix.sockaddr) Address { + var result: Address = undefined; + switch (addr.family) { + posix.AF.INET => result.in = (@as(*const posix.sockaddr.in, @ptrCast(@alignCast(addr)))).*, + posix.AF.INET6 => result.in6 = (@as(*const posix.sockaddr.in6, @ptrCast(@alignCast(addr)))).*, + else => result.any = addr.*, + } + return result; + } + + pub fn getOsSockLen(self: Address) posix.socklen_t { + return switch (self.any.family) { + posix.AF.INET => @sizeOf(posix.sockaddr.in), + posix.AF.INET6 => @sizeOf(posix.sockaddr.in6), + else => @sizeOf(posix.sockaddr), + }; + } + + pub fn toIpAddress(self: Address) std.Io.net.IpAddress { + return switch (self.any.family) { + posix.AF.INET => .{ .ip4 = .{ + .bytes = @bitCast(self.in.addr), + .port = std.mem.bigToNative(u16, self.in.port), + } }, + posix.AF.INET6 => .{ .ip6 = .{ + .bytes = self.in6.addr, + .port = std.mem.bigToNative(u16, self.in6.port), + .flow = self.in6.flowinfo, + .interface = .{ .index = self.in6.scope_id }, + } }, + else => unreachable, + }; + } + + pub fn fromIpAddress(addr: std.Io.net.IpAddress) Address { + return switch (addr) { + .ip4 => |ip4| initIp4(ip4.bytes, ip4.port), + .ip6 => |ip6| fromIp6Address(ip6), + }; + } + + pub fn fromIp6Address(ip6: std.Io.net.Ip6Address) Address { + var result: Address = undefined; + result.in6 = .{ + .port = std.mem.nativeToBig(u16, ip6.port), + .flowinfo = ip6.flow, + .addr = ip6.bytes, + .scope_id = ip6.interface.index, + }; + return result; + } + }; +}; + +pub const ReadError = error{ + AccessDenied, + InputOutput, + IsDir, + NotOpenForReading, + ProcessNotFound, + SocketNotConnected, + SystemResources, + WouldBlock, + ConnectionResetByPeer, + ConnectionTimedOut, +} || posix.UnexpectedError; + +pub const PReadError = ReadError || error{Unseekable}; + +pub const WriteError = error{ + AccessDenied, + BrokenPipe, + DeviceBusy, + DiskQuota, + FileTooBig, + InputOutput, + InvalidArgument, + MessageTooBig, + NoSpaceLeft, + NotOpenForWriting, + PermissionDenied, + ProcessNotFound, + SystemResources, + WouldBlock, + ConnectionResetByPeer, +} || posix.UnexpectedError; + +pub const PWriteError = WriteError || error{Unseekable}; + +pub const SendError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressNotAvailable, + BrokenPipe, + ConnectionRefused, + ConnectionResetByPeer, + FileNotFound, + MessageTooBig, + NameTooLong, + NetworkSubsystemFailed, + NetworkUnreachable, + NotDir, + SocketNotConnected, + SymLinkLoop, + SystemResources, + UnreachableAddress, + WouldBlock, +} || posix.UnexpectedError; + +pub const RecvFromError = error{ + ConnectionRefused, + ConnectionResetByPeer, + ConnectionTimedOut, + MessageTooBig, + NetworkSubsystemFailed, + SocketNotBound, + SocketNotConnected, + SystemResources, + WouldBlock, +} || posix.UnexpectedError; + +pub const SocketError = error{ + AccessDenied, + AddressFamilyNotSupported, + ProcessFdQuotaExceeded, + ProtocolNotSupported, + SocketTypeNotSupported, + SystemFdQuotaExceeded, + SystemResources, +} || posix.UnexpectedError; + +pub const BindError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + AlreadyBound, + FileNotFound, + NameTooLong, + NotDir, + ReadOnlyFileSystem, + SymLinkLoop, + SystemResources, +} || posix.UnexpectedError; + +pub const ListenError = error{ + AddressInUse, + FileDescriptorNotASocket, + OperationNotSupported, + SocketNotBound, + SystemResources, +} || posix.UnexpectedError; + +pub const PipeError = error{ + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, +} || posix.UnexpectedError; + +pub const AcceptError = std.Io.net.Server.AcceptError; + +pub const GetSockNameError = error{ + FileDescriptorNotASocket, + SocketNotBound, + SystemResources, +} || posix.UnexpectedError; + +pub const ConnectError = error{ + AccessDenied, + AddressFamilyNotSupported, + AddressInUse, + AddressNotAvailable, + ConnectionPending, + ConnectionRefused, + ConnectionResetByPeer, + ConnectionTimedOut, + FileNotFound, + NetworkUnreachable, + PermissionDenied, + SystemResources, + WouldBlock, +} || posix.UnexpectedError; + +pub fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) ConnectError!void { + while (true) { + switch (posix.errno(system.connect(sock, sock_addr, len))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .AGAIN, .INPROGRESS => return error.WouldBlock, + .ALREADY => return error.ConnectionPending, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .TIMEDOUT => return error.ConnectionTimedOut, + .NOENT => return error.FileNotFound, + .BADF, .FAULT, .ISCONN, .NOTSOCK, .PROTOTYPE, .CONNABORTED => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn getsockoptError(sockfd: posix.fd_t) ConnectError!void { + const E = std.posix.E; + const SOL = if (@hasDecl(std.posix, "SOL")) std.posix.SOL else std.os.linux.SOL; + const SO = if (@hasDecl(std.posix, "SO")) std.posix.SO else std.os.linux.SO; + var err_code: i32 = undefined; + var size: u32 = @sizeOf(u32); + const rc = system.getsockopt(sockfd, SOL.SOCKET, SO.ERROR, @ptrCast(&err_code), &size); + std.debug.assert(size == 4); + switch (posix.errno(rc)) { + .SUCCESS => switch (@as(E, @enumFromInt(err_code))) { + .SUCCESS => return, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ADDRINUSE => return error.AddressInUse, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .AGAIN => return error.SystemResources, + .ALREADY => return error.ConnectionPending, + .CONNREFUSED => return error.ConnectionRefused, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .TIMEDOUT => return error.ConnectionTimedOut, + .CONNRESET => return error.ConnectionResetByPeer, + .BADF, .FAULT, .ISCONN, .NOTSOCK, .PROTOTYPE => unreachable, + else => |err| return posix.unexpectedErrno(err), + }, + .BADF, .FAULT, .INVAL => unreachable, + .NOPROTOOPT, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn dup(old_fd: posix.fd_t) !posix.fd_t { + const rc = system.dup(old_fd); + return switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .MFILE => error.ProcessFdQuotaExceeded, + .BADF => unreachable, + else => |err| return posix.unexpectedErrno(err), + }; +} + +pub fn close(fd: posix.fd_t) void { + switch (posix.errno(system.close(fd))) { + .SUCCESS, .INTR => {}, + .BADF => unreachable, + else => unreachable, + } +} + +pub fn setCloexec(fd: posix.fd_t) !void { + while (true) switch (posix.errno(system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { + .SUCCESS => return, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + }; +} + +fn getStatusFlags(fd: posix.fd_t) !u32 { + while (true) { + const rc = system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn setStatusFlags(fd: posix.fd_t, flags: u32) !void { + while (true) switch (posix.errno(system.fcntl(fd, posix.F.SETFL, flags))) { + .SUCCESS => return, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + }; +} + +fn setSockFlags(sock: posix.socket_t, flags: u32) !void { + if ((flags & posix.SOCK.CLOEXEC) != 0) try setCloexec(sock); + if ((flags & posix.SOCK.NONBLOCK) != 0) { + const current = try getStatusFlags(sock); + try setStatusFlags(sock, current | @as(u32, @bitCast(posix.O{ .NONBLOCK = true }))); + } +} + +pub fn accept( + sock: posix.socket_t, + addr: *posix.sockaddr, + addr_size: *posix.socklen_t, + flags: u32, +) AcceptError!posix.socket_t { + while (true) { + const rc = system.accept(sock, addr, addr_size); + switch (posix.errno(rc)) { + .SUCCESS => { + const fd: posix.socket_t = @intCast(rc); + if (flags & posix.SOCK.CLOEXEC != 0) try setCloexec(fd); + return fd; + }, + .INTR => continue, + .AGAIN => return error.WouldBlock, + .CONNABORTED => return error.ConnectionAborted, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS, .NOMEM => return error.SystemResources, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!posix.socket_t { + const have_sock_flags = !builtin.target.os.tag.isDarwin() and builtin.target.os.tag != .haiku; + const filtered_sock_type = if (have_sock_flags) + socket_type + else + socket_type & ~@as(u32, posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC); + + const rc = system.socket(domain, filtered_sock_type, protocol); + switch (posix.errno(rc)) { + .SUCCESS => { + const fd: posix.socket_t = @intCast(rc); + errdefer close(fd); + if (!have_sock_flags) try setSockFlags(fd, socket_type); + return fd; + }, + .ACCES => return error.AccessDenied, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .INVAL => return error.ProtocolNotSupported, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS, .NOMEM => return error.SystemResources, + .PROTONOSUPPORT => return error.ProtocolNotSupported, + .PROTOTYPE => return error.SocketTypeNotSupported, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn bind(sock: posix.socket_t, addr: *const posix.sockaddr, len: posix.socklen_t) BindError!void { + switch (posix.errno(system.bind(sock, addr, len))) { + .SUCCESS => return, + .ACCES, .PERM => return error.AccessDenied, + .ADDRINUSE => return error.AddressInUse, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .INVAL => return error.AlreadyBound, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .BADF, .FAULT, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn listen(sock: posix.socket_t, backlog: u31) ListenError!void { + switch (posix.errno(system.listen(sock, backlog))) { + .SUCCESS => return, + .ADDRINUSE => return error.AddressInUse, + .INVAL => return error.SocketNotBound, + .MFILE, .NFILE, .NOBUFS, .NOMEM => return error.SystemResources, + .NOTSOCK => return error.FileDescriptorNotASocket, + .OPNOTSUPP => return error.OperationNotSupported, + .BADF => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn getsockname(sock: posix.socket_t, addr: *posix.sockaddr, addrlen: *posix.socklen_t) GetSockNameError!void { + switch (posix.errno(system.getsockname(sock, addr, addrlen))) { + .SUCCESS => return, + .NOTSOCK => return error.FileDescriptorNotASocket, + .NOBUFS => return error.SystemResources, + .BADF, .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } +} + +pub fn write(fd: posix.fd_t, bytes: []const u8) WriteError!usize { + if (bytes.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + while (true) { + const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => return error.InvalidArgument, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .FAULT, .DESTADDRREQ => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn read(fd: posix.fd_t, buf: []u8) ReadError!usize { + if (buf.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + while (true) { + const rc = system.read(fd, buf.ptr, @min(buf.len, max_count)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS, .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pwrite(fd: posix.fd_t, bytes: []const u8, offset: u64) PWriteError!usize { + if (bytes.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + const pwrite_fn = if (builtin.target.os.tag == .linux and !builtin.target.abi.isMusl() and @hasDecl(system, "pwrite64")) system.pwrite64 else system.pwrite; + while (true) { + const rc = pwrite_fn(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .INVAL => return error.InvalidArgument, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForWriting, + .DQUOT => return error.DiskQuota, + .FBIG => return error.FileTooBig, + .IO => return error.InputOutput, + .NOSPC => return error.NoSpaceLeft, + .PERM => return error.PermissionDenied, + .PIPE => return error.BrokenPipe, + .BUSY => return error.DeviceBusy, + .NXIO, .SPIPE, .OVERFLOW => return error.Unseekable, + .NOBUFS, .NOMEM => return error.SystemResources, + .FAULT, .DESTADDRREQ => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pread(fd: posix.fd_t, buf: []u8, offset: u64) PReadError!usize { + if (buf.len == 0) return 0; + + const max_count = switch (builtin.os.tag) { + .linux => 0x7ffff000, + .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), + else => maxInt(isize), + }; + + const pread_fn = if (builtin.target.os.tag == .linux and !builtin.target.abi.isMusl() and @hasDecl(system, "pread64")) system.pread64 else system.pread; + while (true) { + const rc = pread_fn(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => return error.NotOpenForReading, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS, .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketNotConnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .NXIO, .SPIPE, .OVERFLOW => return error.Unseekable, + .FAULT, .INVAL => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) SendError!usize { + return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) { + error.AddressFamilyNotSupported, + error.AddressNotAvailable, + error.FileNotFound, + error.NameTooLong, + error.NotDir, + error.SymLinkLoop, + error.UnreachableAddress, + => unreachable, + else => |e| return e, + }; +} + +pub fn sendto( + sockfd: posix.socket_t, + buf: []const u8, + flags: u32, + dest_addr: ?*const posix.sockaddr, + addrlen: posix.socklen_t, +) SendError!usize { + while (true) { + const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .AGAIN => return error.WouldBlock, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .INVAL => return error.UnreachableAddress, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .PIPE => return error.BrokenPipe, + .AFNOSUPPORT => return error.AddressFamilyNotSupported, + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .HOSTUNREACH, .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + .ADDRNOTAVAIL => return error.AddressNotAvailable, + .BADF, .DESTADDRREQ, .FAULT, .ISCONN, .NOTSOCK, .OPNOTSUPP => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub const SendMsgError = SendError; + +pub fn sendmsg(sockfd: posix.socket_t, msg: *const std.os.linux.msghdr_const, flags: u32) SendError!usize { + while (true) { + const rc = system.sendmsg(sockfd, @ptrCast(msg), flags); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .ACCES => return error.AccessDenied, + .AGAIN => return error.WouldBlock, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .INTR => continue, + .MSGSIZE => return error.MessageTooBig, + .NOBUFS, .NOMEM => return error.SystemResources, + .PIPE => return error.BrokenPipe, + .NOTCONN => return error.SocketNotConnected, + .NETDOWN => return error.NetworkSubsystemFailed, + .NETUNREACH, .HOSTUNREACH => return error.NetworkUnreachable, + .BADF, .DESTADDRREQ, .FAULT, .INVAL, .ISCONN, .NOTSOCK, .OPNOTSUPP => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn recv(sockfd: posix.socket_t, buf: []u8, flags: u32) RecvFromError!usize { + return recvfrom(sockfd, buf, flags, null, null); +} + +pub fn recvfrom( + sockfd: posix.socket_t, + buf: []u8, + flags: u32, + src_addr: ?*posix.sockaddr, + addrlen: ?*posix.socklen_t, +) RecvFromError!usize { + while (true) { + const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .NOTCONN => return error.SocketNotConnected, + .INTR => continue, + .AGAIN => return error.WouldBlock, + .NOMEM => return error.SystemResources, + .CONNREFUSED => return error.ConnectionRefused, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.ConnectionTimedOut, + .BADF, .FAULT, .INVAL, .NOTSOCK => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { + if (!builtin.target.os.tag.isDarwin() and @hasDecl(system, "pipe2")) { + var fds: [2]posix.fd_t = undefined; + switch (posix.errno(system.pipe2(&fds, flags))) { + .SUCCESS => return fds, + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + .INVAL, .FAULT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + } + + var fds: [2]posix.fd_t = undefined; + switch (posix.errno(system.pipe(&fds))) { + .SUCCESS => {}, + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + .INVAL, .FAULT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } + errdefer { + close(fds[0]); + close(fds[1]); + } + + if (flags.CLOEXEC) { + try setCloexec(fds[0]); + try setCloexec(fds[1]); + } + + var status_flags = flags; + status_flags.CLOEXEC = false; + const status_flags_int = @as(u32, @bitCast(status_flags)); + if (status_flags_int != 0) { + try setStatusFlags(fds[0], status_flags_int); + try setStatusFlags(fds[1], status_flags_int); + } + + return fds; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue.zig new file mode 100644 index 0000000..7cafda3 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const assert = std.debug.assert; + +/// An intrusive queue implementation. The type T must have a field +/// "next" of type `?*T`. +/// +/// For those unaware, an intrusive variant of a data structure is one in which +/// the data type in the list has the pointer to the next element, rather +/// than a higher level "node" or "container" type. The primary benefit +/// of this (and the reason we implement this) is that it defers all memory +/// management to the caller: the data structure implementation doesn't need +/// to allocate "nodes" to contain each element. Instead, the caller provides +/// the element and how its allocated is up to them. +pub fn Intrusive(comptime T: type) type { + return struct { + const Self = @This(); + + /// Head is the front of the queue and tail is the back of the queue. + head: ?*T = null, + tail: ?*T = null, + + /// Enqueue a new element to the back of the queue. + pub fn push(self: *Self, v: *T) void { + assert(v.next == null); + + if (self.tail) |tail| { + // If we have elements in the queue, then we add a new tail. + tail.next = v; + self.tail = v; + } else { + // No elements in the queue we setup the initial state. + self.head = v; + self.tail = v; + } + } + + /// Dequeue the next element from the queue. + pub fn pop(self: *Self) ?*T { + // The next element is in "head". + const next = self.head orelse return null; + + // If the head and tail are equal this is the last element + // so we also set tail to null so we can now be empty. + if (self.head == self.tail) self.tail = null; + + // Head is whatever is next (if we're the last element, + // this will be null); + self.head = next.next; + + // We set the "next" field to null so that this element + // can be inserted again. + next.next = null; + return next; + } + + /// Returns true if the queue is empty. + pub fn empty(self: *const Self) bool { + return self.head == null; + } + }; +} + +test Intrusive { + const testing = std.testing; + + // Types + const Elem = struct { + const Self = @This(); + next: ?*Self = null, + }; + const Queue = Intrusive(Elem); + var q: Queue = .{}; + try testing.expect(q.empty()); + + // Elems + var elems: [10]Elem = .{Elem{}} ** 10; + + // One + try testing.expect(q.pop() == null); + q.push(&elems[0]); + try testing.expect(!q.empty()); + try testing.expect(q.pop().? == &elems[0]); + try testing.expect(q.pop() == null); + try testing.expect(q.empty()); + + // Two + try testing.expect(q.pop() == null); + q.push(&elems[0]); + q.push(&elems[1]); + try testing.expect(q.pop().? == &elems[0]); + try testing.expect(q.pop().? == &elems[1]); + try testing.expect(q.pop() == null); + + // Interleaved + try testing.expect(q.pop() == null); + q.push(&elems[0]); + try testing.expect(q.pop().? == &elems[0]); + q.push(&elems[1]); + try testing.expect(q.pop().? == &elems[1]); + try testing.expect(q.pop() == null); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue_mpsc.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue_mpsc.zig new file mode 100644 index 0000000..3a9f4cb --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue_mpsc.zig @@ -0,0 +1,116 @@ +const std = @import("std"); +const assert = std.debug.assert; + +/// An intrusive MPSC (multi-provider, single consumer) queue implementation. +/// The type T must have a field "next" of type `?*T`. +/// +/// This is an implementatin of a Vyukov Queue[1]. +/// TODO(mitchellh): I haven't audited yet if I got all the atomic operations +/// correct. I was short term more focused on getting something that seemed +/// to work; I need to make sure it actually works. +/// +/// For those unaware, an intrusive variant of a data structure is one in which +/// the data type in the list has the pointer to the next element, rather +/// than a higher level "node" or "container" type. The primary benefit +/// of this (and the reason we implement this) is that it defers all memory +/// management to the caller: the data structure implementation doesn't need +/// to allocate "nodes" to contain each element. Instead, the caller provides +/// the element and how its allocated is up to them. +/// +/// [1]: https://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue +pub fn Intrusive(comptime T: type) type { + return struct { + const Self = @This(); + + /// Head is the front of the queue and tail is the back of the queue. + head: *T, + tail: *T, + stub: T, + + /// Initialize the queue. This requires a stable pointer to itself. + /// This must be called before the queue is used concurrently. + pub fn init(self: *Self) void { + self.head = &self.stub; + self.tail = &self.stub; + self.stub.next = null; + } + + /// Push an item onto the queue. This can be called by any number + /// of producers. + pub fn push(self: *Self, v: *T) void { + @atomicStore(?*T, &v.next, null, .unordered); + const prev = @atomicRmw(*T, &self.head, .Xchg, v, .acq_rel); + @atomicStore(?*T, &prev.next, v, .release); + } + + /// Pop the first in element from the queue. This must be called + /// by only a single consumer at any given time. + pub fn pop(self: *Self) ?*T { + var tail = @atomicLoad(*T, &self.tail, .unordered); + var next_ = @atomicLoad(?*T, &tail.next, .acquire); + if (tail == &self.stub) { + const next = next_ orelse return null; + @atomicStore(*T, &self.tail, next, .unordered); + tail = next; + next_ = @atomicLoad(?*T, &tail.next, .acquire); + } + + if (next_) |next| { + @atomicStore(*T, &self.tail, next, .release); + tail.next = null; + return tail; + } + + const head = @atomicLoad(*T, &self.head, .unordered); + if (tail != head) return null; + self.push(&self.stub); + + next_ = @atomicLoad(?*T, &tail.next, .acquire); + if (next_) |next| { + @atomicStore(*T, &self.tail, next, .unordered); + tail.next = null; + return tail; + } + + return null; + } + }; +} + +test Intrusive { + const testing = std.testing; + + // Types + const Elem = struct { + const Self = @This(); + next: ?*Self = null, + }; + const Queue = Intrusive(Elem); + var q: Queue = undefined; + q.init(); + + // Elems + var elems: [10]Elem = .{Elem{}} ** 10; + + // One + try testing.expect(q.pop() == null); + q.push(&elems[0]); + try testing.expect(q.pop().? == &elems[0]); + try testing.expect(q.pop() == null); + + // Two + try testing.expect(q.pop() == null); + q.push(&elems[0]); + q.push(&elems[1]); + try testing.expect(q.pop().? == &elems[0]); + try testing.expect(q.pop().? == &elems[1]); + try testing.expect(q.pop() == null); + + // // Interleaved + try testing.expect(q.pop() == null); + q.push(&elems[0]); + try testing.expect(q.pop().? == &elems[0]); + q.push(&elems[1]); + try testing.expect(q.pop().? == &elems[1]); + try testing.expect(q.pop() == null); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/async.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/async.zig new file mode 100644 index 0000000..be27d45 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/async.zig @@ -0,0 +1,833 @@ +/// "Wake up" an event loop from any thread using an async completion. +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const posix = std.posix; +const common = @import("common.zig"); +const darwin = @import("../darwin.zig"); +const xev_posix = @import("../posix.zig"); + +pub fn Async(comptime xev: type) type { + if (xev.dynamic) return AsyncDynamic(xev); + + return switch (xev.backend) { + // Supported, uses eventfd + .io_uring, + .epoll, + => AsyncEventFd(xev), + + // Supported, uses the backend API + .wasi_poll => AsyncLoopState(xev, xev.Loop.threaded), + + // Supported, uses mach port on Darwin and eventfd on BSD. + .kqueue => if (comptime builtin.target.os.tag.isDarwin()) + AsyncMachPort(xev) + else + AsyncEventFd(xev), + + .iocp => AsyncIOCP(xev), + }; +} + +/// Async implementation using eventfd (Unix/Linux). +fn AsyncEventFd(comptime xev: type) type { + return struct { + const Self = @This(); + + /// The error that can come in the wait callback. + pub const WaitError = xev.ReadError; + + /// eventfd file descriptor + fd: posix.fd_t, + + /// This is only used for FreeBSD currently. + extern "c" fn eventfd(initval: c_uint, flags: c_uint) c_int; + + /// Create a new async. An async can be assigned to exactly one loop + /// to be woken up. The completion must be allocated in advance. + pub fn init() !Self { + return .{ + .fd = switch (builtin.os.tag) { + // std.posix is unavailable on FreeBSD. We call the + // syscall directly. + // + // TODO: error handling + .freebsd => eventfd( + 0, + 0x100000 | 0x4, // EFD_CLOEXEC | EFD_NONBLOCK + ), + + // Use the raw linux syscall. + else => blk: { + const rc = std.os.linux.eventfd(0, std.os.linux.EFD.CLOEXEC | std.os.linux.EFD.NONBLOCK); + break :blk switch (std.posix.errno(rc)) { + .SUCCESS => @as(std.posix.fd_t, @intCast(rc)), + else => |err| return std.posix.unexpectedErrno(err), + }; + }, + }, + }; + } + + /// Clean up the async. This will forcibly deinitialize any resources + /// and may result in erroneous wait callbacks to be fired. + pub fn deinit(self: *Self) void { + xev_posix.close(self.fd); + } + + /// Wait for a message on this async. Note that async messages may be + /// coalesced (or they may not be) so you should not expect a 1:1 mapping + /// between send and wait. + /// + /// Just like the rest of libxev, the wait must be re-queued if you want + /// to continue to be notified of async events. + /// + /// You should NOT register an async with multiple loops (the same loop + /// is fine -- but unnecessary). The behavior when waiting on multiple + /// loops is undefined. + pub const wait = switch (xev.backend) { + .io_uring, .epoll => waitPoll, + .kqueue => waitRead, + .iocp, .wasi_poll => @compileError("AsyncEventFd does not support wait for this backend"), + }; + + fn waitRead( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .read = .{ + .fd = self.fd, + .buffer = .{ .array = undefined }, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.read) |v| assert(v > 0) else |err| err, + }); + } + }).callback, + }; + loop.add(c); + } + + fn waitPoll( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + // We use a poll operation instead of a read operation + // because in Kernel 6.15.4, read was regressed for + // io_uring on eventfd/timerfd and would block forever. + // However, poll works fine. + .poll = .{ + .fd = self.fd, + .events = posix.POLL.IN, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + if (r.poll) |_| { + // We need to read so that we can consume the + // eventfd value. We only read 8 bytes because + // we only write up to 8 bytes and we own the fd. + // We ignore errors here because we expect the + // read to succeed given we just polled it. + var buf: [8]u8 = undefined; + _ = posix.read(c_inner.op.poll.fd, &buf) catch {}; + } else |_| { + // We'll call the callback with the error later. + } + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.poll) |_| {} else |err| err, + }); + } + }).callback, + }; + loop.add(c); + } + + /// Notify a loop to wake up synchronously. This should never block forever + /// (it will always EVENTUALLY succeed regardless of if the loop is currently + /// ticking or not). + /// + /// The "c" value is the completion associated with the "wait". + /// + /// Internal details subject to change but if you're relying on these + /// details then you may want to consider using a lower level interface + /// using the loop directly: + /// + /// - linux+io_uring: eventfd is used. If the eventfd write would block + /// (EAGAIN) then we assume success because the eventfd is full. + /// + pub fn notify(self: Self) !void { + // We want to just write "1" in the correct byte order as our host. + const val = @as([8]u8, @bitCast(@as(u64, 1))); + _ = xev_posix.write(self.fd, &val) catch |err| switch (err) { + error.WouldBlock => return, + else => return err, + }; + } + + test { + _ = AsyncTests(xev, Self); + } + }; +} + +/// Async implementation using mach ports (Darwin). +/// +/// This allocates a mach port per async request and sends to that mach +/// port to wake up the loop and trigger the completion. +fn AsyncMachPort(comptime xev: type) type { + return struct { + const Self = @This(); + + /// The error that can come in the wait callback. + pub const WaitError = xev.Sys.MachPortError; + + /// Missing Mach APIs from Zig stdlib. Data from xnu: osfmk/mach/port.h + const mach_port_flavor_t = c_int; + const mach_port_limits = extern struct { mpl_qlimit: c_uint }; + const MACH_PORT_LIMITS_INFO = 1; + extern "c" fn mach_port_set_attributes( + task: posix.system.ipc_space_t, + name: posix.system.mach_port_name_t, + flavor: mach_port_flavor_t, + info: *anyopaque, + count: darwin.mach_msg_type_number_t, + ) posix.system.kern_return_t; + extern "c" fn mach_port_destroy( + task: posix.system.ipc_space_t, + name: posix.system.mach_port_name_t, + ) posix.system.kern_return_t; + + /// The mach port + port: posix.system.mach_port_name_t, + + /// Create a new async. An async can be assigned to exactly one loop + /// to be woken up. The completion must be allocated in advance. + pub fn init() !Self { + const mach_self = posix.system.mach_task_self(); + + // Allocate the port + var mach_port: posix.system.mach_port_name_t = undefined; + switch (darwin.getKernError(posix.system.mach_port_allocate( + mach_self, + posix.system.MACH.PORT.RIGHT.RECEIVE, + &mach_port, + ))) { + .SUCCESS => {}, // Success + else => return error.MachPortAllocFailed, + } + errdefer _ = mach_port_destroy(mach_self, mach_port); + + // Insert a send right into the port since we also use this to send + switch (darwin.getKernError(posix.system.mach_port_insert_right( + mach_self, + mach_port, + mach_port, + posix.system.MACH.MSG.TYPE.MAKE_SEND, + ))) { + .SUCCESS => {}, // Success + else => return error.MachPortAllocFailed, + } + + // Modify the port queue size to be 1 because we are only + // using it for notifications and not for any other purpose. + var limits: mach_port_limits = .{ .mpl_qlimit = 1 }; + switch (darwin.getKernError(mach_port_set_attributes( + mach_self, + mach_port, + MACH_PORT_LIMITS_INFO, + &limits, + @sizeOf(@TypeOf(limits)), + ))) { + .SUCCESS => {}, // Success + else => return error.MachPortAllocFailed, + } + + return .{ + .port = mach_port, + }; + } + + /// Clean up the async. This will forcibly deinitialize any resources + /// and may result in erroneous wait callbacks to be fired. + pub fn deinit(self: *Self) void { + _ = mach_port_destroy( + posix.system.mach_task_self(), + self.port, + ); + } + + /// Wait for a message on this async. Note that async messages may be + /// coalesced (or they may not be) so you should not expect a 1:1 mapping + /// between send and wait. + /// + /// Just like the rest of libxev, the wait must be re-queued if you want + /// to continue to be notified of async events. + /// + /// You should NOT register an async with multiple loops (the same loop + /// is fine -- but unnecessary). The behavior when waiting on multiple + /// loops is undefined. + pub fn wait( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .machport = .{ + .port = self.port, + .buffer = .{ .array = undefined }, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + // Drain the mach port so that we only fire one + // notification even if many are queued. + drain(c_inner.op.machport.port); + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.machport) |_| {} else |err| err, + }); + } + }).callback, + }; + + loop.add(c); + } + + /// Drain the given mach port. All message bodies are discarded. + fn drain(port: posix.system.mach_port_name_t) void { + var message: struct { + header: darwin.mach_msg_header_t, + } = undefined; + + while (true) { + switch (darwin.getMachMsgError(darwin.mach_msg( + &message.header, + darwin.MACH_RCV_MSG | darwin.MACH_RCV_TIMEOUT, + 0, + @sizeOf(@TypeOf(message)), + port, + darwin.MACH_MSG_TIMEOUT_NONE, + darwin.MACH_PORT_NULL, + ))) { + // This means a read would've blocked, so we drained. + .RCV_TIMED_OUT => return, + + // We dequeued, so we want to loop again. + .SUCCESS => {}, + + // We dequeued but the message had a body. We ignore + // message bodies for async so we are happy to discard + // it and continue. + .RCV_TOO_LARGE => {}, + + else => |err| { + std.log.warn("mach msg drain err, may duplicate async wakeups err={}", .{err}); + return; + }, + } + } + } + + /// Notify a loop to wake up synchronously. This should never block forever + /// (it will always EVENTUALLY succeed regardless of if the loop is currently + /// ticking or not). + pub fn notify(self: Self) !void { + // This constructs an empty mach message. It has no data. + var msg: darwin.mach_msg_header_t = .{ + // We use COPY_SEND which will not increment any send ref + // counts because it'll reuse the existing send right. + .msgh_bits = @intFromEnum(posix.system.MACH.MSG.TYPE.COPY_SEND), + .msgh_size = @sizeOf(darwin.mach_msg_header_t), + .msgh_remote_port = self.port, + .msgh_local_port = darwin.MACH_PORT_NULL, + .msgh_voucher_port = undefined, + .msgh_id = undefined, + }; + + return switch (darwin.getMachMsgError( + darwin.mach_msg( + &msg, + darwin.MACH_SEND_MSG | darwin.MACH_SEND_TIMEOUT, + msg.msgh_size, + 0, + darwin.MACH_PORT_NULL, + 0, // Fail instantly if the port is full + darwin.MACH_PORT_NULL, + ), + )) { + .SUCCESS => {}, + else => |e| { + std.log.warn("mach msg err={}", .{e}); + return error.MachMsgFailed; + }, + + // This is okay because it means that there was no more buffer + // space meaning that the port will wake up. + .SEND_NO_BUFFER => {}, + + // This means that the send would've blocked because the + // queue is full. We assume success because the port is full. + .SEND_TIMED_OUT => {}, + }; + } + + test { + _ = AsyncTests(xev, Self); + } + }; +} + +/// Async implementation that is deferred to the backend implementation +/// loop state. This is kind of a hacky implementation and not recommended +/// but its the only way currently to get asyncs to work on WASI. +fn AsyncLoopState(comptime xev: type, comptime threaded: bool) type { + // TODO: we don't support threaded loop state async. We _can_ it just + // isn't done yet. To support it we need to have some sort of mutex + // to guard waiter below. + if (threaded) return struct {}; + + return struct { + const Self = @This(); + + wakeup: bool = false, + waiter: ?struct { + loop: *xev.Loop, + c: *xev.Completion, + } = null, + + /// The error that can come in the wait callback. + pub const WaitError = xev.Sys.AsyncError; + + pub fn init() !Self { + return .{}; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn wait( + self: *Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .async_wait = .{}, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.async_wait) |_| {} else |err| err, + }); + } + }).callback, + }; + loop.add(c); + + self.waiter = .{ + .loop = loop, + .c = c, + }; + + if (self.wakeup) self.notify() catch {}; + } + + pub fn notify(self: *Self) !void { + if (self.waiter) |w| + w.loop.async_notify(w.c) + else + self.wakeup = true; + } + + test { + _ = AsyncTests(xev, Self); + } + }; +} + +/// Async implementation for IOCP. +fn AsyncIOCP(comptime xev: type) type { + return struct { + const Self = @This(); + const windows = std.os.windows; + + pub const WaitError = xev.Sys.AsyncError; + + guard: std.Io.Mutex = .init, + wakeup: bool = false, + waiter: ?struct { + loop: *xev.Loop, + c: *xev.Completion, + } = null, + + pub fn init() !Self { + return Self{}; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn wait( + self: *Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ .async_wait = .{} }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.async_wait) |_| {} else |err| err, + }); + } + }).callback, + }; + loop.add(c); + + const io = std.Io.Threaded.global_single_threaded.io(); + self.guard.lockUncancelable(io); + defer self.guard.unlock(io); + + self.waiter = .{ + .loop = loop, + .c = c, + }; + + if (self.wakeup) loop.async_notify(c); + } + + pub fn notify(self: *Self) !void { + const io = std.Io.Threaded.global_single_threaded.io(); + self.guard.lockUncancelable(io); + defer self.guard.unlock(io); + + if (self.waiter) |w| { + w.loop.async_notify(w.c); + } else { + self.wakeup = true; + } + } + + test { + _ = AsyncTests(xev, Self); + } + }; +} + +fn AsyncDynamic(comptime xev: type) type { + return struct { + const Self = @This(); + + backend: Union, + + pub const Union = xev.Union(&.{"Async"}); + pub const WaitError = xev.ErrorSet(&.{ "Async", "WaitError" }); + + pub fn init() !Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.Async.init(), + ); + }, + } }; + } + + pub fn deinit(self: *Self) void { + switch (xev.backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).deinit(), + } + } + + pub fn notify(self: *Self) !void { + switch (xev.backend) { + inline else => |tag| try @field( + self.backend, + @tagName(tag), + ).notify(), + } + } + + pub fn wait( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.Async.WaitError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).wait( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = AsyncTests(xev, Self); + } + }; +} + +fn AsyncTests(comptime xev: type, comptime Impl: type) type { + return struct { + test "async" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var notifier = try Impl.init(); + defer notifier.deinit(); + + // Wait + var wake: bool = false; + var c_wait: xev.Completion = .{}; + notifier.wait(&loop, &c_wait, bool, &wake, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .rearm; + } + }).callback); + + // Send a notification + try notifier.notify(); + + // Wait for wake + try loop.run(.once); + try testing.expect(wake); + + // Make sure it only triggers once + wake = false; + try loop.run(.no_wait); + try testing.expect(!wake); + } + + test "async: notify first" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var notifier = try Impl.init(); + defer notifier.deinit(); + + // Send a notification + try notifier.notify(); + + // Wait + var wake: bool = false; + var c_wait: xev.Completion = .{}; + notifier.wait(&loop, &c_wait, bool, &wake, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + // Wait for wake + try loop.run(.until_done); + try testing.expect(wake); + } + + test "async batches multiple notifications" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var notifier = try Impl.init(); + defer notifier.deinit(); + + // Send a notification many times + try notifier.notify(); + try notifier.notify(); + try notifier.notify(); + try notifier.notify(); + try notifier.notify(); + + // Wait + var count: u32 = 0; + var c_wait: xev.Completion = .{}; + notifier.wait(&loop, &c_wait, u32, &count, (struct { + fn callback( + ud: ?*u32, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* += 1; + return .rearm; + } + }).callback); + + // Send a notification + try notifier.notify(); + + // Wait for wake + try loop.run(.once); + for (0..10) |_| try loop.run(.no_wait); + try testing.expectEqual(@as(u32, 1), count); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/common.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/common.zig new file mode 100644 index 0000000..bb122b9 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/common.zig @@ -0,0 +1,7 @@ +/// Convert the callback value with an opaque pointer into the userdata type +/// that we can pass to our higher level callback types. +pub fn userdataValue(comptime Userdata: type, v: ?*anyopaque) ?*Userdata { + // Void userdata is always a null pointer. + if (Userdata == void) return null; + return @ptrCast(@alignCast(v)); +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/file.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/file.zig new file mode 100644 index 0000000..3ccbcb0 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/file.zig @@ -0,0 +1,980 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const common = @import("common.zig"); +const assert = std.debug.assert; +const posix = std.posix; +const main = @import("../main.zig"); +const stream = @import("stream.zig"); +const xev_posix = @import("../posix.zig"); + +/// File operations. +/// +/// These operations typically run on the event loop thread pool, rather +/// than the core async OS APIs, because most core async OS APIs don't support +/// async operations on regular files (with many caveats attached to that +/// statement). This high-level abstraction will attempt to make the right +/// decision about what to do but this should generally be used by +/// operations that need to run on a thread pool. For operations that you're +/// sure are better supported by core async OS APIs (such as sockets, pipes, +/// TTYs, etc.), use a specific high-level abstraction like xev.TCP or +/// the generic xev.Stream. +/// +/// This is a "higher-level abstraction" in libxev. The goal of higher-level +/// abstractions in libxev are to make it easier to use specific functionality +/// with the event loop, but does not promise perfect flexibility or optimal +/// performance. In almost all cases, the abstraction is good enough. But, +/// if you have specific needs or want to push for the most optimal performance, +/// use the platform-specific Loop directly. +pub fn File(comptime xev: type) type { + if (xev.dynamic) return FileDynamic(xev); + return FileStream(xev); +} + +/// An implementation of File that uses the stream abstractions. +fn FileStream(comptime xev: type) type { + return struct { + const Self = @This(); + const FdType = if (xev.backend == .iocp) std.os.windows.HANDLE else posix.socket_t; + + /// The underlying file + fd: FdType, + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .read, + .write = .write, + .threadpool = true, + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const writeInit = S.writeInit; + pub const queueWrite = S.queueWrite; + + /// Initialize a File from a std.Io.File. + pub fn init(file: std.Io.File) !Self { + return .{ + .fd = file.handle, + }; + } + + /// Initialize a File from a file descriptor. + pub fn initFd(fd: std.Io.File.Handle) Self { + return .{ + .fd = fd, + }; + } + + /// Clean up any watcher resources. This does NOT close the file. + /// If you want to close the file you must call close or do so + /// synchronously. + pub fn deinit(self: *const Self) void { + _ = self; + } + + pub fn pread( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.ReadBuffer, + offset: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .pread = .{ + .fd = self.fd, + .buffer = buf, + .offset = offset, + }, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + Self.initFd(c_inner.op.pread.fd), + c_inner.op.pread.buffer, + if (r.pread) |v| v else |err| err, + }); + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .wasi_poll, + .iocp, + => {}, + + .epoll => { + c.flags.threadpool = true; + }, + + .kqueue => kqueue: { + // If we're not reading any actual data, we don't + // need a threadpool since only read() is blocking. + switch (buf) { + .array => {}, + .slice => |v| if (v.len == 0) break :kqueue {}, + } + + c.flags.threadpool = true; + }, + } + + loop.add(c); + }, + } + } + + pub fn queuePWrite( + self: Self, + loop: *xev.Loop, + q: *xev.WriteQueue, + req: *xev.WriteRequest, + buf: xev.WriteBuffer, + offset: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + // Initialize our completion + req.* = .{ .full_write_buffer = buf }; + self.pwriteInit(&req.completion, buf, offset); + req.completion.userdata = q; + req.completion.callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const q_inner = @as(?*xev.WriteQueue, @ptrCast(@alignCast(ud))).?; + + // The queue MUST have a request because a completion + // can only be added if the queue is not empty, and + // nothing else should be popping!. + const req_inner = q_inner.pop().?; + + const cb_res = pwrite_result(c_inner, r); + const action = @call(.always_inline, cb, .{ + common.userdataValue(Userdata, req_inner.userdata), + l_inner, + c_inner, + cb_res.writer, + cb_res.buf, + cb_res.result, + }); + + // Rearm requeues this request, it doesn't return rearm + // on the actual callback here... + if (action == .rearm) q_inner.push(req_inner); + + // If we have another request, add that completion next. + if (q_inner.head) |req_next| l_inner.add(&req_next.completion); + + // We always disarm because the completion in the next + // request will be used if there is more to queue. + return .disarm; + } + }).callback; + + // The userdata as to go on the WriteRequest because we need + // our actual completion userdata to be the WriteQueue so that + // we can process the queue. + req.userdata = @as(?*anyopaque, @ptrCast(@alignCast(userdata))); + + // If the queue is empty, then we add our completion. Otherwise, + // the previously queued writes will trigger this one. + if (q.empty()) loop.add(&req.completion); + + // We always add this item to our queue no matter what + q.push(req); + } + + pub fn pwrite( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.WriteBuffer, + offset: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + self.pwriteInit(c, buf, offset); + c.userdata = userdata; + c.callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const cb_res = pwrite_result(c_inner, r); + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + cb_res.writer, + cb_res.buf, + cb_res.result, + }); + } + }).callback; + + loop.add(c); + } + + inline fn pwrite_result(c: *xev.Completion, r: xev.Result) struct { + writer: Self, + buf: xev.WriteBuffer, + result: xev.WriteError!usize, + } { + return .{ + .writer = Self.initFd(c.op.pwrite.fd), + .buf = c.op.pwrite.buffer, + .result = if (r.pwrite) |v| v else |err| err, + }; + } + + fn pwriteInit( + self: Self, + c: *xev.Completion, + buf: xev.WriteBuffer, + offset: u64, + ) void { + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .pwrite = .{ + .fd = self.fd, + .buffer = buf, + .offset = offset, + }, + }, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .wasi_poll, + .iocp, + => {}, + + .epoll => { + c.flags.threadpool = true; + }, + + .kqueue => { + c.flags.threadpool = true; + }, + } + }, + } + } + + test { + _ = FileTests(xev, Self); + } + + test "queuePWrite" { + // wasi: local files don't work with poll (always ready) + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // windows: std.fs.File is not opened with OVERLAPPED flag. + if (builtin.os.tag == .windows) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + const io = testing.io; + + var tpool = main.ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Create our file + const path = "test_watcher_file"; + const f = try std.Io.Dir.cwd().createFile(io, path, .{ + .read = true, + .truncate = true, + }); + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; + + const file = try Self.init(f); + var write_queue: xev.WriteQueue = .{}; + var write_req: [2]xev.WriteRequest = undefined; + + // Perform a write and then a read + file.queueWrite( + &loop, + &write_queue, + &write_req[0], + .{ .slice = "1234" }, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Self, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + file.queueWrite( + &loop, + &write_queue, + &write_req[1], + .{ .slice = "5678" }, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Self, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + + file.queuePWrite( + &loop, + &write_queue, + &write_req[1], + .{ .slice = "000" }, + 3, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Self, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + + // Wait for the write + try loop.run(.until_done); + + // Make sure the data is on disk + try f.sync(io); + + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); + const file2 = try Self.init(f2); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + var c_read: xev.Completion = undefined; + file2.read(&loop, &c_read, .{ .slice = &read_buf }, usize, &read_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Self, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expectEqualSlices(u8, "123000", read_buf[0..read_len]); + } + }; +} + +fn FileDynamic(comptime xev: type) type { + return struct { + const Self = @This(); + + backend: Union, + + pub const Union = xev.Union(&.{"File"}); + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .read, + .write = .write, + .threadpool = true, + .type = "File", + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const queueWrite = S.queueWrite; + + pub fn init(file: std.Io.File) !Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.File.init(file), + ); + }, + } }; + } + + pub fn initFd(fd: std.posix.pid_t) Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + api.File.initFd(fd), + ); + }, + } }; + } + + pub fn pwrite( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.WriteBuffer, + offset: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: api.File, + b_inner: api.WriteBuffer, + r_inner: api.WriteError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + xev.WriteBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).pwrite( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + buf.toBackend(tag), + offset, + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn pread( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.ReadBuffer, + offset: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: api.File, + b_inner: api.ReadBuffer, + r_inner: api.ReadError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + xev.ReadBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).pread( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + buf.toBackend(tag), + offset, + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = FileTests(xev, Self); + } + }; +} + +fn FileTests( + comptime xev: type, + comptime Impl: type, +) type { + return struct { + test "File: Stream decls" { + if (!@hasDecl(Impl, "S")) return; + const Stream = Impl.S; + inline for (@typeInfo(Stream).@"struct".decls) |decl| { + const Decl = @TypeOf(@field(Stream, decl.name)); + if (Decl == void) continue; + if (!@hasDecl(Impl, decl.name)) { + @compileError("missing decl: " ++ decl.name); + } + } + } + + test "kqueue: zero-length read for readiness" { + if (builtin.os.tag != .macos) return error.SkipZigTest; + + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + // Create our pipe and write to it so its ready to be read + const pipe = try xev_posix.pipe2(.{ .CLOEXEC = true }); + defer xev_posix.close(pipe[1]); + _ = try xev_posix.write(pipe[1], "x"); + + // Create our file + const file = Impl.initFd(pipe[0]); + + var c: xev.Completion = undefined; + + // Read + var ready: bool = false; + file.read(&loop, &c, .{ .slice = &.{} }, bool, &ready, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expect(ready); + } + + test "poll" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (builtin.os.tag == .windows) return error.SkipZigTest; + + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + // Create our pipe and write to it so its ready to be read + const pipe = try xev_posix.pipe2(.{ .CLOEXEC = true }); + defer xev_posix.close(pipe[1]); + _ = try xev_posix.write(pipe[1], "x"); + + // Create our file + const file = Impl.initFd(pipe[0]); + + var c: xev.Completion = undefined; + + // Poll read + var ready: bool = false; + file.poll(&loop, &c, .read, bool, &ready, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.PollError!xev.PollEvent, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expect(ready); + } + + test "read/write" { + // wasi: local files don't work with poll (always ready) + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // windows: std.fs.File is not opened with OVERLAPPED flag. + if (builtin.os.tag == .windows) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + const io = testing.io; + + var tpool = main.ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Create our file + const path = "test_watcher_file"; + const f = try std.Io.Dir.cwd().createFile(io, path, .{ + .read = true, + .truncate = true, + }); + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; + + const file = try Impl.init(f); + + // Perform a write and then a read + var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: xev.Completion = undefined; + file.write(&loop, &c_write, .{ .slice = &write_buf }, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for the write + try loop.run(.until_done); + + // Make sure the data is on disk + try f.sync(io); + + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); + const file2 = try Impl.init(f2); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + file2.read(&loop, &c_write, .{ .slice = &read_buf }, usize, &read_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expectEqual(read_len, write_buf.len); + try testing.expectEqualSlices(u8, &write_buf, read_buf[0..read_len]); + } + + test "pread/pwrite" { + // wasi: local files don't work with poll (always ready) + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // windows: std.fs.File is not opened with OVERLAPPED flag. + if (builtin.os.tag == .windows) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + const io = testing.io; + + var tpool = main.ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Create our file + const path = "test_watcher_file"; + const f = try std.Io.Dir.cwd().createFile(io, path, .{ + .read = true, + .truncate = true, + }); + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; + + const file = try Impl.init(f); + + // Perform a write and then a read + var write_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: xev.Completion = undefined; + file.pwrite(&loop, &c_write, .{ .slice = &write_buf }, 0, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for the write + try loop.run(.until_done); + + // Make sure the data is on disk + try f.sync(io); + + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); + const file2 = try Impl.init(f2); + + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + file2.pread(&loop, &c_write, .{ .slice = &read_buf }, 0, usize, &read_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &write_buf, read_buf[0..read_len]); + } + + test "queued writes" { + // wasi: local files don't work with poll (always ready) + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // windows: std.fs.File is not opened with OVERLAPPED flag. + if (builtin.os.tag == .windows) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + const io = testing.io; + + var tpool = main.ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Create our file + const path = "test_watcher_file"; + const f = try std.Io.Dir.cwd().createFile(io, path, .{ + .read = true, + .truncate = true, + }); + defer f.close(io); + defer std.Io.Dir.cwd().deleteFile(io, path) catch {}; + + const file = try Impl.init(f); + var write_queue: xev.WriteQueue = .{}; + var write_req: [2]xev.WriteRequest = undefined; + + // Perform a write and then a read + file.queueWrite( + &loop, + &write_queue, + &write_req[0], + .{ .slice = "1234" }, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + file.queueWrite( + &loop, + &write_queue, + &write_req[1], + .{ .slice = "5678" }, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + + // Wait for the write + try loop.run(.until_done); + + // Make sure the data is on disk + try f.sync(io); + + const f2 = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f2.close(io); + const file2 = try Impl.init(f2); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: usize = 0; + var c_read: xev.Completion = undefined; + file2.read(&loop, &c_read, .{ .slice = &read_buf }, usize, &read_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expectEqualSlices(u8, "12345678", read_buf[0..read_len]); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/process.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/process.zig new file mode 100644 index 0000000..a581a92 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/process.zig @@ -0,0 +1,580 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const linux = std.os.linux; +const posix = std.posix; +const common = @import("common.zig"); +const xev_posix = @import("../posix.zig"); + +/// Process management, such as waiting for process exit. +pub fn Process(comptime xev: type) type { + if (xev.dynamic) return ProcessDynamic(xev); + + return switch (xev.backend) { + // Supported, uses pidfd + .io_uring, + .epoll, + => ProcessPidFd(xev), + + .kqueue => ProcessKqueue(xev), + + .iocp => ProcessIocp(xev), + + // Unsupported + .wasi_poll => struct {}, + }; +} + +/// Process implementation using pidfd (Linux). +fn ProcessPidFd(comptime xev: type) type { + return struct { + const Self = @This(); + + /// The error that can come in the wait callback. + pub const WaitError = xev.Sys.PollError || error{ + InvalidChild, + }; + + /// pidfd file descriptor + fd: posix.fd_t, + + /// Create a new process watcher for the given pid. + pub fn init(pid: posix.pid_t) !Self { + // Note: SOCK_NONBLOCK == PIDFD_NONBLOCK but we should PR that + // over to Zig. + const res = linux.pidfd_open(pid, posix.SOCK.NONBLOCK); + const fd = switch (posix.errno(res)) { + .SUCCESS => @as(posix.fd_t, @intCast(res)), + .INVAL => return error.InvalidArgument, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.SystemResources, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + + return .{ + .fd = fd, + }; + } + + /// Clean up the process watcher. + pub fn deinit(self: *Self) void { + xev_posix.close(self.fd); + } + + /// Wait for the process to exit. This will automatically call + /// `waitpid` or equivalent and report the exit status. + pub fn wait( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!u32, + ) xev.CallbackAction, + ) void { + const events: u32 = comptime switch (xev.backend) { + .io_uring => posix.POLL.IN, + .epoll => linux.EPOLL.IN, + else => unreachable, + }; + + c.* = .{ + .op = .{ + .poll = .{ + .fd = self.fd, + .events = events, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const arg: WaitError!u32 = arg: { + // If our poll failed, report that error. + _ = r.poll catch |err| break :arg err; + + // We need to wait on the pidfd because it is noted as ready + const fd = c_inner.op.poll.fd; + var info: linux.siginfo_t = undefined; + const res = linux.waitid(.PIDFD, fd, &info, linux.W.EXITED, null); + + break :arg switch (posix.errno(res)) { + .SUCCESS => @as(u32, @intCast(info.fields.common.second.sigchld.status)), + .CHILD => error.InvalidChild, + + // The fd isn't ready to read, I guess? + .AGAIN => return .rearm, + else => |err| err: { + std.log.warn("unexpected process wait errno={}", .{err}); + break :err error.Unexpected; + }, + }; + }; + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + arg, + }); + } + }).callback, + }; + loop.add(c); + } + + test { + _ = ProcessTests( + xev, + Self, + ); + } + }; +} + +fn reapProcess(pid: posix.pid_t) void { + var status: c_int = undefined; + while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { + .SUCCESS => return, + .INTR => continue, + .CHILD => return, + else => unreachable, + }; +} + +fn ProcessKqueue(comptime xev: type) type { + return struct { + const Self = @This(); + + /// The error that can come in the wait callback. + pub const WaitError = xev.Sys.ProcError; + + /// The pid to watch. + pid: posix.pid_t, + + /// Create a new process watcher for the given pid. + pub fn init(pid: posix.pid_t) !Self { + return .{ + .pid = pid, + }; + } + + /// Does nothing for Kqueue. + pub fn deinit(self: *Self) void { + _ = self; + } + + /// Wait for the process to exit. This will automatically call + /// `waitpid` or equivalent and report the exit status. + pub fn wait( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!u32, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .proc = .{ + .pid = self.pid, + .flags = xev.Sys.NOTE_EXIT_FLAGS, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + // Reap the process. We do this because our xev.Process + // docs note that this is the equivalent of calling + // `wait` on a process. The Linux side (pidfd) does this + // automatically since the `waitid` syscall is used. + if (r.proc) |_| { + reapProcess(c_inner.op.proc.pid); + } else |_| {} + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.proc) |v| v else |err| err, + }); + } + }).callback, + }; + loop.add(c); + } + + test { + _ = ProcessTests( + xev, + Self, + ); + } + }; +} + +const windows = @import("../windows.zig"); +fn ProcessIocp(comptime xev: type) type { + return struct { + const Self = @This(); + + pub const WaitError = xev.Sys.JobObjectError; + + job: windows.HANDLE, + process: windows.HANDLE, + + pub fn init(process: posix.pid_t) !Self { + const current_process = windows.kernel32.GetCurrentProcess(); + + // Duplicate the process handle so we don't rely on the caller keeping it alive + var dup_process: windows.HANDLE = undefined; + const dup_result = windows.kernel32.DuplicateHandle( + current_process, + process, + current_process, + &dup_process, + 0, + windows.FALSE, + windows.DUPLICATE_SAME_ACCESS, + ); + if (dup_result == .FALSE) return windows.unexpectedError(windows.kernel32.GetLastError()); + + const job = try windows.exp.CreateJobObject(null, null); + errdefer _ = windows.CloseHandle(job); + + try windows.exp.AssignProcessToJobObject(job, dup_process); + + return .{ + .job = job, + .process = dup_process, + }; + } + + pub fn deinit(self: *Self) void { + _ = windows.CloseHandle(self.job); + _ = windows.CloseHandle(self.process); + } + + pub fn wait( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: WaitError!u32, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .job_object = .{ + .job = self.job, + .userdata = self.process, + }, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + if (r.job_object) |result| { + switch (result) { + .associated => { + // There was a period of time between when the job object was created + // and when it was associated with the completion port. We may have + // missed a notification, so check if it's still alive. + + var exit_code: windows.DWORD = undefined; + const process: windows.HANDLE = @ptrCast(c_inner.op.job_object.userdata); + const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != .FALSE; + if (!has_code) std.log.warn("unable to get exit code for process={}", .{windows.kernel32.GetLastError()}); + if (exit_code == windows.exp.STILL_ACTIVE) return .rearm; + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + exit_code, + }); + }, + .message => |message| { + const result_inner = switch (message.type) { + .JOB_OBJECT_MSG_EXIT_PROCESS, + .JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS, + => b: { + const process: windows.HANDLE = @ptrCast(c_inner.op.job_object.userdata); + const pid = windows.exp.k32.GetProcessId(process); + if (pid == 0) break :b WaitError.Unexpected; + if (message.value != pid) return .rearm; + + var exit_code: windows.DWORD = undefined; + const has_code = windows.kernel32.GetExitCodeProcess(process, &exit_code) != .FALSE; + if (!has_code) std.log.warn("unable to get exit code for process={}", .{windows.kernel32.GetLastError()}); + break :b if (has_code) exit_code else WaitError.Unexpected; + }, + else => return .rearm, + }; + + return @call(.always_inline, cb, .{ common.userdataValue(Userdata, ud), l_inner, c_inner, result_inner }); + }, + } + } else |err| { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + err, + }); + } + } + }).callback, + }; + loop.add(c); + } + + test { + _ = ProcessTests( + xev, + Self, + ); + } + }; +} + +fn ProcessDynamic(comptime dynamic: type) type { + return struct { + const Self = @This(); + + backend: Union, + + pub const Union = dynamic.Union(&.{"Process"}); + pub const WaitError = dynamic.ErrorSet(&.{ "Process", "WaitError" }); + + pub fn init(fd: posix.pid_t) !Self { + return .{ .backend = switch (dynamic.backend) { + inline else => |tag| backend: { + const api = (comptime dynamic.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.Process.init(fd), + ); + }, + } }; + } + + pub fn deinit(self: *Self) void { + switch (dynamic.backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).deinit(), + } + } + + pub fn wait( + self: Self, + loop: *dynamic.Loop, + c: *dynamic.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *dynamic.Loop, + c: *dynamic.Completion, + r: WaitError!u32, + ) dynamic.CallbackAction, + ) void { + switch (dynamic.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime dynamic.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.Process.WaitError!u32, + ) dynamic.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *dynamic.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *dynamic.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).wait( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = ProcessTests( + dynamic, + Self, + ); + } + }; +} + +fn ProcessTests( + comptime xev: type, + comptime Impl: type, +) type { + return struct { + const argv_0: []const []const u8 = switch (builtin.os.tag) { + .windows => &.{ "cmd.exe", "/C", "exit 0" }, + else => &.{ "sh", "-c", "exit 0" }, + }; + + const argv_42: []const []const u8 = switch (builtin.os.tag) { + .windows => &.{ "cmd.exe", "/C", "exit 42" }, + else => &.{ "sh", "-c", "exit 42" }, + }; + + test "process wait" { + const testing = std.testing; + const io = testing.io; + + const child = try std.process.spawn(io, .{ .argv = argv_0 }); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var p = try Impl.init(child.id.?); + defer p.deinit(); + + // Wait + var code: ?u32 = null; + var c_wait: xev.Completion = .{}; + p.wait(&loop, &c_wait, ?u32, &code, (struct { + fn callback( + ud: ?*?u32, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!u32, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for wake + try loop.run(.until_done); + try testing.expectEqual(@as(u32, 0), code.?); + } + + test "process wait with non-zero exit code" { + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + const testing = std.testing; + const io = testing.io; + + const child = try std.process.spawn(io, .{ .argv = argv_42 }); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var p = try Impl.init(child.id.?); + defer p.deinit(); + + // Wait + var code: ?u32 = null; + var c_wait: xev.Completion = .{}; + p.wait(&loop, &c_wait, ?u32, &code, (struct { + fn callback( + ud: ?*?u32, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!u32, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for wake + try loop.run(.until_done); + try testing.expectEqual(@as(u32, 42), code.?); + } + + test "process wait on a process that already exited" { + const testing = std.testing; + const io = testing.io; + + var child = try std.process.spawn(io, .{ .argv = argv_0 }); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var p = try Impl.init(child.id.?); + defer p.deinit(); + + _ = try child.wait(io); + + // Wait + var code: ?u32 = null; + var c_wait: xev.Completion = .{}; + p.wait(&loop, &c_wait, ?u32, &code, (struct { + fn callback( + ud: ?*?u32, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.WaitError!u32, + ) xev.CallbackAction { + ud.?.* = r catch 0; + return .disarm; + } + }).callback); + + // Wait for wake + try loop.run(.until_done); + try testing.expectEqual(@as(u32, 0), code.?); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/stream.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/stream.zig new file mode 100644 index 0000000..c00e631 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/stream.zig @@ -0,0 +1,1444 @@ +const std = @import("std"); +const assert = std.debug.assert; +const builtin = @import("builtin"); +const common = @import("common.zig"); +const queue = @import("../queue.zig"); +const xev_posix = @import("../posix.zig"); + +/// Options for creating a stream type. Each of the options makes the +/// functionality available for the stream. +pub const Options = struct { + read: ReadMethod = .none, + write: WriteMethod = .none, + close: bool = false, + poll: bool = false, + + /// True to schedule the read/write on the threadpool. + threadpool: bool = false, + + /// Set to non-null for dynamic APIs so we can know what our + /// parent type name is. + type: ?[]const u8 = null, + + pub const ReadMethod = enum { none, read, recv }; + pub const WriteMethod = enum { none, write, send }; +}; + +/// Returns the shared decls for all streams that should be set for +/// the xev type. +pub fn Shared(comptime xev: type) type { + return struct { + /// WriteQueue is the queue of write requests for ordered writes. + /// This can be copied around. + pub const WriteQueue = queue.Intrusive(WriteRequest); + + /// WriteRequest is a single request for a write. It wraps a + /// completion so that it can be inserted into the WriteQueue. + pub const WriteRequest = struct { + completion: xev.Completion = .{}, + userdata: ?*anyopaque = null, + + /// This is the original buffer passed to queueWrite. We have + /// to keep track of this because we may be forced to split + /// the write or rearm the write due to partial writes, but when + /// we call the final callback we want to pass the original + /// complete buffer. + full_write_buffer: xev.WriteBuffer, + + next: ?*@This() = null, + + /// This can be used to convert a completion pointer back to + /// a WriteRequest. This is only safe of course if the completion + /// originally is from a write request. This is useful for getting + /// the WriteRequest back in a callback from queuedWrite. + pub fn from(c: *xev.Completion) *WriteRequest { + return @fieldParentPtr("completion", c); + } + }; + + /// Errors that can be returned from polling. + pub const PollError = switch (xev.backend) { + .io_uring, + .epoll, + => xev.Sys.PollError, + + .kqueue, + => xev.ReadError, + + .iocp, + .wasi_poll, + => error{}, + }; + + /// Events that can be polled for using the high level streams. + pub const PollEvent = enum(u32) { + read = switch (xev.backend) { + .io_uring => std.posix.POLL.IN, + .epoll => std.os.linux.EPOLL.IN, + .kqueue => 0, // doesn't matter + .iocp, .wasi_poll => 0, // invalid + }, + + fn fromResult( + c: *const xev.Completion, + result: xev.Result, + ) PollError!PollEvent { + return switch (xev.backend) { + .io_uring, + .epoll, + => if (result.poll) |_| + @enumFromInt(c.op.poll.events) + else |err| + err, + + .kqueue => switch (c.op) { + .read, .recv => .read, + else => unreachable, + }, + + .iocp, + .wasi_poll, + => @compileError("poll not supported on this backend"), + }; + } + }; + }; +} + +/// Creates a stream type that is meant to be embedded within other types. +/// A stream is something that supports read, write, close, etc. The exact +/// operations supported are defined by the "options" struct. +/// +/// T requirements: +/// - field named "fd" of type fd_t or socket_t +/// - decl named "initFd" to initialize a new T from a fd +/// +pub fn Stream(comptime xev: type, comptime T: type, comptime options: Options) type { + return struct { + const C_: ?type = if (options.close) Closeable(xev, T, options) else null; + pub const close = if (C_) |C| C.close else {}; + + const P_: ?type = if (options.poll) Pollable(xev, T, options) else null; + pub const poll = if (P_) |P| poll: { + if (!@hasDecl(P, "poll")) break :poll {}; + break :poll P.poll; + } else {}; + + const R_: ?type = if (options.read != .none) Readable(xev, T, options) else null; + pub const read = if (R_) |R| R.read else {}; + + const W_: ?type = if (options.write != .none) Writeable(xev, T, options) else null; + pub const writeInit = if (W_) |W| writeInit: { + if (xev.dynamic) break :writeInit {}; + break :writeInit W.writeInit; + } else {}; + pub const write = if (W_) |W| W.write else null; + pub const queueWrite = if (W_) |W| W.queueWrite else {}; + }; +} + +fn Pollable(comptime xev: type, comptime T: type, comptime options: Options) type { + if (xev.dynamic) { + // If all candidate backends do not support poll, our dynamic + // type cannot support poll. + comptime { + for (xev.candidates) |be| { + const CandidateT = @field(be.Api(), options.type.?); + const info = @typeInfo(CandidateT).@"struct"; + for (info.decls) |decl| { + if (std.mem.eql(u8, decl.name, "poll")) break; + } else return struct {}; + } + } + + return struct { + const Self = T; + + pub fn poll( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + event: xev.PollEvent, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.PollError!xev.PollEvent, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const BackendSelf = @field(api, options.type.?); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: BackendSelf, + r_inner: api.PollError!api.PollEvent, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + if (r_inner) |v| + xev.PollEvent.fromBackend(tag, v) + else |err| + err, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).poll( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + event.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + }; + } + + // Do not add the methods for poll if the backend doesn't support it. + switch (xev.backend) { + .io_uring, .epoll, .kqueue => {}, + .iocp, .wasi_poll => return struct {}, + } + + return struct { + const Self = T; + + /// Poll the file descriptor for the given event. The high-level + /// abstraction only allows a single event to be polled at a time. + /// If you want to be more efficient for multiple events then you + /// should use the lower level interfaces for the xev backend. + pub fn poll( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + event: xev.PollEvent, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.PollError!xev.PollEvent, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = switch (xev.backend) { + .io_uring => .{ .poll = .{ + .fd = self.fd, + .events = switch (event) { + .read => std.posix.POLL.IN, + }, + } }, + + .epoll => .{ .poll = .{ + .fd = self.fd, + .events = switch (event) { + .read => std.os.linux.EPOLL.IN, + }, + } }, + + .kqueue => switch (options.read) { + .none => unreachable, + + .recv => .{ .read = .{ + .fd = self.fd, + .buffer = .{ .slice = &.{} }, + } }, + + .read => .{ .read = .{ + .fd = self.fd, + .buffer = .{ .slice = &.{} }, + } }, + }, + + .iocp, + .wasi_poll, + => @compileError("poll not supported on this backend"), + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const fd: Self = switch (xev.backend) { + .io_uring, + .epoll, + => T.initFd(c_inner.op.poll.fd), + + .kqueue => T.initFd(c_inner.op.read.fd), + + .iocp, + .wasi_poll, + => @compileError("poll not supported on this backend"), + }; + + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + fd, + xev.PollEvent.fromResult(c_inner, r), + }); + } + }).callback, + }; + + loop.add(c); + } + }; +} + +pub fn Closeable(comptime xev: type, comptime T: type, comptime options: Options) type { + if (xev.dynamic) return struct { + const Self = T; + + pub fn close( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.CloseError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const BackendSelf = @field(api, options.type.?); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: BackendSelf, + r_inner: api.CloseError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).close( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + }; + + return struct { + const Self = T; + + /// Close the socket. + pub fn close( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.CloseError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ .close = .{ .fd = self.fd } }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const fd = T.initFd(c_inner.op.close.fd); + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + fd, + if (r.close) |_| {} else |err| err, + }); + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .wasi_poll, + .iocp, + => {}, + + .epoll => { + c.flags.threadpool = true; + }, + + .kqueue => { + c.flags.threadpool = true; + }, + } + + loop.add(c); + } + }; +} + +pub fn Readable(comptime xev: type, comptime T: type, comptime options: Options) type { + if (xev.dynamic) return struct { + const Self = T; + + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const BackendSelf = @field(api, options.type.?); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: BackendSelf, + b_inner: api.ReadBuffer, + r_inner: xev.ReadError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + xev.ReadBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).read( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + buf.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + }; + + return struct { + const Self = T; + + /// Read from the socket. This performs a single read. The callback must + /// requeue the read if additional reads want to be performed. Additional + /// reads simultaneously can be queued by calling this multiple times. Note + /// that depending on the backend, the reads can happen out of order. + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = switch (options.read) { + .none => unreachable, + + .read => .{ + .read = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + + .recv => .{ + .recv = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return switch (options.read) { + .none => unreachable, + + .recv => @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + T.initFd(c_inner.op.recv.fd), + c_inner.op.recv.buffer, + if (r.recv) |v| v else |err| err, + }), + + .read => @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + T.initFd(c_inner.op.read.fd), + c_inner.op.read.buffer, + if (r.read) |v| v else |err| err, + }), + }; + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .wasi_poll, + .iocp, + => {}, + + .epoll => { + if (options.threadpool) + c.flags.threadpool = true + else + c.flags.dup = true; + }, + + .kqueue => kqueue: { + // If we're not reading any actual data, we don't + // need a threadpool since only read() is blocking. + switch (buf) { + .array => {}, + .slice => |v| if (v.len == 0) break :kqueue {}, + } + + if (options.threadpool) c.flags.threadpool = true; + }, + } + + loop.add(c); + }, + } + } + }; +} + +pub fn Writeable(comptime xev: type, comptime T: type, comptime options: Options) type { + if (xev.dynamic) return struct { + const Self = T; + + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const BackendSelf = @field(api, options.type.?); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: BackendSelf, + b_inner: api.WriteBuffer, + r_inner: api.WriteError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + xev.WriteBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).write( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + buf.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn queueWrite( + self: Self, + loop: *xev.Loop, + q: *xev.WriteQueue, + req: *xev.WriteRequest, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + const api = (comptime xev.superset(tag)).Api(); + const BackendSelf = @field(api, options.type.?); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: BackendSelf, + b_inner: api.WriteBuffer, + r_inner: api.WriteError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + xev.WriteBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + // Ensure our WriteQueue has the correct tag, since it is + // regularly zero-initialized and our zero-init picks + // an arbitrary backend. + q.ensureTag(tag); + + // Initialize our request since it is usually undefined. + req.* = @unionInit( + xev.WriteRequest, + @tagName(tag), + undefined, + ); + + @field( + self.backend, + @tagName(tag), + ).queueWrite( + &@field(loop.backend, @tagName(tag)), + &@field(q.value, @tagName(tag)), + &@field(req, @tagName(tag)), + buf.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + }; + + return struct { + const Self = T; + + /// Write to the stream. This queues the writes to ensure they + /// remain in order. Queueing has a small overhead: you must + /// maintain a WriteQueue and WriteRequests instead of just + /// Completions. + /// + /// If ordering isn't important, or you can maintain ordering + /// naturally in your program, consider using write since it + /// has a slightly smaller overhead. + /// + /// The "CallbackAction" return value of this callback behaves slightly + /// different. The "rearm" return value will re-queue the same write + /// at the end of the queue. + /// + /// It is safe to call this at anytime from the main thread. + pub fn queueWrite( + self: Self, + loop: *xev.Loop, + q: *xev.WriteQueue, + req: *xev.WriteRequest, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + // Initialize our completion + req.* = .{ .full_write_buffer = buf }; + // Must be kept in sync with partial write logic inside the callback + self.writeInit(&req.completion, buf); + req.completion.userdata = q; + req.completion.callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const q_inner = @as(?*xev.WriteQueue, @ptrCast(@alignCast(ud))).?; + + // The queue MUST have a request because a completion + // can only be added if the queue is not empty, and + // nothing else should be popping!. + // + // We only peek the request here (not pop) because we may + // need to rearm this write if the write was partial. + const req_inner: *xev.WriteRequest = q_inner.head.?; + + const cb_res = write_result(c_inner, r); + var result: xev.WriteError!usize = cb_res.result; + + // Checks whether the entire buffer was written, this is + // necessary to guarantee correct ordering of writes. + // If the write was partial, it re-submits the remainder of + // the buffer. + const queued_len = writeBufferLength(cb_res.buf); + if (cb_res.result) |written_len| { + if (written_len < queued_len) { + // Write remainder of the buffer, reusing the same completion + const rem_buf = writeBufferRemainder(cb_res.buf, written_len); + cb_res.writer.writeInit(&req_inner.completion, rem_buf); + req_inner.completion.userdata = q_inner; + req_inner.completion.callback = callback; + l_inner.add(&req_inner.completion); + return .disarm; + } + + // We wrote the entire buffer, modify the result to indicate + // to the caller that all bytes have been written. + result = writeBufferLength(req_inner.full_write_buffer); + } else |_| {} + + // We can pop previously peeked request. + _ = q_inner.pop().?; + + const action = @call(.always_inline, cb, .{ + common.userdataValue(Userdata, req_inner.userdata), + l_inner, + c_inner, + cb_res.writer, + req_inner.full_write_buffer, + result, + }); + + // Rearm requeues this request, it doesn't return rearm + // on the actual callback here... + if (action == .rearm) q_inner.push(req_inner); + + // If we have another request, add that completion next. + if (q_inner.head) |req_next| l_inner.add(&req_next.completion); + + // We always disarm because the completion in the next + // request will be used if there is more to queue. + return .disarm; + } + }).callback; + + // The userdata as to go on the WriteRequest because we need + // our actual completion userdata to be the WriteQueue so that + // we can process the queue. + req.userdata = @as(?*anyopaque, @ptrCast(@alignCast(userdata))); + + // If the queue is empty, then we add our completion. Otherwise, + // the previously queued writes will trigger this one. + if (q.empty()) loop.add(&req.completion); + + // We always add this item to our queue no matter what + q.push(req); + } + + /// Write to the stream. This performs a single write. Additional + /// writes can be requested by calling this multiple times. + /// + /// IMPORTANT: writes are NOT queued. There is no order guarantee + /// if this is called multiple times. If ordered writes are important + /// (they usually are!) then you should only call write again once + /// the previous write callback is called. + /// + /// If ordering is important, use queueWrite instead. + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + self.writeInit(c, buf); + c.userdata = userdata; + c.callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const cb_res = write_result(c_inner, r); + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + cb_res.writer, + cb_res.buf, + cb_res.result, + }); + } + }).callback; + + loop.add(c); + } + + /// Extracts the result from a completion for a write callback. + inline fn write_result(c: *xev.Completion, r: xev.Result) struct { + writer: Self, + buf: xev.WriteBuffer, + result: xev.WriteError!usize, + } { + return switch (options.write) { + .none => unreachable, + + .send => .{ + .writer = T.initFd(c.op.send.fd), + .buf = c.op.send.buffer, + .result = if (r.send) |v| v else |err| err, + }, + + .write => .{ + .writer = T.initFd(c.op.write.fd), + .buf = c.op.write.buffer, + .result = if (r.write) |v| v else |err| err, + }, + }; + } + + /// Initialize the completion c for a write. This does NOT set + /// userdata or a callback. + fn writeInit( + self: Self, + c: *xev.Completion, + buf: xev.WriteBuffer, + ) void { + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = switch (options.write) { + .none => unreachable, + + .write => .{ + .write = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + + .send => .{ + .send = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + }, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .wasi_poll, + .iocp, + => {}, + + .epoll => { + if (options.threadpool) { + c.flags.threadpool = true; + } else { + c.flags.dup = true; + } + }, + + .kqueue => { + if (options.threadpool) c.flags.threadpool = true; + }, + } + }, + } + } + + /// Returns the length of the write buffer + fn writeBufferLength(buf: xev.WriteBuffer) usize { + return switch (buf) { + .slice => |slice| slice.len, + .array => |array| array.len, + }; + } + + /// Given a `WriteBuffer` and number of bytes written during the previous + /// write operation, returns a new `WriteBuffer` with remaining data. + fn writeBufferRemainder(buf: xev.WriteBuffer, offset: usize) xev.WriteBuffer { + switch (buf) { + .slice => |slice| { + assert(offset <= slice.len); + return .{ .slice = slice[offset..] }; + }, + .array => |array| { + assert(offset <= array.len); + const rem_len = array.len - offset; + var wb = xev.WriteBuffer{ .array = .{ + .array = undefined, + .len = rem_len, + } }; + @memcpy( + wb.array.array[0..rem_len], + array.array[offset..][0..rem_len], + ); + return wb; + }, + } + } + }; +} + +/// Creates a generic stream type that supports read, write, close, poll. This +/// can be used for any file descriptor that would exhibit normal blocking +/// behavior on read/write. This should NOT be used for local files because +/// local files have some special properties; you should use xev.File for that. +pub fn GenericStream(comptime xev: type) type { + if (xev.dynamic) return struct { + const Self = @This(); + + backend: Union, + + pub const Union = xev.Union(&.{"Stream"}); + + const S = Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .read, + .write = .write, + .type = "Stream", + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const writeInit = S.writeInit; + pub const queueWrite = S.queueWrite; + + pub fn initFd(fd: std.posix.pid_t) Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + api.Stream.initFd(fd), + ); + }, + } }; + } + + pub fn deinit(self: *Self) void { + switch (xev.backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).deinit(), + } + } + + test { + _ = GenericStreamTests(xev, Self); + } + }; + + return struct { + const Self = @This(); + + /// The underlying file + fd: std.posix.fd_t, + + const S = Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .read, + .write = .write, + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const writeInit = S.writeInit; + pub const queueWrite = S.queueWrite; + + /// Initialize a generic stream from a file descriptor. + pub fn initFd(fd: std.posix.fd_t) Self { + return .{ + .fd = fd, + }; + } + + /// Clean up any watcher resources. This does NOT close the file. + /// If you want to close the file you must call close or do so + /// synchronously. + pub fn deinit(self: *const Self) void { + _ = self; + } + + test { + _ = GenericStreamTests(xev, Self); + } + }; +} + +fn GenericStreamTests(comptime xev: type, comptime Impl: type) type { + return struct { + test "Stream decls" { + if (!@hasDecl(Impl, "S")) return; + inline for (@typeInfo(Impl.S).@"struct".decls) |decl| { + const Decl = @TypeOf(@field(Impl.S, decl.name)); + if (Decl == void) continue; + if (!@hasDecl(Impl, decl.name)) { + @compileError("missing decl: " ++ decl.name); + } + } + } + + test "pty: child to parent" { + const testing = std.testing; + switch (builtin.os.tag) { + .linux, .macos => {}, + else => return error.SkipZigTest, + } + + // Create the pty parent/child side. + var pty = try Pty.init(); + defer pty.deinit(); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + const parent = Impl.initFd(pty.parent); + const child = Impl.initFd(pty.child); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: ?usize = null; + var c_read: xev.Completion = undefined; + parent.read(&loop, &c_read, .{ .slice = &read_buf }, ?usize, &read_len, (struct { + fn callback( + ud: ?*?usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // This should not block! + try loop.run(.no_wait); + try testing.expect(read_len == null); + + // Send + const send_buf = "hello, world!"; + var c_write: xev.Completion = undefined; + child.write(&loop, &c_write, .{ .slice = send_buf }, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + c: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = c; + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // The write and read should trigger + try loop.run(.until_done); + try testing.expect(read_len != null); + try testing.expectEqualSlices(u8, send_buf, read_buf[0..read_len.?]); + } + + test "pty: parent to child" { + const testing = std.testing; + switch (builtin.os.tag) { + .linux, .macos => {}, + else => return error.SkipZigTest, + } + + // Create the pty parent/child side. + var pty = try Pty.init(); + defer pty.deinit(); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + const parent = Impl.initFd(pty.parent); + const child = Impl.initFd(pty.child); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: ?usize = null; + var c_read: xev.Completion = undefined; + child.read(&loop, &c_read, .{ .slice = &read_buf }, ?usize, &read_len, (struct { + fn callback( + ud: ?*?usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // This should not block! + try loop.run(.no_wait); + try testing.expect(read_len == null); + + // Send (note the newline at the end of the buf is important + // since we're in cooked mode) + const send_buf = "hello, world!\n"; + var c_write: xev.Completion = undefined; + parent.write(&loop, &c_write, .{ .slice = send_buf }, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + c: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = c; + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // The write and read should trigger + try loop.run(.until_done); + try testing.expect(read_len != null); + try testing.expectEqualSlices(u8, send_buf, read_buf[0..read_len.?]); + } + + test "pty: queued writes" { + const testing = std.testing; + switch (builtin.os.tag) { + .linux, .macos => {}, + else => return error.SkipZigTest, + } + + // this test fails on x86_64 with a strange error but passes + // on aarch64. for now, just let it go until we investigate. + if (xev.dynamic and builtin.cpu.arch == .x86_64) return error.SkipZigTest; + + // Create the pty parent/child side. + var pty = try Pty.init(); + defer pty.deinit(); + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + const parent = Impl.initFd(pty.parent); + const child = Impl.initFd(pty.child); + + // Read + var read_buf: [128]u8 = undefined; + var read_len: ?usize = null; + var c_read: xev.Completion = undefined; + child.read(&loop, &c_read, .{ .slice = &read_buf }, ?usize, &read_len, (struct { + fn callback( + ud: ?*?usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // This should not block! + try loop.run(.no_wait); + try testing.expect(read_len == null); + + var write_queue: xev.WriteQueue = .{}; + var write_req: [2]xev.WriteRequest = undefined; + + // Send (note the newline at the end of the buf is important + // since we're in cooked mode) + parent.queueWrite( + &loop, + &write_queue, + &write_req[0], + .{ .slice = "hello, " }, + void, + null, + (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + c: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = c; + _ = r catch unreachable; + return .disarm; + } + }).callback, + ); + + var c_result: ?*xev.Completion = null; + parent.queueWrite( + &loop, + &write_queue, + &write_req[1], + .{ .slice = "world!\n" }, + ?*xev.Completion, + &c_result, + (struct { + fn callback( + ud: ?*?*xev.Completion, + _: *xev.Loop, + c: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = c; + return .disarm; + } + }).callback, + ); + + // The write and read should trigger + try loop.run(.until_done); + try testing.expect(read_len != null); + try testing.expectEqualSlices(u8, "hello, world!\n", read_buf[0..read_len.?]); + + // Verify our completion is equal to our request + if (!xev.dynamic) { + try testing.expect( + xev.WriteRequest.from(c_result.?) == &write_req[1], + ); + } + } + }; +} + +/// Helper to open a pty. This isn't exposed as a public API this is only +/// used for tests. +const Pty = struct { + /// The file descriptors for the parent/child side of the pty. This refers + /// to the master/slave side respectively, and while that terminology is + /// the officially used terminology of the syscall, I will use parent/child + /// here. + parent: std.posix.fd_t, + child: std.posix.fd_t, + + /// Redeclare this winsize struct so we can just use a Zig struct. This + /// layout should be correct on all tested platforms. + const Winsize = extern struct { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, + }; + + // libc pty.h + extern "c" fn openpty( + parent: *std.posix.fd_t, + child: *std.posix.fd_t, + name: ?[*]u8, + termios: ?*const anyopaque, // termios but we don't use it + winsize: ?*const Winsize, + ) c_int; + + pub fn init() !Pty { + // Reasonable size + var size: Winsize = .{ + .ws_row = 80, + .ws_col = 80, + .ws_xpixel = 800, + .ws_ypixel = 600, + }; + + var parent_fd: std.posix.fd_t = undefined; + var child_fd: std.posix.fd_t = undefined; + if (openpty( + &parent_fd, + &child_fd, + null, + null, + &size, + ) < 0) + return error.OpenptyFailed; + errdefer { + _ = std.posix.system.close(parent_fd); + _ = std.posix.system.close(child_fd); + } + + return .{ + .parent = parent_fd, + .child = child_fd, + }; + } + + pub fn deinit(self: *Pty) void { + xev_posix.close(self.parent); + xev_posix.close(self.child); + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/tcp.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/tcp.zig new file mode 100644 index 0000000..040378f --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/tcp.zig @@ -0,0 +1,951 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const posix = std.posix; +const stream = @import("stream.zig"); +const common = @import("common.zig"); +const ThreadPool = @import("../ThreadPool.zig"); +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); +const windows = @import("../windows.zig"); + +/// TCP client and server. +/// +/// This is a "higher-level abstraction" in libxev. The goal of higher-level +/// abstractions in libxev are to make it easier to use specific functionality +/// with the event loop, but does not promise perfect flexibility or optimal +/// performance. In almost all cases, the abstraction is good enough. But, +/// if you have specific needs or want to push for the most optimal performance, +/// use the platform-specific Loop directly. +pub fn TCP(comptime xev: type) type { + if (xev.dynamic) return TCPDynamic(xev); + return TCPStream(xev); +} + +fn TCPStream(comptime xev: type) type { + return struct { + const Self = @This(); + const FdType = if (xev.backend == .iocp) std.os.windows.HANDLE else posix.socket_t; + + fd: FdType, + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .recv, + .write = .send, + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const writeInit = S.writeInit; + pub const queueWrite = S.queueWrite; + + /// Initialize a new TCP with the family from the given address. Only + /// the family is used, the actual address has no impact on the created + /// resource. + pub fn init(addr: std.Io.net.IpAddress) !Self { + if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + + const posix_addr = net.Address.fromIpAddress(addr); + const fd = if (xev.backend == .iocp) + try windows.WSASocketW(posix_addr.any.family, windows.ws2_32.SOCK.STREAM, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED) + else fd: { + // On io_uring we don't use non-blocking sockets because we may + // just get EAGAIN over and over from completions. + const flags = flags: { + var flags: u32 = posix.SOCK.STREAM | posix.SOCK.CLOEXEC; + if (xev.backend != .io_uring) flags |= posix.SOCK.NONBLOCK; + break :flags flags; + }; + break :fd try xev_posix.socket(posix_addr.any.family, flags, 0); + }; + + return .{ + .fd = fd, + }; + } + + /// Initialize a TCP socket from a file descriptor. + pub fn initFd(fd: FdType) Self { + return .{ + .fd = fd, + }; + } + + /// Bind the address to the socket. + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + + const posix_addr = net.Address.fromIpAddress(addr); + if (xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); + if (windows.ws2_32.setsockopt(sock, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)), @sizeOf(c_int)) != 0) return error.Unexpected; + if (windows.ws2_32.bind(sock, &posix_addr.any, @as(i32, @intCast(posix_addr.getOsSockLen()))) != 0) return error.Unexpected; + } else { + const fd = self.fd; + try posix.setsockopt(fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(fd, &posix_addr.any, posix_addr.getOsSockLen()); + } + } + + /// Listen for connections on the socket. This puts the socket into passive + /// listening mode. Connections must still be accepted one at a time. + pub fn listen(self: Self, backlog: u31) !void { + if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + + if (xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); + if (windows.ws2_32.listen(sock, @as(i32, backlog)) != 0) return error.Unexpected; + } else { + try xev_posix.listen(self.fd, backlog); + } + } + + /// Accept a single connection. + pub fn accept( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: xev.AcceptError!Self, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .accept = .{ + .socket = self.fd, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + if (r.accept) |fd| initFd(fd) else |err| err, + }); + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .kqueue, + .wasi_poll, + .iocp, + => {}, + + .epoll => c.flags.dup = true, + } + + loop.add(c); + } + + /// Establish a connection as a client. + pub fn connect( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + addr: std.Io.net.IpAddress, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.ConnectError!void, + ) xev.CallbackAction, + ) void { + if (xev.backend == .wasi_poll) @compileError("unsupported in WASI"); + + const posix_addr = net.Address.fromIpAddress(addr); + c.* = .{ + .op = .{ + .connect = .{ + .socket = self.fd, + .addr = posix_addr, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + initFd(c_inner.op.connect.socket), + if (r.connect) |_| {} else |err| err, + }); + } + }).callback, + }; + + loop.add(c); + } + + /// Shutdown the socket. This always only shuts down the writer side. You + /// can use the lower level interface directly to control this if the + /// platform supports it. + pub fn shutdown( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.ShutdownError!void, + ) xev.CallbackAction, + ) void { + c.* = .{ + .op = .{ + .shutdown = .{ + .socket = self.fd, + .how = .send, + }, + }, + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, ud), + l_inner, + c_inner, + initFd(c_inner.op.shutdown.socket), + if (r.shutdown) |_| {} else |err| err, + }); + } + }).callback, + }; + + loop.add(c); + } + + test { + _ = TCPTests(xev, Self); + } + }; +} + +fn TCPDynamic(comptime xev: type) type { + return struct { + const Self = @This(); + const FdType = if (builtin.os.tag == .windows) + std.os.windows.HANDLE + else + posix.socket_t; + + backend: Union, + + pub const Union = xev.Union(&.{"TCP"}); + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + .read = .read, + .write = .write, + .threadpool = true, + .type = "TCP", + }); + pub const close = S.close; + pub const poll = S.poll; + pub const read = S.read; + pub const write = S.write; + pub const queueWrite = S.queueWrite; + + pub fn init(addr: std.Io.net.IpAddress) !Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.TCP.init(addr), + ); + }, + } }; + } + + pub fn initFd(fdvalue: std.posix.pid_t) Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + api.TCP.initFd(fdvalue), + ); + }, + } }; + } + + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + switch (xev.backend) { + inline else => |tag| try @field( + self.backend, + @tagName(tag), + ).bind(addr), + } + } + + pub fn listen(self: Self, backlog: u31) !void { + switch (xev.backend) { + inline else => |tag| try @field( + self.backend, + @tagName(tag), + ).listen(backlog), + } + } + + pub fn fd(self: Self) FdType { + switch (xev.backend) { + inline else => |tag| return @field( + self.backend, + @tagName(tag), + ).fd, + } + } + + pub fn accept( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: xev.AcceptError!Self, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.AcceptError!api.TCP, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + if (r_inner) |tcp| + initFd(tcp.fd) + else |err| + err, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).accept( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn connect( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + addr: std.Io.net.IpAddress, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.ConnectError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: api.TCP, + r_inner: api.ConnectError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).connect( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + addr, + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn shutdown( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: Self, + r: xev.ShutdownError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + s_inner: api.TCP, + r_inner: api.ShutdownError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + Self.initFd(s_inner.fd), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).shutdown( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = TCPTests(xev, Self); + } + }; +} + +fn TCPTests(comptime xev: type, comptime Impl: type) type { + return struct { + test "TCP: Stream decls" { + if (!@hasDecl(Impl, "S")) return; + const Stream = Impl.S; + inline for (@typeInfo(Stream).@"struct".decls) |decl| { + const Decl = @TypeOf(@field(Stream, decl.name)); + if (Decl == void) continue; + if (!@hasDecl(Impl, decl.name)) { + @compileError("missing decl: " ++ decl.name); + } + } + } + + test "TCP: accept/connect/send/recv/close" { + // We have no way to get a socket in WASI from a WASI context. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + + var tpool = ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Choose random available port (Zig #14907) + var address = try std.Io.net.IpAddress.parse("127.0.0.1", 0); + const server = try Impl.init(address); + + // Bind and listen + try server.bind(address); + try server.listen(1); + + // Retrieve bound port and initialize client + var internal_addr = net.Address.fromIpAddress(address); + var sock_len = internal_addr.getOsSockLen(); + if (@hasField(@TypeOf(xev.backend), "iocp") and xev.backend == .iocp) { + const sock = @as(windows.ws2_32.SOCKET, @ptrCast(if (xev.dynamic) server.fd() else server.fd)); + var sl: i32 = @intCast(sock_len); + std.debug.assert(windows.ws2_32.getsockname(sock, &internal_addr.any, &sl) == 0); + sock_len = @intCast(sl); + } else { + const fd = if (xev.dynamic) server.fd() else server.fd; + try xev_posix.getsockname(fd, &internal_addr.any, &sock_len); + } + address = internal_addr.toIpAddress(); + const client = try Impl.init(address); + + // Completions we need + var c_accept: xev.Completion = undefined; + var c_connect: xev.Completion = undefined; + + // Accept + var server_conn: ?Impl = null; + server.accept(&loop, &c_accept, ?Impl, &server_conn, (struct { + fn callback( + ud: ?*?Impl, + _: *xev.Loop, + _: *xev.Completion, + r: xev.AcceptError!Impl, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Connect + var connected: bool = false; + client.connect(&loop, &c_connect, address, bool, &connected, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.ConnectError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + // Wait for the connection to be established + try loop.run(.until_done); + try testing.expect(server_conn != null); + try testing.expect(connected); + + // Close the server + var server_closed = false; + server.close(&loop, &c_accept, bool, &server_closed, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + try loop.run(.until_done); + try testing.expect(server_closed); + + // Send + var send_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + client.write(&loop, &c_connect, .{ .slice = &send_buf }, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + c: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = c; + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // Receive + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + server_conn.?.read(&loop, &c_accept, .{ .slice = &recv_buf }, usize, &recv_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &send_buf, recv_buf[0..recv_len]); + + // Close + server_conn.?.close(&loop, &c_accept, ?Impl, &server_conn, (struct { + fn callback( + ud: ?*?Impl, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = null; + return .disarm; + } + }).callback); + client.close(&loop, &c_connect, bool, &connected, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = false; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expect(server_conn == null); + try testing.expect(!connected); + try testing.expect(server_closed); + } + + // Potentially flaky - this test could hang if the sender is unable to + // write everything to the socket for whatever reason + // (e.g. incorrectly sized buffer on the receiver side), or if the + // receiver is trying to receive while sender has nothing left to send. + // + // Overview: + // 1. Set up server and client sockets + // 2. connect & accept, set SO_SNDBUF to 8kB on the client + // 3. Try to send 1MB buffer from client to server without queuing, this _should_ fail + // and theoretically send <= 8kB, but in practice, it seems to write ~32kB. + // Asserts that <= 100kB was written + // 4. Set up a queued write with the remaining buffer, shutdown() the socket afterwards + // 5. Set up a receiver that loops until it receives the entire buffer + // 6. Assert send_buf == recv_buf + test "TCP: Queued writes" { + // We have no way to get a socket in WASI from a WASI context. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // Windows doesn't seem to respect the SNDBUF socket option. + if (builtin.os.tag == .windows) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + + const testing = std.testing; + + var tpool = ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + // Choose random available port (Zig #14907) + var address = try std.Io.net.IpAddress.parse("127.0.0.1", 0); + const server = try Impl.init(address); + + // Bind and listen + try server.bind(address); + try server.listen(1); + + // Retrieve bound port and initialize client + var internal_addr = net.Address.fromIpAddress(address); + var sock_len = internal_addr.getOsSockLen(); + try xev_posix.getsockname(if (xev.dynamic) + server.fd() + else + server.fd, &internal_addr.any, &sock_len); + address = internal_addr.toIpAddress(); + const client = try Impl.init(address); + + // Completions we need + var c_accept: xev.Completion = undefined; + var c_connect: xev.Completion = undefined; + + // Accept + var server_conn: ?Impl = null; + server.accept(&loop, &c_accept, ?Impl, &server_conn, (struct { + fn callback( + ud: ?*?Impl, + _: *xev.Loop, + _: *xev.Completion, + r: xev.AcceptError!Impl, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Connect + var connected: bool = false; + client.connect(&loop, &c_connect, address, bool, &connected, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.ConnectError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + // Wait for the connection to be established + try loop.run(.until_done); + try testing.expect(server_conn != null); + try testing.expect(connected); + + // Close the server + var server_closed = false; + server.close(&loop, &c_accept, bool, &server_closed, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + try loop.run(.until_done); + try testing.expect(server_closed); + + // Unqueued send - Limit send buffer to 8kB, this should force partial writes. + try posix.setsockopt( + if (xev.dynamic) + client.fd() + else + client.fd, + posix.SOL.SOCKET, + posix.SO.SNDBUF, + &std.mem.toBytes(@as(c_int, 8192)), + ); + + const send_buf = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 } ** 100_000; + var sent_unqueued: usize = 0; + + // First we try to send the whole 1MB buffer in one write operation, this _should_ result + // in a partial write. + client.write(&loop, &c_connect, .{ .slice = &send_buf }, usize, &sent_unqueued, (struct { + fn callback( + sent_unqueued_inner: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + sent_unqueued_inner.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Make sure that we sent a small fraction of the buffer + try loop.run(.until_done); + // SO_SNDBUF doesn't seem to be respected exactly, sent_unqueued will often be ~32kB + // even though SO_SNDBUF was set to 8kB + try testing.expect(sent_unqueued < (send_buf.len / 10)); + + // Set up queued write + var w_queue = xev.WriteQueue{}; + var wr_send: xev.WriteRequest = undefined; + var sent_queued: usize = 0; + const queued_slice = send_buf[sent_unqueued..]; + client.queueWrite(&loop, &w_queue, &wr_send, .{ .slice = queued_slice }, usize, &sent_queued, (struct { + fn callback( + sent_queued_inner: ?*usize, + l: *xev.Loop, + c: *xev.Completion, + tcp: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + sent_queued_inner.?.* = r catch unreachable; + + tcp.shutdown(l, c, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ShutdownError!void, + ) xev.CallbackAction { + return .disarm; + } + }).callback); + + return .disarm; + } + }).callback); + + // Set up receiver which is going to keep reading until it reads the full + // send buffer + const Receiver = struct { + loop: *xev.Loop, + conn: Impl, + completion: xev.Completion = .{}, + buf: [send_buf.len]u8 = undefined, + bytes_read: usize = 0, + + pub fn read(receiver: *@This()) void { + if (receiver.bytes_read == receiver.buf.len) return; + + const read_buf = xev.ReadBuffer{ + .slice = receiver.buf[receiver.bytes_read..], + }; + receiver.conn.read(receiver.loop, &receiver.completion, read_buf, @This(), receiver, readCb); + } + + pub fn readCb( + receiver_opt: ?*@This(), + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + var receiver = receiver_opt.?; + const n_bytes = r catch unreachable; + + receiver.bytes_read += n_bytes; + if (receiver.bytes_read < send_buf.len) { + receiver.read(); + } + + return .disarm; + } + }; + var receiver = Receiver{ + .loop = &loop, + .conn = server_conn.?, + }; + receiver.read(); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expectEqualSlices(u8, &send_buf, receiver.buf[0..receiver.bytes_read]); + try testing.expect(send_buf.len == sent_unqueued + sent_queued); + + // Close + server_conn.?.close(&loop, &c_accept, ?Impl, &server_conn, (struct { + fn callback( + ud: ?*?Impl, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = null; + return .disarm; + } + }).callback); + client.close(&loop, &c_connect, bool, &connected, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = false; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + try testing.expect(server_conn == null); + try testing.expect(!connected); + try testing.expect(server_closed); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/timer.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/timer.zig new file mode 100644 index 0000000..18ebf97 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/timer.zig @@ -0,0 +1,641 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const os = std.os; + +/// A timer fires a callback after a specified amount of time. A timer can +/// repeat by returning "rearm" in the callback or by rescheduling the +/// start within the callback. +pub fn Timer(comptime xev: type) type { + if (xev.dynamic) return TimerDynamic(xev); + return TimerLoop(xev); +} + +/// An implementation that uses the loop timer methods. +fn TimerLoop(comptime xev: type) type { + return struct { + const Self = @This(); + + /// Create a new timer. + pub fn init() !Self { + return .{}; + } + + pub fn deinit(self: *const Self) void { + // Nothing for now. + _ = self; + } + + /// Start the timer. The timer will execute in next_ms milliseconds from + /// now. + /// + /// This will use the monotonic clock on your system if available so + /// this is immune to system clock changes or drift. The callback is + /// guaranteed to fire NO EARLIER THAN "next_ms" milliseconds. We can't + /// make any guarantees about exactness or time bounds because its possible + /// for your OS to just... pause.. the process for an indefinite period of + /// time. + /// + /// Like everything else in libxev, if you want something to repeat, you + /// must then requeue the completion manually. This punts off one of the + /// "hard" aspects of timers: it is up to you to determine what the semantic + /// meaning of intervals are. For example, if you want a timer to repeat every + /// 10 seconds, is it every 10th second of a wall clock? every 10th second + /// after an invocation? every 10th second after the work time from the + /// invocation? You have the power to answer these questions, manually. + pub fn run( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + next_ms: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: RunError!void, + ) xev.CallbackAction, + ) void { + _ = self; + + loop.timer(c, next_ms, userdata, (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), + l_inner, + c_inner, + if (r.timer) |trigger| @as(RunError!void, switch (trigger) { + .request, .expiration => {}, + .cancel => error.Canceled, + }) else |err| err, + }); + } + }).callback); + } + + /// Reset a timer to execute in next_ms milliseconds. If the timer + /// is already started, this will stop it and restart it. If the + /// timer has never been started, this is equivalent to running "run". + /// In every case, the timer callback is updated to the given userdata + /// and callback. + /// + /// This requires an additional completion c_cancel to represent + /// the need to possibly cancel the previous timer. You can check + /// if c_cancel was used by checking the state() after the call. + /// + /// VERY IMPORTANT: both c and c_cancel MUST NOT be undefined. They + /// must be initialized to ".{}" if being used for the first time. + pub fn reset( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + c_cancel: *xev.Completion, + next_ms: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: RunError!void, + ) xev.CallbackAction, + ) void { + _ = self; + + loop.timer_reset(c, c_cancel, next_ms, userdata, (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), + l_inner, + c_inner, + if (r.timer) |trigger| @as(RunError!void, switch (trigger) { + .request, .expiration => {}, + .cancel => error.Canceled, + }) else |err| err, + }); + } + }).callback); + } + + /// Cancel a previously started timer. The timer to cancel used the completion + /// "c_cancel". A new completion "c" must be specified which will be called + /// with the callback once cancellation is complete. + /// + /// The original timer will still have its callback fired but with the + /// error "error.Canceled". + pub fn cancel( + self: Self, + loop: *xev.Loop, + c_timer: *xev.Completion, + c_cancel: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: CancelError!void, + ) xev.CallbackAction, + ) void { + _ = self; + + c_cancel.* = switch (xev.backend) { + .io_uring => .{ + .op = .{ + .timer_remove = .{ + .timer = c_timer, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), + l_inner, + c_inner, + if (r.timer_remove) |_| {} else |err| err, + }); + } + }).callback, + }, + + .epoll, + .kqueue, + .wasi_poll, + .iocp, + => .{ + .op = .{ + .cancel = .{ + .c = c_timer, + }, + }, + + .userdata = userdata, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + return @call(.always_inline, cb, .{ + @as(?*Userdata, if (Userdata == void) null else @ptrCast(@alignCast(ud))), + l_inner, + c_inner, + if (r.cancel) |_| {} else |err| err, + }); + } + }).callback, + }, + }; + + loop.add(c_cancel); + } + + /// Error that could happen while running a timer. + pub const RunError = error{ + /// The timer was canceled before it could expire + Canceled, + + /// Some unexpected error. + Unexpected, + }; + + pub const CancelError = xev.CancelError; + + test { + _ = TimerTests(xev, Self); + } + }; +} + +fn TimerDynamic(comptime xev: type) type { + return struct { + const Self = @This(); + + backend: Union, + + pub const Union = xev.Union(&.{"Timer"}); + pub const RunError = xev.ErrorSet(&.{ "Timer", "RunError" }); + pub const CancelError = xev.ErrorSet(&.{ "Timer", "CancelError" }); + + pub fn init() !Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.Timer.init(), + ); + }, + } }; + } + + pub fn deinit(self: *Self) void { + switch (xev.backend) { + inline else => |tag| @field( + self.backend, + @tagName(tag), + ).deinit(), + } + } + + pub fn run( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + next_ms: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: RunError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.Timer.RunError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).run( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + next_ms, + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn reset( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + c_cancel: *xev.Completion, + next_ms: u64, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: RunError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c.ensureTag(tag); + c_cancel.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.Timer.RunError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).reset( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + &@field(c_cancel.value, @tagName(tag)), + next_ms, + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn cancel( + self: Self, + loop: *xev.Loop, + c_timer: *xev.Completion, + c_cancel: *xev.Completion, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + r: CancelError!void, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + c_timer.ensureTag(tag); + c_cancel.ensureTag(tag); + + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + r_inner: api.Timer.CancelError!void, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + r_inner, + ); + } + }).callback; + + @field( + self.backend, + @tagName(tag), + ).cancel( + &@field(loop.backend, @tagName(tag)), + &@field(c_timer.value, @tagName(tag)), + &@field(c_cancel.value, @tagName(tag)), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = TimerTests( + xev, + Self, + ); + } + }; +} + +fn TimerTests( + comptime xev: type, + comptime Impl: type, +) type { + return struct { + test "timer" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var timer = try Impl.init(); + defer timer.deinit(); + + // Add the timer + var called = false; + var c1: xev.Completion = undefined; + timer.run(&loop, &c1, 1, bool, &called, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.RunError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + // Wait + try loop.run(.until_done); + try testing.expect(called); + } + + test "timer reset" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var timer = try Impl.init(); + defer timer.deinit(); + + var c_timer: xev.Completion = .{}; + var c_cancel: xev.Completion = .{}; + const cb = (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.RunError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback; + + // Add the timer + var canceled = false; + timer.run(&loop, &c_timer, 100_000, bool, &canceled, cb); + + // Wait + try loop.run(.no_wait); + try testing.expect(!canceled); + + // Reset it + timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); + + try loop.run(.until_done); + try testing.expect(canceled); + try testing.expect(c_timer.state() == .dead); + try testing.expect(c_cancel.state() == .dead); + } + + test "timer reset before tick" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var timer = try Impl.init(); + defer timer.deinit(); + + var c_timer: xev.Completion = .{}; + var c_cancel: xev.Completion = .{}; + const cb = (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.RunError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback; + + // Add the timer + var canceled = false; + timer.run(&loop, &c_timer, 100_000, bool, &canceled, cb); + + // Reset it + timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); + + try loop.run(.until_done); + try testing.expect(canceled); + try testing.expect(c_timer.state() == .dead); + try testing.expect(c_cancel.state() == .dead); + } + + test "timer reset after trigger" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var timer = try Impl.init(); + defer timer.deinit(); + + var c_timer: xev.Completion = .{}; + var c_cancel: xev.Completion = .{}; + const cb = (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.RunError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback; + + // Add the timer + var canceled = false; + timer.run(&loop, &c_timer, 1, bool, &canceled, cb); + try loop.run(.until_done); + try testing.expect(canceled); + canceled = false; + + // Reset it + timer.reset(&loop, &c_timer, &c_cancel, 1, bool, &canceled, cb); + + try loop.run(.until_done); + try testing.expect(canceled); + try testing.expect(c_timer.state() == .dead); + try testing.expect(c_cancel.state() == .dead); + } + + test "timer cancel" { + const testing = std.testing; + + var loop = try xev.Loop.init(.{}); + defer loop.deinit(); + + var timer = try Impl.init(); + defer timer.deinit(); + + // Add the timer + var canceled = false; + var c1: xev.Completion = undefined; + timer.run(&loop, &c1, 100_000, bool, &canceled, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.RunError!void, + ) xev.CallbackAction { + ud.?.* = if (r) false else |err| err == error.Canceled; + return .disarm; + } + }).callback); + + // Cancel + var cancel_confirm = false; + var c2: xev.Completion = undefined; + timer.cancel(&loop, &c1, &c2, bool, &cancel_confirm, (struct { + fn callback( + ud: ?*bool, + _: *xev.Loop, + _: *xev.Completion, + r: Impl.CancelError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + ud.?.* = true; + return .disarm; + } + }).callback); + + // Wait + try loop.run(.until_done); + try testing.expect(canceled); + try testing.expect(cancel_confirm); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/udp.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/udp.zig new file mode 100644 index 0000000..a480e9e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/udp.zig @@ -0,0 +1,1016 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const posix = std.posix; +const stream = @import("stream.zig"); +const common = @import("common.zig"); +const ThreadPool = @import("../ThreadPool.zig"); +const net = @import("../posix.zig").net; +const xev_posix = @import("../posix.zig"); + +/// UDP client and server. +/// +/// This is a "higher-level abstraction" in libxev. The goal of higher-level +/// abstractions in libxev are to make it easier to use specific functionality +/// with the event loop, but does not promise perfect flexibility or optimal +/// performance. In almost all cases, the abstraction is good enough. But, +/// if you have specific needs or want to push for the most optimal performance, +/// use the platform-specific Loop directly. +pub fn UDP(comptime xev: type) type { + if (xev.dynamic) return UDPDynamic(xev); + + return switch (xev.backend) { + // Supported, uses sendmsg/recvmsg exclusively + .io_uring, + .epoll, + => UDPSendMsg(xev), + + // Supported, uses sendto/recvfrom + .kqueue => UDPSendto(xev), + + // Supported with tweaks + .iocp => UDPSendtoIOCP(xev), + + // Noop + .wasi_poll => struct {}, + }; +} + +/// UDP implementation that uses sendto/recvfrom. +fn UDPSendto(comptime xev: type) type { + return struct { + const Self = @This(); + + fd: posix.socket_t, + + /// See UDPSendMsg.State + pub const State = struct { + userdata: ?*anyopaque, + }; + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + }); + pub const close = S.close; + pub const poll = S.poll; + + /// Initialize a new UDP with the family from the given address. Only + /// the family is used, the actual address has no impact on the created + /// resource. + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); + return .{ + .fd = try xev_posix.socket( + posix_addr.any.family, + posix.SOCK.NONBLOCK | posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, + 0, + ), + }; + } + + /// Initialize a UDP socket from a file descriptor. + pub fn initFd(fd: posix.socket_t) Self { + return .{ + .fd = fd, + }; + } + + /// Bind the address to the socket. + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); + try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); + try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(self.fd, &posix_addr.any, posix_addr.getOsSockLen()); + } + + /// Read from the socket. This performs a single read. The callback must + /// requeue the read if additional reads want to be performed. Additional + /// reads simultaneously can be queued by calling this multiple times. Note + /// that depending on the backend, the reads can happen out of order. + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + s.* = .{ + .userdata = userdata, + }; + + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .recvfrom = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner = @as(?*State, @ptrCast(@alignCast(ud))).?; + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)).toIpAddress(), + initFd(c_inner.op.recvfrom.fd), + c_inner.op.recvfrom.buffer, + r.recvfrom, + }); + } + }).callback, + }; + + loop.add(c); + }, + } + } + + /// Write to the socket. This performs a single write. Additional writes + /// can be queued by calling this multiple times. Note that depending on the + /// backend, writes can happen out of order. + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + const posix_addr = net.Address.fromIpAddress(addr); + s.* = .{ + .userdata = userdata, + }; + + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .sendto = .{ + .fd = self.fd, + .buffer = buf, + .addr = posix_addr, + }, + }, + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner = @as(?*State, @ptrCast(@alignCast(ud))).?; + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + initFd(c_inner.op.sendto.fd), + c_inner.op.sendto.buffer, + r.sendto, + }); + } + }).callback, + }; + + loop.add(c); + }, + } + } + + test { + _ = UDPTests(xev, Self); + } + }; +} + +/// UDP implementation that uses sendto/recvfrom. +fn UDPSendtoIOCP(comptime xev: type) type { + return struct { + const Self = @This(); + const windows = @import("../windows.zig"); + + fd: windows.HANDLE, + + /// See UDPSendMsg.State + pub const State = struct { + userdata: ?*anyopaque, + }; + + const S = stream.Stream(xev, Self, .{ + .close = true, + }); + pub const close = S.close; + + /// Initialize a new UDP with the family from the given address. Only + /// the family is used, the actual address has no impact on the created + /// resource. + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); + const socket = try windows.WSASocketW(posix_addr.any.family, windows.ws2_32.SOCK.DGRAM, 0, null, 0, windows.ws2_32.WSA_FLAG_OVERLAPPED); + + return .{ + .fd = socket, + }; + } + + /// Initialize a UDP socket from a file descriptor. + pub fn initFd(fd: windows.HANDLE) Self { + return .{ + .fd = fd, + }; + } + + /// Bind the address to the socket. + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); + const socket = @as(windows.ws2_32.SOCKET, @ptrCast(self.fd)); + if (windows.ws2_32.setsockopt(socket, windows.ws2_32.SOL.SOCKET, windows.ws2_32.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)), @sizeOf(c_int)) != 0) return error.Unexpected; + if (windows.ws2_32.bind(socket, &posix_addr.any, @as(i32, @intCast(posix_addr.getOsSockLen()))) != 0) return error.Unexpected; + } + + /// Read from the socket. This performs a single read. The callback must + /// requeue the read if additional reads want to be performed. Additional + /// reads simultaneously can be queued by calling this multiple times. Note + /// that depending on the backend, the reads can happen out of order. + /// + /// TODO(mitchellh): a way to receive the remote addr + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + s.* = .{ + .userdata = userdata, + }; + + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .recvfrom = .{ + .fd = self.fd, + .buffer = buf, + }, + }, + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner: *State = @ptrCast(@alignCast(ud.?)); + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + net.Address.initPosix(@alignCast(&c_inner.op.recvfrom.addr)).toIpAddress(), + initFd(c_inner.op.recvfrom.fd), + c_inner.op.recvfrom.buffer, + r.recvfrom, + }); + } + }).callback, + }; + + loop.add(c); + }, + } + } + + /// Write to the socket. This performs a single write. Additional writes + /// can be queued by calling this multiple times. Note that depending on the + /// backend, writes can happen out of order. + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + const posix_addr = net.Address.fromIpAddress(addr); + s.* = .{ + .userdata = userdata, + }; + + switch (buf) { + inline .slice, .array => { + c.* = .{ + .op = .{ + .sendto = .{ + .fd = self.fd, + .buffer = buf, + .addr = posix_addr, + }, + }, + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner: *State = @ptrCast(@alignCast(ud.?)); + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + initFd(c_inner.op.sendto.fd), + c_inner.op.sendto.buffer, + r.sendto, + }); + } + }).callback, + }; + + loop.add(c); + }, + } + } + + test { + _ = UDPTests(xev, Self); + } + }; +} + +/// UDP implementation that uses sendmsg/recvmsg +fn UDPSendMsg(comptime xev: type) type { + return struct { + const Self = @This(); + + const linux = std.os.linux; + + fd: posix.socket_t, + + /// UDP requires some extra state to perform operations. The state is + /// opaque. This isn't part of xev.Completion because it is relatively + /// large and would force ALL operations (not just UDP) to have a relatively + /// large structure size and we didn't want to pay that cost. + pub const State = struct { + userdata: ?*anyopaque = null, + op: union { + recv: struct { + buf: xev.ReadBuffer, + addr_buffer: std.posix.sockaddr.storage = undefined, + msghdr: linux.msghdr, + iov: [1]posix.iovec, + }, + + send: struct { + buf: xev.WriteBuffer, + addr: net.Address, + msghdr: linux.msghdr_const, + iov: [1]posix.iovec_const, + }, + }, + }; + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + }); + pub const close = S.close; + pub const poll = S.poll; + + /// Initialize a new UDP with the family from the given address. Only + /// the family is used, the actual address has no impact on the created + /// resource. + pub fn init(addr: std.Io.net.IpAddress) !Self { + const posix_addr = net.Address.fromIpAddress(addr); + // On io_uring we don't use non-blocking sockets because we may + // just get EAGAIN over and over from completions. + const flags = flags: { + var flags: u32 = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC; + if (xev.backend != .io_uring) flags |= posix.SOCK.NONBLOCK; + break :flags flags; + }; + + return .{ + .fd = try xev_posix.socket(posix_addr.any.family, flags, 0), + }; + } + + /// Initialize a UDP socket from a file descriptor. + pub fn initFd(fd: posix.socket_t) Self { + return .{ + .fd = fd, + }; + } + + /// Bind the address to the socket. + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + const posix_addr = net.Address.fromIpAddress(addr); + try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); + try posix.setsockopt(self.fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); + try xev_posix.bind(self.fd, &posix_addr.any, posix_addr.getOsSockLen()); + } + + /// Read from the socket. This performs a single read. The callback must + /// requeue the read if additional reads want to be performed. Additional + /// reads simultaneously can be queued by calling this multiple times. Note + /// that depending on the backend, the reads can happen out of order. + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + s.op = .{ .recv = undefined }; + s.* = .{ + .userdata = userdata, + .op = .{ + .recv = .{ + .buf = buf, + .msghdr = .{ + .name = @ptrCast(&s.op.recv.addr_buffer), + .namelen = @sizeOf(@TypeOf(s.op.recv.addr_buffer)), + .iov = &s.op.recv.iov, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }, + .iov = undefined, + }, + }, + }; + + switch (s.op.recv.buf) { + .slice => |v| { + s.op.recv.iov[0] = .{ + .base = v.ptr, + .len = v.len, + }; + }, + + .array => |*arr| { + s.op.recv.iov[0] = .{ + .base = arr, + .len = arr.len, + }; + }, + } + + c.* = .{ + .op = .{ + .recvmsg = .{ + .fd = self.fd, + .msghdr = &s.op.recv.msghdr, + }, + }, + + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner = @as(?*State, @ptrCast(@alignCast(ud))).?; + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + net.Address.initPosix(@ptrCast(&s_inner.op.recv.addr_buffer)).toIpAddress(), + initFd(c_inner.op.recvmsg.fd), + s_inner.op.recv.buf, + if (r.recvmsg) |v| v else |err| err, + }); + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .kqueue, + .wasi_poll, + .iocp, + => {}, + + .epoll => c.flags.dup = true, + } + + loop.add(c); + } + + /// Write to the socket. This performs a single write. Additional writes + /// can be queued by calling this multiple times. Note that depending on the + /// backend, writes can happen out of order. + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + const posix_addr = net.Address.fromIpAddress(addr); + // Set the active field for runtime safety + s.op = .{ .send = undefined }; + s.* = .{ + .userdata = userdata, + .op = .{ + .send = .{ + .addr = posix_addr, + .buf = buf, + .msghdr = .{ + .name = &s.op.send.addr.any, + .namelen = posix_addr.getOsSockLen(), + .iov = &s.op.send.iov, + .iovlen = 1, + .control = null, + .controllen = 0, + .flags = 0, + }, + .iov = undefined, + }, + }, + }; + + switch (s.op.send.buf) { + .slice => |v| { + s.op.send.iov[0] = .{ + .base = v.ptr, + .len = v.len, + }; + }, + + .array => |*arr| { + s.op.send.iov[0] = .{ + .base = &arr.array, + .len = arr.len, + }; + }, + } + + // On backends like epoll, you watch file descriptors for + // specific events. Our implementation doesn't merge multiple + // completions for a single fd, so we have to dup the fd. This + // means we use more fds than we could optimally. This isn't a + // problem with io_uring. + + c.* = .{ + .op = .{ + .sendmsg = .{ + .fd = self.fd, + .msghdr = &s.op.send.msghdr, + }, + }, + + .userdata = s, + .callback = (struct { + fn callback( + ud: ?*anyopaque, + l_inner: *xev.Loop, + c_inner: *xev.Completion, + r: xev.Result, + ) xev.CallbackAction { + const s_inner = @as(?*State, @ptrCast(@alignCast(ud))).?; + return @call(.always_inline, cb, .{ + common.userdataValue(Userdata, s_inner.userdata), + l_inner, + c_inner, + s_inner, + initFd(c_inner.op.sendmsg.fd), + s_inner.op.send.buf, + if (r.sendmsg) |v| v else |err| err, + }); + } + }).callback, + }; + + // If we're dup-ing, then we ask the backend to manage the fd. + switch (xev.backend) { + .io_uring, + .kqueue, + .wasi_poll, + .iocp, + => {}, + + .epoll => c.flags.dup = true, + } + + loop.add(c); + } + + test { + _ = UDPTests(xev, Self); + } + }; +} + +fn UDPDynamic(comptime xev: type) type { + return struct { + const Self = @This(); + const FdType = if (builtin.os.tag == .windows) + std.os.windows.HANDLE + else + posix.socket_t; + + backend: Union, + + pub const Union = xev.Union(&.{"UDP"}); + pub const State = xev.Union(&.{ "UDP", "State" }); + + const S = stream.Stream(xev, Self, .{ + .close = true, + .poll = true, + .type = "UDP", + }); + pub const close = S.close; + pub const poll = S.poll; + + pub fn init(addr: std.Io.net.IpAddress) !Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + try api.UDP.init(addr), + ); + }, + } }; + } + + pub fn initFd(fdvalue: std.posix.pid_t) Self { + return .{ .backend = switch (xev.backend) { + inline else => |tag| backend: { + const api = (comptime xev.superset(tag)).Api(); + break :backend @unionInit( + Union, + @tagName(tag), + api.UDP.initFd(fdvalue), + ); + }, + } }; + } + + pub fn bind(self: Self, addr: std.Io.net.IpAddress) !void { + switch (xev.backend) { + inline else => |tag| try @field( + self.backend, + @tagName(tag), + ).bind(addr), + } + } + + pub fn fd(self: Self) FdType { + switch (xev.backend) { + inline else => |tag| return @field( + self.backend, + @tagName(tag), + ).fd, + } + } + + pub fn read( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + buf: xev.ReadBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + s: Self, + b: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + st_inner: *api.UDP.State, + addr_inner: std.Io.net.IpAddress, + s_inner: api.UDP, + b_inner: api.ReadBuffer, + r_inner: api.ReadError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + @fieldParentPtr( + @tagName(tag), + st_inner, + ), + addr_inner, + Self.initFd(s_inner.fd), + xev.ReadBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + c.ensureTag(tag); + s.* = @unionInit(State, @tagName(tag), undefined); + + @field( + self.backend, + @tagName(tag), + ).read( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + &@field(s, @tagName(tag)), + buf.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + pub fn write( + self: Self, + loop: *xev.Loop, + c: *xev.Completion, + s: *State, + addr: std.Io.net.IpAddress, + buf: xev.WriteBuffer, + comptime Userdata: type, + userdata: ?*Userdata, + comptime cb: *const fn ( + ud: ?*Userdata, + l: *xev.Loop, + c: *xev.Completion, + s: *State, + s: Self, + b: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction, + ) void { + switch (xev.backend) { + inline else => |tag| { + const api = (comptime xev.superset(tag)).Api(); + const api_cb = (struct { + fn callback( + ud_inner: ?*Userdata, + l_inner: *api.Loop, + c_inner: *api.Completion, + st_inner: *api.UDP.State, + s_inner: api.UDP, + b_inner: api.WriteBuffer, + r_inner: api.WriteError!usize, + ) xev.CallbackAction { + return cb( + ud_inner, + @fieldParentPtr("backend", @as( + *xev.Loop.Union, + @fieldParentPtr(@tagName(tag), l_inner), + )), + @fieldParentPtr("value", @as( + *xev.Completion.Union, + @fieldParentPtr(@tagName(tag), c_inner), + )), + @fieldParentPtr( + @tagName(tag), + st_inner, + ), + Self.initFd(s_inner.fd), + xev.WriteBuffer.fromBackend(tag, b_inner), + r_inner, + ); + } + }).callback; + + c.ensureTag(tag); + s.* = @unionInit(State, @tagName(tag), undefined); + + @field( + self.backend, + @tagName(tag), + ).write( + &@field(loop.backend, @tagName(tag)), + &@field(c.value, @tagName(tag)), + &@field(s, @tagName(tag)), + addr, + buf.toBackend(tag), + Userdata, + userdata, + api_cb, + ); + }, + } + } + + test { + _ = UDPTests(xev, Self); + } + }; +} + +fn UDPTests(comptime xev: type, comptime Impl: type) type { + return struct { + test "UDP: Stream decls" { + if (!@hasDecl(Impl, "S")) return; + const Stream = Impl.S; + inline for (@typeInfo(Stream).@"struct".decls) |decl| { + const Decl = @TypeOf(@field(Stream, decl.name)); + if (Decl == void) continue; + if (!@hasDecl(Impl, decl.name)) { + @compileError("missing decl: " ++ decl.name); + } + } + } + + test "UDP: read/write" { + if (builtin.os.tag == .freebsd) return error.SkipZigTest; + const testing = std.testing; + + var tpool = ThreadPool.init(.{}); + defer tpool.deinit(); + defer tpool.shutdown(); + var loop = try xev.Loop.init(.{ .thread_pool = &tpool }); + defer loop.deinit(); + + const address = try std.Io.net.IpAddress.parse("127.0.0.1", 3132); + const server = try Impl.init(address); + const client = try Impl.init(address); + + // Bind / Recv + try server.bind(address); + var c_read: xev.Completion = undefined; + var s_read: Impl.State = undefined; + var recv_buf: [128]u8 = undefined; + var recv_len: usize = 0; + server.read(&loop, &c_read, &s_read, .{ .slice = &recv_buf }, usize, &recv_len, (struct { + fn callback( + ud: ?*usize, + _: *xev.Loop, + _: *xev.Completion, + _: *Impl.State, + _: std.Io.net.IpAddress, + _: Impl, + _: xev.ReadBuffer, + r: xev.ReadError!usize, + ) xev.CallbackAction { + ud.?.* = r catch unreachable; + return .disarm; + } + }).callback); + + // Send + var send_buf = [_]u8{ 1, 1, 2, 3, 5, 8, 13 }; + var c_write: xev.Completion = undefined; + var s_write: Impl.State = undefined; + client.write(&loop, &c_write, &s_write, address, .{ .slice = &send_buf }, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: *Impl.State, + _: Impl, + _: xev.WriteBuffer, + r: xev.WriteError!usize, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback); + + // Wait for the send/receive + try loop.run(.until_done); + try testing.expect(recv_len > 0); + try testing.expectEqualSlices(u8, &send_buf, recv_buf[0..recv_len]); + + // Close + server.close(&loop, &c_read, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback); + client.close(&loop, &c_write, void, null, (struct { + fn callback( + _: ?*void, + _: *xev.Loop, + _: *xev.Completion, + _: Impl, + r: xev.CloseError!void, + ) xev.CallbackAction { + _ = r catch unreachable; + return .disarm; + } + }).callback); + + try loop.run(.until_done); + } + }; +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/windows.zig b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/windows.zig new file mode 100644 index 0000000..8ddbe3a --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/windows.zig @@ -0,0 +1,815 @@ +//! Namespace containing missing utils from std. +//! This acts as a compat shim for Win32 APIs removed from std.os.windows in Zig 0.16. + +const std = @import("std"); +const win = std.os.windows; +const posix = std.posix; + +// Forwarded declarations of std.os.windows types that still exist. +pub const ULONG_PTR = win.ULONG_PTR; +pub const PVOID = win.PVOID; +pub const DWORD = win.DWORD; +pub const HANDLE = win.HANDLE; +pub const INVALID_HANDLE_VALUE = win.INVALID_HANDLE_VALUE; +pub const Win32Error = win.Win32Error; +pub const DUPLICATE_SAME_ACCESS = win.DUPLICATE_SAME_ACCESS; +pub const unexpectedError = win.unexpectedError; +pub const CloseHandle = win.CloseHandle; + +pub const BOOL = win.BOOL; +pub const FALSE: BOOL = .FALSE; +pub const TRUE: BOOL = BOOL.TRUE; + +// Constants removed from std.os.windows in Zig 0.16. +pub const INFINITE: DWORD = 0xFFFF_FFFF; +pub const GENERIC_READ: DWORD = 0x80000000; +pub const GENERIC_WRITE: DWORD = 0x40000000; +pub const OPEN_ALWAYS: DWORD = 4; +pub const FILE_FLAG_OVERLAPPED: DWORD = 0x40000000; + +pub const OVERLAPPED = extern struct { + Internal: ULONG_PTR = 0, + InternalHigh: ULONG_PTR = 0, + DUMMYUNIONNAME: extern union { + DUMMYSTRUCTNAME: extern struct { + Offset: DWORD, + OffsetHigh: DWORD, + }, + Pointer: ?PVOID, + } = .{ .DUMMYSTRUCTNAME = .{ .Offset = 0, .OffsetHigh = 0 } }, + hEvent: ?HANDLE = null, +}; +pub const OVERLAPPED_ENTRY = extern struct { + lpCompletionKey: ULONG_PTR, + lpOverlapped: *OVERLAPPED, + Internal: ULONG_PTR, + dwNumberOfBytesTransferred: DWORD, +}; + +pub const WSABUF = extern struct { + len: win.ULONG, + buf: [*]u8, +}; + +pub const ReadFileError = error{ + BrokenPipe, + ConnectionResetByPeer, + OperationAborted, + LockViolation, + AccessDenied, + NotOpenForReading, + Unexpected, +}; +pub const WriteFileError = error{ + SystemResources, + OperationAborted, + BrokenPipe, + NotOpenForWriting, + LockViolation, + ConnectionResetByPeer, + AccessDenied, + Unexpected, +}; + +// --- kernel32 compat shim --- +// In Zig 0.16, nearly all kernel32 extern functions were removed from std. +// We re-declare the ones this project needs. +pub const kernel32 = struct { + pub fn GetLastError() Win32Error { + return win.GetLastError(); + } + + pub fn GetCurrentProcess() HANDLE { + return win.GetCurrentProcess(); + } + + pub extern "kernel32" fn CreateFileW( + lpFileName: [*:0]const u16, + dwDesiredAccess: DWORD, + dwShareMode: DWORD, + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + dwCreationDisposition: DWORD, + dwFlagsAndAttributes: DWORD, + hTemplateFile: ?HANDLE, + ) callconv(.winapi) HANDLE; + + pub extern "kernel32" fn ReadFile( + hFile: HANDLE, + lpBuffer: *anyopaque, + nNumberOfBytesToRead: DWORD, + lpNumberOfBytesRead: ?*DWORD, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn WriteFile( + in_hFile: HANDLE, + in_lpBuffer: [*]const u8, + in_nNumberOfBytesToWrite: DWORD, + out_lpNumberOfBytesWritten: ?*DWORD, + in_out_lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn CancelIoEx( + hFile: HANDLE, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn DuplicateHandle( + hSourceProcessHandle: HANDLE, + hSourceHandle: HANDLE, + hTargetProcessHandle: HANDLE, + lpTargetHandle: *HANDLE, + dwDesiredAccess: DWORD, + bInheritHandle: BOOL, + dwOptions: DWORD, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn GetExitCodeProcess( + hProcess: HANDLE, + lpExitCode: *DWORD, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn GetOverlappedResult( + hFile: HANDLE, + lpOverlapped: *OVERLAPPED, + lpNumberOfBytesTransferred: *DWORD, + bWait: BOOL, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn CreateIoCompletionPort( + FileHandle: HANDLE, + ExistingCompletionPort: ?HANDLE, + CompletionKey: ULONG_PTR, + NumberOfConcurrentThreads: DWORD, + ) callconv(.winapi) ?HANDLE; + + pub extern "kernel32" fn GetQueuedCompletionStatusEx( + CompletionPort: HANDLE, + lpCompletionPortEntries: [*]OVERLAPPED_ENTRY, + ulCount: win.ULONG, + ulNumEntriesRemoved: *win.ULONG, + dwMilliseconds: DWORD, + fAlertable: BOOL, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn PostQueuedCompletionStatus( + CompletionPort: HANDLE, + dwNumberOfBytesTransferred: DWORD, + dwCompletionKey: ULONG_PTR, + lpOverlapped: ?*OVERLAPPED, + ) callconv(.winapi) BOOL; + + pub extern "kernel32" fn DeleteFileW( + lpFileName: [*:0]const u16, + ) callconv(.winapi) BOOL; +}; + +// --- ws2_32 compat shim --- +// In Zig 0.16, all ws2_32 extern functions, WinsockError, WSABUF, WSA_FLAG_OVERLAPPED, etc. +// were removed. We re-declare the ones this project needs, and forward remaining constants. +pub const ws2_32 = struct { + pub const SOCKET = *opaque {}; + pub const INVALID_SOCKET: SOCKET = @ptrFromInt(~@as(usize, 0)); + pub const SOCKET_ERROR: i32 = -1; + pub const WSA_FLAG_OVERLAPPED: u32 = 1; + + pub const LPWSAOVERLAPPED_COMPLETION_ROUTINE = *const fn ( + dwError: DWORD, + cbTransferred: DWORD, + lpOverlapped: *OVERLAPPED, + dwFlags: DWORD, + ) callconv(.winapi) void; + + pub const WSAPROTOCOL_INFOW = extern struct { + dwServiceFlags1: DWORD, + dwServiceFlags2: DWORD, + dwServiceFlags3: DWORD, + dwServiceFlags4: DWORD, + dwProviderFlags: DWORD, + ProviderId: win.GUID, + dwCatalogEntryId: DWORD, + ProtocolChain: WSAPROTOCOLCHAIN, + iVersion: c_int, + iAddressFamily: c_int, + iMaxSockAddr: c_int, + iMinSockAddr: c_int, + iSocketType: c_int, + iProtocol: c_int, + iProtocolMaxOffset: c_int, + iNetworkByteOrder: c_int, + iSecurityScheme: c_int, + dwMessageSize: DWORD, + dwProviderReserved: DWORD, + szProtocol: [256]u16, + }; + + pub const WSAPROTOCOLCHAIN = extern struct { + ChainLen: c_int, + ChainEntries: [7]DWORD, + }; + + pub const WSADATA = extern struct { + wVersion: u16, + wHighVersion: u16, + iMaxSockets: u16, + iMaxUdpDg: u16, + lpVendorInfo: ?[*]u8, + szDescription: [257]u8, + szSystemStatus: [129]u8, + }; + + pub const WinsockError = enum(u16) { + WSA_INVALID_HANDLE = 6, + WSA_NOT_ENOUGH_MEMORY = 8, + WSA_INVALID_PARAMETER = 87, + WSA_OPERATION_ABORTED = 995, + WSA_IO_INCOMPLETE = 996, + WSA_IO_PENDING = 997, + WSAEINTR = 10004, + WSAEBADF = 10009, + WSAEACCES = 10013, + WSAEFAULT = 10014, + WSAEINVAL = 10022, + WSAEMFILE = 10024, + WSAEWOULDBLOCK = 10035, + WSAEINPROGRESS = 10036, + WSAEALREADY = 10037, + WSAENOTSOCK = 10038, + WSAEDESTADDRREQ = 10039, + WSAEMSGSIZE = 10040, + WSAEPROTOTYPE = 10041, + WSAENOPROTOOPT = 10042, + WSAEPROTONOSUPPORT = 10043, + WSAESOCKTNOSUPPORT = 10044, + WSAEOPNOTSUPP = 10045, + WSAEPFNOSUPPORT = 10046, + WSAEAFNOSUPPORT = 10047, + WSAEADDRINUSE = 10048, + WSAEADDRNOTAVAIL = 10049, + WSAENETDOWN = 10050, + WSAENETUNREACH = 10051, + WSAENETRESET = 10052, + WSAECONNABORTED = 10053, + WSAECONNRESET = 10054, + WSAENOBUFS = 10055, + WSAEISCONN = 10056, + WSAENOTCONN = 10057, + WSAESHUTDOWN = 10058, + WSAETOOMANYREFS = 10059, + WSAETIMEDOUT = 10060, + WSAECONNREFUSED = 10061, + WSAELOOP = 10062, + WSAENAMETOOLONG = 10063, + WSAEHOSTDOWN = 10064, + WSAEHOSTUNREACH = 10065, + WSAENOTEMPTY = 10066, + WSAEPROCLIM = 10067, + WSAEUSERS = 10068, + WSAEDQUOT = 10069, + WSAESTALE = 10070, + WSAEREMOTE = 10071, + WSASYSNOTREADY = 10091, + WSAVERNOTSUPPORTED = 10092, + WSANOTINITIALISED = 10093, + WSAEDISCON = 10101, + WSAENOMORE = 10102, + WSAECANCELLED = 10103, + WSAEINVALIDPROCTABLE = 10104, + WSAEINVALIDPROVIDER = 10105, + WSAEPROVIDERFAILEDINIT = 10106, + WSASYSCALLFAILURE = 10107, + WSASERVICE_NOT_FOUND = 10108, + WSATYPE_NOT_FOUND = 10109, + WSA_E_NO_MORE = 10110, + WSA_E_CANCELLED = 10111, + WSAEREFUSED = 10112, + WSAHOST_NOT_FOUND = 11001, + WSATRY_AGAIN = 11002, + WSANO_RECOVERY = 11003, + WSANO_DATA = 11004, + WSA_QOS_RECEIVERS = 11005, + WSA_QOS_SENDERS = 11006, + WSA_QOS_NO_SENDERS = 11007, + WSA_QOS_NO_RECEIVERS = 11008, + WSA_QOS_REQUEST_CONFIRMED = 11009, + WSA_QOS_ADMISSION_FAILURE = 11010, + WSA_QOS_POLICY_FAILURE = 11011, + WSA_QOS_BAD_STYLE = 11012, + WSA_QOS_BAD_OBJECT = 11013, + WSA_QOS_TRAFFIC_CTRL_ERROR = 11014, + WSA_QOS_GENERIC_ERROR = 11015, + WSA_QOS_ESERVICETYPE = 11016, + WSA_QOS_EFLOWSPEC = 11017, + WSA_QOS_EPROVSPECBUF = 11018, + WSA_QOS_EFILTERSTYLE = 11019, + WSA_QOS_EFILTERTYPE = 11020, + WSA_QOS_EFILTERCOUNT = 11021, + WSA_QOS_EOBJLENGTH = 11022, + WSA_QOS_EFLOWCOUNT = 11023, + WSA_QOS_EUNKOWNPSOBJ = 11024, + WSA_QOS_EPOLICYOBJ = 11025, + WSA_QOS_EFLOWDESC = 11026, + WSA_QOS_EPSFLOWSPEC = 11027, + WSA_QOS_EPSFILTERSPEC = 11028, + WSA_QOS_ESDMODEOBJ = 11029, + WSA_QOS_ESHAPERATEOBJ = 11030, + WSA_QOS_RESERVED_PETYPE = 11031, + _, + }; + + // Forward constants/types from std that still exist. + pub const sockaddr = win.ws2_32.sockaddr; + pub const AF = win.ws2_32.AF; + pub const SOCK = win.ws2_32.SOCK; + pub const SOL = win.ws2_32.SOL; + pub const SO = win.ws2_32.SO; + pub const IPPROTO = win.ws2_32.IPPROTO; + pub const SD_RECEIVE: i32 = 0; + pub const SD_SEND: i32 = 1; + pub const SD_BOTH: i32 = 2; + + pub extern "ws2_32" fn WSASocketW( + af: i32, + @"type": i32, + protocol: i32, + lpProtocolInfo: ?*WSAPROTOCOL_INFOW, + g: u32, + dwFlags: u32, + ) callconv(.winapi) SOCKET; + + pub extern "ws2_32" fn WSAStartup( + wVersionRequested: u16, + lpWSAData: *WSADATA, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn connect( + s: SOCKET, + name: *const posix.sockaddr, + namelen: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn getsockname( + s: SOCKET, + name: *posix.sockaddr, + namelen: *i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn getsockopt( + s: SOCKET, + level: i32, + optname: i32, + optval: [*]u8, + optlen: *i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSAGetLastError() callconv(.winapi) WinsockError; + + pub extern "ws2_32" fn WSAGetOverlappedResult( + s: SOCKET, + lpOverlapped: *OVERLAPPED, + lpcbTransfer: *u32, + fWait: BOOL, + lpdwFlags: *u32, + ) callconv(.winapi) BOOL; + + pub extern "ws2_32" fn WSASend( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesSent: ?*u32, + dwFlags: u32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSARecv( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesRecv: ?*u32, + lpFlags: *u32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSASendTo( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesSent: ?*u32, + dwFlags: u32, + lpTo: ?*const posix.sockaddr, + iToLen: i32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn WSARecvFrom( + s: SOCKET, + lpBuffers: [*]WSABUF, + dwBufferCount: u32, + lpNumberOfBytesRecvd: ?*u32, + lpFlags: *u32, + lpFrom: ?*posix.sockaddr, + lpFromlen: ?*i32, + lpOverlapped: ?*OVERLAPPED, + lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn shutdown( + s: SOCKET, + how: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn closesocket( + s: SOCKET, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn bind( + s: SOCKET, + name: *const posix.sockaddr, + namelen: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn listen( + s: SOCKET, + backlog: i32, + ) callconv(.winapi) i32; + + pub extern "ws2_32" fn setsockopt( + s: SOCKET, + level: i32, + optname: i32, + optval: [*]const u8, + optlen: i32, + ) callconv(.winapi) i32; + + pub extern "mswsock" fn AcceptEx( + sListenSocket: SOCKET, + sAcceptSocket: SOCKET, + lpOutputBuffer: *anyopaque, + dwReceiveDataLength: u32, + dwLocalAddressLength: u32, + dwRemoteAddressLength: u32, + lpdwBytesReceived: *u32, + lpOverlapped: *OVERLAPPED, + ) callconv(.winapi) BOOL; +}; + +// --- High-level wrapper functions --- + +pub fn unexpectedWSAError(err: ws2_32.WinsockError) error{Unexpected} { + return unexpectedError(@as(Win32Error, @enumFromInt(@intFromEnum(err)))); +} + +pub fn QueryPerformanceCounter() u64 { + var result: win.LARGE_INTEGER = undefined; + std.debug.assert(win.ntdll.RtlQueryPerformanceCounter(&result) != .FALSE); + return @as(u64, @bitCast(result)); +} + +pub fn QueryPerformanceFrequency() u64 { + var result: win.LARGE_INTEGER = undefined; + std.debug.assert(win.ntdll.RtlQueryPerformanceFrequency(&result) != .FALSE); + return @as(u64, @bitCast(result)); +} + +pub const GetQueuedCompletionStatusError = error{ + Aborted, + Cancelled, + EOF, + Timeout, +} || error{Unexpected}; + +pub fn GetQueuedCompletionStatusEx( + completion_port: HANDLE, + completion_port_entries: []OVERLAPPED_ENTRY, + timeout_ms: ?DWORD, + alertable: bool, +) GetQueuedCompletionStatusError!u32 { + var num_entries_removed: u32 = 0; + + const success = kernel32.GetQueuedCompletionStatusEx( + completion_port, + completion_port_entries.ptr, + @as(win.ULONG, @intCast(completion_port_entries.len)), + &num_entries_removed, + timeout_ms orelse INFINITE, + BOOL.fromBool(alertable), + ); + + if (success == .FALSE) { + return switch (kernel32.GetLastError()) { + .ABANDONED_WAIT_0 => error.Aborted, + .OPERATION_ABORTED => error.Cancelled, + .HANDLE_EOF => error.EOF, + .WAIT_TIMEOUT => error.Timeout, + else => |err| unexpectedError(err), + }; + } + + return num_entries_removed; +} + +pub const PostQueuedCompletionStatusError = error{Unexpected}; + +pub fn PostQueuedCompletionStatus( + completion_port: HANDLE, + bytes_transferred_count: DWORD, + completion_key: usize, + lpOverlapped: ?*OVERLAPPED, +) PostQueuedCompletionStatusError!void { + if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == .FALSE) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } +} + +pub fn CreateIoCompletionPort( + file_handle: HANDLE, + existing_completion_port: ?HANDLE, + completion_key: usize, + concurrent_thread_count: DWORD, +) error{Unexpected}!HANDLE { + const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { + switch (kernel32.GetLastError()) { + .INVALID_PARAMETER => unreachable, + else => |err| return unexpectedError(err), + } + }; + return handle; +} + +var wsa_initialized: bool = false; + +fn callWSAStartup() !void { + if (@atomicLoad(bool, &wsa_initialized, .acquire)) return; + + var wsadata: ws2_32.WSADATA = undefined; + const rc = ws2_32.WSAStartup(0x0202, &wsadata); + if (rc != 0) return error.Unexpected; + + @atomicStore(bool, &wsa_initialized, true, .release); +} + +pub fn WSASocketW( + af: i32, + socket_type: i32, + protocol: i32, + protocolInfo: ?*ws2_32.WSAPROTOCOL_INFOW, + g: u32, + dwFlags: DWORD, +) !ws2_32.SOCKET { + var first = true; + while (true) { + const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags); + if (rc == ws2_32.INVALID_SOCKET) { + switch (ws2_32.WSAGetLastError()) { + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEMFILE => return error.ProcessFdQuotaExceeded, + .WSAENOBUFS => return error.SystemResources, + .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported, + .WSANOTINITIALISED => { + if (!first) return error.Unexpected; + first = false; + try callWSAStartup(); + continue; + }, + else => |err| return unexpectedWSAError(err), + } + } + return rc; + } +} + +pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) error{ InvalidWtf8, BadPathName, NameTooLong, Unexpected, AccessDenied, FileNotFound }!PathSpace { + _ = dir; + var result: PathSpace = undefined; + result.len = std.unicode.wtf8ToWtf16Le(&result.data, path) catch return error.InvalidWtf8; + result.data[result.len] = 0; + return result; +} + +pub const PathSpace = struct { + data: [260]u16, + len: usize, + + pub fn span(self: *const PathSpace) [:0]const u16 { + return self.data[0..self.len :0]; + } +}; + +pub const exp = struct { + pub const STATUS_PENDING = 0x00000103; + pub const STILL_ACTIVE = STATUS_PENDING; + + pub const JOBOBJECT_ASSOCIATE_COMPLETION_PORT = extern struct { + CompletionKey: win.ULONG_PTR, + CompletionPort: win.HANDLE, + }; + + pub const JOBOBJECT_BASIC_LIMIT_INFORMATION = extern struct { + PerProcessUserTimeLimit: win.LARGE_INTEGER, + PerJobUserTimeLimit: win.LARGE_INTEGER, + LimitFlags: win.DWORD, + MinimumWorkingSetSize: win.SIZE_T, + MaximumWorkingSetSize: win.SIZE_T, + ActiveProcessLimit: win.DWORD, + Affinity: win.ULONG_PTR, + PriorityClass: win.DWORD, + SchedulingClass: win.DWORD, + }; + + pub const IO_COUNTERS = extern struct { + ReadOperationCount: win.ULONGLONG, + WriteOperationCount: win.ULONGLONG, + OtherOperationCount: win.ULONGLONG, + ReadTransferCount: win.ULONGLONG, + WriteTransferCount: win.ULONGLONG, + OtherTransferCount: win.ULONGLONG, + }; + + pub const JOBOBJECT_EXTENDED_LIMIT_INFORMATION = extern struct { + BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION, + IoInfo: IO_COUNTERS, + ProcessMemoryLimit: win.SIZE_T, + JobMemoryLimit: win.SIZE_T, + PeakProcessMemoryUsed: win.SIZE_T, + PeakJobMemoryUsed: win.SIZE_T, + }; + + pub const JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008; + pub const JOB_OBJECT_LIMIT_AFFINITY = 0x00000010; + pub const JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800; + pub const JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400; + pub const JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200; + pub const JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004; + pub const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000; + pub const JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000004; + pub const JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020; + pub const JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100; + pub const JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002; + pub const JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080; + pub const JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000; + pub const JOB_OBJECT_LIMIT_SUBSET_AFFINITY = 0x00004000; + pub const JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001; + + pub const JOBOBJECT_INFORMATION_CLASS = enum(c_int) { + JobObjectAssociateCompletionPortInformation = 7, + JobObjectBasicLimitInformation = 2, + JobObjectBasicUIRestrictions = 4, + JobObjectCpuRateControlInformation = 15, + JobObjectEndOfJobTimeInformation = 6, + JobObjectExtendedLimitInformation = 9, + JobObjectGroupInformation = 11, + JobObjectGroupInformationEx = 14, + JobObjectLimitViolationInformation2 = 34, + JobObjectNetRateControlInformation = 32, + JobObjectNotificationLimitInformation = 12, + JobObjectNotificationLimitInformation2 = 33, + JobObjectSecurityLimitInformation = 5, + }; + + pub const JOB_OBJECT_MSG_TYPE = enum(win.DWORD) { + JOB_OBJECT_MSG_END_OF_JOB_TIME = 1, + JOB_OBJECT_MSG_END_OF_PROCESS_TIME = 2, + JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT = 3, + JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO = 4, + JOB_OBJECT_MSG_NEW_PROCESS = 6, + JOB_OBJECT_MSG_EXIT_PROCESS = 7, + JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8, + JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT = 9, + JOB_OBJECT_MSG_JOB_MEMORY_LIMIT = 10, + JOB_OBJECT_MSG_NOTIFICATION_LIMIT = 11, + JOB_OBJECT_MSG_JOB_CYCLE_TIME_LIMIT = 12, + JOB_OBJECT_MSG_SILO_TERMINATED = 13, + _, + }; + + pub const k32 = struct { + pub extern "kernel32" fn GetProcessId(Process: win.HANDLE) callconv(.winapi) win.DWORD; + pub extern "kernel32" fn CreateJobObjectA(lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, lpName: ?win.LPCSTR) callconv(.winapi) win.HANDLE; + pub extern "kernel32" fn AssignProcessToJobObject(hJob: win.HANDLE, hProcess: win.HANDLE) callconv(.winapi) win.BOOL; + pub extern "kernel32" fn SetInformationJobObject( + hJob: win.HANDLE, + JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS, + lpJobObjectInformation: win.LPVOID, + cbJobObjectInformationLength: win.DWORD, + ) callconv(.winapi) win.BOOL; + }; + + pub const CreateFileError = error{} || posix.UnexpectedError; + + pub fn CreateFile( + lpFileName: [*:0]const u16, + dwDesiredAccess: win.DWORD, + dwShareMode: win.DWORD, + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + dwCreationDisposition: win.DWORD, + dwFlagsAndAttributes: win.DWORD, + hTemplateFile: ?win.HANDLE, + ) CreateFileError!win.HANDLE { + const handle = kernel32.CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + if (handle == win.INVALID_HANDLE_VALUE) { + const err = kernel32.GetLastError(); + return switch (err) { + else => unexpectedError(err), + }; + } + + return handle; + } + + pub fn ReadFile( + handle: win.HANDLE, + buffer: []u8, + overlapped: ?*OVERLAPPED, + ) ReadFileError!?usize { + var read: win.DWORD = 0; + const result = kernel32.ReadFile(handle, buffer.ptr, @as(win.DWORD, @intCast(buffer.len)), &read, overlapped); + if (result == .FALSE) { + const err = kernel32.GetLastError(); + return switch (err) { + .IO_PENDING => null, + else => unexpectedError(err), + }; + } + + return @as(usize, @intCast(read)); + } + + pub fn WriteFile( + handle: win.HANDLE, + buffer: []const u8, + overlapped: ?*OVERLAPPED, + ) WriteFileError!?usize { + var written: win.DWORD = 0; + const result = kernel32.WriteFile(handle, buffer.ptr, @as(win.DWORD, @intCast(buffer.len)), &written, overlapped); + if (result == .FALSE) { + const err = kernel32.GetLastError(); + return switch (err) { + .IO_PENDING => null, + else => unexpectedError(err), + }; + } + + return @as(usize, @intCast(written)); + } + + pub const DeleteFileError = error{} || posix.UnexpectedError; + + pub fn DeleteFile(name: [*:0]const u16) DeleteFileError!void { + const result = kernel32.DeleteFileW(name); + if (result == .FALSE) { + const err = kernel32.GetLastError(); + return switch (err) { + else => unexpectedError(err), + }; + } + } + + pub const CreateJobObjectError = error{AlreadyExists} || posix.UnexpectedError; + pub fn CreateJobObject( + lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, + lpName: ?win.LPCSTR, + ) !win.HANDLE { + const handle = exp.k32.CreateJobObjectA(lpSecurityAttributes, lpName); + return switch (kernel32.GetLastError()) { + .SUCCESS => handle, + .ALREADY_EXISTS => CreateJobObjectError.AlreadyExists, + else => |err| unexpectedError(err), + }; + } + + pub fn AssignProcessToJobObject(hJob: win.HANDLE, hProcess: win.HANDLE) posix.UnexpectedError!void { + const result = exp.k32.AssignProcessToJobObject(hJob, hProcess); + if (result == .FALSE) { + const err = kernel32.GetLastError(); + return switch (err) { + else => unexpectedError(err), + }; + } + } + + pub fn SetInformationJobObject( + hJob: win.HANDLE, + JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS, + lpJobObjectInformation: win.LPVOID, + cbJobObjectInformationLength: win.DWORD, + ) posix.UnexpectedError!void { + const result = exp.k32.SetInformationJobObject( + hJob, + JobObjectInformationClass, + lpJobObjectInformation, + cbJobObjectInformationLength, + ); + + if (result == .FALSE) { + const err = kernel32.GetLastError(); + return switch (err) { + else => unexpectedError(err), + }; + } + } +}; diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.eslintrc.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.gitignore b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.gitignore new file mode 100644 index 0000000..c87c9b3 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/next.config.js b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/next.config.js new file mode 100644 index 0000000..401b33e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/next.config.js @@ -0,0 +1,11 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +} + +const withNextra = require('nextra')({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.jsx', +}) + +module.exports = withNextra(nextConfig) diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package-lock.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package-lock.json new file mode 100644 index 0000000..5b82534 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package-lock.json @@ -0,0 +1,10539 @@ +{ + "name": "website", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "website", + "version": "0.1.0", + "dependencies": { + "@next/font": "13.1.2", + "@types/node": "18.11.18", + "@types/react": "18.0.26", + "@types/react-dom": "18.0.10", + "eslint": "8.32.0", + "eslint-config-next": "13.1.2", + "next": "13.1.2", + "nextra": "^2.2.5", + "nextra-theme-docs": "^2.2.5", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.9.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@headlessui/react": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz", + "integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==", + "dependencies": { + "client-only": "^0.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@mdx-js/mdx": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.2.1.tgz", + "integrity": "sha512-hZ3ex7exYLJn6FfReq8yTvA6TE53uW9UHJQM9IlSauOuS55J9y8RtA7W+dzp6Yrzr00/U1sd7q+Wf61q6SfiTQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/mdx": "^2.0.0", + "estree-util-build-jsx": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-to-js": "^1.1.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^2.0.0", + "markdown-extensions": "^1.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^2.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "unified": "^10.0.0", + "unist-util-position-from-estree": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.2.1.tgz", + "integrity": "sha512-YdXcMcEnqZhzql98RNrqYo9cEhTTesBiCclEtoiQUbJwx87q9453GTapYU6kJ8ZZ2ek1Vp25SiAXEFy5O/eAPw==", + "dependencies": { + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@napi-rs/simple-git": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.8.tgz", + "integrity": "sha512-BvOMdkkofTz6lEE35itJ/laUokPhr/5ToMGlOH25YnhLD2yN1KpRAT4blW9tT8281/1aZjW3xyi73bs//IrDKA==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/simple-git-android-arm-eabi": "0.1.8", + "@napi-rs/simple-git-android-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-x64": "0.1.8", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.8", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.8", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-x64-musl": "0.1.8", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.8", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.8" + } + }, + "node_modules/@napi-rs/simple-git-android-arm-eabi": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.8.tgz", + "integrity": "sha512-JJCejHBB1G6O8nxjQLT4quWCcvLpC3oRdJJ9G3MFYSCoYS8i1bWCWeU+K7Br+xT+D6s1t9q8kNJAwJv9Ygpi0g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-android-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.8.tgz", + "integrity": "sha512-mraHzwWBw3tdRetNOS5KnFSjvdAbNBnjFLA8I4PwTCPJj3Q4txrigcPp2d59cJ0TC51xpnPXnZjYdNwwSI9g6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.8.tgz", + "integrity": "sha512-ufy/36eI/j4UskEuvqSH7uXtp3oXeLDmjQCfKJz3u5Vx98KmOMKrqAm2H81AB2WOtCo5mqS6PbBeUXR8BJX8lQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-darwin-x64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.8.tgz", + "integrity": "sha512-Vb21U+v3tPJNl+8JtIHHT8HGe6WZ8o1Tq3f6p+Jx9Cz71zEbcIiB9FCEMY1knS/jwQEOuhhlI9Qk7d4HY+rprA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm-gnueabihf": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.8.tgz", + "integrity": "sha512-6BPTJ7CzpSm2t54mRLVaUr3S7ORJfVJoCk2rQ8v8oDg0XAMKvmQQxOsAgqKBo9gYNHJnqrOx3AEuEgvB586BuQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.8.tgz", + "integrity": "sha512-qfESqUCAA/XoQpRXHptSQ8gIFnETCQt1zY9VOkplx6tgYk9PCeaX4B1Xuzrh3eZamSCMJFn+1YB9Ut8NwyGgAA==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-arm64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.8.tgz", + "integrity": "sha512-G80BQPpaRmQpn8dJGHp4I2/YVhWDUNJwcCrJAtAdbKFDCMyCHJBln2ERL/+IEUlIAT05zK/c1Z5WEprvXEdXow==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.8.tgz", + "integrity": "sha512-NI6o1sZYEf6vPtNWJAm9w8BxJt+LlSFW0liSjYe3lc3e4dhMfV240f0ALeqlwdIldRPaDFwZSJX5/QbS7nMzhw==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-linux-x64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.8.tgz", + "integrity": "sha512-wljGAEOW41er45VTiU8kXJmO480pQKzsgRCvPlJJSCaEVBbmo6XXbFIXnZy1a2J3Zyy2IOsRB4PVkUZaNuPkZQ==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-arm64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.8.tgz", + "integrity": "sha512-QuV4QILyKPfbWHoQKrhXqjiCClx0SxbCTVogkR89BwivekqJMd9UlMxZdoCmwLWutRx4z9KmzQqokvYI5QeepA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/simple-git-win32-x64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.8.tgz", + "integrity": "sha512-UzNS4JtjhZhZ5hRLq7BIUq+4JOwt1ThIKv11CsF1ag2l99f0123XvfEpjczKTaa94nHtjXYc2Mv9TjccBqYOew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/env": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.2.tgz", + "integrity": "sha512-PpT4UZIX66VMTqXt4HKEJ+/PwbS+tWmmhZlazaws1a+dbUA5pPdjntQ46Jvj616i3ZKN9doS9LHx3y50RLjAWg==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.1.2.tgz", + "integrity": "sha512-WGaNVvIYphdriesP6r7jq/8l7u38tzotnVQuxc1RYKLqYYApSsrebti3OCPoT3Gx0pw2smPIFHH98RzcsgW5GQ==", + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/font": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.2.tgz", + "integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==" + }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.2.tgz", + "integrity": "sha512-7mRz1owoGsbfIcdOJA3kk7KEwPZ+OvVT1z9DkR/yru4QdVLF69h/1SHy0vlUNQMxDRllabhxCfkoZCB34GOGAg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.2.tgz", + "integrity": "sha512-mgjZ2eJSayovQm1LcE54BLSI4jjnnnLtq5GY5g+DdPuUiCT644gKtjZ/w2BQvuIecCqqBO+Ph9yzo/wUTq7NLg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.2.tgz", + "integrity": "sha512-RikoQqy109r2222UJlyGs4dZw2BibkfPqpeFdW5JEGv+L2PStlHID8DwyVYbmHfQ0VIBGvbf/NAUtFakAWlhwg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.2.tgz", + "integrity": "sha512-JbDZjaTvL8gyPC5TAH6OnD4jmXPkyUxRYPvu08ZmhT/XAFBb/Cso0BdXyDax/BPCG70mimP9d3hXNKNq+A0VtQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-freebsd-x64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.2.tgz", + "integrity": "sha512-ax4j8VrdFQ/xc3W7Om0u1vnDxVApQHKsChBbAMynCrnycZmpbqK4MZu4ZkycT+mx2eccCiqZROpbzDbEdPosEw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.2.tgz", + "integrity": "sha512-NcRHTesnCxnUvSJa637PQJffBBkmqi5XS/xVWGY7dI6nyJ+pC96Oj7kd+mcjnFUQI5lHKbg39qBWKtOzbezc4w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.2.tgz", + "integrity": "sha512-AxJdjocLtPrsBY4P2COSBIc3crT5bpjgGenNuINoensOlXhBkYM0aRDYZdydwXOhG+kN2ngUvfgitop9pa204w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.2.tgz", + "integrity": "sha512-JmNimDkcCRq7P5zpkdqeaSZ69qKDntEPtyIaMNWqy5M0WUJxGim0Fs6Qzxayiyvuuh9Guxks4woQ/j/ZvX/c8Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.2.tgz", + "integrity": "sha512-TsLsjZwUlgmvI42neTuIoD6K9RlXCUzqPtvIClgXxVO0um0DiZwK+M+0zX/uVXhMVphfPY2c5YeR1zFSIONY4A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.2.tgz", + "integrity": "sha512-eSkyXgCXydEFPTkcncQOGepafedPte6JT/OofB9uvruucrrMVBagCASOuPxodWEMrlfEKSXVnExMKIlfmQMD7A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.2.tgz", + "integrity": "sha512-DmXFaRTgt2KrV9dmRLifDJE+cYiutHVFIw5/C9BtnwXH39uf3YbPxeD98vNrtqqqZVVLXY/1ySaSIwzYnqeY9g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.2.tgz", + "integrity": "sha512-3+nBkuFs/wT+lmRVQNH5SyDT7I4vUlNPntosEaEP63FuYQdPLaxz0GvcR66MdFSFh2fsvazpe4wciOwVS4FItQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.2.tgz", + "integrity": "sha512-avsyveEvcvH42PvKjR4Pb8JlLttuGURr2H3ZhS2b85pHOiZ7yjH3rMUoGnNzuLMApyxYaCvd4MedPrLhnNhkog==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dependencies": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + }, + "node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.0.tgz", + "integrity": "sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/katex": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz", + "integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==" + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz", + "integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==" + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", + "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", + "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.2", + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/typescript-estree": "5.48.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", + "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", + "dependencies": { + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/visitor-keys": "5.48.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", + "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", + "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", + "dependencies": { + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/visitor-keys": "5.48.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", + "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", + "dependencies": { + "@typescript-eslint/types": "5.48.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-1.0.0.tgz", + "integrity": "sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "node_modules/astring": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.4.tgz", + "integrity": "sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001445", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz", + "integrity": "sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clipboardy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.2.tgz", + "integrity": "sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==", + "dependencies": { + "arch": "^2.1.0", + "execa": "^0.8.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz", + "integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==", + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.1.2.tgz", + "integrity": "sha512-zdRAQOr8v69ZwJRtBrGqAqm160ONqKxU/pV1FB1KlgfyqveGsLZmlQ7l31otwtw763901J7xdiTVkj2y3YxXZA==", + "dependencies": { + "@next/eslint-plugin-next": "13.1.2", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.42.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "^4.5.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz", + "integrity": "sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==", + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.10.0", + "get-tsconfig": "^4.2.0", + "globby": "^13.1.2", + "is-core-module": "^2.10.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.1.tgz", + "integrity": "sha512-vOjdgyd0ZHBXNsmvU+785xY8Bfe57EFbTYYk8XrROzWpr9QBvpjITvAXt9xqcE6+8cjR/g1+mfumPToxsl1www==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.0.tgz", + "integrity": "sha512-rJz6I4L0GaXYtHpoMScgDIwM0/Vwbu5shbMeER596rB2D1EWF6+Gj0e0UKzJPZrpoOc87+Q2kgVFHfjAymIqmw==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz", + "integrity": "sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.0.1.tgz", + "integrity": "sha512-rxZj1GkQhY4x1j/CSnybK9cGuMFQYFPLq0iNyopqf14aOVLFtMv7Esika+ObJWPWiOHuMOAHz3YkWoLYYRnzWQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-1.1.0.tgz", + "integrity": "sha512-490lbfCcpLk+ofK6HCgqDfYs4KAfq6QVvDw3+Bm1YoKRgiOjKiKYGAVQE1uwh7zVxBgWhqp4FDtp5SqunpUk1A==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-1.3.0.tgz", + "integrity": "sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==", + "dependencies": { + "is-plain-obj": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/estree-util-visit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.0.tgz", + "integrity": "sha512-wdsoqhWueuJKsh5hqLw3j8lwFqNStm92VcwtAOAny8g/KS/l5Y8RISjR4k5W6skCj3Nirag/WUCMS0Nfy3sgsg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.2.tgz", + "integrity": "sha512-C03BvXCQIH/po+PNPONx/zSM9ziPr9weX8xNhYb/IJtdJ9z+L4z9VKPTB+UTHdmhnIopA2kc419ueyVyHVktwA==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/flexsearch": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.31.tgz", + "integrity": "sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==" + }, + "node_modules/focus-visible": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.0.tgz", + "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.3.0.tgz", + "integrity": "sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==", + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "node_modules/git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "dependencies": { + "git-up": "^7.0.0" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz", + "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==", + "dependencies": { + "is-obj": "^3.0.0", + "sort-keys": "^5.0.0", + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hash-obj/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.1.tgz", + "integrity": "sha512-R6PoNcUs89ZxLJmMWsVbwSWuz95/9OriyQZ3e2ybwqGsRXzhA6gv49rgGmQvLbZuSNDv9fCg7vV7gXUsvtUFaA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", + "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.2.1.tgz", + "integrity": "sha512-kiGD9WIW3gRKK8Gao3n1f+ahUeTMeJUJILnIT2QNrPigDNdH7rJxzhEbh81UajGeAdAHFecT1a+fLVOCTq9B4Q==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "estree-util-attach-comments": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.1", + "unist-util-position": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz", + "integrity": "sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-reference": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", + "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/katex": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.4.tgz", + "integrity": "sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", + "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", + "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", + "dependencies": { + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", + "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", + "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", + "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", + "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.2.tgz", + "integrity": "sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz", + "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", + "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.1.tgz", + "integrity": "sha512-ZZtjyRwobsiVg4bY0Q5CzAZztpbjRIA7ZlMMb0PNkwTXOnJTUoHvzBhVG95LIuek5Mlj1l2P+jBvWviqW7G+0A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.0.tgz", + "integrity": "sha512-M09lW0CcBT1VrJUaF/PYxemxxHa7SLDHdSn94Q9FhxjCQfuW7nMAWKWimTmA3OyDMSTH981NN1csW1X+HPSluw==", + "dependencies": { + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdx-jsx": "^2.0.0", + "mdast-util-mdxjs-esm": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.1.tgz", + "integrity": "sha512-TTb6cKyTA1RD+1su1iStZ5PAv3rFfOUKcoU5EstUpv/IZo63uDX03R8+jXjMEhcobXnNOiG6/ccekvVl4eV1zQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.0.tgz", + "integrity": "sha512-KzgzfWMhdteDkrY4mQtyvTU5bc/W4ppxhe9SzelO6QUUiwLAM+Et2Dnjjprik74a336kHdo0zKm7Tp+n6FFeRg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-to-markdown": "^1.3.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.0.tgz", + "integrity": "sha512-7N5ihsOkAEGjFotIX9p/YPdl4TqUoMxL4ajNz7PbT89BqsdWJuBC9rvgt6wpbwTZqWWR0jKWqQbwsOWDBUZv4g==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.0.tgz", + "integrity": "sha512-S+QYsDRLkGi8U7o5JF1agKa/sdP+CNGXXLqC17pdTVL8FHHgQEiwFGa9yE5aYtUxNiFGYoaDy9V1kC85Sz86Gg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.2.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.5.tgz", + "integrity": "sha512-EFNhT35ZR/VZ85/EedDdCNTq0oFM+NM/+qBomVGQ0+Lcg0nhI8xIwmdCzNMlVlCJNXRprpobtKP/IUh8cfz6zQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", + "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", + "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", + "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", + "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz", + "integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==", + "dependencies": { + "@types/katex": "^0.11.0", + "katex": "^0.13.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/katex": { + "version": "0.13.24", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz", + "integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.3.tgz", + "integrity": "sha512-TjYtjEMszWze51NJCZmhv7MEBcgYRgb3tJeMAJ+HQCAaZHHRBaDCccqQzGizR/H4ODefP44wRTgOn2vE5I6nZA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.3.tgz", + "integrity": "sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA==", + "dependencies": { + "@types/acorn": "^4.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.0.tgz", + "integrity": "sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.0.tgz", + "integrity": "sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^1.0.0", + "micromark-extension-mdx-jsx": "^1.0.0", + "micromark-extension-mdx-md": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.3.tgz", + "integrity": "sha512-2N13ol4KMoxb85rdDwTAC6uzs8lMX0zeqpcyx7FhS7PxXomOnLactu8WI8iBNXW8AVyea3KIJd/1CKnUmwrK9A==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.1.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.6.tgz", + "integrity": "sha512-WRQIc78FV7KrCfjsEf/sETopbYjElh3xAmNpLkd1ODPqxEngP42eVRGbiPEQWpRV27LzqW+XVTvQAMIIRLPnNA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.0.tgz", + "integrity": "sha512-WWp3bf7xT9MppNuw3yPjpnOxa8cj5ACivEzXJKu0WwnjBYfzaBvIAT9KfeyI0Qkll+bfQtfftSwdgTH6QhTOKw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "estree-util-visit": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-location": "^4.0.0", + "vfile-message": "^3.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-13.1.2.tgz", + "integrity": "sha512-Rdnnb2YH///w78FEOR/IQ6TXga+qpth4OqFSem48ng1PYYKr6XBsIk1XVaRcIGM3o6iiHnun0nJvkJHDf+ICyQ==", + "dependencies": { + "@next/env": "13.1.2", + "@swc/helpers": "0.4.14", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=14.6.0" + }, + "optionalDependencies": { + "@next/swc-android-arm-eabi": "13.1.2", + "@next/swc-android-arm64": "13.1.2", + "@next/swc-darwin-arm64": "13.1.2", + "@next/swc-darwin-x64": "13.1.2", + "@next/swc-freebsd-x64": "13.1.2", + "@next/swc-linux-arm-gnueabihf": "13.1.2", + "@next/swc-linux-arm64-gnu": "13.1.2", + "@next/swc-linux-arm64-musl": "13.1.2", + "@next/swc-linux-x64-gnu": "13.1.2", + "@next/swc-linux-x64-musl": "13.1.2", + "@next/swc-win32-arm64-msvc": "13.1.2", + "@next/swc-win32-ia32-msvc": "13.1.2", + "@next/swc-win32-x64-msvc": "13.1.2" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^6.0.0 || ^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-mdx-remote": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.2.1.tgz", + "integrity": "sha512-PcVF1r5XTBjiNVXw0GyaIcOwQsklHo36+7ycfmtJb52TIkT0nM4Hzv4wgJwNg7+jvTbap99qWsMwdKUYR9WxAA==", + "dependencies": { + "@mdx-js/mdx": "^2.2.1", + "@mdx-js/react": "^2.2.1", + "vfile": "^5.3.0", + "vfile-matter": "^3.0.1" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "peerDependencies": { + "react": ">=16.x <=18.x", + "react-dom": ">=16.x <=18.x" + } + }, + "node_modules/next-seo": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/next-seo/-/next-seo-5.15.0.tgz", + "integrity": "sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg==", + "peerDependencies": { + "next": "^8.1.1-canary.54 || >=9.0.0", + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, + "node_modules/nextra": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nextra/-/nextra-2.2.5.tgz", + "integrity": "sha512-sm8mtKxLhfGWNv0yGd9GRL+DPDdVJlyEGn+6zk7gOPQor4X6G1VLcNjGlGYFovS4ZHNrg8oKmhOZSkq79PBrtw==", + "dependencies": { + "@mdx-js/mdx": "^2.1.5", + "@mdx-js/react": "^2.1.5", + "@napi-rs/simple-git": "^0.1.8", + "github-slugger": "^2.0.0", + "graceful-fs": "^4.2.10", + "gray-matter": "^4.0.3", + "katex": "^0.16.4", + "lodash.get": "^4.4.2", + "next-mdx-remote": "^4.2.0", + "p-limit": "^3.1.0", + "rehype-katex": "^6.0.2", + "rehype-pretty-code": "0.9.2", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-reading-time": "^2.0.1", + "shiki": "^0.12.1", + "slash": "^3.0.0", + "title": "^3.5.3", + "unist-util-visit": "^4.1.1" + }, + "peerDependencies": { + "next": ">=9.5.3", + "react": ">=16.13.1", + "react-dom": ">=16.13.1" + } + }, + "node_modules/nextra-theme-docs": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nextra-theme-docs/-/nextra-theme-docs-2.2.5.tgz", + "integrity": "sha512-dpfOr8O4w3+q2BpBvnOXsmuvDCrrVl9QPrETzN8xKc9z6+oJNt4PEZOHRHo2rADHyV4832sSnWxhERrVzvqfNA==", + "dependencies": { + "@headlessui/react": "^1.7.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "flexsearch": "^0.7.21", + "focus-visible": "^5.2.0", + "git-url-parse": "^13.1.0", + "intersection-observer": "^0.12.2", + "match-sorter": "^6.3.1", + "next-seo": "^5.5.0", + "next-themes": "^0.2.1", + "scroll-into-view-if-needed": "^3.0.0", + "zod": "^3.20.2" + }, + "peerDependencies": { + "next": ">=9.5.3", + "react": ">=16.13.1", + "react-dom": ">=16.13.1" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.0.tgz", + "integrity": "sha512-5nk9Fn03x3rEhGaX1FU6IDwG/k+GxLXlFAkgrbM1asuAFl3BhdQWvASaIsmwWypRNcZKHPYnIuOSfIWEyEQnPQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "dependencies": { + "parse-path": "^7.0.0" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/periscopic": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.0.4.tgz", + "integrity": "sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg==", + "dependencies": { + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/punycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz", + "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/rehype-katex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.2.tgz", + "integrity": "sha512-C4gDAlS1+l0hJqctyiU64f9CvT00S03qV1T6HiMzbSuLBgWUtcqydWHY9OpKrm0SpkK16FNd62CDKyWLwV2ppg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/katex": "^0.11.0", + "hast-util-to-text": "^3.1.0", + "katex": "^0.15.0", + "rehype-parse": "^8.0.0", + "unified": "^10.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.2.tgz", + "integrity": "sha512-l369pvBK6ihBEuy2+VDpHU+zbbY8I+Z4LiyIOunHAt3xyw6selaOFKc/DnX94jI5OJb3+NgjbOxXx2yaAypjZw==", + "dependencies": { + "hash-obj": "^4.0.0", + "nanoid": "^4.0.0", + "parse-numeric-range": "^1.3.0" + }, + "engines": { + "node": "^12.16.0 || >=13.2.0" + }, + "peerDependencies": { + "shiki": "*" + } + }, + "node_modules/rehype-pretty-code/node_modules/nanoid": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz", + "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-math": "^2.0.0", + "micromark-extension-math": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.2.1.tgz", + "integrity": "sha512-R9wcN+/THRXTKyRBp6Npo/mcbGA2iT3N4G8qUqLA5pOEg7kBidHv8K2hHidCMYZ6DXmwK18umu0K4cicgA2PPQ==", + "dependencies": { + "mdast-util-mdx": "^2.0.0", + "micromark-extension-mdxjs": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reading-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remark-reading-time/-/remark-reading-time-2.0.1.tgz", + "integrity": "sha512-fy4BKy9SRhtYbEHvp6AItbRTnrhiDGbqLQTSYVbQPGuRCncU1ubSsh9p/W5QZSxtYcUXv8KGL0xBgPLyNJA1xw==", + "dependencies": { + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-value-to-estree": "^1.3.0", + "reading-time": "^1.3.0", + "unist-util-visit": "^3.1.0" + } + }, + "node_modules/remark-reading-time/node_modules/unist-util-visit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", + "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reading-time/node_modules/unist-util-visit-parents": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", + "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.4.tgz", + "integrity": "sha512-s+/F50jwTOUt+u5oEIAzum9MN2lUQNvWBe/zfEsVQcbaERjGkKLq1s+2wCHkahMLC8nMLbzMVKivx9JhunXaZg==", + "dependencies": { + "compute-scroll-into-view": "^2.0.4" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", + "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sort-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz", + "integrity": "sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==", + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/title": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/title/-/title-3.5.3.tgz", + "integrity": "sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==", + "dependencies": { + "arg": "1.0.0", + "chalk": "2.3.0", + "clipboardy": "1.2.2", + "titleize": "1.0.0" + }, + "bin": { + "title": "bin/title.js" + } + }, + "node_modules/title/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/title/node_modules/chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dependencies": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/title/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/title/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/title/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/title/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/title/node_modules/supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==", + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/titleize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-1.0.0.tgz", + "integrity": "sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-builder": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", + "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.0.tgz", + "integrity": "sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", + "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", + "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.1.tgz", + "integrity": "sha512-xtoY50b5+7IH8tFbkw64gisG9tMSpxDjhX9TmaJJae/XuxQ9R/Kc8Nv1eOsf43Gt4KV/LkriMy9mptDr7XLcaw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.1.tgz", + "integrity": "sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", + "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", + "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.6.tgz", + "integrity": "sha512-ADBsmerdGBs2WYckrLBEmuETSPyTD4TuLxTrw0DvjirxW1ra4ZwkbzG8ndsv3Q57smvHxo677MHaQrY9yxH8cA==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", + "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-matter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-3.0.1.tgz", + "integrity": "sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==", + "dependencies": { + "@types/js-yaml": "^4.0.0", + "is-buffer": "^2.0.0", + "js-yaml": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.3.tgz", + "integrity": "sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.2.tgz", + "integrity": "sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@headlessui/react": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz", + "integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==", + "requires": { + "client-only": "^0.0.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "@mdx-js/mdx": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.2.1.tgz", + "integrity": "sha512-hZ3ex7exYLJn6FfReq8yTvA6TE53uW9UHJQM9IlSauOuS55J9y8RtA7W+dzp6Yrzr00/U1sd7q+Wf61q6SfiTQ==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/mdx": "^2.0.0", + "estree-util-build-jsx": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-to-js": "^1.1.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^2.0.0", + "markdown-extensions": "^1.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^2.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "unified": "^10.0.0", + "unist-util-position-from-estree": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + } + }, + "@mdx-js/react": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.2.1.tgz", + "integrity": "sha512-YdXcMcEnqZhzql98RNrqYo9cEhTTesBiCclEtoiQUbJwx87q9453GTapYU6kJ8ZZ2ek1Vp25SiAXEFy5O/eAPw==", + "requires": { + "@types/mdx": "^2.0.0", + "@types/react": ">=16" + } + }, + "@napi-rs/simple-git": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.8.tgz", + "integrity": "sha512-BvOMdkkofTz6lEE35itJ/laUokPhr/5ToMGlOH25YnhLD2yN1KpRAT4blW9tT8281/1aZjW3xyi73bs//IrDKA==", + "requires": { + "@napi-rs/simple-git-android-arm-eabi": "0.1.8", + "@napi-rs/simple-git-android-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-arm64": "0.1.8", + "@napi-rs/simple-git-darwin-x64": "0.1.8", + "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.8", + "@napi-rs/simple-git-linux-arm64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-arm64-musl": "0.1.8", + "@napi-rs/simple-git-linux-x64-gnu": "0.1.8", + "@napi-rs/simple-git-linux-x64-musl": "0.1.8", + "@napi-rs/simple-git-win32-arm64-msvc": "0.1.8", + "@napi-rs/simple-git-win32-x64-msvc": "0.1.8" + } + }, + "@napi-rs/simple-git-android-arm-eabi": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.8.tgz", + "integrity": "sha512-JJCejHBB1G6O8nxjQLT4quWCcvLpC3oRdJJ9G3MFYSCoYS8i1bWCWeU+K7Br+xT+D6s1t9q8kNJAwJv9Ygpi0g==", + "optional": true + }, + "@napi-rs/simple-git-android-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.8.tgz", + "integrity": "sha512-mraHzwWBw3tdRetNOS5KnFSjvdAbNBnjFLA8I4PwTCPJj3Q4txrigcPp2d59cJ0TC51xpnPXnZjYdNwwSI9g6g==", + "optional": true + }, + "@napi-rs/simple-git-darwin-arm64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.8.tgz", + "integrity": "sha512-ufy/36eI/j4UskEuvqSH7uXtp3oXeLDmjQCfKJz3u5Vx98KmOMKrqAm2H81AB2WOtCo5mqS6PbBeUXR8BJX8lQ==", + "optional": true + }, + "@napi-rs/simple-git-darwin-x64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.8.tgz", + "integrity": "sha512-Vb21U+v3tPJNl+8JtIHHT8HGe6WZ8o1Tq3f6p+Jx9Cz71zEbcIiB9FCEMY1knS/jwQEOuhhlI9Qk7d4HY+rprA==", + "optional": true + }, + "@napi-rs/simple-git-linux-arm-gnueabihf": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.8.tgz", + "integrity": "sha512-6BPTJ7CzpSm2t54mRLVaUr3S7ORJfVJoCk2rQ8v8oDg0XAMKvmQQxOsAgqKBo9gYNHJnqrOx3AEuEgvB586BuQ==", + "optional": true + }, + "@napi-rs/simple-git-linux-arm64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.8.tgz", + "integrity": "sha512-qfESqUCAA/XoQpRXHptSQ8gIFnETCQt1zY9VOkplx6tgYk9PCeaX4B1Xuzrh3eZamSCMJFn+1YB9Ut8NwyGgAA==", + "optional": true + }, + "@napi-rs/simple-git-linux-arm64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.8.tgz", + "integrity": "sha512-G80BQPpaRmQpn8dJGHp4I2/YVhWDUNJwcCrJAtAdbKFDCMyCHJBln2ERL/+IEUlIAT05zK/c1Z5WEprvXEdXow==", + "optional": true + }, + "@napi-rs/simple-git-linux-x64-gnu": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.8.tgz", + "integrity": "sha512-NI6o1sZYEf6vPtNWJAm9w8BxJt+LlSFW0liSjYe3lc3e4dhMfV240f0ALeqlwdIldRPaDFwZSJX5/QbS7nMzhw==", + "optional": true + }, + "@napi-rs/simple-git-linux-x64-musl": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.8.tgz", + "integrity": "sha512-wljGAEOW41er45VTiU8kXJmO480pQKzsgRCvPlJJSCaEVBbmo6XXbFIXnZy1a2J3Zyy2IOsRB4PVkUZaNuPkZQ==", + "optional": true + }, + "@napi-rs/simple-git-win32-arm64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.8.tgz", + "integrity": "sha512-QuV4QILyKPfbWHoQKrhXqjiCClx0SxbCTVogkR89BwivekqJMd9UlMxZdoCmwLWutRx4z9KmzQqokvYI5QeepA==", + "optional": true + }, + "@napi-rs/simple-git-win32-x64-msvc": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.8.tgz", + "integrity": "sha512-UzNS4JtjhZhZ5hRLq7BIUq+4JOwt1ThIKv11CsF1ag2l99f0123XvfEpjczKTaa94nHtjXYc2Mv9TjccBqYOew==", + "optional": true + }, + "@next/env": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.2.tgz", + "integrity": "sha512-PpT4UZIX66VMTqXt4HKEJ+/PwbS+tWmmhZlazaws1a+dbUA5pPdjntQ46Jvj616i3ZKN9doS9LHx3y50RLjAWg==" + }, + "@next/eslint-plugin-next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.1.2.tgz", + "integrity": "sha512-WGaNVvIYphdriesP6r7jq/8l7u38tzotnVQuxc1RYKLqYYApSsrebti3OCPoT3Gx0pw2smPIFHH98RzcsgW5GQ==", + "requires": { + "glob": "7.1.7" + } + }, + "@next/font": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.2.tgz", + "integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==" + }, + "@next/swc-android-arm-eabi": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.2.tgz", + "integrity": "sha512-7mRz1owoGsbfIcdOJA3kk7KEwPZ+OvVT1z9DkR/yru4QdVLF69h/1SHy0vlUNQMxDRllabhxCfkoZCB34GOGAg==", + "optional": true + }, + "@next/swc-android-arm64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.1.2.tgz", + "integrity": "sha512-mgjZ2eJSayovQm1LcE54BLSI4jjnnnLtq5GY5g+DdPuUiCT644gKtjZ/w2BQvuIecCqqBO+Ph9yzo/wUTq7NLg==", + "optional": true + }, + "@next/swc-darwin-arm64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.2.tgz", + "integrity": "sha512-RikoQqy109r2222UJlyGs4dZw2BibkfPqpeFdW5JEGv+L2PStlHID8DwyVYbmHfQ0VIBGvbf/NAUtFakAWlhwg==", + "optional": true + }, + "@next/swc-darwin-x64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.2.tgz", + "integrity": "sha512-JbDZjaTvL8gyPC5TAH6OnD4jmXPkyUxRYPvu08ZmhT/XAFBb/Cso0BdXyDax/BPCG70mimP9d3hXNKNq+A0VtQ==", + "optional": true + }, + "@next/swc-freebsd-x64": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.2.tgz", + "integrity": "sha512-ax4j8VrdFQ/xc3W7Om0u1vnDxVApQHKsChBbAMynCrnycZmpbqK4MZu4ZkycT+mx2eccCiqZROpbzDbEdPosEw==", + "optional": true + }, + "@next/swc-linux-arm-gnueabihf": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.2.tgz", + "integrity": "sha512-NcRHTesnCxnUvSJa637PQJffBBkmqi5XS/xVWGY7dI6nyJ+pC96Oj7kd+mcjnFUQI5lHKbg39qBWKtOzbezc4w==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.2.tgz", + "integrity": "sha512-AxJdjocLtPrsBY4P2COSBIc3crT5bpjgGenNuINoensOlXhBkYM0aRDYZdydwXOhG+kN2ngUvfgitop9pa204w==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.2.tgz", + "integrity": "sha512-JmNimDkcCRq7P5zpkdqeaSZ69qKDntEPtyIaMNWqy5M0WUJxGim0Fs6Qzxayiyvuuh9Guxks4woQ/j/ZvX/c8Q==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.2.tgz", + "integrity": "sha512-TsLsjZwUlgmvI42neTuIoD6K9RlXCUzqPtvIClgXxVO0um0DiZwK+M+0zX/uVXhMVphfPY2c5YeR1zFSIONY4A==", + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.2.tgz", + "integrity": "sha512-eSkyXgCXydEFPTkcncQOGepafedPte6JT/OofB9uvruucrrMVBagCASOuPxodWEMrlfEKSXVnExMKIlfmQMD7A==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.2.tgz", + "integrity": "sha512-DmXFaRTgt2KrV9dmRLifDJE+cYiutHVFIw5/C9BtnwXH39uf3YbPxeD98vNrtqqqZVVLXY/1ySaSIwzYnqeY9g==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.2.tgz", + "integrity": "sha512-3+nBkuFs/wT+lmRVQNH5SyDT7I4vUlNPntosEaEP63FuYQdPLaxz0GvcR66MdFSFh2fsvazpe4wciOwVS4FItQ==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.2.tgz", + "integrity": "sha512-avsyveEvcvH42PvKjR4Pb8JlLttuGURr2H3ZhS2b85pHOiZ7yjH3rMUoGnNzuLMApyxYaCvd4MedPrLhnNhkog==", + "optional": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "requires": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + } + }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, + "@rushstack/eslint-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", + "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==" + }, + "@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "requires": { + "tslib": "^2.4.0" + } + }, + "@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "requires": { + "@types/estree": "*" + } + }, + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "requires": { + "@types/ms": "*" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" + }, + "@types/estree-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.0.tgz", + "integrity": "sha512-3qvGd0z8F2ENTGr/GG1yViqfiKmRfrXVx5sJyHGFu3z7m5g5utCQtGp/g29JnjflhtQJBv1WDQukHiT58xPcYQ==", + "requires": { + "@types/estree": "*" + } + }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "requires": { + "@types/unist": "*" + } + }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "@types/katex": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz", + "integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==" + }, + "@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "requires": { + "@types/unist": "*" + } + }, + "@types/mdx": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz", + "integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==" + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "@types/react": { + "version": "18.0.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", + "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.10", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", + "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, + "@typescript-eslint/parser": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.2.tgz", + "integrity": "sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==", + "requires": { + "@typescript-eslint/scope-manager": "5.48.2", + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/typescript-estree": "5.48.2", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz", + "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==", + "requires": { + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/visitor-keys": "5.48.2" + } + }, + "@typescript-eslint/types": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz", + "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz", + "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==", + "requires": { + "@typescript-eslint/types": "5.48.2", + "@typescript-eslint/visitor-keys": "5.48.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.48.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz", + "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==", + "requires": { + "@typescript-eslint/types": "5.48.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" + }, + "arg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-1.0.0.tgz", + "integrity": "sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "requires": { + "deep-equal": "^2.0.5" + } + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "astring": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.4.tgz", + "integrity": "sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "axe-core": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==" + }, + "axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "requires": { + "deep-equal": "^2.0.5" + } + }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite": { + "version": "1.0.30001445", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz", + "integrity": "sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==" + }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" + }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "clipboardy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.2.tgz", + "integrity": "sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==", + "requires": { + "arch": "^2.1.0", + "execa": "^0.8.0" + } + }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, + "compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "requires": { + "character-entities": "^2.0.0" + } + }, + "deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint": { + "version": "8.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz", + "integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==", + "requires": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.1.2.tgz", + "integrity": "sha512-zdRAQOr8v69ZwJRtBrGqAqm160ONqKxU/pV1FB1KlgfyqveGsLZmlQ7l31otwtw763901J7xdiTVkj2y3YxXZA==", + "requires": { + "@next/eslint-plugin-next": "13.1.2", + "@rushstack/eslint-patch": "^1.1.3", + "@typescript-eslint/parser": "^5.42.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.31.7", + "eslint-plugin-react-hooks": "^4.5.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-import-resolver-typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz", + "integrity": "sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==", + "requires": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.10.0", + "get-tsconfig": "^4.2.0", + "globby": "^13.1.2", + "is-core-module": "^2.10.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.4" + }, + "dependencies": { + "globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", + "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "requires": { + "@babel/runtime": "^7.20.7", + "aria-query": "^5.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.6.2", + "axobject-query": "^3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.3", + "language-tags": "=1.0.5", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "eslint-plugin-react": { + "version": "7.32.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.1.tgz", + "integrity": "sha512-vOjdgyd0ZHBXNsmvU+785xY8Bfe57EFbTYYk8XrROzWpr9QBvpjITvAXt9xqcE6+8cjR/g1+mfumPToxsl1www==", + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "estree-util-attach-comments": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.0.tgz", + "integrity": "sha512-rJz6I4L0GaXYtHpoMScgDIwM0/Vwbu5shbMeER596rB2D1EWF6+Gj0e0UKzJPZrpoOc87+Q2kgVFHfjAymIqmw==", + "requires": { + "@types/estree": "^1.0.0" + } + }, + "estree-util-build-jsx": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-2.2.2.tgz", + "integrity": "sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "estree-walker": "^3.0.0" + } + }, + "estree-util-is-identifier-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.0.1.tgz", + "integrity": "sha512-rxZj1GkQhY4x1j/CSnybK9cGuMFQYFPLq0iNyopqf14aOVLFtMv7Esika+ObJWPWiOHuMOAHz3YkWoLYYRnzWQ==" + }, + "estree-util-to-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-1.1.0.tgz", + "integrity": "sha512-490lbfCcpLk+ofK6HCgqDfYs4KAfq6QVvDw3+Bm1YoKRgiOjKiKYGAVQE1uwh7zVxBgWhqp4FDtp5SqunpUk1A==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + } + }, + "estree-util-value-to-estree": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-1.3.0.tgz", + "integrity": "sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==", + "requires": { + "is-plain-obj": "^3.0.0" + } + }, + "estree-util-visit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.0.tgz", + "integrity": "sha512-wdsoqhWueuJKsh5hqLw3j8lwFqNStm92VcwtAOAny8g/KS/l5Y8RISjR4k5W6skCj3Nirag/WUCMS0Nfy3sgsg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^2.0.0" + } + }, + "estree-walker": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.2.tgz", + "integrity": "sha512-C03BvXCQIH/po+PNPONx/zSM9ziPr9weX8xNhYb/IJtdJ9z+L4z9VKPTB+UTHdmhnIopA2kc419ueyVyHVktwA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "flexsearch": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.31.tgz", + "integrity": "sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==" + }, + "focus-visible": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.0.tgz", + "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==" + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-tsconfig": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.3.0.tgz", + "integrity": "sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==" + }, + "git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "requires": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "requires": { + "git-up": "^7.0.0" + } + }, + "github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "requires": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hash-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz", + "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==", + "requires": { + "is-obj": "^3.0.0", + "sort-keys": "^5.0.0", + "type-fest": "^1.0.2" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + } + } + }, + "hast-util-from-parse5": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.1.tgz", + "integrity": "sha512-R6PoNcUs89ZxLJmMWsVbwSWuz95/9OriyQZ3e2ybwqGsRXzhA6gv49rgGmQvLbZuSNDv9fCg7vV7gXUsvtUFaA==", + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + } + }, + "hast-util-is-element": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", + "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==", + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + } + }, + "hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "requires": { + "@types/hast": "^2.0.0" + } + }, + "hast-util-to-estree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.2.1.tgz", + "integrity": "sha512-kiGD9WIW3gRKK8Gao3n1f+ahUeTMeJUJILnIT2QNrPigDNdH7rJxzhEbh81UajGeAdAHFecT1a+fLVOCTq9B4Q==", + "requires": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "estree-util-attach-comments": "^2.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdxjs-esm": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.1", + "unist-util-position": "^4.0.0", + "zwitch": "^2.0.0" + } + }, + "hast-util-to-text": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz", + "integrity": "sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==" + }, + "hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, + "is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" + }, + "is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "requires": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + } + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + }, + "is-reference": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", + "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", + "requires": { + "@types/estree": "*" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "requires": { + "protocols": "^2.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, + "katex": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.4.tgz", + "integrity": "sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==", + "requires": { + "commander": "^8.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "markdown-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", + "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==" + }, + "markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==" + }, + "match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "requires": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, + "mdast-util-definitions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", + "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-find-and-replace": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", + "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", + "requires": { + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" + } + } + }, + "mdast-util-from-markdown": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", + "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", + "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", + "requires": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", + "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "requires": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + } + }, + "mdast-util-gfm-footnote": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", + "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.2.tgz", + "integrity": "sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-gfm-table": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz", + "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==", + "requires": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", + "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-math": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.1.tgz", + "integrity": "sha512-ZZtjyRwobsiVg4bY0Q5CzAZztpbjRIA7ZlMMb0PNkwTXOnJTUoHvzBhVG95LIuek5Mlj1l2P+jBvWviqW7G+0A==", + "requires": { + "@types/mdast": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-mdx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.0.tgz", + "integrity": "sha512-M09lW0CcBT1VrJUaF/PYxemxxHa7SLDHdSn94Q9FhxjCQfuW7nMAWKWimTmA3OyDMSTH981NN1csW1X+HPSluw==", + "requires": { + "mdast-util-mdx-expression": "^1.0.0", + "mdast-util-mdx-jsx": "^2.0.0", + "mdast-util-mdxjs-esm": "^1.0.0" + } + }, + "mdast-util-mdx-expression": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.3.1.tgz", + "integrity": "sha512-TTb6cKyTA1RD+1su1iStZ5PAv3rFfOUKcoU5EstUpv/IZo63uDX03R8+jXjMEhcobXnNOiG6/ccekvVl4eV1zQ==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + } + }, + "mdast-util-mdx-jsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-2.1.0.tgz", + "integrity": "sha512-KzgzfWMhdteDkrY4mQtyvTU5bc/W4ppxhe9SzelO6QUUiwLAM+Et2Dnjjprik74a336kHdo0zKm7Tp+n6FFeRg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-to-markdown": "^1.3.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "mdast-util-mdxjs-esm": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-1.3.0.tgz", + "integrity": "sha512-7N5ihsOkAEGjFotIX9p/YPdl4TqUoMxL4ajNz7PbT89BqsdWJuBC9rvgt6wpbwTZqWWR0jKWqQbwsOWDBUZv4g==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + } + }, + "mdast-util-phrasing": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.0.tgz", + "integrity": "sha512-S+QYsDRLkGi8U7o5JF1agKa/sdP+CNGXXLqC17pdTVL8FHHgQEiwFGa9yE5aYtUxNiFGYoaDy9V1kC85Sz86Gg==", + "requires": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + } + }, + "mdast-util-to-hast": { + "version": "12.2.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.5.tgz", + "integrity": "sha512-EFNhT35ZR/VZ85/EedDdCNTq0oFM+NM/+qBomVGQ0+Lcg0nhI8xIwmdCzNMlVlCJNXRprpobtKP/IUh8cfz6zQ==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "requires": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-footnote": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", + "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", + "requires": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", + "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-table": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", + "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-gfm-task-list-item": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", + "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-math": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz", + "integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==", + "requires": { + "@types/katex": "^0.11.0", + "katex": "^0.13.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "dependencies": { + "katex": { + "version": "0.13.24", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz", + "integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==", + "requires": { + "commander": "^8.0.0" + } + } + } + }, + "micromark-extension-mdx-expression": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.3.tgz", + "integrity": "sha512-TjYtjEMszWze51NJCZmhv7MEBcgYRgb3tJeMAJ+HQCAaZHHRBaDCccqQzGizR/H4ODefP44wRTgOn2vE5I6nZA==", + "requires": { + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-mdx-jsx": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-1.0.3.tgz", + "integrity": "sha512-VfA369RdqUISF0qGgv2FfV7gGjHDfn9+Qfiv5hEwpyr1xscRj/CiVRkU7rywGFCO7JwJ5L0e7CJz60lY52+qOA==", + "requires": { + "@types/acorn": "^4.0.0", + "estree-util-is-identifier-name": "^2.0.0", + "micromark-factory-mdx-expression": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "micromark-extension-mdx-md": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-1.0.0.tgz", + "integrity": "sha512-xaRAMoSkKdqZXDAoSgp20Azm0aRQKGOl0RrS81yGu8Hr/JhMsBmfs4wR7m9kgVUIO36cMUQjNyiyDKPrsv8gOw==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-mdxjs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-1.0.0.tgz", + "integrity": "sha512-TZZRZgeHvtgm+IhtgC2+uDMR7h8eTKF0QUX9YsgoL9+bADBpBY6SiLvWqnBlLbCEevITmTqmEuY3FoxMKVs1rQ==", + "requires": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^1.0.0", + "micromark-extension-mdx-jsx": "^1.0.0", + "micromark-extension-mdx-md": "^1.0.0", + "micromark-extension-mdxjs-esm": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-mdxjs-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-1.0.3.tgz", + "integrity": "sha512-2N13ol4KMoxb85rdDwTAC6uzs8lMX0zeqpcyx7FhS7PxXomOnLactu8WI8iBNXW8AVyea3KIJd/1CKnUmwrK9A==", + "requires": { + "micromark-core-commonmark": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.1.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-mdx-expression": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-1.0.6.tgz", + "integrity": "sha512-WRQIc78FV7KrCfjsEf/sETopbYjElh3xAmNpLkd1ODPqxEngP42eVRGbiPEQWpRV27LzqW+XVTvQAMIIRLPnNA==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-events-to-acorn": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-position-from-estree": "^1.0.0", + "uvu": "^0.5.0", + "vfile-message": "^3.0.0" + } + }, + "micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==" + }, + "micromark-util-events-to-acorn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-1.2.0.tgz", + "integrity": "sha512-WWp3bf7xT9MppNuw3yPjpnOxa8cj5ACivEzXJKu0WwnjBYfzaBvIAT9KfeyI0Qkll+bfQtfftSwdgTH6QhTOKw==", + "requires": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "estree-util-visit": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0", + "vfile-location": "^4.0.0", + "vfile-message": "^3.0.0" + } + }, + "micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==" + }, + "micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==" + }, + "micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "next": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-13.1.2.tgz", + "integrity": "sha512-Rdnnb2YH///w78FEOR/IQ6TXga+qpth4OqFSem48ng1PYYKr6XBsIk1XVaRcIGM3o6iiHnun0nJvkJHDf+ICyQ==", + "requires": { + "@next/env": "13.1.2", + "@next/swc-android-arm-eabi": "13.1.2", + "@next/swc-android-arm64": "13.1.2", + "@next/swc-darwin-arm64": "13.1.2", + "@next/swc-darwin-x64": "13.1.2", + "@next/swc-freebsd-x64": "13.1.2", + "@next/swc-linux-arm-gnueabihf": "13.1.2", + "@next/swc-linux-arm64-gnu": "13.1.2", + "@next/swc-linux-arm64-musl": "13.1.2", + "@next/swc-linux-x64-gnu": "13.1.2", + "@next/swc-linux-x64-musl": "13.1.2", + "@next/swc-win32-arm64-msvc": "13.1.2", + "@next/swc-win32-ia32-msvc": "13.1.2", + "@next/swc-win32-x64-msvc": "13.1.2", + "@swc/helpers": "0.4.14", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.1.1" + } + }, + "next-mdx-remote": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.2.1.tgz", + "integrity": "sha512-PcVF1r5XTBjiNVXw0GyaIcOwQsklHo36+7ycfmtJb52TIkT0nM4Hzv4wgJwNg7+jvTbap99qWsMwdKUYR9WxAA==", + "requires": { + "@mdx-js/mdx": "^2.2.1", + "@mdx-js/react": "^2.2.1", + "vfile": "^5.3.0", + "vfile-matter": "^3.0.1" + } + }, + "next-seo": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/next-seo/-/next-seo-5.15.0.tgz", + "integrity": "sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg==", + "requires": {} + }, + "next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "requires": {} + }, + "nextra": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nextra/-/nextra-2.2.5.tgz", + "integrity": "sha512-sm8mtKxLhfGWNv0yGd9GRL+DPDdVJlyEGn+6zk7gOPQor4X6G1VLcNjGlGYFovS4ZHNrg8oKmhOZSkq79PBrtw==", + "requires": { + "@mdx-js/mdx": "^2.1.5", + "@mdx-js/react": "^2.1.5", + "@napi-rs/simple-git": "^0.1.8", + "github-slugger": "^2.0.0", + "graceful-fs": "^4.2.10", + "gray-matter": "^4.0.3", + "katex": "^0.16.4", + "lodash.get": "^4.4.2", + "next-mdx-remote": "^4.2.0", + "p-limit": "^3.1.0", + "rehype-katex": "^6.0.2", + "rehype-pretty-code": "0.9.2", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "remark-reading-time": "^2.0.1", + "shiki": "^0.12.1", + "slash": "^3.0.0", + "title": "^3.5.3", + "unist-util-visit": "^4.1.1" + } + }, + "nextra-theme-docs": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nextra-theme-docs/-/nextra-theme-docs-2.2.5.tgz", + "integrity": "sha512-dpfOr8O4w3+q2BpBvnOXsmuvDCrrVl9QPrETzN8xKc9z6+oJNt4PEZOHRHo2rADHyV4832sSnWxhERrVzvqfNA==", + "requires": { + "@headlessui/react": "^1.7.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "flexsearch": "^0.7.21", + "focus-visible": "^5.2.0", + "git-url-parse": "^13.1.0", + "intersection-observer": "^0.12.2", + "match-sorter": "^6.3.1", + "next-seo": "^5.5.0", + "next-themes": "^0.2.1", + "scroll-into-view-if-needed": "^3.0.0", + "zod": "^3.20.2" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-entities": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.0.tgz", + "integrity": "sha512-5nk9Fn03x3rEhGaX1FU6IDwG/k+GxLXlFAkgrbM1asuAFl3BhdQWvASaIsmwWypRNcZKHPYnIuOSfIWEyEQnPQ==", + "requires": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + } + }, + "parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "requires": { + "protocols": "^2.0.0" + } + }, + "parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "requires": { + "parse-path": "^7.0.0" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "periscopic": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.0.4.tgz", + "integrity": "sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg==", + "requires": { + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" + }, + "protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "punycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz", + "integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + }, + "rehype-katex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.2.tgz", + "integrity": "sha512-C4gDAlS1+l0hJqctyiU64f9CvT00S03qV1T6HiMzbSuLBgWUtcqydWHY9OpKrm0SpkK16FNd62CDKyWLwV2ppg==", + "requires": { + "@types/hast": "^2.0.0", + "@types/katex": "^0.11.0", + "hast-util-to-text": "^3.1.0", + "katex": "^0.15.0", + "rehype-parse": "^8.0.0", + "unified": "^10.0.0", + "unist-util-remove-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "dependencies": { + "katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "requires": { + "commander": "^8.0.0" + } + } + } + }, + "rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "requires": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + } + }, + "rehype-pretty-code": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.2.tgz", + "integrity": "sha512-l369pvBK6ihBEuy2+VDpHU+zbbY8I+Z4LiyIOunHAt3xyw6selaOFKc/DnX94jI5OJb3+NgjbOxXx2yaAypjZw==", + "requires": { + "hash-obj": "^4.0.0", + "nanoid": "^4.0.0", + "parse-numeric-range": "^1.3.0" + }, + "dependencies": { + "nanoid": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==" + } + } + }, + "remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + } + }, + "remark-math": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz", + "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-math": "^2.0.0", + "micromark-extension-math": "^2.0.0", + "unified": "^10.0.0" + } + }, + "remark-mdx": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.2.1.tgz", + "integrity": "sha512-R9wcN+/THRXTKyRBp6Npo/mcbGA2iT3N4G8qUqLA5pOEg7kBidHv8K2hHidCMYZ6DXmwK18umu0K4cicgA2PPQ==", + "requires": { + "mdast-util-mdx": "^2.0.0", + "micromark-extension-mdxjs": "^1.0.0" + } + }, + "remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + } + }, + "remark-reading-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remark-reading-time/-/remark-reading-time-2.0.1.tgz", + "integrity": "sha512-fy4BKy9SRhtYbEHvp6AItbRTnrhiDGbqLQTSYVbQPGuRCncU1ubSsh9p/W5QZSxtYcUXv8KGL0xBgPLyNJA1xw==", + "requires": { + "estree-util-is-identifier-name": "^2.0.0", + "estree-util-value-to-estree": "^1.3.0", + "reading-time": "^1.3.0", + "unist-util-visit": "^3.1.0" + }, + "dependencies": { + "unist-util-visit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", + "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" + } + }, + "unist-util-visit-parents": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", + "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + } + } + }, + "remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + } + }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "requires": { + "mri": "^1.1.0" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "scroll-into-view-if-needed": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.4.tgz", + "integrity": "sha512-s+/F50jwTOUt+u5oEIAzum9MN2lUQNvWBe/zfEsVQcbaERjGkKLq1s+2wCHkahMLC8nMLbzMVKivx9JhunXaZg==", + "requires": { + "compute-scroll-into-view": "^2.0.4" + } + }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shiki": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", + "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", + "requires": { + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "sort-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", + "requires": { + "is-plain-obj": "^4.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, + "styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "requires": { + "client-only": "0.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "synckit": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz", + "integrity": "sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==", + "requires": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.4.0" + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "requires": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "title": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/title/-/title-3.5.3.tgz", + "integrity": "sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==", + "requires": { + "arg": "1.0.0", + "chalk": "2.3.0", + "clipboardy": "1.2.2", + "titleize": "1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "requires": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==" + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==", + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "titleize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-1.0.0.tgz", + "integrity": "sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==" + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, + "unist-builder": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", + "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-find-after": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.0.tgz", + "integrity": "sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, + "unist-util-generated": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", + "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==" + }, + "unist-util-is": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==" + }, + "unist-util-position": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", + "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-position-from-estree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-1.1.1.tgz", + "integrity": "sha512-xtoY50b5+7IH8tFbkw64gisG9tMSpxDjhX9TmaJJae/XuxQ9R/Kc8Nv1eOsf43Gt4KV/LkriMy9mptDr7XLcaw==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-remove-position": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.1.tgz", + "integrity": "sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", + "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + } + }, + "unist-util-visit-parents": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", + "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, + "vfile": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.6.tgz", + "integrity": "sha512-ADBsmerdGBs2WYckrLBEmuETSPyTD4TuLxTrw0DvjirxW1ra4ZwkbzG8ndsv3Q57smvHxo677MHaQrY9yxH8cA==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-location": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", + "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "requires": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "vfile-matter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-3.0.1.tgz", + "integrity": "sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==", + "requires": { + "@types/js-yaml": "^4.0.0", + "is-buffer": "^2.0.0", + "js-yaml": "^4.0.0" + } + }, + "vfile-message": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.3.tgz", + "integrity": "sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, + "web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "zod": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.2.tgz", + "integrity": "sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==" + }, + "zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" + } + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package.json new file mode 100644 index 0000000..da6a899 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package.json @@ -0,0 +1,25 @@ +{ + "name": "website", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@next/font": "13.1.2", + "@types/node": "18.11.18", + "@types/react": "18.0.26", + "@types/react-dom": "18.0.10", + "eslint": "8.32.0", + "eslint-config-next": "13.1.2", + "next": "13.1.2", + "nextra": "^2.2.5", + "nextra-theme-docs": "^2.2.5", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "4.9.4" + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_app.tsx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_app.tsx new file mode 100644 index 0000000..021681f --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_app.tsx @@ -0,0 +1,6 @@ +import '@/styles/globals.css' +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_document.tsx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_document.tsx new file mode 100644 index 0000000..54e8bf3 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_meta.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_meta.json new file mode 100644 index 0000000..baafb5b --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_meta.json @@ -0,0 +1,5 @@ +{ + "index": "Home", + "getting-started": "Getting Started", + "api-docs": "API" +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/_meta.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/_meta.json new file mode 100644 index 0000000..306b425 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/_meta.json @@ -0,0 +1,3 @@ +{ + "concepts": "Concepts" +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/concepts.mdx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/concepts.mdx new file mode 100644 index 0000000..f3e1f2f --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/concepts.mdx @@ -0,0 +1,2 @@ +# General API Concepts + diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api/hello.ts b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api/hello.ts new file mode 100644 index 0000000..f8bcc7e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next' + +type Data = { + name: string +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started.mdx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started.mdx new file mode 100644 index 0000000..d5eca4a --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started.mdx @@ -0,0 +1,3 @@ +# Getting Started + +TODO diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/_meta.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/_meta.json new file mode 100644 index 0000000..306b425 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/_meta.json @@ -0,0 +1,3 @@ +{ + "concepts": "Concepts" +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/concepts.mdx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/concepts.mdx new file mode 100644 index 0000000..90f6f35 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/concepts.mdx @@ -0,0 +1,3 @@ +# General Concepts + +TODO diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/index.mdx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/index.mdx new file mode 100644 index 0000000..789ef3d --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/index.mdx @@ -0,0 +1,9 @@ +# libxev + +import { Callout } from 'nextra-theme-docs' + +TODO! See the GitHub repo for now. + + + **Coming soon.** I've just got some scaffolding down. + diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/favicon.ico b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/favicon.ico differ diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/next.svg b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/thirteen.svg b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/thirteen.svg new file mode 100644 index 0000000..8977c1b --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/vercel.svg b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/vercel.svg new file mode 100644 index 0000000..d2f8422 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/Home.module.css b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/Home.module.css new file mode 100644 index 0000000..27dfff5 --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/Home.module.css @@ -0,0 +1,278 @@ +.main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 6rem; + min-height: 100vh; +} + +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); +} + +.description a { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; +} + +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); +} + +.code { + font-weight: 700; + font-family: var(--font-mono); +} + +.grid { + display: grid; + grid-template-columns: repeat(4, minmax(25%, auto)); + width: var(--max-width); + max-width: 100%; +} + +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; +} + +.card span { + display: inline-block; + transition: transform 200ms; +} + +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; +} + +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; + justify-content: center; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; +} + +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; +} + +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); +} + +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; +} + +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } +} + +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } +} + +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; + width: 100%; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); + } +} + +@media (prefers-color-scheme: dark) { + .vercelLogo { + filter: invert(1); + } + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/globals.css b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/globals.css new file mode 100644 index 0000000..d4f491e --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/globals.css @@ -0,0 +1,107 @@ +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/theme.config.jsx b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/theme.config.jsx new file mode 100644 index 0000000..ac713dd --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/theme.config.jsx @@ -0,0 +1,33 @@ +import { useRouter } from 'next/router' + +export default { + logo: libxev, + docsRepositoryBase: 'https://github.com/mitchellh/libxev/blob/website/pages', + + project: { + link: 'https://github.com/mitchellh/libxev', + }, + + feedback: { + content: null, + }, + + footer: { + text: + © {new Date().getFullYear()} + libxev + , + }, + + useNextSeoProps() { + const { route } = useRouter() + if (route !== '/') { + return { + titleTemplate: '%s – libxev' + } + } + }, + + // Hide the last updated date. + gitTimestamp: , +} diff --git a/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/tsconfig.json b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/tsconfig.json new file mode 100644 index 0000000..f4ab65f --- /dev/null +++ b/zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}