From a84353d6e2ea6fc1589b35e87df22114f7efcaff Mon Sep 17 00:00:00 2001 From: Serhii Mariiekha Date: Sun, 19 Apr 2026 22:38:56 +0200 Subject: [PATCH 1/2] Port toolchain to Zig 0.16 Updates build system, source modules, benchmarks, tests, and docs to the Zig 0.16 API, and fixes CI regressions discovered on the way. Also extends .gitignore for locally-generated test binaries and the Claude Code plugin lock so they don't get committed by mistake. --- .github/workflows/release.yml | 4 +- .github/workflows/runtime-ci.yml | 4 +- .github/workflows/zig-ci.yml | 10 +- .gitignore | 6 + AGENTS.md | 4 +- CLAUDE.md | 4 +- CONTRIBUTING.md | 4 +- README.md | 2 +- benchmarks/bench.zig | 123 ++++--- build.zig | 64 ++-- build.zig.zon | 6 +- packaging/deb/control.template | 2 +- packaging/rpm/run.spec | 4 +- packaging/snap/snapcraft.yaml | 2 +- scripts/create-dmg.sh | 2 +- src/codegen_c.zig | 16 +- src/dap.zig | 304 +++++++++--------- src/diagnostics.zig | 80 +++-- src/driver.zig | 209 ++++++++---- src/formatter.zig | 2 +- src/gdb_mi.zig | 82 +++-- src/init.zig | 68 ++-- src/lower.zig | 17 + src/lsp.zig | 153 +++++---- src/main.zig | 185 ++++++----- src/rasm.zig | 46 +-- src/typecheck.zig | 29 +- tests/e2e/runner.zig | 133 +++----- tests/examples/runner.zig | 99 +++--- .../components/landing/GettingStarted.astro | 2 +- .../docs/getting-started/installation.md | 6 +- .../content/docs/reference/compiler-status.md | 2 +- 32 files changed, 915 insertions(+), 759 deletions(-) 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 From 4bcfd96a822bc278dfc6710d89cc5554b503931b Mon Sep 17 00:00:00 2001 From: Serhii Mariiekha Date: Sun, 19 Apr 2026 23:52:30 +0200 Subject: [PATCH 2/2] Vendor libxev package cache at 86vtcwIRF hash Adds the Zig package-cache copy of libxev at zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/ alongside the existing main-vendored 86vtc689 hash. --- .../.envrc | 5 + .../.github/dependabot.yml | 8 + .../.github/workflows/test.yml | 156 + .../.gitignore | 2 + .../LICENSE | 21 + .../README.md | 336 + .../build.zig | 342 + .../build.zig.zon | 7 + .../docs/xev-c.7.scd | 12 + .../docs/xev-faq.7.scd | 48 + .../docs/xev-zig.7.scd | 136 + .../docs/xev.7.scd | 338 + .../docs/xev_completion_state.3.scd | 48 + .../docs/xev_completion_zero.3.scd | 44 + .../docs/xev_threadpool.3.scd | 167 + .../examples/_basic.c | 42 + .../examples/_basic.zig | 30 + .../examples/async.c | 71 + .../examples/million-timers.c | 91 + .../examples/threadpool.c | 80 + .../flake.lock | 126 + .../flake.nix | 64 + .../include/xev.h | 103 + .../nix/package.nix | 39 + .../shell.nix | 12 + .../src/ThreadPool.zig | 833 ++ .../src/api.zig | 88 + .../src/backend.zig | 58 + .../src/backend/epoll.zig | 2134 ++++ .../src/backend/io_uring.zig | 1792 +++ .../src/backend/iocp.zig | 2421 ++++ .../src/backend/kqueue.zig | 2972 +++++ .../src/backend/wasi_poll.zig | 1652 +++ .../src/bench/async1.zig | 106 + .../src/bench/async2.zig | 10 + .../src/bench/async4.zig | 10 + .../src/bench/async8.zig | 10 + .../src/bench/async_pummel_1.zig | 82 + .../src/bench/async_pummel_2.zig | 10 + .../src/bench/async_pummel_4.zig | 10 + .../src/bench/async_pummel_8.zig | 10 + .../src/bench/million-timers.zig | 67 + .../src/bench/ping-pongs.zig | 356 + .../src/bench/ping-udp1.zig | 178 + .../src/bench/udp_pummel_1v1.zig | 148 + .../src/c_api.zig | 360 + .../src/darwin.zig | 376 + .../src/debug.zig | 58 + .../src/dynamic.zig | 515 + .../src/heap.zig | 379 + .../src/linux/timerfd.zig | 90 + .../src/loop.zig | 103 + .../src/main.zig | 130 + .../src/posix.zig | 694 + .../src/queue.zig | 101 + .../src/queue_mpsc.zig | 116 + .../src/watcher/async.zig | 833 ++ .../src/watcher/common.zig | 7 + .../src/watcher/file.zig | 980 ++ .../src/watcher/process.zig | 580 + .../src/watcher/stream.zig | 1444 +++ .../src/watcher/tcp.zig | 951 ++ .../src/watcher/timer.zig | 641 + .../src/watcher/udp.zig | 1016 ++ .../src/windows.zig | 815 ++ .../website/.eslintrc.json | 3 + .../website/.gitignore | 36 + .../website/next.config.js | 11 + .../website/package-lock.json | 10539 ++++++++++++++++ .../website/package.json | 25 + .../website/pages/_app.tsx | 6 + .../website/pages/_document.tsx | 13 + .../website/pages/_meta.json | 5 + .../website/pages/api-docs/_meta.json | 3 + .../website/pages/api-docs/concepts.mdx | 2 + .../website/pages/api/hello.ts | 13 + .../website/pages/getting-started.mdx | 3 + .../website/pages/getting-started/_meta.json | 3 + .../pages/getting-started/concepts.mdx | 3 + .../website/pages/index.mdx | 9 + .../website/public/favicon.ico | Bin 0 -> 25931 bytes .../website/public/next.svg | 1 + .../website/public/thirteen.svg | 1 + .../website/public/vercel.svg | 1 + .../website/styles/Home.module.css | 278 + .../website/styles/globals.css | 107 + .../website/theme.config.jsx | 33 + .../website/tsconfig.json | 24 + 88 files changed, 36583 insertions(+) create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.envrc create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/dependabot.yml create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.github/workflows/test.yml create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/.gitignore create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/LICENSE create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/README.md create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/build.zig.zon create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-c.7.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-faq.7.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev-zig.7.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev.7.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_state.3.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_completion_zero.3.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/docs/xev_threadpool.3.scd create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.c create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/_basic.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/async.c create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/million-timers.c create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/examples/threadpool.c create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.lock create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/flake.nix create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/include/xev.h create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/nix/package.nix create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/shell.nix create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/ThreadPool.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/api.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/epoll.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/io_uring.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/iocp.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/kqueue.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/backend/wasi_poll.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async1.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async2.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async4.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async8.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_1.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_2.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_4.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/async_pummel_8.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/million-timers.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-pongs.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/ping-udp1.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/bench/udp_pummel_1v1.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/c_api.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/darwin.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/debug.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/dynamic.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/heap.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/linux/timerfd.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/loop.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/main.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/posix.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/queue_mpsc.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/async.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/common.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/file.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/process.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/stream.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/tcp.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/timer.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/watcher/udp.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/src/windows.zig create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.eslintrc.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/.gitignore create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/next.config.js create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package-lock.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/package.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_app.tsx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_document.tsx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/_meta.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/_meta.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api-docs/concepts.mdx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/api/hello.ts create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started.mdx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/_meta.json create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/getting-started/concepts.mdx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/pages/index.mdx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/favicon.ico create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/next.svg create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/thirteen.svg create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/public/vercel.svg create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/Home.module.css create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/styles/globals.css create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/theme.config.jsx create mode 100644 zig-pkg/libxev-0.0.0-86vtcwIRFACVrx54GaHsMFFlyC4dTi0tcVh10V7btRUc/website/tsconfig.json 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 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 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"] +}