From 446edebaaae344ef08117689778ecae0d7cbe7c9 Mon Sep 17 00:00:00 2001 From: armagidon-exception Date: Tue, 3 Mar 2026 21:05:39 +0300 Subject: [PATCH] Port to latest zig-master --- .github/workflows/release.yml | 2 +- README.md | 43 +-- TODO.md | 48 ++- benchmarks/basic.zig | 20 +- benchmarks/prefix_optimization.zig | 51 ++-- benchmarks/simple.zig | 40 ++- build.zig | 1 + docs/ADVANCED_FEATURES.md | 26 +- docs/API.md | 21 ++ docs/ARCHITECTURE.md | 92 ++++-- docs/BENCHMARKS.md | 47 +-- docs/WASM.md | 142 +++++---- examples/advanced_features.zig | 12 +- examples/basic.zig | 12 +- examples/debug_visualization.zig | 6 +- examples/error_handling.zig | 22 +- examples/profiling_example.zig | 40 +-- examples/thread_safety.zig | 36 +-- src/advanced_features.zig | 8 +- src/backtrack.zig | 1 - src/benchmark.zig | 4 +- src/c_api.zig | 429 +++++++++++++-------------- src/compiler.zig | 2 +- src/main.zig | 2 +- src/parser.zig | 10 +- src/pattern_analyzer.zig | 3 +- src/profiling.zig | 43 ++- src/regex.zig | 52 ++-- src/root.zig | 2 +- src/thread_safety.zig | 12 +- src/unicode.zig | 153 ++++++---- tests/advanced_edge_cases.zig | 48 +-- tests/backreferences.zig | 46 +-- tests/case_insensitive.zig | 18 +- tests/comprehensive.zig | 28 +- tests/edge_cases.zig | 50 ++-- tests/fuzz.zig | 28 +- tests/integration.zig | 44 +-- tests/iterator.zig | 18 +- tests/lazy_quantifiers.zig | 46 +-- tests/lookahead.zig | 22 +- tests/lookbehind.zig | 22 +- tests/multiline_dotall.zig | 24 +- tests/named_captures.zig | 24 +- tests/non_capturing_groups.zig | 32 +- tests/parser_compiler_edge_cases.zig | 70 ++--- tests/posix_classes.zig | 36 +-- tests/quantifiers.zig | 18 +- tests/regression.zig | 34 +-- tests/stress_edge_cases.zig | 116 ++++---- tests/string_anchors.zig | 12 +- tests/thread_safety.zig | 20 +- tests/utf8_unicode.zig | 28 +- 53 files changed, 1149 insertions(+), 1017 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d46087d..66719fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - 'v*' + - "v*" permissions: contents: write diff --git a/README.md b/README.md index ef541af..bd74262 100644 --- a/README.md +++ b/README.md @@ -21,29 +21,29 @@ zig-regex is a regular expression engine for Zig featuring Thompson NFA construc ### Core Regex Features -| Feature | Syntax | Status | -|---------|--------|--------| -| **Literals** | `abc`, `123` | Stable | -| **Quantifiers** | `*`, `+`, `?`, `{n}`, `{m,n}` | Stable | -| **Alternation** | `a\|b\|c` | Stable | -| **Character Classes** | `\d`, `\w`, `\s`, `\D`, `\W`, `\S` | Stable | -| **Custom Classes** | `[abc]`, `[a-z]`, `[^0-9]` | Stable | -| **Anchors** | `^`, `$`, `\b`, `\B` | Stable | -| **Wildcards** | `.` | Stable | -| **Capturing Groups** | `(...)` | Stable | -| **Named Groups** | `(?P...)`, `(?...)` | Stable | -| **Non-capturing** | `(?:...)` | Stable | -| **Lookahead** | `(?=...)`, `(?!...)` | Stable | -| **Lookbehind** | `(?<=...)`, `(?...)`, `(?...)` | Stable | +| **Non-capturing** | `(?:...)` | Stable | +| **Lookahead** | `(?=...)`, `(?!...)` | Stable | +| **Lookbehind** | `(?<=...)`, `(? s1` | -| Concatenation 'ab' | `s0 --a--> s1 --ε--> s2 --b--> s3` | +| Pattern | NFA Fragment | +| ------------------ | ---------------------------------------------------------------------------- | +| Literal 'a' | `s0 --a--> s1` | +| Concatenation 'ab' | `s0 --a--> s1 --ε--> s2 --b--> s3` | | Alternation 'a\|b' | `s0 --ε--> (s1 --a--> s2)
    └--ε--> (s3 --b--> s4)` | -| Star 'a*' | `s0 --ε--> s1 --a--> s2 --ε--> s1
    └--ε--> accept` | +| Star 'a\*' | `s0 --ε--> s1 --a--> s2 --ε--> s1
    └--ε--> accept` | **Design Notes:** + - Each AST node compiles to a Fragment (start, accept states) - Epsilon transitions used for control flow - Greedy matching achieved through simulation order - Capture groups marked on states, not transitions ### 5. vm.zig (340 lines) + **Purpose:** NFA simulation engine **Thread-Based Simulation:** + ``` Thread = { state: StateId, @@ -190,29 +206,35 @@ Algorithm: ``` **Key Features:** + - **Greedy Matching:** Continues matching to find longest match - **Epsilon Closure:** Precomputed before consuming input - **Capture Tracking:** Each thread maintains capture positions - **No Backtracking:** All paths explored simultaneously **Time Complexity:** -- O(n * m) where n = input length, m = number of states -- Worst case: O(n * 2^p) where p = number of alternations + +- O(n \* m) where n = input length, m = number of states +- Worst case: O(n \* 2^p) where p = number of alternations **Space Complexity:** -- O(m * c) where m = states, c = capture groups + +- O(m \* c) where m = states, c = capture groups - Thread list grows with state complexity **Design Notes:** + - Threads represent possible execution paths - Visited states tracked to avoid infinite loops - Anchors checked during epsilon closure - Match preference: longer match > earlier match ### 6. regex.zig (385 lines) + **Purpose:** Public API and convenience functions **API Surface:** + ```zig // Compilation Regex.compile(allocator, pattern) -> Regex @@ -230,15 +252,18 @@ regex.split(allocator, input) -> [][]const u8 ``` **Design Notes:** + - Immutable compiled patterns (can reuse) - All allocations explicit and user-controlled - Match results contain slices (no copies) - Iterator pattern not yet implemented ### 7. errors.zig (67 lines) + **Purpose:** Error types and reporting **Error Types:** + ```zig pub const RegexError = error{ // Parse errors @@ -260,6 +285,7 @@ pub const RegexError = error{ ``` **Error Context:** + - Position tracking in pattern - Human-readable messages - Pointer to error location @@ -269,12 +295,14 @@ pub const RegexError = error{ ### Thompson's NFA Construction **Why Thompson?** + 1. Simple to implement 2. Guaranteed linear time matching 3. No catastrophic backtracking 4. Naturally supports alternation **How it works:** + 1. Each regex operator has a standard NFA fragment pattern 2. Fragments compose recursively 3. Epsilon transitions connect fragments @@ -283,16 +311,19 @@ pub const RegexError = error{ ### Thread-Based Simulation **Why Threads?** + - Simulates NFA without building DFA (space efficient) - Handles large state spaces - Natural support for captures **Greedy Matching:** + - Continue simulation even after finding a match - Keep longest match found - Ensures `a*` matches "aaa" not "" **Epsilon Closure:** + - Precompute all states reachable via ε-transitions - Check anchors during closure - Avoids redundant state exploration @@ -300,6 +331,7 @@ pub const RegexError = error{ ## Memory Management **Ownership Model:** + ``` Regex └─ owns NFA @@ -315,6 +347,7 @@ Matches ``` **Allocation Points:** + 1. Pattern string duplication 2. AST node creation (freed after compilation) 3. NFA states and transitions @@ -322,6 +355,7 @@ Matches 5. Match results and captures **Guidelines:** + - User provides allocator for all operations - No hidden global allocations - Clear ownership semantics @@ -331,27 +365,28 @@ Matches ### Time Complexity -| Operation | Complexity | Notes | -|-----------|------------|-------| -| Compile | O(p) | p = pattern length | -| isMatch | O(n * m) | n = input, m = states | -| find | O(n * m * k) | k = positions to try | -| findAll | O(n * m) | Amortized | -| replace | O(n + r) | r = replacement length | -| split | O(n * m) | Plus allocation | +| Operation | Complexity | Notes | +| --------- | ------------ | ---------------------- | +| Compile | O(p) | p = pattern length | +| isMatch | O(n \* m) | n = input, m = states | +| find | O(n _ m _ k) | k = positions to try | +| findAll | O(n \* m) | Amortized | +| replace | O(n + r) | r = replacement length | +| split | O(n \* m) | Plus allocation | ### Space Complexity -| Component | Complexity | Notes | -|-----------|------------|-------| -| AST | O(p) | Temporary | -| NFA | O(p) | Persistent | -| VM Threads | O(m * c) | c = captures | -| Matches | O(r) | r = result count | +| Component | Complexity | Notes | +| ---------- | ---------- | ---------------- | +| AST | O(p) | Temporary | +| NFA | O(p) | Persistent | +| VM Threads | O(m \* c) | c = captures | +| Matches | O(r) | r = result count | ### Optimization Opportunities **Not Yet Implemented:** + 1. DFA construction for static patterns 2. State minimization 3. Literal prefix extraction @@ -362,6 +397,7 @@ Matches ## Testing Strategy **Test Categories:** + 1. **Unit Tests** - Each module tested independently 2. **Integration Tests** - End-to-end pattern matching 3. **Edge Cases** - Empty strings, boundary conditions @@ -369,6 +405,7 @@ Matches 5. **Property Tests** - Invariants (future) **Current Coverage:** + - 35+ tests across all modules - All basic features tested - Edge cases covered @@ -377,18 +414,21 @@ Matches ## Future Enhancements ### Phase 6: Quality & Testing + - Comprehensive fuzzing - Property-based tests - Performance regression tests - Memory leak detection ### Phase 7: Performance + - DFA construction option - Literal prefix optimization - State minimization - Benchmark suite ### Phase 8: Extended Features + - Named capture groups - Backreferences - Lookahead/lookbehind @@ -396,6 +436,7 @@ Matches - Unicode properties ### Phase 9: API Improvements + - Iterator interface - Streaming matches - Partial matching @@ -404,10 +445,12 @@ Matches ## References **Algorithms:** + - Thompson, Ken (1968). "Regular Expression Search Algorithm" - Cox, Russ (2007). "Regular Expression Matching Can Be Simple And Fast" **Implementations:** + - RE2 (Google) - Inspiration for architecture - Rust regex crate - API design reference - PCRE - Feature completeness reference @@ -415,6 +458,7 @@ Matches ## Contributing When modifying the architecture: + 1. Maintain phase separation 2. Keep allocations explicit 3. Preserve linear time guarantees diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index 4cda54e..b9f732f 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -32,12 +32,12 @@ All benchmarks run with 10,000 iterations unless otherwise noted. ### Core Operations -| Pattern | Input | Avg Time | Ops/sec | Description | -|---------|-------|----------|---------|-------------| -| `hello` | `hello world` | ~6.87 µs | ~145,000 | Simple literal match | -| `a+` | `aaaa` | ~5.50 µs | ~182,000 | Quantifier (one or more) | -| `\d+` | `12345` | ~5.01 µs | ~200,000 | Digit character class | -| `hello` (case-insensitive) | `HELLO` | ~4.62 µs | ~216,000 | Case-insensitive match | +| Pattern | Input | Avg Time | Ops/sec | Description | +| -------------------------- | ------------- | -------- | -------- | ------------------------ | +| `hello` | `hello world` | ~6.87 µs | ~145,000 | Simple literal match | +| `a+` | `aaaa` | ~5.50 µs | ~182,000 | Quantifier (one or more) | +| `\d+` | `12345` | ~5.01 µs | ~200,000 | Digit character class | +| `hello` (case-insensitive) | `HELLO` | ~4.62 µs | ~216,000 | Case-insensitive match | ### Detailed Results @@ -65,24 +65,24 @@ Test 4: Case-insensitive matching... ### Time Complexity -| Operation | Complexity | Notes | -|-----------|------------|-------| -| Compile | O(p) | p = pattern length | -| isMatch | O(n × m) | n = input length, m = NFA states | -| find | O(n × m × k) | k = starting positions attempted | -| findAll | O(n × m) | Amortized over all matches | -| replace | O(n + r) | r = replacement length | -| replaceAll | O(n × m + r) | Includes matching cost | -| split | O(n × m) | Plus array allocation | +| Operation | Complexity | Notes | +| ---------- | ------------ | -------------------------------- | +| Compile | O(p) | p = pattern length | +| isMatch | O(n × m) | n = input length, m = NFA states | +| find | O(n × m × k) | k = starting positions attempted | +| findAll | O(n × m) | Amortized over all matches | +| replace | O(n + r) | r = replacement length | +| replaceAll | O(n × m + r) | Includes matching cost | +| split | O(n × m) | Plus array allocation | ### Space Complexity -| Component | Complexity | Notes | -|-----------|------------|-------| -| Compiled Regex (NFA) | O(p) | Persistent storage | -| VM Execution | O(m × c) | c = capture group count | -| Match Results | O(r) | r = number of results | -| Temporary (AST) | O(p) | Freed after compilation | +| Component | Complexity | Notes | +| -------------------- | ---------- | ----------------------- | +| Compiled Regex (NFA) | O(p) | Persistent storage | +| VM Execution | O(m × c) | c = capture group count | +| Match Results | O(r) | r = number of results | +| Temporary (AST) | O(p) | Freed after compilation | ### Greedy Matching Performance @@ -255,11 +255,13 @@ Pattern: "\\d{3}-\\d{4}" ### vs. Backtracking Engines (PCRE, Python re) **Advantages:** + - ✅ **No catastrophic backtracking** - Always linear time - ✅ **Predictable performance** - O(n×m) guaranteed - ✅ **Memory safe** - No stack overflow on deep recursion **Trade-offs:** + - ❌ **Slower on simple patterns** - More overhead for simple cases - ❌ **No backreferences** - Not currently supported - ❌ **No look-around** - Not currently supported @@ -267,10 +269,12 @@ Pattern: "\\d{3}-\\d{4}" ### vs. DFA Engines (RE2, ripgrep) **Similarities:** + - ✅ **Linear time** - Both guarantee O(n×m) - ✅ **No backtracking** - Deterministic matching **Trade-offs:** + - ❌ **NFA simulation** - We don't build full DFA (saves memory) - ❌ **Capture groups** - Our approach is simpler for captures - ✅ **Memory usage** - Lower than full DFA construction @@ -410,6 +414,7 @@ CSV splitting (100,000 rows): ~300ms total ``` These are estimates. Actual performance depends on: + - Pattern complexity - Input characteristics - Hardware specifications diff --git a/docs/WASM.md b/docs/WASM.md index e73bf87..576cc06 100644 --- a/docs/WASM.md +++ b/docs/WASM.md @@ -29,14 +29,11 @@ This will produce `regex.wasm` which can be loaded in browsers or Node.js. ```javascript // Load the WASM module -const wasmModule = await WebAssembly.instantiateStreaming( - fetch('regex.wasm'), - { - env: { - // Provide any required imports - } - } -); +const wasmModule = await WebAssembly.instantiateStreaming(fetch("regex.wasm"), { + env: { + // Provide any required imports + }, +}); // Use the exported functions const exports = wasmModule.instance.exports; @@ -48,7 +45,7 @@ const regex = exports.zig_regex_compile(pattern); // Check for match const input = "test 123"; const matches = exports.zig_regex_is_match(regex, input); -console.log('Matches:', matches === 1); +console.log("Matches:", matches === 1); // Clean up exports.zig_regex_free(regex); @@ -57,28 +54,29 @@ exports.zig_regex_free(regex); ### Node.js Usage ```javascript -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); // Load WASM file -const wasmPath = path.join(__dirname, 'regex.wasm'); +const wasmPath = path.join(__dirname, "regex.wasm"); const wasmBuffer = fs.readFileSync(wasmPath); // Instantiate WebAssembly.instantiate(wasmBuffer, { - env: { - // Provide imports if needed - } -}).then(result => { - const { zig_regex_compile, zig_regex_is_match, zig_regex_free } = result.instance.exports; + env: { + // Provide imports if needed + }, +}).then((result) => { + const { zig_regex_compile, zig_regex_is_match, zig_regex_free } = + result.instance.exports; - // Use the library - const regex = zig_regex_compile("hello.*world"); - const matches = zig_regex_is_match(regex, "hello beautiful world"); + // Use the library + const regex = zig_regex_compile("hello.*world"); + const matches = zig_regex_is_match(regex, "hello beautiful world"); - console.log('Pattern matches:', matches === 1); + console.log("Pattern matches:", matches === 1); - zig_regex_free(regex); + zig_regex_free(regex); }); ``` @@ -89,43 +87,43 @@ Create a TypeScript wrapper for type safety: ```typescript // regex.d.ts export interface WasmRegex { - compile(pattern: string): number | null; - isMatch(regex: number, input: string): boolean; - find(regex: number, input: string): Match | null; - free(regex: number): void; + compile(pattern: string): number | null; + isMatch(regex: number, input: string): boolean; + find(regex: number, input: string): Match | null; + free(regex: number): void; } export interface Match { - text: string; - start: number; - end: number; + text: string; + start: number; + end: number; } // regex.ts export class Regex { - private wasm: WasmRegex; - private handle: number; - - constructor(wasm: WasmRegex, pattern: string) { - this.wasm = wasm; - const handle = wasm.compile(pattern); - if (handle === null) { - throw new Error('Failed to compile regex pattern'); - } - this.handle = handle; + private wasm: WasmRegex; + private handle: number; + + constructor(wasm: WasmRegex, pattern: string) { + this.wasm = wasm; + const handle = wasm.compile(pattern); + if (handle === null) { + throw new Error("Failed to compile regex pattern"); } + this.handle = handle; + } - isMatch(input: string): boolean { - return this.wasm.isMatch(this.handle, input); - } + isMatch(input: string): boolean { + return this.wasm.isMatch(this.handle, input); + } - find(input: string): Match | null { - return this.wasm.find(this.handle, input); - } + find(input: string): Match | null { + return this.wasm.find(this.handle, input); + } - free(): void { - this.wasm.free(this.handle); - } + free(): void { + this.wasm.free(this.handle); + } } ``` @@ -141,36 +139,36 @@ The WASM build uses the C allocator, which means: ```javascript class RegexManager { - constructor(wasmExports) { - this.exports = wasmExports; - this.regexes = new Map(); - } - - compile(pattern) { - const handle = this.exports.zig_regex_compile(pattern); - if (handle) { - this.regexes.set(handle, pattern); - } - return handle; + constructor(wasmExports) { + this.exports = wasmExports; + this.regexes = new Map(); + } + + compile(pattern) { + const handle = this.exports.zig_regex_compile(pattern); + if (handle) { + this.regexes.set(handle, pattern); } + return handle; + } - isMatch(handle, input) { - return this.exports.zig_regex_is_match(handle, input) === 1; - } + isMatch(handle, input) { + return this.exports.zig_regex_is_match(handle, input) === 1; + } - free(handle) { - if (this.regexes.has(handle)) { - this.exports.zig_regex_free(handle); - this.regexes.delete(handle); - } + free(handle) { + if (this.regexes.has(handle)) { + this.exports.zig_regex_free(handle); + this.regexes.delete(handle); } + } - freeAll() { - for (const handle of this.regexes.keys()) { - this.exports.zig_regex_free(handle); - } - this.regexes.clear(); + freeAll() { + for (const handle of this.regexes.keys()) { + this.exports.zig_regex_free(handle); } + this.regexes.clear(); + } } ``` diff --git a/examples/advanced_features.zig b/examples/advanced_features.zig index e9112ab..45c00d2 100644 --- a/examples/advanced_features.zig +++ b/examples/advanced_features.zig @@ -6,7 +6,7 @@ const Composer = @import("regex").Composer; const Lint = @import("regex").Lint; const ComplexityAnalyzer = @import("regex").ComplexityAnalyzer; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -39,7 +39,7 @@ pub fn main() !void { std.debug.print("Built pattern: {s}\n", .{pattern}); - var regex = try Regex.compile(allocator, pattern); + var regex = try Regex.compile(allocator, init.io, pattern); defer regex.deinit(); const test_emails = [_][]const u8{ @@ -78,7 +78,7 @@ pub fn main() !void { std.debug.print("Phone pattern: {s}\n\n", .{phone_pattern}); // Test phone number pattern - var phone_regex = try Regex.compile(allocator, phone_pattern); + var phone_regex = try Regex.compile(allocator, init.io, phone_pattern); defer phone_regex.deinit(); const test_phones = [_][]const u8{ @@ -116,7 +116,7 @@ pub fn main() !void { std.debug.print("Composed pattern (OR): {s}\n", .{alternatives}); - var regex = try Regex.compile(allocator, alternatives); + var regex = try Regex.compile(allocator, init.io, alternatives); defer regex.deinit(); const test_strings = [_][]const u8{ @@ -232,7 +232,7 @@ pub fn main() !void { std.debug.print("Complexity: {s} (score: {d})\n\n", .{ @tagName(complexity.getLevel()), complexity.complexity_score }); // Test the pattern - var regex = try Regex.compile(allocator, url_pattern_built); + var regex = try Regex.compile(allocator, init.io, url_pattern_built); defer regex.deinit(); const test_urls = [_][]const u8{ @@ -305,7 +305,7 @@ pub fn main() !void { std.debug.print("Built pattern: {s}\n\n", .{ipv4_pattern}); - var regex = try Regex.compile(allocator, ipv4_pattern); + var regex = try Regex.compile(allocator, init.io, ipv4_pattern); defer regex.deinit(); const test_ips = [_][]const u8{ diff --git a/examples/basic.zig b/examples/basic.zig index 877f801..0d50b04 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Regex = @import("regex").Regex; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -11,7 +11,7 @@ pub fn main() !void { // Example 1: Simple literal matching { std.debug.print("Example 1: Literal matching\n", .{}); - var regex = try Regex.compile(allocator, "hello"); + var regex = try Regex.compile(allocator, init.io, "hello"); defer regex.deinit(); const matches = try regex.isMatch("hello"); @@ -22,7 +22,7 @@ pub fn main() !void { // Example 2: Alternation { std.debug.print("Example 2: Alternation (cat|dog)\n", .{}); - var regex = try Regex.compile(allocator, "cat|dog"); + var regex = try Regex.compile(allocator, init.io, "cat|dog"); defer regex.deinit(); std.debug.print(" 'cat' matches: {}\n", .{try regex.isMatch("cat")}); @@ -34,7 +34,7 @@ pub fn main() !void { // Example 3: Star quantifier { std.debug.print("Example 3: Star quantifier (a*)\n", .{}); - var regex = try Regex.compile(allocator, "a*"); + var regex = try Regex.compile(allocator, init.io, "a*"); defer regex.deinit(); std.debug.print(" '' matches: {}\n", .{try regex.isMatch("")}); @@ -46,7 +46,7 @@ pub fn main() !void { // Example 4: Finding matches { std.debug.print("Example 4: Finding matches\n", .{}); - var regex = try Regex.compile(allocator, "world"); + var regex = try Regex.compile(allocator,init.io, "world"); defer regex.deinit(); if (try regex.find("hello world")) |match_result| { @@ -60,7 +60,7 @@ pub fn main() !void { // Example 5: Replace { std.debug.print("Example 5: Replace\n", .{}); - var regex = try Regex.compile(allocator, "world"); + var regex = try Regex.compile(allocator, init.io, "world"); defer regex.deinit(); const result = try regex.replace(allocator, "hello world", "Zig"); diff --git a/examples/debug_visualization.zig b/examples/debug_visualization.zig index f2f0a4b..7811c35 100644 --- a/examples/debug_visualization.zig +++ b/examples/debug_visualization.zig @@ -4,7 +4,7 @@ const debug = @import("regex").debug; const Parser = @import("regex").parser.Parser; const Optimizer = @import("regex").optimizer.Optimizer; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -25,7 +25,7 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────────────\n", .{}); // Example 1: Show optimization info - var regex = Regex.compile(allocator, pattern) catch |err| { + var regex = Regex.compile(allocator,init.io, pattern) catch |err| { std.debug.print(" ❌ Compilation error: {}\n\n", .{err}); continue; }; @@ -88,7 +88,7 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────────────\n", .{}); const test_pattern = "hello.*world"; - var test_regex = try Regex.compile(allocator, test_pattern); + var test_regex = try Regex.compile(allocator,init.io, test_pattern); defer test_regex.deinit(); const test_inputs = [_][]const u8{ diff --git a/examples/error_handling.zig b/examples/error_handling.zig index 9a9be1c..cab667f 100644 --- a/examples/error_handling.zig +++ b/examples/error_handling.zig @@ -4,7 +4,7 @@ const ErrorContext = @import("regex").ErrorContext; const ErrorHelper = @import("regex").ErrorHelper; const RegexError = @import("regex").RegexError; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -13,11 +13,11 @@ pub fn main() !void { // Example 1: Invalid patterns with helpful error messages const invalid_patterns = [_][]const u8{ - "abc(def", // Unmatched parenthesis - "abc[def", // Unmatched bracket - "abc+(", // Invalid quantifier usage - "abc\\q", // Invalid escape sequence - "[z-a]", // Invalid character range + "abc(def", // Unmatched parenthesis + "abc[def", // Unmatched bracket + "abc+(", // Invalid quantifier usage + "abc\\q", // Invalid escape sequence + "[z-a]", // Invalid character range "(a(b(c(d(e(f(g(h(i(j(k(l(m(n(o(p(q", // Too many nested groups }; @@ -26,7 +26,7 @@ pub fn main() !void { std.debug.print("Testing pattern: \"{s}\"\n", .{pattern}); std.debug.print("───────────────────────────────────────────────\n", .{}); - if (Regex.compile(allocator, pattern)) |regex_value| { + if (Regex.compile(allocator, init.io, pattern)) |regex_value| { var regex = regex_value; defer regex.deinit(); std.debug.print("✓ Pattern compiled successfully (unexpected)\n\n", .{}); @@ -53,7 +53,7 @@ pub fn main() !void { const user_pattern = "hello|world"; std.debug.print("User provided pattern: \"{s}\"\n", .{user_pattern}); - if (Regex.compile(allocator, user_pattern)) |regex_value| { + if (Regex.compile(allocator, init.io, user_pattern)) |regex_value| { var regex = regex_value; defer regex.deinit(); std.debug.print("✓ Pattern compiled successfully\n", .{}); @@ -84,7 +84,7 @@ pub fn main() !void { const problematic_pattern = "abc[def"; std.debug.print("Problematic pattern: \"{s}\"\n", .{problematic_pattern}); - if (Regex.compile(allocator, problematic_pattern)) |regex_value| { + if (Regex.compile(allocator, init.io, problematic_pattern)) |regex_value| { var regex = regex_value; regex.deinit(); } else |err| { @@ -95,7 +95,7 @@ pub fn main() !void { const fixed_pattern = "abc[def]"; std.debug.print("Fixed pattern: \"{s}\"\n", .{fixed_pattern}); - if (Regex.compile(allocator, fixed_pattern)) |regex_value| { + if (Regex.compile(allocator, init.io, fixed_pattern)) |regex_value| { var regex = regex_value; defer regex.deinit(); std.debug.print("✓ Fixed pattern compiles successfully!\n", .{}); @@ -119,7 +119,7 @@ pub fn main() !void { for (patterns_to_validate) |pattern| { std.debug.print("Validating: \"{s}\" ... ", .{pattern}); - if (Regex.compile(allocator, pattern)) |regex_value| { + if (Regex.compile(allocator, init.io, pattern)) |regex_value| { var regex = regex_value; regex.deinit(); std.debug.print("✓ Valid\n", .{}); diff --git a/examples/profiling_example.zig b/examples/profiling_example.zig index 558a6e1..ebe57c0 100644 --- a/examples/profiling_example.zig +++ b/examples/profiling_example.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; const Profiler = @import("regex").Profiler; const ScopedTimer = @import("regex").ScopedTimer; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -16,11 +16,11 @@ pub fn main() !void { std.debug.print("Example 1: Basic profiling\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, init.io, true); // Profile compilation profiler.startCompilation(); - var regex = try Regex.compile(allocator, "hello.*world"); + var regex = try Regex.compile(allocator, init.io, "hello.*world"); profiler.endCompilation(); defer regex.deinit(); @@ -48,13 +48,13 @@ pub fn main() !void { std.debug.print("Example 2: Scoped timers\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, init.io, true); { var timer = ScopedTimer.init(&profiler, .compilation); defer timer.deinit(); - var regex = try Regex.compile(allocator, "[a-z]+@[a-z]+\\.[a-z]+"); + var regex = try Regex.compile(allocator, init.io, "[a-z]+@[a-z]+\\.[a-z]+"); regex.deinit(); } @@ -69,19 +69,19 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────────────\n", .{}); const patterns = [_][]const u8{ - "hello", // Literal - should have prefix optimization - ".*hello", // No prefix - slower - "hello.*world", // Prefix optimization + "hello", // Literal - should have prefix optimization + ".*hello", // No prefix - slower + "hello.*world", // Prefix optimization }; const test_text = ("some random text " ** 10) ++ "hello world"; for (patterns) |pattern| { - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, init.io, true); std.debug.print("\nPattern: \"{s}\"\n", .{pattern}); - var regex = try Regex.compile(allocator, pattern); + var regex = try Regex.compile(allocator, init.io, pattern); defer regex.deinit(); // Run multiple matches @@ -94,8 +94,8 @@ pub fn main() !void { } const metrics = profiler.getMetrics(); - const avg_ns = metrics.match_time_ns / iterations; - std.debug.print("Average match time: {d}μs ({d} iterations)\n", .{ avg_ns / 1000, iterations }); + const avg_ns = @divExact(metrics.match_time_ns, iterations); + std.debug.print("Average match time: {d}μs ({d} iterations)\n", .{ @divExact(avg_ns, 1000), iterations }); } } @@ -105,12 +105,12 @@ pub fn main() !void { std.debug.print("Example 4: Memory tracking\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, init.io, true); const complex_pattern = "(a|b)+c(d|e)*f"; std.debug.print("Pattern: \"{s}\"\n", .{complex_pattern}); - var regex = try Regex.compile(allocator, complex_pattern); + var regex = try Regex.compile(allocator, init.io, complex_pattern); defer regex.deinit(); // Simulate memory tracking (in real implementation, this would be integrated) @@ -131,9 +131,9 @@ pub fn main() !void { const test_pattern = "hello"; // Baseline - var baseline_profiler = Profiler.init(allocator, true); + var baseline_profiler = Profiler.init(allocator, init.io, true); { - var regex = try Regex.compile(allocator, baseline_pattern); + var regex = try Regex.compile(allocator, init.io, baseline_pattern); defer regex.deinit(); var i: usize = 0; @@ -145,9 +145,9 @@ pub fn main() !void { } // Test - var test_profiler = Profiler.init(allocator, true); + var test_profiler = Profiler.init(allocator, init.io, true); { - var regex = try Regex.compile(allocator, test_pattern); + var regex = try Regex.compile(allocator, init.io, test_pattern); defer regex.deinit(); var i: usize = 0; @@ -162,8 +162,8 @@ pub fn main() !void { const test_time = test_profiler.getMetrics().match_time_ns; const diff_percent = @as(f64, @floatFromInt(test_time)) / @as(f64, @floatFromInt(baseline_time)) * 100.0 - 100.0; - std.debug.print("\nBaseline time: {d}μs\n", .{baseline_time / 1000}); - std.debug.print("Test time: {d}μs\n", .{test_time / 1000}); + std.debug.print("\nBaseline time: {d}μs\n", .{@divExact(baseline_time, 1000)}); + std.debug.print("Test time: {d}μs\n", .{@divExact(test_time, 1000)}); std.debug.print("Difference: {d:.1}%\n", .{diff_percent}); if (diff_percent > 10.0) { diff --git a/examples/thread_safety.zig b/examples/thread_safety.zig index 0b40b1a..85209ab 100644 --- a/examples/thread_safety.zig +++ b/examples/thread_safety.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; const SharedRegex = @import("regex").SharedRegex; const RegexCache = @import("regex").RegexCache; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -17,7 +17,7 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────────────\n", .{}); // Compile once - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, init.io, "\\d+"); defer regex.deinit(); const Worker = struct { @@ -63,7 +63,7 @@ pub fn main() !void { std.debug.print("Example 2: SharedRegex with reference counting\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var shared = try SharedRegex.init(allocator, "test"); + var shared = try SharedRegex.init(allocator, init.io, "test"); defer shared.deinit(); const Worker = struct { @@ -103,9 +103,9 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────────────\n", .{}); const Worker = struct { - fn run(alloc: std.mem.Allocator, thread_id: usize) void { + fn run(alloc: std.mem.Allocator, io: std.Io, thread_id: usize) void { // Each thread gets its own cache - var cache = RegexCache.init(alloc); + var cache = RegexCache.init(alloc, io); defer cache.deinit(); std.debug.print("Thread {d}: Created local cache\n", .{thread_id}); @@ -128,7 +128,7 @@ pub fn main() !void { var threads: [thread_count]std.Thread = undefined; for (&threads, 0..) |*thread, i| { - thread.* = try std.Thread.spawn(.{}, Worker.run, .{ allocator, i }); + thread.* = try std.Thread.spawn(.{}, Worker.run, .{ allocator, init.io, i }); } for (threads) |thread| { @@ -144,12 +144,12 @@ pub fn main() !void { std.debug.print("Example 4: Performance with concurrent matching\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var regex = try Regex.compile(allocator, "test"); + var regex = try Regex.compile(allocator, init.io, "test"); defer regex.deinit(); const Worker = struct { - fn run(r: *const Regex) usize { - var timer = std.time.Timer.start() catch return 0; + fn run(r: *const Regex) i64 { + const mark = std.Io.Clock.awake.now(r.io); var count: usize = 0; var i: usize = 0; @@ -159,8 +159,8 @@ pub fn main() !void { } else |_| {} } - const elapsed_ms = timer.read() / 1_000_000; - return elapsed_ms; + const elapsed = mark.untilNow(r.io, std.Io.Clock.awake); + return elapsed.toMilliseconds(); } }; @@ -171,15 +171,15 @@ pub fn main() !void { // Parallel execution const thread_count = 4; var threads: [thread_count]std.Thread = undefined; - var times: [thread_count]usize = undefined; + var times: [thread_count]i64 = undefined; const ParallelWorker = struct { - fn run(r: *const Regex, time_ptr: *usize) void { + fn run(r: *const Regex, time_ptr: *i64) void { time_ptr.* = Worker.run(r); } }; - var parallel_timer = try std.time.Timer.start(); + var parallel_timer = std.Io.Clock.awake.now(init.io); for (&threads, 0..) |*thread, i| { thread.* = try std.Thread.spawn(.{}, ParallelWorker.run, .{ ®ex, ×[i] }); @@ -189,7 +189,7 @@ pub fn main() !void { thread.join(); } - const total_parallel = parallel_timer.read() / 1_000_000; + const total_parallel = @divExact(parallel_timer.untilNow(init.io, .awake).toNanoseconds(), 1_000_000); std.debug.print("Parallel ({d} threads): 40,000 matches in {d}ms\n", .{ thread_count, total_parallel }); std.debug.print("Individual thread times: ", .{}); @@ -206,13 +206,13 @@ pub fn main() !void { std.debug.print("Example 5: Multiple patterns with concurrent access\n", .{}); std.debug.print("───────────────────────────────────────────────\n", .{}); - var email_regex = try Regex.compile(allocator, "[a-z]+@[a-z]+\\.[a-z]+"); + var email_regex = try Regex.compile(allocator, init.io, "[a-z]+@[a-z]+\\.[a-z]+"); defer email_regex.deinit(); - var phone_regex = try Regex.compile(allocator, "\\d{3}-\\d{4}"); + var phone_regex = try Regex.compile(allocator, init.io, "\\d{3}-\\d{4}"); defer phone_regex.deinit(); - var url_regex = try Regex.compile(allocator, "https?://"); + var url_regex = try Regex.compile(allocator, init.io, "https?://"); defer url_regex.deinit(); const Worker = struct { diff --git a/src/advanced_features.zig b/src/advanced_features.zig index 679ba26..acbaf79 100644 --- a/src/advanced_features.zig +++ b/src/advanced_features.zig @@ -16,10 +16,10 @@ pub const AtomicGroupNode = struct { /// Possessive Quantifiers: *+, ++, ?+, {n,m}+ /// Like greedy quantifiers but don't backtrack pub const PossessiveQuantifier = enum { - star_possessive, // *+ - plus_possessive, // ++ + star_possessive, // *+ + plus_possessive, // ++ optional_possessive, // ?+ - repeat_possessive, // {n,m}+ + repeat_possessive, // {n,m}+ pub fn fromGreedy(_: bool) ?PossessiveQuantifier { // Helper to detect possessive syntax @@ -74,7 +74,7 @@ pub const AdvancedNodeType = enum { // Tests test "atomic group creation" { const allocator = std.testing.allocator; - + // Create a simple child node (literal 'a') const child = try ast.Node.createLiteral(allocator, 'a', .{ .start = 0, .end = 1 }); defer allocator.destroy(child); diff --git a/src/backtrack.zig b/src/backtrack.zig index f825daf..e340ccd 100644 --- a/src/backtrack.zig +++ b/src/backtrack.zig @@ -5,7 +5,6 @@ const common = @import("common.zig"); /// Backtracking-based regex engine /// Supports: lazy quantifiers, lookahead/lookbehind, backreferences /// Trade-off: O(2^n) worst case, but supports features impossible in Thompson NFA - /// Match result from backtracking engine pub const BacktrackMatch = struct { start: usize, diff --git a/src/benchmark.zig b/src/benchmark.zig index f10a6f6..30f677a 100644 --- a/src/benchmark.zig +++ b/src/benchmark.zig @@ -26,7 +26,7 @@ pub fn benchmark( const avg_ns = elapsed_ns / iterations; const avg_us = avg_ns / 1000; - std.debug.print("{s}: {d} iterations, avg {d}μs per match\n", .{name, iterations, avg_us}); + std.debug.print("{s}: {d} iterations, avg {d}μs per match\n", .{ name, iterations, avg_us }); } pub fn benchmarkCompile( @@ -47,5 +47,5 @@ pub fn benchmarkCompile( const avg_ns = elapsed_ns / iterations; const avg_us = avg_ns / 1000; - std.debug.print("{s}: {d} compilations, avg {d}μs per compile\n", .{name, iterations, avg_us}); + std.debug.print("{s}: {d} compilations, avg {d}μs per compile\n", .{ name, iterations, avg_us }); } diff --git a/src/c_api.zig b/src/c_api.zig index 572427e..a4be469 100644 --- a/src/c_api.zig +++ b/src/c_api.zig @@ -1,215 +1,214 @@ -const std = @import("std"); -const Regex = @import("regex.zig").Regex; -const Match = @import("regex.zig").Match; - -/// C FFI for the Zig regex library -/// -/// This module provides a C-compatible API for using the regex library -/// from other languages (C, C++, Python via ctypes, etc.) -/// -/// Example usage from C: -/// ```c -/// #include "zig_regex.h" -/// -/// ZigRegex* regex = zig_regex_compile("\\d+", NULL); -/// if (regex) { -/// bool matches = zig_regex_is_match(regex, "test 123"); -/// printf("Matches: %d\n", matches); -/// zig_regex_free(regex); -/// } -/// ``` - -/// Opaque handle to a compiled regex -pub const ZigRegex = opaque {}; - -/// Opaque handle to a match result -pub const ZigMatch = opaque {}; - -/// Error codes -pub const ZigRegexError = enum(c_int) { - ok = 0, - compile_error = -1, - invalid_pattern = -2, - out_of_memory = -3, - no_match = -4, - null_pointer = -5, -}; - -// Global allocator for C API (uses C allocator) -var c_allocator = std.heap.c_allocator; - -/// Compile a regex pattern -/// -/// Returns NULL on error. Call zig_regex_get_last_error() to get error details. -/// The returned regex must be freed with zig_regex_free(). -export fn zig_regex_compile(pattern: [*:0]const u8) ?*ZigRegex { - const pattern_slice = std.mem.span(pattern); - - const regex_ptr = c_allocator.create(Regex) catch return null; - regex_ptr.* = Regex.compile(c_allocator, pattern_slice) catch { - c_allocator.destroy(regex_ptr); - return null; - }; - - return @ptrCast(regex_ptr); -} - -/// Free a compiled regex -export fn zig_regex_free(regex: ?*ZigRegex) void { - if (regex) |r| { - const regex_ptr: *Regex = @ptrCast(@alignCast(r)); - regex_ptr.deinit(); - c_allocator.destroy(regex_ptr); - } -} - -/// Check if a pattern matches a string -/// -/// Returns 1 if matches, 0 if no match, -1 on error -export fn zig_regex_is_match(regex: ?*ZigRegex, input: [*:0]const u8) c_int { - if (regex == null) return @intFromEnum(ZigRegexError.null_pointer); - - const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?)); - const input_slice = std.mem.span(input); - - const result = regex_ptr.isMatch(input_slice) catch return -1; - return if (result) 1 else 0; -} - -/// Find the first match in a string -/// -/// Returns NULL if no match or on error. -/// The returned match must be freed with zig_match_free(). -export fn zig_regex_find(regex: ?*ZigRegex, input: [*:0]const u8) ?*ZigMatch { - if (regex == null) return null; - - const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?)); - const input_slice = std.mem.span(input); - - const maybe_match = regex_ptr.find(input_slice) catch return null; - if (maybe_match) |match| { - const match_ptr = c_allocator.create(Match) catch return null; - match_ptr.* = match; - return @ptrCast(match_ptr); - } - - return null; -} - -/// Get the matched substring -/// -/// Returns NULL if match is NULL. -/// The returned string is owned by the match and should not be freed separately. -export fn zig_match_get_text(match: ?*ZigMatch) ?[*:0]const u8 { - if (match == null) return null; - - const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); - // Note: This assumes the slice is null-terminated, which may not always be true - // In production, you'd want to copy to a null-terminated buffer - return @ptrCast(match_ptr.slice.ptr); -} - -/// Get the start position of a match -export fn zig_match_get_start(match: ?*ZigMatch) c_int { - if (match == null) return -1; - - const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); - return @intCast(match_ptr.start); -} - -/// Get the end position of a match -export fn zig_match_get_end(match: ?*ZigMatch) c_int { - if (match == null) return -1; - - const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); - return @intCast(match_ptr.end); -} - -/// Free a match result -export fn zig_match_free(match: ?*ZigMatch) void { - if (match) |m| { - const match_ptr: *Match = @ptrCast(@alignCast(m)); - var mut_match = match_ptr.*; - mut_match.deinit(c_allocator); - c_allocator.destroy(match_ptr); - } -} - -/// Get version string -export fn zig_regex_version() [*:0]const u8 { - return "0.1.0"; -} - -// Header file generator (for documentation) -pub fn generateHeader(writer: anytype) !void { - try writer.writeAll( - \\#ifndef ZIG_REGEX_H - \\#define ZIG_REGEX_H - \\ - \\#ifdef __cplusplus - \\extern "C" { - \\#endif - \\ - \\#include - \\#include - \\ - \\/* Opaque types */ - \\typedef struct ZigRegex ZigRegex; - \\typedef struct ZigMatch ZigMatch; - \\ - \\/* Error codes */ - \\typedef enum { - \\ ZIG_REGEX_OK = 0, - \\ ZIG_REGEX_COMPILE_ERROR = -1, - \\ ZIG_REGEX_INVALID_PATTERN = -2, - \\ ZIG_REGEX_OUT_OF_MEMORY = -3, - \\ ZIG_REGEX_NO_MATCH = -4, - \\ ZIG_REGEX_NULL_POINTER = -5, - \\} ZigRegexError; - \\ - \\/* Compile a regex pattern */ - \\ZigRegex* zig_regex_compile(const char* pattern); - \\ - \\/* Free a compiled regex */ - \\void zig_regex_free(ZigRegex* regex); - \\ - \\/* Check if pattern matches string (returns 1=match, 0=no match, -1=error) */ - \\int zig_regex_is_match(ZigRegex* regex, const char* input); - \\ - \\/* Find first match in string (returns NULL if no match) */ - \\ZigMatch* zig_regex_find(ZigRegex* regex, const char* input); - \\ - \\/* Get matched text from match result */ - \\const char* zig_match_get_text(ZigMatch* match); - \\ - \\/* Get start position of match */ - \\int zig_match_get_start(ZigMatch* match); - \\ - \\/* Get end position of match */ - \\int zig_match_get_end(ZigMatch* match); - \\ - \\/* Free a match result */ - \\void zig_match_free(ZigMatch* match); - \\ - \\/* Get library version */ - \\const char* zig_regex_version(void); - \\ - \\#ifdef __cplusplus - \\} - \\#endif - \\ - \\#endif /* ZIG_REGEX_H */ - \\ - ); -} - -test "C API header generation" { - var aw: std.Io.Writer.Allocating = .init(std.testing.allocator); - defer aw.deinit(); - - try generateHeader(&aw.writer); - var result = aw.toArrayList(); - defer result.deinit(std.testing.allocator); - try std.testing.expect(result.items.len > 0); - try std.testing.expect(std.mem.indexOf(u8, result.items, "ZIG_REGEX_H") != null); -} +//const std = @import("std"); +//const Regex = @import("regex.zig").Regex; +//const Match = @import("regex.zig").Match; +// +///// C FFI for the Zig regex library +///// +///// This module provides a C-compatible API for using the regex library +///// from other languages (C, C++, Python via ctypes, etc.) +///// +///// Example usage from C: +///// ```c +///// #include "zig_regex.h" +///// +///// ZigRegex* regex = zig_regex_compile("\\d+", NULL); +///// if (regex) { +///// bool matches = zig_regex_is_match(regex, "test 123"); +///// printf("Matches: %d\n", matches); +///// zig_regex_free(regex); +///// } +///// ``` +///// Opaque handle to a compiled regex +//pub const ZigRegex = opaque {}; +// +///// Opaque handle to a match result +//pub const ZigMatch = opaque {}; +// +///// Error codes +//pub const ZigRegexError = enum(c_int) { +// ok = 0, +// compile_error = -1, +// invalid_pattern = -2, +// out_of_memory = -3, +// no_match = -4, +// null_pointer = -5, +//}; +// +//// Global allocator for C API (uses C allocator) +//var c_allocator = std.heap.c_allocator; +// +///// Compile a regex pattern +///// +///// Returns NULL on error. Call zig_regex_get_last_error() to get error details. +///// The returned regex must be freed with zig_regex_free(). +//export fn zig_regex_compile(pattern: [*:0]const u8) ?*ZigRegex { +// const pattern_slice = std.mem.span(pattern); +// +// const regex_ptr = c_allocator.create(Regex) catch return null; +// regex_ptr.* = Regex.compile(c_allocator, pattern_slice) catch { +// c_allocator.destroy(regex_ptr); +// return null; +// }; +// +// return @ptrCast(regex_ptr); +//} +// +///// Free a compiled regex +//export fn zig_regex_free(regex: ?*ZigRegex) void { +// if (regex) |r| { +// const regex_ptr: *Regex = @ptrCast(@alignCast(r)); +// regex_ptr.deinit(); +// c_allocator.destroy(regex_ptr); +// } +//} +// +///// Check if a pattern matches a string +///// +///// Returns 1 if matches, 0 if no match, -1 on error +//export fn zig_regex_is_match(regex: ?*ZigRegex, input: [*:0]const u8) c_int { +// if (regex == null) return @intFromEnum(ZigRegexError.null_pointer); +// +// const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?)); +// const input_slice = std.mem.span(input); +// +// const result = regex_ptr.isMatch(input_slice) catch return -1; +// return if (result) 1 else 0; +//} +// +///// Find the first match in a string +///// +///// Returns NULL if no match or on error. +///// The returned match must be freed with zig_match_free(). +//export fn zig_regex_find(regex: ?*ZigRegex, input: [*:0]const u8) ?*ZigMatch { +// if (regex == null) return null; +// +// const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?)); +// const input_slice = std.mem.span(input); +// +// const maybe_match = regex_ptr.find(input_slice) catch return null; +// if (maybe_match) |match| { +// const match_ptr = c_allocator.create(Match) catch return null; +// match_ptr.* = match; +// return @ptrCast(match_ptr); +// } +// +// return null; +//} +// +///// Get the matched substring +///// +///// Returns NULL if match is NULL. +///// The returned string is owned by the match and should not be freed separately. +//export fn zig_match_get_text(match: ?*ZigMatch) ?[*:0]const u8 { +// if (match == null) return null; +// +// const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); +// // Note: This assumes the slice is null-terminated, which may not always be true +// // In production, you'd want to copy to a null-terminated buffer +// return @ptrCast(match_ptr.slice.ptr); +//} +// +///// Get the start position of a match +//export fn zig_match_get_start(match: ?*ZigMatch) c_int { +// if (match == null) return -1; +// +// const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); +// return @intCast(match_ptr.start); +//} +// +///// Get the end position of a match +//export fn zig_match_get_end(match: ?*ZigMatch) c_int { +// if (match == null) return -1; +// +// const match_ptr: *const Match = @ptrCast(@alignCast(match.?)); +// return @intCast(match_ptr.end); +//} +// +///// Free a match result +//export fn zig_match_free(match: ?*ZigMatch) void { +// if (match) |m| { +// const match_ptr: *Match = @ptrCast(@alignCast(m)); +// var mut_match = match_ptr.*; +// mut_match.deinit(c_allocator); +// c_allocator.destroy(match_ptr); +// } +//} +// +///// Get version string +//export fn zig_regex_version() [*:0]const u8 { +// return "0.1.0"; +//} +// +//// Header file generator (for documentation) +//pub fn generateHeader(writer: anytype) !void { +// try writer.writeAll( +// \\#ifndef ZIG_REGEX_H +// \\#define ZIG_REGEX_H +// \\ +// \\#ifdef __cplusplus +// \\extern "C" { +// \\#endif +// \\ +// \\#include +// \\#include +// \\ +// \\/* Opaque types */ +// \\typedef struct ZigRegex ZigRegex; +// \\typedef struct ZigMatch ZigMatch; +// \\ +// \\/* Error codes */ +// \\typedef enum { +// \\ ZIG_REGEX_OK = 0, +// \\ ZIG_REGEX_COMPILE_ERROR = -1, +// \\ ZIG_REGEX_INVALID_PATTERN = -2, +// \\ ZIG_REGEX_OUT_OF_MEMORY = -3, +// \\ ZIG_REGEX_NO_MATCH = -4, +// \\ ZIG_REGEX_NULL_POINTER = -5, +// \\} ZigRegexError; +// \\ +// \\/* Compile a regex pattern */ +// \\ZigRegex* zig_regex_compile(const char* pattern); +// \\ +// \\/* Free a compiled regex */ +// \\void zig_regex_free(ZigRegex* regex); +// \\ +// \\/* Check if pattern matches string (returns 1=match, 0=no match, -1=error) */ +// \\int zig_regex_is_match(ZigRegex* regex, const char* input); +// \\ +// \\/* Find first match in string (returns NULL if no match) */ +// \\ZigMatch* zig_regex_find(ZigRegex* regex, const char* input); +// \\ +// \\/* Get matched text from match result */ +// \\const char* zig_match_get_text(ZigMatch* match); +// \\ +// \\/* Get start position of match */ +// \\int zig_match_get_start(ZigMatch* match); +// \\ +// \\/* Get end position of match */ +// \\int zig_match_get_end(ZigMatch* match); +// \\ +// \\/* Free a match result */ +// \\void zig_match_free(ZigMatch* match); +// \\ +// \\/* Get library version */ +// \\const char* zig_regex_version(void); +// \\ +// \\#ifdef __cplusplus +// \\} +// \\#endif +// \\ +// \\#endif /* ZIG_REGEX_H */ +// \\ +// ); +//} +// +//test "C API header generation" { +// var aw: std.Io.Writer.Allocating = .init(std.testing.allocator); +// defer aw.deinit(); +// +// try generateHeader(&aw.writer); +// var result = aw.toArrayList(); +// defer result.deinit(std.testing.allocator); +// try std.testing.expect(result.items.len > 0); +// try std.testing.expect(std.mem.indexOf(u8, result.items, "ZIG_REGEX_H") != null); +//} diff --git a/src/compiler.zig b/src/compiler.zig index d56969b..f9640f8 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -57,7 +57,7 @@ pub const Transition = struct { .data = .{ .char_class = .{ .ranges = ranges_copy, .negated = class.negated, - }}, + } }, }; } diff --git a/src/main.zig b/src/main.zig index 4fd24af..5ef60a3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -106,7 +106,7 @@ pub fn main(init: std.process.Init) !void { break :blk stdin_alloc.?; }; - var re = regex.Regex.compileWithFlags(allocator, pattern, .{ + var re = regex.Regex.compileWithFlags(allocator,io, pattern, .{ .case_insensitive = case_insensitive, .multiline = multiline, }) catch |err| { diff --git a/src/parser.zig b/src/parser.zig index da1d7f1..97bab0d 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -657,10 +657,7 @@ pub const Parser = struct { .dollar => '$', .lbracket => '[', // Allow [ as literal (for non-POSIX cases) // These should not appear here - .rbracket, .caret, .backslash, - .escape_d, .escape_D, .escape_w, .escape_W, - .escape_s, .escape_S, .escape_b, .escape_B, - .escape_A, .escape_z, .escape_Z, .backref, .eof => null, + .rbracket, .caret, .backslash, .escape_d, .escape_D, .escape_w, .escape_W, .escape_s, .escape_S, .escape_b, .escape_B, .escape_A, .escape_z, .escape_Z, .backref, .eof => null, }; } @@ -685,7 +682,8 @@ pub const Parser = struct { if (current_pos + 1 < self.lexer.input.len and self.lexer.input[current_pos] == '[' and - self.lexer.input[current_pos + 1] == ':') { + self.lexer.input[current_pos + 1] == ':') + { // Find the closing :] var found_posix = false; @@ -693,7 +691,7 @@ pub const Parser = struct { while (i + 1 < self.lexer.input.len) : (i += 1) { if (self.lexer.input[i] == ':' and self.lexer.input[i + 1] == ']') { // Found [:name:] - const class_name = self.lexer.input[current_pos + 2..i]; + const class_name = self.lexer.input[current_pos + 2 .. i]; // Skip to the character AFTER ':]' which should be the outer ']' or more chars // We want the lexer to be positioned so that the NEXT token read will be correct diff --git a/src/pattern_analyzer.zig b/src/pattern_analyzer.zig index 3914e3a..96848e8 100644 --- a/src/pattern_analyzer.zig +++ b/src/pattern_analyzer.zig @@ -324,7 +324,6 @@ pub const PatternAnalyzer = struct { } fn nodesAreIdentical(self: *PatternAnalyzer, left: *ast.Node, right: *ast.Node) bool { - if (left.node_type != right.node_type) return false; return switch (left.node_type) { @@ -337,7 +336,7 @@ pub const PatternAnalyzer = struct { const left_concat = left.data.concat; const right_concat = right.data.concat; return self.nodesAreIdentical(left_concat.left, right_concat.left) and - self.nodesAreIdentical(left_concat.right, right_concat.right); + self.nodesAreIdentical(left_concat.right, right_concat.right); }, else => false, }; diff --git a/src/profiling.zig b/src/profiling.zig index 42d655a..de8bb6a 100644 --- a/src/profiling.zig +++ b/src/profiling.zig @@ -1,15 +1,17 @@ const std = @import("std"); +const Clock = std.Io.Clock; /// Performance profiling and metrics tracking for regex operations pub const Profiler = struct { allocator: std.mem.Allocator, + io: std.Io, enabled: bool, metrics: Metrics, - start_time: ?std.time.Instant, + start_time: ?std.Io.Timestamp, pub const Metrics = struct { - compilation_time_ns: u64 = 0, - match_time_ns: u64 = 0, + compilation_time_ns: i96 = 0, + match_time_ns: i96 = 0, match_count: usize = 0, nfa_states_created: usize = 0, backtrack_count: usize = 0, @@ -25,13 +27,8 @@ pub const Profiler = struct { pub fn format( self: Metrics, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = fmt; - _ = options; - try writer.writeAll("\n=== Regex Performance Metrics ===\n"); try writer.print("Compilation time: {d}μs\n", .{self.compilation_time_ns / 1000}); try writer.print("Match time: {d}μs\n", .{self.match_time_ns / 1000}); @@ -49,41 +46,40 @@ pub const Profiler = struct { } }; - pub fn init(allocator: std.mem.Allocator, enabled: bool) Profiler { + pub fn init(allocator: std.mem.Allocator, io: std.Io, enabled: bool) Profiler { return .{ + .io = io, .allocator = allocator, .enabled = enabled, .metrics = Metrics{}, - .start_time = if (enabled) std.time.Instant.now() catch null else null, + .start_time = if (enabled) Clock.awake.now(io) else null, }; } /// Start timing a compilation phase pub fn startCompilation(self: *Profiler) void { if (!self.enabled) return; - self.start_time = std.time.Instant.now() catch null; + self.start_time = Clock.awake.now(self.io); } /// End timing a compilation phase pub fn endCompilation(self: *Profiler) void { if (!self.enabled) return; const start = self.start_time orelse return; - const now = std.time.Instant.now() catch return; - self.metrics.compilation_time_ns += now.since(start); + self.metrics.compilation_time_ns += start.untilNow(self.io, Clock.awake).toNanoseconds(); } /// Start timing a match operation pub fn startMatch(self: *Profiler) void { if (!self.enabled) return; - self.start_time = std.time.Instant.now() catch null; + self.start_time = Clock.awake.now(self.io); } /// End timing a match operation pub fn endMatch(self: *Profiler) void { if (!self.enabled) return; const start = self.start_time orelse return; - const now = std.time.Instant.now() catch return; - self.metrics.match_time_ns += now.since(start); + self.metrics.match_time_ns += start.untilNow(self.io, .awake).toNanoseconds(); self.metrics.match_count += 1; } @@ -142,7 +138,7 @@ pub const Profiler = struct { /// Reset all metrics pub fn reset(self: *Profiler) void { self.metrics.reset(); - self.start_time = if (self.enabled) std.time.Instant.now() catch null else null; + self.start_time = if (self.enabled) Clock.now(self.io) else null; } /// Print metrics to stderr @@ -156,7 +152,7 @@ pub const Profiler = struct { pub const ScopedTimer = struct { profiler: *Profiler, timer_type: TimerType, - start: ?std.time.Instant, + start: ?std.Io.Timestamp, pub const TimerType = enum { compilation, @@ -167,15 +163,14 @@ pub const ScopedTimer = struct { return .{ .profiler = profiler, .timer_type = timer_type, - .start = if (profiler.enabled) std.time.Instant.now() catch null else null, + .start = if (profiler.enabled) Clock.awake.now(profiler.io) else null, }; } pub fn deinit(self: *ScopedTimer) void { if (!self.profiler.enabled) return; const start = self.start orelse return; - const now = std.time.Instant.now() catch return; - const elapsed = now.since(start); + const elapsed = start.untilNow(self.profiler.io, Clock.awake).toNanoseconds(); switch (self.timer_type) { .compilation => self.profiler.metrics.compilation_time_ns += elapsed, .match => { @@ -188,7 +183,7 @@ pub const ScopedTimer = struct { test "profiler basic operations" { const allocator = std.testing.allocator; - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, std.testing.io, true); profiler.startCompilation(); // Do some work (just loop to consume time) @@ -210,7 +205,7 @@ test "profiler basic operations" { test "scoped timer" { const allocator = std.testing.allocator; - var profiler = Profiler.init(allocator, true); + var profiler = Profiler.init(allocator, std.testing.io, true); { var timer = ScopedTimer.init(&profiler, .match); diff --git a/src/regex.zig b/src/regex.zig index dfac94f..6fb4bf3 100644 --- a/src/regex.zig +++ b/src/regex.zig @@ -41,6 +41,7 @@ pub const EngineType = enum { /// Main regex type - represents a compiled regular expression pattern pub const Regex = struct { allocator: std.mem.Allocator, + io: std.Io, pattern: []const u8, nfa: compiler.NFA, backtrack_engine: ?backtrack.BacktrackEngine, @@ -52,12 +53,12 @@ pub const Regex = struct { named_captures: std.StringHashMap(usize), // name -> capture_index mapping /// Compile a regex pattern with default flags - pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex { - return compileWithFlags(allocator, pattern, .{}); + pub fn compile(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8) !Regex { + return compileWithFlags(allocator, io, pattern, .{}); } /// Compile a regex pattern with custom flags - pub fn compileWithFlags(allocator: std.mem.Allocator, pattern: []const u8, flags: common.CompileFlags) !Regex { + pub fn compileWithFlags(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8, flags: common.CompileFlags) !Regex { if (pattern.len == 0) { return RegexError.EmptyPattern; } @@ -92,12 +93,7 @@ pub const Regex = struct { if (needs_backtracking) { // Use backtracking engine - var backtrack_engine = try backtrack.BacktrackEngine.init( - allocator, - tree.root, - tree.capture_count, - flags - ); + var backtrack_engine = try backtrack.BacktrackEngine.init(allocator, tree.root, tree.capture_count, flags); errdefer backtrack_engine.deinit(); // Create a dummy NFA (not used) @@ -106,6 +102,7 @@ pub const Regex = struct { _ = try dummy_nfa.addState(); return Regex{ + .io = io, .allocator = allocator, .pattern = owned_pattern, .nfa = dummy_nfa, @@ -126,6 +123,7 @@ pub const Regex = struct { _ = try comp.compile(&tree); return Regex{ + .io = io, .allocator = allocator, .pattern = owned_pattern, .nfa = comp.nfa, @@ -676,11 +674,11 @@ fn requiresBacktracking(node: *ast.Node) bool { // Recursively check compound nodes .concat => { return requiresBacktracking(node.data.concat.left) or - requiresBacktracking(node.data.concat.right); + requiresBacktracking(node.data.concat.right); }, .alternation => { return requiresBacktracking(node.data.alternation.left) or - requiresBacktracking(node.data.alternation.right); + requiresBacktracking(node.data.alternation.right); }, .group => return requiresBacktracking(node.data.group.child), @@ -727,20 +725,20 @@ fn collectNamedCaptures(node: *ast.Node, map: *std.StringHashMap(usize)) !void { test "compile empty pattern" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, ""); + const result = Regex.compile(allocator, std.testing.io, ""); try std.testing.expectError(RegexError.EmptyPattern, result); } test "compile basic pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "test"); + var regex = try Regex.compile(allocator, std.testing.io, "test"); defer regex.deinit(); try std.testing.expectEqualStrings("test", regex.pattern); } test "match literal" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "hello"); + var regex = try Regex.compile(allocator, std.testing.io, "hello"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -749,7 +747,7 @@ test "match literal" { test "find literal" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "world"); + var regex = try Regex.compile(allocator, std.testing.io, "world"); defer regex.deinit(); if (try regex.find("hello world")) |match_result| { @@ -765,7 +763,7 @@ test "find literal" { test "alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "cat|dog"); + var regex = try Regex.compile(allocator, std.testing.io, "cat|dog"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("cat")); @@ -775,7 +773,7 @@ test "alternation" { test "star quantifier" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*"); + var regex = try Regex.compile(allocator, std.testing.io, "a*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -785,7 +783,7 @@ test "star quantifier" { test "plus quantifier" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+"); + var regex = try Regex.compile(allocator, std.testing.io, "a+"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("")); @@ -795,7 +793,7 @@ test "plus quantifier" { test "optional quantifier" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a?"); + var regex = try Regex.compile(allocator, std.testing.io, "a?"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -804,7 +802,7 @@ test "optional quantifier" { test "dot wildcard" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a.c"); + var regex = try Regex.compile(allocator, std.testing.io, "a.c"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); @@ -814,7 +812,7 @@ test "dot wildcard" { test "character class \\d" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); if (try regex.find("abc123def")) |match_result| { @@ -828,7 +826,7 @@ test "character class \\d" { test "replace" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "world"); + var regex = try Regex.compile(allocator, std.testing.io, "world"); defer regex.deinit(); const result = try regex.replace(allocator, "hello world", "Zig"); @@ -839,7 +837,7 @@ test "replace" { test "replace all" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a"); + var regex = try Regex.compile(allocator, std.testing.io, "a"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "banana", "o"); @@ -850,7 +848,7 @@ test "replace all" { test "split" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const parts = try regex.split(allocator, "a,b,c"); @@ -866,7 +864,7 @@ test "compile rejects dangerous nested quantifiers" { const allocator = std.testing.allocator; // This pattern should be rejected as critical risk - const result = Regex.compile(allocator, "(a+)+"); + const result = Regex.compile(allocator, std.testing.io, "(a+)+"); try std.testing.expectError(RegexError.PatternTooComplex, result); } @@ -874,7 +872,7 @@ test "compile rejects nested stars" { const allocator = std.testing.allocator; // This pattern should be rejected - const result = Regex.compile(allocator, "(a*)*"); + const result = Regex.compile(allocator, std.testing.io, "(a*)*"); try std.testing.expectError(RegexError.PatternTooComplex, result); } @@ -882,6 +880,6 @@ test "compile accepts safe complex patterns" { const allocator = std.testing.allocator; // This pattern should be accepted (medium risk is OK) - var regex = try Regex.compile(allocator, "a+b*c?d{2,5}"); + var regex = try Regex.compile(allocator, std.testing.io, "a+b*c?d{2,5}"); defer regex.deinit(); } diff --git a/src/root.zig b/src/root.zig index dd2aaf9..c101fef 100644 --- a/src/root.zig +++ b/src/root.zig @@ -8,7 +8,7 @@ const std = @import("std"); /// Example usage: /// ```zig /// const Regex = @import("regex").Regex; -/// const regex = try Regex.compile(allocator, "\\d+"); +/// const regex = try Regex.compile(allocator, init.io, "\\d+"); /// defer regex.deinit(); /// if (try regex.find("abc123")) |match| { /// std.debug.print("Found: {s}\n", .{match.slice}); diff --git a/src/thread_safety.zig b/src/thread_safety.zig index 289641f..1478a95 100644 --- a/src/thread_safety.zig +++ b/src/thread_safety.zig @@ -43,7 +43,7 @@ const std = @import("std"); /// var gpa = std.heap.GeneralPurposeAllocator(.{}){}; /// const allocator = gpa.allocator(); /// -/// const regex = try Regex.compile(allocator, "\\d+"); +/// const regex = try Regex.compile(allocator, init.io, "\\d+"); /// defer regex.deinit(); /// /// // Thread 1 @@ -115,12 +115,12 @@ pub fn SharedRegex(comptime Regex: type) type { allocator: std.mem.Allocator, /// Initialize a shared regex pattern - pub fn init(allocator: std.mem.Allocator, pattern: []const u8) !*Self { + pub fn init(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8) !*Self { const self = try allocator.create(Self); errdefer allocator.destroy(self); self.* = .{ - .regex = try Regex.compile(allocator, pattern), + .regex = try Regex.compile(allocator, io, pattern), .ref_count = std.atomic.Value(usize).init(1), .allocator = allocator, }; @@ -198,11 +198,13 @@ pub fn RegexCache(comptime Regex: type) type { }; cache: std.StringHashMap(Regex), + io: std.Io, allocator: std.mem.Allocator, initialized: bool = false, - pub fn init(allocator: std.mem.Allocator) Self { + pub fn init(allocator: std.mem.Allocator, io: std.Io) Self { return .{ + .io = io, .cache = std.StringHashMap(Regex).init(allocator), .allocator = allocator, .initialized = true, @@ -229,7 +231,7 @@ pub fn RegexCache(comptime Regex: type) type { } // Not in cache, compile and store - const regex = try Regex.compile(self.allocator, pattern); + const regex = try Regex.compile(self.allocator,self.io, pattern); errdefer { var mut_regex = regex; mut_regex.deinit(); diff --git a/src/unicode.zig b/src/unicode.zig index 3a1adfe..e198d50 100644 --- a/src/unicode.zig +++ b/src/unicode.zig @@ -261,62 +261,99 @@ test "UTF-8 encoding" { /// Unicode property names for \p{Property} matching pub const UnicodeProperty = enum { // General categories (short & long forms) - Letter, L, - Lowercase_Letter, Ll, - Uppercase_Letter, Lu, - Titlecase_Letter, Lt, - Modifier_Letter, Lm, - Other_Letter, Lo, - - Mark, M, - Nonspacing_Mark, Mn, - Spacing_Mark, Mc, - Enclosing_Mark, Me, - - Number, N, - Decimal_Number, Nd, - Letter_Number, Nl, - Other_Number, No, - - Punctuation, P, - Connector_Punctuation, Pc, - Dash_Punctuation, Pd, - Open_Punctuation, Ps, - Close_Punctuation, Pe, - Initial_Punctuation, Pi, - Final_Punctuation, Pf, - Other_Punctuation, Po, - - Symbol, S, - Math_Symbol, Sm, - Currency_Symbol, Sc, - Modifier_Symbol, Sk, - Other_Symbol, So, - - Separator, Z, - Space_Separator, Zs, - Line_Separator, Zl, - Paragraph_Separator, Zp, - - Other, C, - Control, Cc, - Format, Cf, - Surrogate, Cs, - Private_Use, Co, - Not_Assigned, Cn, + Letter, + L, + Lowercase_Letter, + Ll, + Uppercase_Letter, + Lu, + Titlecase_Letter, + Lt, + Modifier_Letter, + Lm, + Other_Letter, + Lo, + + Mark, + M, + Nonspacing_Mark, + Mn, + Spacing_Mark, + Mc, + Enclosing_Mark, + Me, + + Number, + N, + Decimal_Number, + Nd, + Letter_Number, + Nl, + Other_Number, + No, + + Punctuation, + P, + Connector_Punctuation, + Pc, + Dash_Punctuation, + Pd, + Open_Punctuation, + Ps, + Close_Punctuation, + Pe, + Initial_Punctuation, + Pi, + Final_Punctuation, + Pf, + Other_Punctuation, + Po, + + Symbol, + S, + Math_Symbol, + Sm, + Currency_Symbol, + Sc, + Modifier_Symbol, + Sk, + Other_Symbol, + So, + + Separator, + Z, + Space_Separator, + Zs, + Line_Separator, + Zl, + Paragraph_Separator, + Zp, + + Other, + C, + Control, + Cc, + Format, + Cf, + Surrogate, + Cs, + Private_Use, + Co, + Not_Assigned, + Cn, pub fn fromString(s: []const u8) ?UnicodeProperty { const map = std.StaticStringMap(UnicodeProperty).initComptime(.{ - .{ "Letter", .Letter }, .{ "L", .L }, + .{ "Letter", .Letter }, .{ "L", .L }, .{ "Lowercase_Letter", .Lowercase_Letter }, .{ "Ll", .Ll }, .{ "Uppercase_Letter", .Uppercase_Letter }, .{ "Lu", .Lu }, - .{ "Number", .Number }, .{ "N", .N }, - .{ "Decimal_Number", .Decimal_Number }, .{ "Nd", .Nd }, - .{ "Punctuation", .Punctuation }, .{ "P", .P }, - .{ "Symbol", .Symbol }, .{ "S", .S }, - .{ "Separator", .Separator }, .{ "Z", .Z }, - .{ "Space_Separator", .Space_Separator }, .{ "Zs", .Zs }, - .{ "Control", .Control }, .{ "Cc", .Cc }, + .{ "Number", .Number }, .{ "N", .N }, + .{ "Decimal_Number", .Decimal_Number }, .{ "Nd", .Nd }, + .{ "Punctuation", .Punctuation }, .{ "P", .P }, + .{ "Symbol", .Symbol }, .{ "S", .S }, + .{ "Separator", .Separator }, .{ "Z", .Z }, + .{ "Space_Separator", .Space_Separator }, .{ "Zs", .Zs }, + .{ "Control", .Control }, .{ "Cc", .Cc }, }); return map.get(s); } @@ -368,7 +405,7 @@ test "Unicode property matching" { // Edge case tests test "unicode: invalid UTF-8 sequences" { // Truncated multi-byte sequence - const truncated = [_]u8{ 0xC3 }; // Should be 2 bytes but only 1 + const truncated = [_]u8{0xC3}; // Should be 2 bytes but only 1 try std.testing.expectError(error.InvalidUtf8, decodeUtf8(&truncated)); // Invalid continuation byte @@ -432,12 +469,12 @@ test "unicode: round-trip encoding/decoding" { // Test various codepoints can be encoded and decoded back const test_codepoints = [_]Codepoint{ - 0x0000, 0x007F, // ASCII boundaries - 0x0080, 0x07FF, // 2-byte boundaries - 0x0800, 0xFFFF, // 3-byte boundaries - 0x10000, 0x10FFFF, // 4-byte boundaries - 'a', 'Z', '0', '9', // Common ASCII - 0x00E9, 0x20AC, // Common non-ASCII + 0x0000, 0x007F, // ASCII boundaries + 0x0080, 0x07FF, // 2-byte boundaries + 0x0800, 0xFFFF, // 3-byte boundaries + 0x10000, 0x10FFFF, // 4-byte boundaries + 'a', 'Z', '0', '9', // Common ASCII + 0x00E9, 0x20AC, // Common non-ASCII }; for (test_codepoints) |cp| { diff --git a/tests/advanced_edge_cases.zig b/tests/advanced_edge_cases.zig index 9438d17..592fcee 100644 --- a/tests/advanced_edge_cases.zig +++ b/tests/advanced_edge_cases.zig @@ -10,7 +10,7 @@ const RegexError = @import("regex").RegexError; test "advanced: positive lookahead basic" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar)"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)"); defer regex.deinit(); // "foobar" should match "foo" (lookahead is zero-width) @@ -28,7 +28,7 @@ test "advanced: positive lookahead basic" { test "advanced: negative lookahead basic" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?!bar)"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?!bar)"); defer regex.deinit(); // "foobaz" should match "foo" (bar not following) @@ -41,7 +41,7 @@ test "advanced: negative lookahead basic" { test "advanced: lookahead is zero-width" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar)bar"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)bar"); defer regex.deinit(); // Lookahead doesn't consume, so "bar" after it should match the same "bar" @@ -52,7 +52,7 @@ test "advanced: lookahead is zero-width" { test "advanced: positive lookbehind" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?<=@)\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "(?<=@)\\w+"); defer regex.deinit(); if (try regex.find("user@domain")) |match| { @@ -66,7 +66,7 @@ test "advanced: positive lookbehind" { test "advanced: negative lookbehind" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?.*"); + var regex = try Regex.compile(allocator, std.testing.io, "<(\\w+)>.*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("bold")); @@ -104,7 +104,7 @@ test "advanced: backreference HTML tag matching" { test "advanced: non-capturing group doesn't affect capture numbering" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:abc)(def)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:abc)(def)"); defer regex.deinit(); if (try regex.find("abcdef")) |match| { @@ -121,7 +121,7 @@ test "advanced: non-capturing group doesn't affect capture numbering" { test "advanced: non-capturing with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:cat|dog)fish"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:cat|dog)fish"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("catfish")); @@ -131,7 +131,7 @@ test "advanced: non-capturing with alternation" { test "advanced: mixed capturing and non-capturing" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)(?:\\s+)(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)(?:\\s+)(\\w+)"); defer regex.deinit(); if (try regex.find("hello world")) |match| { @@ -149,7 +149,7 @@ test "advanced: mixed capturing and non-capturing" { test "advanced: named capture python style" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\d{4})-(?P\\d{2})"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\d{4})-(?P\\d{2})"); defer regex.deinit(); if (try regex.find("date: 2024-01 ok")) |match| { @@ -166,7 +166,7 @@ test "advanced: named capture python style" { test "advanced: named capture angle bracket style" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?\\w+)\\s(?\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?\\w+)\\s(?\\w+)"); defer regex.deinit(); if (try regex.find("John Doe")) |match| { @@ -184,7 +184,7 @@ test "advanced: named capture angle bracket style" { test "advanced: anchored pattern with captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(\\w+)\\s+(\\w+)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(\\w+)\\s+(\\w+)$"); defer regex.deinit(); if (try regex.find("hello world")) |match| { @@ -202,7 +202,7 @@ test "advanced: anchored pattern with captures" { test "advanced: case insensitive find with captures" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "(hello) (world)", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "(hello) (world)", .{ .case_insensitive = true }); defer regex.deinit(); if (try regex.find("HELLO WORLD")) |match| { @@ -221,7 +221,7 @@ test "advanced: case insensitive find with captures" { test "advanced: IPv4 address pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("192.168.1.1")); @@ -231,7 +231,7 @@ test "advanced: IPv4 address pattern" { test "advanced: hex color pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "#[0-9a-fA-F]{6}"); + var regex = try Regex.compile(allocator, std.testing.io, "#[0-9a-fA-F]{6}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("#ff0000")); @@ -241,7 +241,7 @@ test "advanced: hex color pattern" { test "advanced: semver pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d+)\\.(\\d+)\\.(\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)\\.(\\d+)\\.(\\d+)"); defer regex.deinit(); if (try regex.find("version 1.2.3-beta")) |match| { @@ -259,7 +259,7 @@ test "advanced: semver pattern" { test "advanced: log line pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\[(\\w+)\\]\\s+(.+)"); + var regex = try Regex.compile(allocator, std.testing.io, "\\[(\\w+)\\]\\s+(.+)"); defer regex.deinit(); if (try regex.find("[ERROR] Something went wrong")) |match| { @@ -275,7 +275,7 @@ test "advanced: log line pattern" { test "advanced: URL extraction" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "https?://[a-zA-Z0-9._/-]+"); + var regex = try Regex.compile(allocator, std.testing.io, "https?://[a-zA-Z0-9._/-]+"); defer regex.deinit(); if (try regex.find("visit http://example.com/path today")) |match| { @@ -291,7 +291,7 @@ test "advanced: URL extraction" { test "advanced: iterator over empty result" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "xyz"); + var regex = try Regex.compile(allocator, std.testing.io, "xyz"); defer regex.deinit(); var iter = regex.iterator("hello world"); @@ -301,7 +301,7 @@ test "advanced: iterator over empty result" { test "advanced: iterator collects all matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); var iter = regex.iterator("a1b23c456"); @@ -320,7 +320,7 @@ test "advanced: iterator collects all matches" { test "advanced: multiline with captures" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "^(\\w+):", .{ .multiline = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^(\\w+):", .{ .multiline = true }); defer regex.deinit(); const matches = try regex.findAll(allocator, "key1: val\nkey2: val"); @@ -343,7 +343,7 @@ test "advanced: multiline with captures" { test "advanced: match at very start of input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); if (try regex.find("123abc")) |match| { @@ -358,7 +358,7 @@ test "advanced: match at very start of input" { test "advanced: match consuming entire input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); defer regex.deinit(); if (try regex.find("hello")) |match| { diff --git a/tests/backreferences.zig b/tests/backreferences.zig index c4b6873..253a363 100644 --- a/tests/backreferences.zig +++ b/tests/backreferences.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "backreference: simple capture group replacement" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "hello", "$1!"); @@ -16,7 +16,7 @@ test "backreference: simple capture group replacement" { test "backreference: swap two words" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) (\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) (\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "hello world", "$2 $1"); @@ -27,7 +27,7 @@ test "backreference: swap two words" { test "backreference: repeat capture" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "test", "$1-$1"); @@ -38,7 +38,7 @@ test "backreference: repeat capture" { test "backreference: multiple captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d+)-(\\d+)-(\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)-(\\d+)-(\\d+)"); defer regex.deinit(); const result = try regex.replace(allocator, "2025-10-26", "$3/$2/$1"); @@ -49,7 +49,7 @@ test "backreference: multiple captures" { test "backreference: escaped dollar sign" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)"); defer regex.deinit(); const result = try regex.replace(allocator, "100", "$$$1"); @@ -60,7 +60,7 @@ test "backreference: escaped dollar sign" { test "backreference: replaceAll with captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "user@example and admin@test", "$1 at $2"); @@ -71,7 +71,7 @@ test "backreference: replaceAll with captures" { test "backreference: extract and format phone numbers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d{3})-(\\d{3})-(\\d{4})"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d{3})-(\\d{3})-(\\d{4})"); defer regex.deinit(); const result = try regex.replace(allocator, "555-123-4567", "($1) $2-$3"); @@ -82,7 +82,7 @@ test "backreference: extract and format phone numbers" { test "backreference: reformat dates" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d{4})-(\\d{2})-(\\d{2})"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d{4})-(\\d{2})-(\\d{2})"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "2025-10-26 and 2024-12-31", "$2/$3/$1"); @@ -93,7 +93,7 @@ test "backreference: reformat dates" { test "backreference: wrap matches in tags" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "hello world", "$1"); @@ -104,7 +104,7 @@ test "backreference: wrap matches in tags" { test "backreference: invalid capture index" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); defer regex.deinit(); // Only one capture group, $2 should be treated as literal @@ -116,7 +116,7 @@ test "backreference: invalid capture index" { test "backreference: nested captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "((\\w+)@(\\w+))"); + var regex = try Regex.compile(allocator, std.testing.io, "((\\w+)@(\\w+))"); defer regex.deinit(); const result = try regex.replace(allocator, "user@example.com", "Email: $1 (user=$2, domain=$3)"); @@ -127,7 +127,7 @@ test "backreference: nested captures" { test "backreference: quote words" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\b(\\w+)\\b"); + var regex = try Regex.compile(allocator, std.testing.io, "\\b(\\w+)\\b"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "hello world", "'$1'"); @@ -138,7 +138,7 @@ test "backreference: quote words" { test "backreference: transform case context" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(Mr|Mrs|Ms) (\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(Mr|Mrs|Ms) (\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "Hello Mr Smith", "$1. $2"); @@ -151,7 +151,7 @@ test "backreference: transform case context" { test "pattern backreference: basic \\1" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) \\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1"); defer regex.deinit(); // Should match repeated words @@ -171,7 +171,7 @@ test "pattern backreference: basic \\1" { test "pattern backreference: multiple captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) (\\w+) \\1 \\2"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) (\\w+) \\1 \\2"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foo bar foo bar")); @@ -191,7 +191,7 @@ test "pattern backreference: multiple captures" { test "pattern backreference: with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d+)\\+\\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)\\+\\1"); defer regex.deinit(); // Match patterns like "5+5", "123+123" @@ -202,7 +202,7 @@ test "pattern backreference: with quantifiers" { test "pattern backreference: HTML tag matching" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "<(\\w+)>.*"); + var regex = try Regex.compile(allocator, std.testing.io, "<(\\w+)>.*"); defer regex.deinit(); // Match matching HTML tags @@ -213,7 +213,7 @@ test "pattern backreference: HTML tag matching" { test "pattern backreference: case sensitive" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) \\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1"); defer regex.deinit(); // Backreferences should be case sensitive @@ -223,7 +223,7 @@ test "pattern backreference: case sensitive" { test "pattern backreference: nested groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "((\\w)\\w)\\2"); + var regex = try Regex.compile(allocator, std.testing.io, "((\\w)\\w)\\2"); defer regex.deinit(); // \\2 refers to second group (single char), so "aba" should match: @@ -246,7 +246,7 @@ test "pattern backreference: nested groups" { test "pattern backreference: with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a|b)\\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(a|b)\\1"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("aa")); @@ -257,7 +257,7 @@ test "pattern backreference: with alternation" { test "pattern backreference: multiple in same pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w)\\1\\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w)\\1\\1"); defer regex.deinit(); // Match three of the same character @@ -269,7 +269,7 @@ test "pattern backreference: multiple in same pattern" { test "pattern backreference: findAll" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) \\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1"); defer regex.deinit(); const matches = try regex.findAll(allocator, "foo foo bar bar baz qux"); @@ -288,7 +288,7 @@ test "pattern backreference: findAll" { test "pattern backreference: quoted strings" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(['\"]).*\\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(['\"]).*\\1"); defer regex.deinit(); // Match quoted strings with same quote type diff --git a/tests/case_insensitive.zig b/tests/case_insensitive.zig index 90212ba..e1208c1 100644 --- a/tests/case_insensitive.zig +++ b/tests/case_insensitive.zig @@ -4,7 +4,7 @@ const common = @import("regex").common; test "case insensitive: simple literal" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -16,7 +16,7 @@ test "case insensitive: simple literal" { test "case insensitive: with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "a+b*", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "a+b*", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("aaa")); @@ -29,7 +29,7 @@ test "case insensitive: with quantifiers" { test "case insensitive: mixed with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "cat|dog", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "cat|dog", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("cat")); @@ -42,7 +42,7 @@ test "case insensitive: mixed with alternation" { test "case insensitive: find in text" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true }); defer regex.deinit(); if (try regex.find("Say HELLO world")) |match| { @@ -56,7 +56,7 @@ test "case insensitive: find in text" { test "case insensitive: with anchors" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "^hello$", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^hello$", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -68,7 +68,7 @@ test "case insensitive: with anchors" { test "case insensitive: replace" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true }); defer regex.deinit(); const result = try regex.replace(allocator, "Say HELLO world", "hi"); @@ -79,7 +79,7 @@ test "case insensitive: replace" { test "case insensitive: replaceAll" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "a", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "a", .{ .case_insensitive = true }); defer regex.deinit(); const result = try regex.replaceAll(allocator, "AaAa", "X"); @@ -90,7 +90,7 @@ test "case insensitive: replaceAll" { test "case insensitive: disabled by default" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "hello"); + var regex = try Regex.compile(allocator, std.testing.io, "hello"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -100,7 +100,7 @@ test "case insensitive: disabled by default" { test "case insensitive: with numbers and special chars" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "test123", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "test123", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("test123")); diff --git a/tests/comprehensive.zig b/tests/comprehensive.zig index f8bb337..d7ed258 100644 --- a/tests/comprehensive.zig +++ b/tests/comprehensive.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; test "comprehensive: email-like pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+@\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("user@domain")); @@ -12,7 +12,7 @@ test "comprehensive: email-like pattern" { test "comprehensive: phone number pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+-\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+-\\d+"); defer regex.deinit(); if (try regex.find("Call 555-1234 now")) |match| { @@ -26,7 +26,7 @@ test "comprehensive: phone number pattern" { test "comprehensive: complex alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "cat|dog|bird"); + var regex = try Regex.compile(allocator, std.testing.io, "cat|dog|bird"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("cat")); @@ -37,7 +37,7 @@ test "comprehensive: complex alternation" { test "comprehensive: nested grouping" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a(b)c)"); + var regex = try Regex.compile(allocator, std.testing.io, "(a(b)c)"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); @@ -46,7 +46,7 @@ test "comprehensive: nested grouping" { test "comprehensive: multiple quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+b*c?"); + var regex = try Regex.compile(allocator, std.testing.io, "a+b*c?"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -57,7 +57,7 @@ test "comprehensive: multiple quantifiers" { test "comprehensive: start and end anchors" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^hello$"); + var regex = try Regex.compile(allocator, std.testing.io, "^hello$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -67,7 +67,7 @@ test "comprehensive: start and end anchors" { test "comprehensive: character class ranges" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a-z]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+"); defer regex.deinit(); if (try regex.find("ABC123xyz")) |match| { @@ -81,7 +81,7 @@ test "comprehensive: character class ranges" { test "comprehensive: negated character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[^0-9]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[^0-9]+"); defer regex.deinit(); if (try regex.find("123abc456")) |match| { @@ -95,7 +95,7 @@ test "comprehensive: negated character class" { test "comprehensive: findAll with multiple matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const matches = try regex.findAll(allocator, "10 cats and 20 dogs"); @@ -114,7 +114,7 @@ test "comprehensive: findAll with multiple matches" { test "comprehensive: replaceAll multiple occurrences" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "I have 2 cats and 3 dogs", "many"); @@ -125,7 +125,7 @@ test "comprehensive: replaceAll multiple occurrences" { test "comprehensive: split by whitespace pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); defer regex.deinit(); const parts = try regex.split(allocator, "hello world test"); @@ -139,7 +139,7 @@ test "comprehensive: split by whitespace pattern" { test "comprehensive: dot matches any character" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a.c"); + var regex = try Regex.compile(allocator, std.testing.io, "a.c"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); @@ -150,7 +150,7 @@ test "comprehensive: dot matches any character" { test "comprehensive: escape special characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\$\\d+\\.\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\$\\d+\\.\\d+"); defer regex.deinit(); if (try regex.find("Price: $19.99")) |match| { @@ -164,7 +164,7 @@ test "comprehensive: escape special characters" { test "comprehensive: word boundary" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\bword\\b"); + var regex = try Regex.compile(allocator, std.testing.io, "\\bword\\b"); defer regex.deinit(); if (try regex.find("a word here")) |match| { diff --git a/tests/edge_cases.zig b/tests/edge_cases.zig index ec77466..1d9baa0 100644 --- a/tests/edge_cases.zig +++ b/tests/edge_cases.zig @@ -5,7 +5,7 @@ const RegexError = @import("regex").RegexError; // Edge case: Empty input test "edge: empty input with star" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*"); + var regex = try Regex.compile(allocator, std.testing.io, "a*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -13,7 +13,7 @@ test "edge: empty input with star" { test "edge: empty input with plus should fail" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+"); + var regex = try Regex.compile(allocator, std.testing.io, "a+"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("")); @@ -22,7 +22,7 @@ test "edge: empty input with plus should fail" { // Edge case: Single character patterns test "edge: single character literal" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "x"); + var regex = try Regex.compile(allocator, std.testing.io, "x"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("x")); @@ -33,7 +33,7 @@ test "edge: single character literal" { // Edge case: Nested quantifiers behavior test "edge: star after plus" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+b*"); + var regex = try Regex.compile(allocator, std.testing.io, "a+b*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -44,7 +44,7 @@ test "edge: star after plus" { // Edge case: Multiple alternations test "edge: multiple alternations" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a|b|c|d"); + var regex = try Regex.compile(allocator, std.testing.io, "a|b|c|d"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -57,7 +57,7 @@ test "edge: multiple alternations" { // Edge case: Alternation with empty branch test "edge: alternation with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*|b+"); + var regex = try Regex.compile(allocator, std.testing.io, "a*|b+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -68,7 +68,7 @@ test "edge: alternation with quantifiers" { // Edge case: Complex nested groups test "edge: nested groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "((a))"); + var regex = try Regex.compile(allocator, std.testing.io, "((a))"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -77,7 +77,7 @@ test "edge: nested groups" { // Edge case: Character class edge cases test "edge: empty character class range" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a]"); + var regex = try Regex.compile(allocator, std.testing.io, "[a]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -86,7 +86,7 @@ test "edge: empty character class range" { test "edge: character class with single range" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a-a]"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-a]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -95,7 +95,7 @@ test "edge: character class with single range" { test "edge: negated character class matching nothing in range" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[^a-z]"); + var regex = try Regex.compile(allocator, std.testing.io, "[^a-z]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("A")); @@ -106,7 +106,7 @@ test "edge: negated character class matching nothing in range" { // Edge case: Anchors at different positions test "edge: start anchor not at beginning" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a^b"); + var regex = try Regex.compile(allocator, std.testing.io, "a^b"); defer regex.deinit(); // This should not match anything since ^ is not at the start @@ -115,7 +115,7 @@ test "edge: start anchor not at beginning" { test "edge: end anchor not at end" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a$b"); + var regex = try Regex.compile(allocator, std.testing.io, "a$b"); defer regex.deinit(); // This should not match anything since $ is not at the end @@ -125,7 +125,7 @@ test "edge: end anchor not at end" { // Edge case: Dot matching test "edge: dot with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ".*"); + var regex = try Regex.compile(allocator, std.testing.io, ".*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -136,7 +136,7 @@ test "edge: dot with quantifiers" { // Edge case: Escape sequences test "edge: escaped backslash" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\\\"); + var regex = try Regex.compile(allocator, std.testing.io, "\\\\"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("\\")); @@ -145,7 +145,7 @@ test "edge: escaped backslash" { test "edge: escaped dot" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\."); + var regex = try Regex.compile(allocator, std.testing.io, "\\."); defer regex.deinit(); try std.testing.expect(try regex.isMatch(".")); @@ -155,7 +155,7 @@ test "edge: escaped dot" { // Edge case: findAll edge cases test "edge: findAll with no matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "x"); + var regex = try Regex.compile(allocator, std.testing.io, "x"); defer regex.deinit(); const matches = try regex.findAll(allocator, "aaabbb"); @@ -166,7 +166,7 @@ test "edge: findAll with no matches" { test "edge: findAll with overlapping potential matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+"); + var regex = try Regex.compile(allocator, std.testing.io, "a+"); defer regex.deinit(); const matches = try regex.findAll(allocator, "aaa"); @@ -186,7 +186,7 @@ test "edge: findAll with overlapping potential matches" { // Edge case: replace edge cases test "edge: replace with empty string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a"); + var regex = try Regex.compile(allocator, std.testing.io, "a"); defer regex.deinit(); const result = try regex.replace(allocator, "banana", ""); @@ -197,7 +197,7 @@ test "edge: replace with empty string" { test "edge: replace in empty string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a"); + var regex = try Regex.compile(allocator, std.testing.io, "a"); defer regex.deinit(); const result = try regex.replace(allocator, "", "x"); @@ -209,7 +209,7 @@ test "edge: replace in empty string" { // Edge case: split edge cases test "edge: split empty string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const parts = try regex.split(allocator, ""); @@ -221,7 +221,7 @@ test "edge: split empty string" { test "edge: split with no delimiter found" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const parts = try regex.split(allocator, "hello"); @@ -234,7 +234,7 @@ test "edge: split with no delimiter found" { // Edge case: Word boundaries test "edge: word boundary at start" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\bword"); + var regex = try Regex.compile(allocator, std.testing.io, "\\bword"); defer regex.deinit(); if (try regex.find("word here")) |match| { @@ -248,7 +248,7 @@ test "edge: word boundary at start" { test "edge: word boundary at end" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "word\\b"); + var regex = try Regex.compile(allocator, std.testing.io, "word\\b"); defer regex.deinit(); if (try regex.find("a word")) |match| { @@ -263,7 +263,7 @@ test "edge: word boundary at end" { // Edge case: Character classes with special chars test "edge: whitespace character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); defer regex.deinit(); if (try regex.find("hello world")) |match| { @@ -277,7 +277,7 @@ test "edge: whitespace character class" { test "edge: digit character class at boundaries" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^\\d+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("123")); diff --git a/tests/fuzz.zig b/tests/fuzz.zig index 4d33c05..ad4d914 100644 --- a/tests/fuzz.zig +++ b/tests/fuzz.zig @@ -21,7 +21,7 @@ test "fuzz: random valid patterns" { const char_choice = random.intRangeAtMost(u8, 0, 4); pattern_buf[j] = switch (char_choice) { 0 => 'a' + random.intRangeAtMost(u8, 0, 25), // a-z - 1 => '0' + random.intRangeAtMost(u8, 0, 9), // 0-9 + 1 => '0' + random.intRangeAtMost(u8, 0, 9), // 0-9 2 => '.', // wildcard 3 => '*', // quantifier else => '+', // quantifier @@ -129,18 +129,18 @@ test "fuzz: malformed patterns should return errors" { const allocator = std.testing.allocator; const bad_patterns = [_][]const u8{ - "(abc", // Unmatched paren - "abc)", // Unmatched paren - "[abc", // Unmatched bracket - "abc]", // Unmatched bracket - "*", // Quantifier without target - "+abc", // Quantifier without target - "?abc", // Quantifier without target - "{2,3}", // Quantifier without target - "\\", // Incomplete escape - "[z-a]", // Invalid range - "{-1,5}", // Invalid repetition - "{5,2}", // Invalid repetition (min > max) + "(abc", // Unmatched paren + "abc)", // Unmatched paren + "[abc", // Unmatched bracket + "abc]", // Unmatched bracket + "*", // Quantifier without target + "+abc", // Quantifier without target + "?abc", // Quantifier without target + "{2,3}", // Quantifier without target + "\\", // Incomplete escape + "[z-a]", // Invalid range + "{-1,5}", // Invalid repetition + "{5,2}", // Invalid repetition (min > max) }; for (bad_patterns) |pattern| { @@ -180,7 +180,7 @@ test "fuzz: edge cases with empty strings" { test "fuzz: long input strings" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*b+c?"); + var regex = try Regex.compile(allocator, std.testing.io, "a*b+c?"); defer regex.deinit(); // Create progressively longer inputs diff --git a/tests/integration.zig b/tests/integration.zig index 84cb51d..9561848 100644 --- a/tests/integration.zig +++ b/tests/integration.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "integration: email validation - basic pattern" { const allocator = std.testing.allocator; // Simplified email pattern: word@word.word - var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+\\.\\w+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("user@example.com")); @@ -16,7 +16,7 @@ test "integration: email validation - basic pattern" { test "integration: email validation - extract from text" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+\\.\\w+"); defer regex.deinit(); const text = "Contact support@example.com for help"; @@ -33,7 +33,7 @@ test "integration: email validation - extract from text" { test "integration: URL extraction from text" { const allocator = std.testing.allocator; // Simplified: just match http:// or https:// followed by word.word - var regex = try Regex.compile(allocator, "https://\\w+\\.\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "https://\\w+\\.\\w+"); defer regex.deinit(); const text = "Visit https://example.com for more info."; @@ -49,7 +49,7 @@ test "integration: URL extraction from text" { // Phone Number Tests test "integration: US phone number validation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^\\d{3}-\\d{3}-\\d{4}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d{3}-\\d{3}-\\d{4}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("555-123-4567")); @@ -60,7 +60,7 @@ test "integration: US phone number validation" { test "integration: extract phone numbers from text" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d{3}-\\d{3}-\\d{4}"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d{3}-\\d{3}-\\d{4}"); defer regex.deinit(); const text = "Call 555-123-4567 or 800-555-9876 for support."; @@ -81,7 +81,7 @@ test "integration: extract phone numbers from text" { // Date/Time Patterns test "integration: ISO date format" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^\\d{4}-\\d{2}-\\d{2}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d{4}-\\d{2}-\\d{2}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("2025-01-26")); @@ -92,7 +92,7 @@ test "integration: ISO date format" { test "integration: time format HH:MM:SS" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^\\d{2}:\\d{2}:\\d{2}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d{2}:\\d{2}:\\d{2}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("14:30:45")); @@ -106,7 +106,7 @@ test "integration: time format HH:MM:SS" { test "integration: extract log levels" { const allocator = std.testing.allocator; // Match INFO specifically (alternation with 4 options seems to cause overflow) - var regex = try Regex.compile(allocator, "INFO"); + var regex = try Regex.compile(allocator, std.testing.io, "INFO"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("[INFO] Application started")); @@ -115,7 +115,7 @@ test "integration: extract log levels" { test "integration: parse log timestamp and message" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"); defer regex.deinit(); const log = "[2025-01-26 14:30:45] User login successful"; @@ -132,7 +132,7 @@ test "integration: parse log timestamp and message" { test "integration: IPv4 address matching" { const allocator = std.testing.allocator; // Simplified pattern (doesn't validate ranges) - var regex = try Regex.compile(allocator, "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("192.168.1.1")); @@ -145,7 +145,7 @@ test "integration: IPv4 address matching" { test "integration: password strength - minimum length" { const allocator = std.testing.allocator; // Check if string has at least 8 characters (simplified - just check length differently) - var regex = try Regex.compile(allocator, ".{8}"); + var regex = try Regex.compile(allocator, std.testing.io, ".{8}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("password123")); // has 8+ chars @@ -155,7 +155,7 @@ test "integration: password strength - minimum length" { test "integration: password contains digit" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("password1")); @@ -167,7 +167,7 @@ test "integration: password contains digit" { test "integration: username alphanumeric with underscores" { const allocator = std.testing.allocator; // Simplified - removed anchors and large quantifier bound to avoid crash - var regex = try Regex.compile(allocator, "[a-zA-Z0-9_]{3}"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-zA-Z0-9_]{3}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("user123")); @@ -179,7 +179,7 @@ test "integration: username alphanumeric with underscores" { // Hashtag Extraction test "integration: extract hashtags from text" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "#\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "#\\w+"); defer regex.deinit(); const tweet = "Loving #Zig programming! #opensource #systems"; @@ -202,7 +202,7 @@ test "integration: extract hashtags from text" { test "integration: match HTML tags" { const allocator = std.testing.allocator; // Match opening tags only (no closing tags with /) - var regex = try Regex.compile(allocator, "<[a-z]+>"); + var regex = try Regex.compile(allocator, std.testing.io, "<[a-z]+>"); defer regex.deinit(); const html = "
contenttext"; @@ -223,7 +223,7 @@ test "integration: match HTML tags" { // CSV Parsing test "integration: split CSV line" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const csv = "John,Doe,30,Engineer"; @@ -240,7 +240,7 @@ test "integration: split CSV line" { // Whitespace Normalization test "integration: replace multiple spaces with single space" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); defer regex.deinit(); const text = "This has too many spaces"; @@ -254,7 +254,7 @@ test "integration: replace multiple spaces with single space" { test "integration: extract markdown links" { const allocator = std.testing.allocator; // Simplified pattern - just find one link - var regex = try Regex.compile(allocator, "\\[\\w+\\]\\(https://\\w+\\.\\w+\\)"); + var regex = try Regex.compile(allocator, std.testing.io, "\\[\\w+\\]\\(https://\\w+\\.\\w+\\)"); defer regex.deinit(); const markdown = "Check [Zig](https://ziglang.org) for more info"; @@ -270,7 +270,7 @@ test "integration: extract markdown links" { // Version Number Extraction test "integration: semver version numbers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+\\.\\d+\\.\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+\\.\\d+\\.\\d+"); defer regex.deinit(); const text = "Version 1.2.3 released. Upgrade from 1.0.0 required."; @@ -291,7 +291,7 @@ test "integration: semver version numbers" { // Hex Color Codes test "integration: match hex color codes" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "#[0-9a-fA-F]{6}"); + var regex = try Regex.compile(allocator, std.testing.io, "#[0-9a-fA-F]{6}"); defer regex.deinit(); const css = "color: #FF5733; background: #FFFFFF;"; @@ -312,7 +312,7 @@ test "integration: match hex color codes" { // Code Comment Extraction test "integration: extract single-line comments" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "//.*"); + var regex = try Regex.compile(allocator, std.testing.io, "//.*"); defer regex.deinit(); const code = "const x = 5; // This is a comment"; @@ -328,7 +328,7 @@ test "integration: extract single-line comments" { // Environment Variable Format test "integration: match environment variables" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\$[A-Z_]+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\$[A-Z_]+"); defer regex.deinit(); const script = "export PATH=$HOME/bin:$PATH"; diff --git a/tests/iterator.zig b/tests/iterator.zig index 750035f..57c0522 100644 --- a/tests/iterator.zig +++ b/tests/iterator.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "iterator: basic iteration" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const text = "123 abc 456 def 789"; @@ -29,7 +29,7 @@ test "iterator: basic iteration" { test "iterator: empty input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const text = ""; @@ -41,7 +41,7 @@ test "iterator: empty input" { test "iterator: no matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const text = "abc def"; @@ -53,7 +53,7 @@ test "iterator: no matches" { test "iterator: reset functionality" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); defer regex.deinit(); const text = "hello world"; @@ -72,7 +72,7 @@ test "iterator: reset functionality" { test "iterator: captures in iteration" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)"); defer regex.deinit(); const text = "user@example and admin@test"; @@ -94,7 +94,7 @@ test "iterator: captures in iteration" { test "iterator: large input streaming" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); // Simulate processing large input without loading all matches @@ -115,7 +115,7 @@ test "iterator: large input streaming" { test "iterator: manual loop pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a-z]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+"); defer regex.deinit(); const text = "hello world test"; @@ -146,7 +146,7 @@ test "iterator: manual loop pattern" { test "iterator: memory efficiency" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); defer regex.deinit(); // Iterator should not allocate all matches at once @@ -171,7 +171,7 @@ test "iterator: memory efficiency" { test "iterator: overlapping matches prevention" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w\\w"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w\\w"); defer regex.deinit(); const text = "abcd"; diff --git a/tests/lazy_quantifiers.zig b/tests/lazy_quantifiers.zig index 608e620..179550d 100644 --- a/tests/lazy_quantifiers.zig +++ b/tests/lazy_quantifiers.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "lazy star: a*? matches minimal" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*?b"); + var regex = try Regex.compile(allocator, std.testing.io, "a*?b"); defer regex.deinit(); // Greedy would match "aaab", lazy should match "b" @@ -22,7 +22,7 @@ test "lazy star vs greedy star" { const allocator = std.testing.allocator; // Greedy star - var greedy = try Regex.compile(allocator, "a*b"); + var greedy = try Regex.compile(allocator, std.testing.io, "a*b"); defer greedy.deinit(); if (try greedy.find("aaab")) |match| { @@ -35,7 +35,7 @@ test "lazy star vs greedy star" { } // Lazy star - var lazy = try Regex.compile(allocator, "a*?b"); + var lazy = try Regex.compile(allocator, std.testing.io, "a*?b"); defer lazy.deinit(); if (try lazy.find("aaab")) |match| { @@ -50,7 +50,7 @@ test "lazy star vs greedy star" { test "lazy plus: a+? matches minimal (at least one)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+?b"); + var regex = try Regex.compile(allocator, std.testing.io, "a+?b"); defer regex.deinit(); // Lazy plus must match at least one 'a' @@ -67,7 +67,7 @@ test "lazy plus vs greedy plus" { const allocator = std.testing.allocator; // Greedy plus - var greedy = try Regex.compile(allocator, "a+b"); + var greedy = try Regex.compile(allocator, std.testing.io, "a+b"); defer greedy.deinit(); if (try greedy.find("aaab")) |match| { @@ -80,7 +80,7 @@ test "lazy plus vs greedy plus" { } // Lazy plus - var lazy = try Regex.compile(allocator, "a+?b"); + var lazy = try Regex.compile(allocator, std.testing.io, "a+?b"); defer lazy.deinit(); if (try lazy.find("aaab")) |match| { @@ -95,7 +95,7 @@ test "lazy plus vs greedy plus" { test "lazy optional: a?? matches minimal (zero)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a??b"); + var regex = try Regex.compile(allocator, std.testing.io, "a??b"); defer regex.deinit(); // Lazy optional prefers zero matches @@ -112,7 +112,7 @@ test "lazy optional vs greedy optional" { const allocator = std.testing.allocator; // Greedy optional - var greedy = try Regex.compile(allocator, "a?b"); + var greedy = try Regex.compile(allocator, std.testing.io, "a?b"); defer greedy.deinit(); if (try greedy.find("ab")) |match| { @@ -125,7 +125,7 @@ test "lazy optional vs greedy optional" { } // Lazy optional - var lazy = try Regex.compile(allocator, "a??b"); + var lazy = try Regex.compile(allocator, std.testing.io, "a??b"); defer lazy.deinit(); if (try lazy.find("ab")) |match| { @@ -140,7 +140,7 @@ test "lazy optional vs greedy optional" { test "lazy repeat: a{2,4}? matches minimal" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{2,4}?b"); + var regex = try Regex.compile(allocator, std.testing.io, "a{2,4}?b"); defer regex.deinit(); // Lazy repeat matches minimum (2 'a's) @@ -157,7 +157,7 @@ test "lazy repeat vs greedy repeat" { const allocator = std.testing.allocator; // Greedy repeat - var greedy = try Regex.compile(allocator, "a{2,4}b"); + var greedy = try Regex.compile(allocator, std.testing.io, "a{2,4}b"); defer greedy.deinit(); if (try greedy.find("aaaaab")) |match| { @@ -170,7 +170,7 @@ test "lazy repeat vs greedy repeat" { } // Lazy repeat - var lazy = try Regex.compile(allocator, "a{2,4}?b"); + var lazy = try Regex.compile(allocator, std.testing.io, "a{2,4}?b"); defer lazy.deinit(); if (try lazy.find("aaaaab")) |match| { @@ -185,7 +185,7 @@ test "lazy repeat vs greedy repeat" { test "lazy quantifier in alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*?b|c"); + var regex = try Regex.compile(allocator, std.testing.io, "a*?b|c"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("b")); @@ -195,7 +195,7 @@ test "lazy quantifier in alternation" { test "multiple lazy quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a*?b+?c"); + var regex = try Regex.compile(allocator, std.testing.io, "a*?b+?c"); defer regex.deinit(); if (try regex.find("aaabbbbc")) |match| { @@ -210,7 +210,7 @@ test "multiple lazy quantifiers" { test "lazy quantifier with character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a-z]+?\\d"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+?\\d"); defer regex.deinit(); if (try regex.find("abc123")) |match| { @@ -225,7 +225,7 @@ test "lazy quantifier with character class" { test "lazy star with dot" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ".*?x"); + var regex = try Regex.compile(allocator, std.testing.io, ".*?x"); defer regex.deinit(); if (try regex.find("abcxyz")) |match| { @@ -240,7 +240,7 @@ test "lazy star with dot" { test "lazy quantifier in capture group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a+?)b"); + var regex = try Regex.compile(allocator, std.testing.io, "(a+?)b"); defer regex.deinit(); if (try regex.find("aaab")) |match| { @@ -258,7 +258,7 @@ test "lazy quantifier in capture group" { test "lazy repeat {n,}?" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{2,}?b"); + var regex = try Regex.compile(allocator, std.testing.io, "a{2,}?b"); defer regex.deinit(); if (try regex.find("aaaaab")) |match| { @@ -274,7 +274,7 @@ test "lazy repeat {n,}?" { test "lazy quantifier backtracking" { const allocator = std.testing.allocator; // Even though lazy, it must backtrack if necessary to match - var regex = try Regex.compile(allocator, "a*?aab"); + var regex = try Regex.compile(allocator, std.testing.io, "a*?aab"); defer regex.deinit(); if (try regex.find("aaab")) |match| { @@ -289,7 +289,7 @@ test "lazy quantifier backtracking" { test "nested lazy quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a*?b+?)*?c"); + var regex = try Regex.compile(allocator, std.testing.io, "(a*?b+?)*?c"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("c")); @@ -299,7 +299,7 @@ test "nested lazy quantifiers" { test "lazy quantifier at end of pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+?"); + var regex = try Regex.compile(allocator, std.testing.io, "a+?"); defer regex.deinit(); if (try regex.find("aaa")) |match| { @@ -318,10 +318,10 @@ test "lazy vs greedy performance comparison" { const input = "a" ** 100 ++ "b"; // Both should match, but with different lengths - var greedy = try Regex.compile(allocator, "a*b"); + var greedy = try Regex.compile(allocator, std.testing.io, "a*b"); defer greedy.deinit(); - var lazy = try Regex.compile(allocator, "a*?b"); + var lazy = try Regex.compile(allocator, std.testing.io, "a*?b"); defer lazy.deinit(); // Greedy matches all 100 'a's + 'b' diff --git a/tests/lookahead.zig b/tests/lookahead.zig index bb116a1..fba625f 100644 --- a/tests/lookahead.zig +++ b/tests/lookahead.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; test "positive lookahead: basic (?=...)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar)"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)"); defer regex.deinit(); // Should match "foo" only when followed by "bar" @@ -24,7 +24,7 @@ test "positive lookahead: basic (?=...)" { test "positive lookahead: at end of string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar)bar"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)bar"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foobar")); @@ -40,7 +40,7 @@ test "positive lookahead: at end of string" { test "positive lookahead: with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar|baz)"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar|baz)"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foobar")); @@ -50,7 +50,7 @@ test "positive lookahead: with alternation" { test "negative lookahead: basic (?!...)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?!bar)"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?!bar)"); defer regex.deinit(); // Should match "foo" when NOT followed by "bar" @@ -68,7 +68,7 @@ test "negative lookahead: basic (?!...)" { test "negative lookahead: with character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+(?!\\d)"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+(?!\\d)"); defer regex.deinit(); // Should match digits not followed by another digit @@ -84,7 +84,7 @@ test "negative lookahead: with character class" { test "multiple lookaheads" { const allocator = std.testing.allocator; // Password validation: at least one digit AND at least one letter - var regex = try Regex.compile(allocator, "(?=.*\\d)(?=.*[a-z]).+"); + var regex = try Regex.compile(allocator, std.testing.io, "(?=.*\\d)(?=.*[a-z]).+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc123")); @@ -95,7 +95,7 @@ test "multiple lookaheads" { test "lookahead in alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?=foo)|(?=bar)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?=foo)|(?=bar)"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foo")); @@ -104,7 +104,7 @@ test "lookahead in alternation" { test "lookahead with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a+(?=b+)"); + var regex = try Regex.compile(allocator, std.testing.io, "a+(?=b+)"); defer regex.deinit(); if (try regex.find("aaabbb")) |match| { @@ -118,7 +118,7 @@ test "lookahead with quantifiers" { test "nested lookahead" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "foo(?=bar(?=baz))"); + var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar(?=baz))"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foobarbaz")); @@ -127,7 +127,7 @@ test "nested lookahead" { test "lookahead: zero-width assertion" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?=a)a"); + var regex = try Regex.compile(allocator, std.testing.io, "(?=a)a"); defer regex.deinit(); if (try regex.find("a")) |match| { @@ -144,7 +144,7 @@ test "lookahead: zero-width assertion" { test "lookahead with capture groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)(?= world)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)(?= world)"); defer regex.deinit(); if (try regex.find("hello world")) |match| { diff --git a/tests/lookbehind.zig b/tests/lookbehind.zig index 6b80a62..155df5b 100644 --- a/tests/lookbehind.zig +++ b/tests/lookbehind.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; test "positive lookbehind: basic (?<=...)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?<=foo)bar"); + var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo)bar"); defer regex.deinit(); // Should match "bar" only when preceded by "foo" @@ -25,7 +25,7 @@ test "positive lookbehind: basic (?<=...)" { test "positive lookbehind: at start of pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?<=foo)bar"); + var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo)bar"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foobar")); @@ -34,7 +34,7 @@ test "positive lookbehind: at start of pattern" { test "positive lookbehind: with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?<=foo|baz)bar"); + var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo|baz)bar"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("foobar")); @@ -44,7 +44,7 @@ test "positive lookbehind: with alternation" { test "negative lookbehind: basic (?...)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\w+)"); defer regex.deinit(); if (try regex.find("hello")) |match| { @@ -33,7 +33,7 @@ test "named group: Python style (?P...)" { test "named group: .NET/Perl style (?...)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?\\d+)"); defer regex.deinit(); if (try regex.find("123")) |match| { @@ -52,7 +52,7 @@ test "named group: .NET/Perl style (?...)" { test "named group: multiple named groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\d{4})-(?P\\d{2})-(?P\\d{2})"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\d{4})-(?P\\d{2})-(?P\\d{2})"); defer regex.deinit(); if (try regex.find("2025-10-27")) |match| { @@ -79,7 +79,7 @@ test "named group: multiple named groups" { test "named group: mixed with numbered groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(?P\\w+\\.\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(?P\\w+\\.\\w+)"); defer regex.deinit(); if (try regex.find("user@example.com")) |match| { @@ -100,7 +100,7 @@ test "named group: mixed with numbered groups" { test "named group: non-existent name returns null" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\w+)"); defer regex.deinit(); if (try regex.find("hello")) |match| { @@ -119,7 +119,7 @@ test "named group: non-existent name returns null" { test "named group: with alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?Phttp|https)://(?P[^/]+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?Phttp|https)://(?P[^/]+)"); defer regex.deinit(); if (try regex.find("https://example.com")) |match| { @@ -141,7 +141,7 @@ test "named group: with alternation" { test "named group: with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P<[^>]+>)(?P.*)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P<[^>]+>)(?P.*)"); defer regex.deinit(); if (try regex.find("
hello world
")) |match| { @@ -163,7 +163,7 @@ test "named group: with quantifiers" { test "named group: nested groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P(inner))"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P(inner))"); defer regex.deinit(); if (try regex.find("inner")) |match| { @@ -186,7 +186,7 @@ test "named group: nested groups" { test "named group: with non-capturing group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:prefix-)?(?P\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:prefix-)?(?P\\w+)"); defer regex.deinit(); if (try regex.find("prefix-test")) |match| { @@ -206,7 +206,7 @@ test "named group: with non-capturing group" { test "named group: email parser example" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P[^@]+)@(?P[^@]+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P[^@]+)@(?P[^@]+)"); defer regex.deinit(); if (try regex.find("john.doe@example.com")) |match| { @@ -228,7 +228,7 @@ test "named group: email parser example" { test "named group: IP address parser" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"); defer regex.deinit(); if (try regex.find("192.168.1.1")) |match| { @@ -256,7 +256,7 @@ test "named group: IP address parser" { test "named group: findAll preserves names" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?P\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?P\\d+)"); defer regex.deinit(); var matches = try regex.findAll(allocator, "123 456 789"); diff --git a/tests/non_capturing_groups.zig b/tests/non_capturing_groups.zig index aba0a62..667e721 100644 --- a/tests/non_capturing_groups.zig +++ b/tests/non_capturing_groups.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "non-capturing: basic non-capturing group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:\\d+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)"); defer regex.deinit(); const result = try regex.find("123"); @@ -22,7 +22,7 @@ test "non-capturing: basic non-capturing group" { test "non-capturing: mixed capturing and non-capturing" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(?:\\w+)\\.(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(?:\\w+)\\.(\\w+)"); defer regex.deinit(); const result = try regex.find("user@example.com"); @@ -41,7 +41,7 @@ test "non-capturing: mixed capturing and non-capturing" { test "non-capturing: alternation in non-capturing group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:cat|dog) (\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:cat|dog) (\\w+)"); defer regex.deinit(); const result1 = try regex.find("cat food"); @@ -69,7 +69,7 @@ test "non-capturing: alternation in non-capturing group" { test "non-capturing: nested non-capturing groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:(?:a|b)(?:c|d))"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:(?:a|b)(?:c|d))"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("ac")); @@ -82,7 +82,7 @@ test "non-capturing: nested non-capturing groups" { test "non-capturing: capture numbering with non-capturing groups" { const allocator = std.testing.allocator; // (a) (?:b) (c) - should have captures $1=a, $2=c (not $1=a, $2=b, $3=c) - var regex = try Regex.compile(allocator, "(a)(?:b)(c)"); + var regex = try Regex.compile(allocator, std.testing.io, "(a)(?:b)(c)"); defer regex.deinit(); const result = try regex.find("abc"); @@ -100,7 +100,7 @@ test "non-capturing: capture numbering with non-capturing groups" { test "non-capturing: with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:\\d+)+"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("123")); @@ -110,7 +110,7 @@ test "non-capturing: with quantifiers" { test "non-capturing: complex pattern" { const allocator = std.testing.allocator; // Simplified pattern - removed [\w.] which has parsing issues - var regex = try Regex.compile(allocator, "(?:https?://)?([a-z]+)(?:/\\w+)*"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:https?://)?([a-z]+)(?:/\\w+)*"); defer regex.deinit(); const result1 = try regex.find("http://example/path/to/page"); @@ -138,7 +138,7 @@ test "non-capturing: complex pattern" { test "non-capturing: in replacement" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:Mr|Mrs|Ms) (\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:Mr|Mrs|Ms) (\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "Hello Mr Smith", "$1"); @@ -149,7 +149,7 @@ test "non-capturing: in replacement" { test "non-capturing: multiple non-capturing with one capturing" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:a)(?:b)(c)(?:d)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(?:b)(c)(?:d)"); defer regex.deinit(); const result = try regex.find("abcd"); @@ -166,7 +166,7 @@ test "non-capturing: multiple non-capturing with one capturing" { test "non-capturing: with character classes" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:[a-z]+)@(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:[a-z]+)@(\\w+)"); defer regex.deinit(); const result = try regex.find("user@example"); @@ -183,7 +183,7 @@ test "non-capturing: with character classes" { test "non-capturing: nested capturing inside non-capturing" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:(\\d+):(\\d+))"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:(\\d+):(\\d+))"); defer regex.deinit(); const result = try regex.find("12:30"); @@ -201,7 +201,7 @@ test "non-capturing: nested capturing inside non-capturing" { test "non-capturing: all groups non-capturing" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:a)(?:b)(?:c)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(?:b)(?:c)"); defer regex.deinit(); const result = try regex.find("abc"); @@ -218,7 +218,7 @@ test "non-capturing: all groups non-capturing" { test "non-capturing: with anchors" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(?:hello|hi) (\\w+)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(?:hello|hi) (\\w+)$"); defer regex.deinit(); const result = try regex.find("hello world"); @@ -237,7 +237,7 @@ test "non-capturing: with anchors" { test "non-capturing: replaceAll with non-capturing groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:the|a) (\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:the|a) (\\w+)"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "the cat and a dog", "$1"); @@ -248,7 +248,7 @@ test "non-capturing: replaceAll with non-capturing groups" { test "non-capturing: empty non-capturing group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a(?:)b"); + var regex = try Regex.compile(allocator, std.testing.io, "a(?:)b"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("ab")); @@ -258,7 +258,7 @@ test "non-capturing: verify capture count" { const allocator = std.testing.allocator; // Pattern with 3 non-capturing and 2 capturing groups - var regex = try Regex.compile(allocator, "(?:a)(b)(?:c)(d)(?:e)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(b)(?:c)(d)(?:e)"); defer regex.deinit(); // Should only have 2 captures, not 5 diff --git a/tests/parser_compiler_edge_cases.zig b/tests/parser_compiler_edge_cases.zig index e3d204b..f4490aa 100644 --- a/tests/parser_compiler_edge_cases.zig +++ b/tests/parser_compiler_edge_cases.zig @@ -10,13 +10,13 @@ const RegexError = @import("regex").RegexError; test "parser: quantifier ? at start is rejected" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "?abc"); + const result = Regex.compile(allocator, std.testing.io, "?abc"); try std.testing.expectError(RegexError.UnexpectedCharacter, result); } test "parser: nested empty groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(())"); + var regex = try Regex.compile(allocator, std.testing.io, "(())"); defer regex.deinit(); // Should compile without error try std.testing.expect(try regex.isMatch("")); @@ -25,7 +25,7 @@ test "parser: nested empty groups" { test "parser: deeply nested groups" { const allocator = std.testing.allocator; // 20 levels of nesting - should be within limits - var regex = try Regex.compile(allocator, "((((((((((((((((((((a))))))))))))))))))))"); + var regex = try Regex.compile(allocator, std.testing.io, "((((((((((((((((((((a))))))))))))))))))))"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); } @@ -33,20 +33,20 @@ test "parser: deeply nested groups" { test "parser: consecutive quantifiers rejected by analyzer" { const allocator = std.testing.allocator; // a** parses as (a*)* which is rejected as nested quantifiers - const result = Regex.compile(allocator, "a**"); + const result = Regex.compile(allocator, std.testing.io, "a**"); try std.testing.expectError(RegexError.PatternTooComplex, result); } test "parser: quantifier on quantifier rejected by analyzer" { const allocator = std.testing.allocator; // a+* parses as (a+)* which is rejected as nested quantifiers - const result = Regex.compile(allocator, "a+*"); + const result = Regex.compile(allocator, std.testing.io, "a+*"); try std.testing.expectError(RegexError.PatternTooComplex, result); } test "parser: empty alternation branch is valid" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a|"); + var regex = try Regex.compile(allocator, std.testing.io, "a|"); defer regex.deinit(); // "a|" means "a or empty string" - should match empty try std.testing.expect(try regex.isMatch("a")); @@ -55,7 +55,7 @@ test "parser: empty alternation branch is valid" { test "parser: leading pipe alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "|a"); + var regex = try Regex.compile(allocator, std.testing.io, "|a"); defer regex.deinit(); // "|a" means "empty string or a" try std.testing.expect(try regex.isMatch("")); @@ -66,7 +66,7 @@ test "parser: leading pipe alternation" { test "parser: {0} means zero occurrences" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{0}b$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{0}b$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("b")); @@ -75,7 +75,7 @@ test "parser: {0} means zero occurrences" { test "parser: {1} means exactly once" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{1}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{1}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -86,7 +86,7 @@ test "parser: {1} means exactly once" { test "parser: large but valid quantifier" { const allocator = std.testing.allocator; // {0,100} should be valid - var regex = try Regex.compile(allocator, "a{0,100}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{0,100}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); } @@ -95,7 +95,7 @@ test "parser: large but valid quantifier" { test "parser: character class with escaped ]" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[\\]]"); + var regex = try Regex.compile(allocator, std.testing.io, "[\\]]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("]")); @@ -105,7 +105,7 @@ test "parser: character class with escaped ]" { test "parser: character class with hyphen between ranges" { const allocator = std.testing.allocator; // Hyphen at start is literal, not range - var regex = try Regex.compile(allocator, "[-az]"); + var regex = try Regex.compile(allocator, std.testing.io, "[-az]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("-")); @@ -116,7 +116,7 @@ test "parser: character class with hyphen between ranges" { test "parser: negated class with range" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[^0-9]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[^0-9]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); @@ -128,7 +128,7 @@ test "parser: negated class with range" { test "parser: tab and newline escapes" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\t"); + var regex = try Regex.compile(allocator, std.testing.io, "\\t"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("\t")); @@ -137,7 +137,7 @@ test "parser: tab and newline escapes" { test "parser: carriage return escape" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\r\\n"); + var regex = try Regex.compile(allocator, std.testing.io, "\\r\\n"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("\r\n")); @@ -145,7 +145,7 @@ test "parser: carriage return escape" { test "parser: escaped caret and dollar" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\^\\$"); + var regex = try Regex.compile(allocator, std.testing.io, "\\^\\$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("^$")); @@ -156,7 +156,7 @@ test "parser: escaped caret and dollar" { test "compiler: alternation with quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(ab+|cd*)e$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(ab+|cd*)e$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abe")); @@ -169,7 +169,7 @@ test "compiler: alternation with quantifiers" { test "compiler: character class in alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^([0-9]+|[a-z]+)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^([0-9]+|[a-z]+)$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("123")); @@ -180,7 +180,7 @@ test "compiler: character class in alternation" { test "compiler: optional group" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^colou?r$"); + var regex = try Regex.compile(allocator, std.testing.io, "^colou?r$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("color")); @@ -190,7 +190,7 @@ test "compiler: optional group" { test "compiler: optional group with parentheses" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(un)?happy$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(un)?happy$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("happy")); @@ -200,7 +200,7 @@ test "compiler: optional group with parentheses" { test "compiler: multiple capture groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\d{4})-(\\d{2})-(\\d{2})"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\d{4})-(\\d{2})-(\\d{2})"); defer regex.deinit(); if (try regex.find("Today is 2024-01-15!")) |match| { @@ -220,7 +220,7 @@ test "compiler: multiple capture groups" { test "compiler: anchored pattern only tries position 0" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^abc"); + var regex = try Regex.compile(allocator, std.testing.io, "^abc"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abcdef")); @@ -230,7 +230,7 @@ test "compiler: anchored pattern only tries position 0" { test "compiler: end-anchored pattern" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "abc$"); + var regex = try Regex.compile(allocator, std.testing.io, "abc$"); defer regex.deinit(); if (try regex.find("xyzabc")) |match| { @@ -244,7 +244,7 @@ test "compiler: end-anchored pattern" { test "compiler: both anchors" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^exact$"); + var regex = try Regex.compile(allocator, std.testing.io, "^exact$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("exact")); @@ -257,13 +257,13 @@ test "compiler: both anchors" { test "compiler: password strength check" { const allocator = std.testing.allocator; - var has_digit = try Regex.compile(allocator, "\\d"); + var has_digit = try Regex.compile(allocator, std.testing.io, "\\d"); defer has_digit.deinit(); - var has_lower = try Regex.compile(allocator, "[a-z]"); + var has_lower = try Regex.compile(allocator, std.testing.io, "[a-z]"); defer has_lower.deinit(); - var has_upper = try Regex.compile(allocator, "[A-Z]"); + var has_upper = try Regex.compile(allocator, std.testing.io, "[A-Z]"); defer has_upper.deinit(); - var min_length = try Regex.compile(allocator, ".{8,}"); + var min_length = try Regex.compile(allocator, std.testing.io, ".{8,}"); defer min_length.deinit(); const password = "MyP4ssword"; @@ -280,7 +280,7 @@ test "compiler: password strength check" { test "compiler: CSV field extraction" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[^,]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[^,]+"); defer regex.deinit(); const matches = try regex.findAll(allocator, "foo,bar,baz"); @@ -300,7 +300,7 @@ test "compiler: CSV field extraction" { test "compiler: whitespace normalization via replaceAll" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); defer regex.deinit(); const result = try regex.replaceAll(allocator, " hello world ", " "); @@ -312,7 +312,7 @@ test "compiler: whitespace normalization via replaceAll" { test "compiler: pattern with only ^ anchor" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^"); + var regex = try Regex.compile(allocator, std.testing.io, "^"); defer regex.deinit(); // ^ matches at position 0 of any string (matches zero-width) @@ -322,7 +322,7 @@ test "compiler: pattern with only ^ anchor" { test "compiler: pattern with only $ anchor" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "$"); + var regex = try Regex.compile(allocator, std.testing.io, "$"); defer regex.deinit(); // $ matches at end of any string (zero-width) @@ -334,7 +334,7 @@ test "compiler: pattern with only $ anchor" { test "compiler: dot plus" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^.+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^.+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -344,7 +344,7 @@ test "compiler: dot plus" { test "compiler: dot question" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^.?$"); + var regex = try Regex.compile(allocator, std.testing.io, "^.?$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -354,7 +354,7 @@ test "compiler: dot question" { test "compiler: dot repeat" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^.{3}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^.{3}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); diff --git a/tests/posix_classes.zig b/tests/posix_classes.zig index 6a1137b..17ca3ea 100644 --- a/tests/posix_classes.zig +++ b/tests/posix_classes.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "posix: [:alpha:] matches letters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:alpha:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -17,7 +17,7 @@ test "posix: [:alpha:] matches letters" { test "posix: [:digit:] matches digits" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:digit:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:digit:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("123")); @@ -29,7 +29,7 @@ test "posix: [:digit:] matches digits" { test "posix: [:alnum:] matches alphanumeric" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:alnum:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:alnum:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello123")); @@ -41,7 +41,7 @@ test "posix: [:alnum:] matches alphanumeric" { test "posix: [:space:] matches whitespace" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:space:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch(" ")); @@ -53,7 +53,7 @@ test "posix: [:space:] matches whitespace" { test "posix: [:upper:] matches uppercase" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:upper:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:upper:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("HELLO")); @@ -65,7 +65,7 @@ test "posix: [:upper:] matches uppercase" { test "posix: [:lower:] matches lowercase" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:lower:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:lower:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -77,7 +77,7 @@ test "posix: [:lower:] matches lowercase" { test "posix: [:xdigit:] matches hex digits" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:xdigit:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:xdigit:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("0123456789")); @@ -90,7 +90,7 @@ test "posix: [:xdigit:] matches hex digits" { test "posix: [:punct:] matches punctuation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:punct:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:punct:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("!@#$%")); @@ -102,7 +102,7 @@ test "posix: [:punct:] matches punctuation" { test "posix: [:blank:] matches space and tab" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:blank:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:blank:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch(" ")); @@ -114,7 +114,7 @@ test "posix: [:blank:] matches space and tab" { test "posix: [:print:] matches printable characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:print:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:print:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("Hello World!")); @@ -124,7 +124,7 @@ test "posix: [:print:] matches printable characters" { test "posix: [:graph:] matches visible characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:graph:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:graph:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("Hello")); @@ -136,7 +136,7 @@ test "posix: [:graph:] matches visible characters" { test "posix: [:cntrl:] matches control characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:cntrl:]]"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:cntrl:]]"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("\x00")); @@ -149,7 +149,7 @@ test "posix: [:cntrl:] matches control characters" { test "posix: combined with regular characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:alpha:]0-9_]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]0-9_]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello123")); @@ -159,7 +159,7 @@ test "posix: combined with regular characters" { test "posix: multiple POSIX classes in one bracket" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:alpha:][:digit:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:][:digit:]]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc123")); @@ -169,7 +169,7 @@ test "posix: multiple POSIX classes in one bracket" { test "posix: extract words with [:alpha:]" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:alpha:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+"); defer regex.deinit(); const text = "Hello World 123"; @@ -189,7 +189,7 @@ test "posix: extract words with [:alpha:]" { test "posix: extract hex numbers with [:xdigit:]" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "0x[[:xdigit:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "0x[[:xdigit:]]+"); defer regex.deinit(); const text = "Colors: 0xFF5733 and 0xABCDEF"; @@ -209,7 +209,7 @@ test "posix: extract hex numbers with [:xdigit:]" { test "posix: validate identifier with [:alnum:]" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[[:alpha:]_][[:alnum:]_]*$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[[:alpha:]_][[:alnum:]_]*$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("valid_identifier")); @@ -221,7 +221,7 @@ test "posix: validate identifier with [:alnum:]" { test "posix: split on whitespace with [:space:]" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[[:space:]]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+"); defer regex.deinit(); const text = "one\ttwo\nthree four"; diff --git a/tests/quantifiers.zig b/tests/quantifiers.zig index fa5df65..6f30cc3 100644 --- a/tests/quantifiers.zig +++ b/tests/quantifiers.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; test "quantifier: exactly n times {3}" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{3}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{3}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("aa")); @@ -13,7 +13,7 @@ test "quantifier: exactly n times {3}" { test "quantifier: range {2,4}" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{2,4}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{2,4}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("a")); @@ -25,7 +25,7 @@ test "quantifier: range {2,4}" { test "quantifier: at least n {2,}" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{2,}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{2,}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("a")); @@ -36,7 +36,7 @@ test "quantifier: at least n {2,}" { test "quantifier: {0,1} equivalent to ?" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{0,1}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{0,1}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -46,7 +46,7 @@ test "quantifier: {0,1} equivalent to ?" { test "quantifier: {1,} equivalent to +" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{1,}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{1,}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("")); @@ -56,7 +56,7 @@ test "quantifier: {1,} equivalent to +" { test "quantifier: {0,} equivalent to *" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{0,}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{0,}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -66,7 +66,7 @@ test "quantifier: {0,} equivalent to *" { test "quantifier: complex pattern with {m,n}" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d{3}-\\d{4}"); defer regex.deinit(); if (try regex.find("Call 555-1234 now")) |match| { @@ -80,7 +80,7 @@ test "quantifier: complex pattern with {m,n}" { test "quantifier: multiple bounded quantifiers" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{2}b{3}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{2}b{3}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("ab")); @@ -91,7 +91,7 @@ test "quantifier: multiple bounded quantifiers" { test "quantifier: {m,n} with character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "[a-z]{3,5}"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-z]{3,5}"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("ab")); diff --git a/tests/regression.zig b/tests/regression.zig index e7f96e7..840a540 100644 --- a/tests/regression.zig +++ b/tests/regression.zig @@ -10,7 +10,7 @@ const RegexError = @import("regex").RegexError; test "regression: $0 replacement expands to full match" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); const result = try regex.replace(allocator, "abc 123 def", "[$0]"); @@ -20,7 +20,7 @@ test "regression: $0 replacement expands to full match" { test "regression: $0 replacement with captures" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)"); defer regex.deinit(); const result = try regex.replace(allocator, "email: user@host ok", "match=$0,user=$1,host=$2"); @@ -30,7 +30,7 @@ test "regression: $0 replacement with captures" { test "regression: $0 in replaceAll" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "a b c", "[$0]"); @@ -42,7 +42,7 @@ test "regression: $0 in replaceAll" { test "regression: case-insensitive backreference" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "(\\w+) \\1", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "(\\w+) \\1", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello HELLO")); @@ -52,7 +52,7 @@ test "regression: case-insensitive backreference" { test "regression: case-sensitive backreference rejects case mismatch" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+) \\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("hello HELLO")); @@ -63,19 +63,19 @@ test "regression: case-sensitive backreference rejects case mismatch" { test "regression: {10,5} is rejected as invalid" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "a{10,5}"); + const result = Regex.compile(allocator, std.testing.io, "a{10,5}"); try std.testing.expectError(RegexError.InvalidQuantifier, result); } test "regression: {5,3} is rejected" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "x{5,3}"); + const result = Regex.compile(allocator, std.testing.io, "x{5,3}"); try std.testing.expectError(RegexError.InvalidQuantifier, result); } test "regression: {3,3} is accepted (min == max)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a{3,3}"); + var regex = try Regex.compile(allocator, std.testing.io, "a{3,3}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("aaa")); @@ -86,7 +86,7 @@ test "regression: {3,3} is accepted (min == max)" { test "regression: {0,3} matches 0 to 3 occurrences" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{0,3}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{0,3}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -98,9 +98,9 @@ test "regression: {0,3} matches 0 to 3 occurrences" { test "regression: {0,1} is equivalent to ?" { const allocator = std.testing.allocator; - var regex1 = try Regex.compile(allocator, "^ab{0,1}c$"); + var regex1 = try Regex.compile(allocator, std.testing.io, "^ab{0,1}c$"); defer regex1.deinit(); - var regex2 = try Regex.compile(allocator, "^ab?c$"); + var regex2 = try Regex.compile(allocator, std.testing.io, "^ab?c$"); defer regex2.deinit(); const inputs = [_][]const u8{ "ac", "abc", "abbc", "" }; @@ -111,7 +111,7 @@ test "regression: {0,1} is equivalent to ?" { test "regression: {0,0} matches empty only" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{0,0}b$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{0,0}b$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("b")); @@ -123,7 +123,7 @@ test "regression: {0,0} matches empty only" { test "regression: prefix optimization finds match after false prefix start" { const allocator = std.testing.allocator; // Pattern with prefix "ab" - but first "ab" at position 0 doesn't complete the full match - var regex = try Regex.compile(allocator, "abc\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "abc\\d+"); defer regex.deinit(); // "ab" appears at position 0, but "abc" + digits is only at position 5 @@ -141,7 +141,7 @@ test "regression: prefix optimization finds match after false prefix start" { test "regression: backtrack step counter resets between positions" { const allocator = std.testing.allocator; // This pattern uses backtracking via backreference. - var regex = try Regex.compile(allocator, "(\\w+) \\1"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1"); defer regex.deinit(); // Verify basic backreference works @@ -161,7 +161,7 @@ test "regression: backtrack step counter resets between positions" { test "regression: deinit on regex that was never used" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "test"); + var regex = try Regex.compile(allocator, std.testing.io, "test"); regex.deinit(); // Should not crash - just testing that deinit works without any find/match calls } @@ -169,12 +169,12 @@ test "regression: deinit on regex that was never used" { test "regression: double pattern compile and deinit" { const allocator = std.testing.allocator; { - var regex = try Regex.compile(allocator, "abc"); + var regex = try Regex.compile(allocator, std.testing.io, "abc"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); } { - var regex = try Regex.compile(allocator, "def"); + var regex = try Regex.compile(allocator, std.testing.io, "def"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("def")); } diff --git a/tests/stress_edge_cases.zig b/tests/stress_edge_cases.zig index 3331ac2..a1b21ac 100644 --- a/tests/stress_edge_cases.zig +++ b/tests/stress_edge_cases.zig @@ -11,7 +11,7 @@ const RegexError = @import("regex").RegexError; test "stress: empty alternation branch" { const allocator = std.testing.allocator; // Pattern like (|a) should match empty string or "a" - var regex = try Regex.compile(allocator, "^(|a)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(|a)$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -21,7 +21,7 @@ test "stress: empty alternation branch" { test "stress: pattern that only matches empty string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^$"); + var regex = try Regex.compile(allocator, std.testing.io, "^$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -31,7 +31,7 @@ test "stress: pattern that only matches empty string" { test "stress: dot star matches everything" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ".*"); + var regex = try Regex.compile(allocator, std.testing.io, ".*"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -41,7 +41,7 @@ test "stress: dot star matches everything" { test "stress: single dot" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^.$"); + var regex = try Regex.compile(allocator, std.testing.io, "^.$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -55,7 +55,7 @@ test "stress: single dot" { test "stress: {1,1} is same as no quantifier" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{1,1}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{1,1}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -65,7 +65,7 @@ test "stress: {1,1} is same as no quantifier" { test "stress: {2,} matches 2 or more" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a{2,}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a{2,}$"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("")); @@ -77,7 +77,7 @@ test "stress: {2,} matches 2 or more" { test "stress: exact repeat {5}" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^x{5}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^x{5}$"); defer regex.deinit(); try std.testing.expect(!try regex.isMatch("xxxx")); @@ -87,7 +87,7 @@ test "stress: exact repeat {5}" { test "stress: mixed quantifiers in sequence" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^a+b*c?d{2}$"); + var regex = try Regex.compile(allocator, std.testing.io, "^a+b*c?d{2}$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("add")); @@ -101,7 +101,7 @@ test "stress: mixed quantifiers in sequence" { test "stress: character class with hyphen at start" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[-abc]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[-abc]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("-")); @@ -112,7 +112,7 @@ test "stress: character class with hyphen at start" { test "stress: character class with hyphen at end" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[abc-]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[abc-]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("-")); @@ -121,7 +121,7 @@ test "stress: character class with hyphen at end" { test "stress: negated character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[^abc]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[^abc]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("xyz")); @@ -132,7 +132,7 @@ test "stress: negated character class" { test "stress: character class with single char range" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[a-a]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[a-a]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -142,7 +142,7 @@ test "stress: character class with single char range" { test "stress: overlapping ranges in character class" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^[a-zA-Z]+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^[a-zA-Z]+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -154,20 +154,20 @@ test "stress: overlapping ranges in character class" { test "stress: shorthand classes \\d \\w \\s" { const allocator = std.testing.allocator; { - var regex = try Regex.compile(allocator, "^\\d+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("12345")); try std.testing.expect(!try regex.isMatch("abc")); try std.testing.expect(!try regex.isMatch("")); } { - var regex = try Regex.compile(allocator, "^\\w+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\w+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello_123")); try std.testing.expect(!try regex.isMatch("hello world")); } { - var regex = try Regex.compile(allocator, "^\\s+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\s+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch(" ")); try std.testing.expect(try regex.isMatch("\t\n")); @@ -178,19 +178,19 @@ test "stress: shorthand classes \\d \\w \\s" { test "stress: negated shorthand classes \\D \\W \\S" { const allocator = std.testing.allocator; { - var regex = try Regex.compile(allocator, "^\\D+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\D+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); try std.testing.expect(!try regex.isMatch("123")); } { - var regex = try Regex.compile(allocator, "^\\W+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\W+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch(" !@#")); try std.testing.expect(!try regex.isMatch("abc")); } { - var regex = try Regex.compile(allocator, "^\\S+$"); + var regex = try Regex.compile(allocator, std.testing.io, "^\\S+$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abc")); try std.testing.expect(!try regex.isMatch("a b")); @@ -201,7 +201,7 @@ test "stress: negated shorthand classes \\D \\W \\S" { test "stress: ^ only matches at start" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^abc"); + var regex = try Regex.compile(allocator, std.testing.io, "^abc"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abcdef")); @@ -210,7 +210,7 @@ test "stress: ^ only matches at start" { test "stress: $ only matches at end" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "abc$"); + var regex = try Regex.compile(allocator, std.testing.io, "abc$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("xyzabc")); @@ -219,7 +219,7 @@ test "stress: $ only matches at end" { test "stress: ^$ on empty string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^$"); + var regex = try Regex.compile(allocator, std.testing.io, "^$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("")); @@ -228,7 +228,7 @@ test "stress: ^$ on empty string" { test "stress: word boundary \\b" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\bword\\b"); + var regex = try Regex.compile(allocator, std.testing.io, "\\bword\\b"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a word here")); @@ -239,7 +239,7 @@ test "stress: word boundary \\b" { test "stress: non-word boundary \\B" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\Bword\\B"); + var regex = try Regex.compile(allocator, std.testing.io, "\\Bword\\B"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("swordwords")); @@ -251,7 +251,7 @@ test "stress: non-word boundary \\B" { test "stress: alternation with different lengths" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(a|bb|ccc)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(a|bb|ccc)$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a")); @@ -263,7 +263,7 @@ test "stress: alternation with different lengths" { test "stress: nested alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^(a(b|c)d|efg)$"); + var regex = try Regex.compile(allocator, std.testing.io, "^(a(b|c)d|efg)$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("abd")); @@ -275,7 +275,7 @@ test "stress: nested alternation" { test "stress: NFA finds longest match for alternation" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "cat|catfish"); + var regex = try Regex.compile(allocator, std.testing.io, "cat|catfish"); defer regex.deinit(); if (try regex.find("catfish")) |match| { @@ -292,7 +292,7 @@ test "stress: NFA finds longest match for alternation" { test "stress: capture group with alternation - only one branch matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a)|(b)"); + var regex = try Regex.compile(allocator, std.testing.io, "(a)|(b)"); defer regex.deinit(); if (try regex.find("b")) |match| { @@ -306,7 +306,7 @@ test "stress: capture group with alternation - only one branch matches" { test "stress: nested capture groups" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "((a)(b))"); + var regex = try Regex.compile(allocator, std.testing.io, "((a)(b))"); defer regex.deinit(); if (try regex.find("ab")) |match| { @@ -325,7 +325,7 @@ test "stress: nested capture groups" { test "stress: capture with quantifier" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(a)+"); + var regex = try Regex.compile(allocator, std.testing.io, "(a)+"); defer regex.deinit(); if (try regex.find("aaa")) |match| { @@ -341,7 +341,7 @@ test "stress: capture with quantifier" { test "stress: findAll with no matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "xyz"); + var regex = try Regex.compile(allocator, std.testing.io, "xyz"); defer regex.deinit(); const matches = try regex.findAll(allocator, "abc def ghi"); @@ -358,7 +358,7 @@ test "stress: findAll with no matches" { test "stress: findAll adjacent matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d"); defer regex.deinit(); const matches = try regex.findAll(allocator, "123"); @@ -378,7 +378,7 @@ test "stress: findAll adjacent matches" { test "stress: findAll non-overlapping" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "aa"); + var regex = try Regex.compile(allocator, std.testing.io, "aa"); defer regex.deinit(); const matches = try regex.findAll(allocator, "aaaa"); @@ -398,7 +398,7 @@ test "stress: findAll non-overlapping" { test "stress: replace with no match returns input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "xyz"); + var regex = try Regex.compile(allocator, std.testing.io, "xyz"); defer regex.deinit(); const result = try regex.replace(allocator, "hello world", "!"); @@ -408,7 +408,7 @@ test "stress: replace with no match returns input" { test "stress: replace with empty replacement" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); defer regex.deinit(); const result = try regex.replace(allocator, "hello world", ""); @@ -418,7 +418,7 @@ test "stress: replace with empty replacement" { test "stress: replaceAll removes all matches" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\d"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d"); defer regex.deinit(); const result = try regex.replaceAll(allocator, "a1b2c3", ""); @@ -428,7 +428,7 @@ test "stress: replaceAll removes all matches" { test "stress: replace with $$ escaped dollar" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "price"); + var regex = try Regex.compile(allocator, std.testing.io, "price"); defer regex.deinit(); const result = try regex.replace(allocator, "the price is", "$$5"); @@ -440,7 +440,7 @@ test "stress: replace with $$ escaped dollar" { test "stress: split with no match returns whole string" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const parts = try regex.split(allocator, "hello"); @@ -452,7 +452,7 @@ test "stress: split with no match returns whole string" { test "stress: split on every character" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, ","); + var regex = try Regex.compile(allocator, std.testing.io, ","); defer regex.deinit(); const parts = try regex.split(allocator, "a,b,c"); @@ -466,7 +466,7 @@ test "stress: split on every character" { test "stress: split with regex separator" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\s*,\\s*"); + var regex = try Regex.compile(allocator, std.testing.io, "\\s*,\\s*"); defer regex.deinit(); const parts = try regex.split(allocator, "a , b , c"); @@ -482,7 +482,7 @@ test "stress: split with regex separator" { test "stress: escaped special characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\(\\)\\[\\]\\{\\}"); + var regex = try Regex.compile(allocator, std.testing.io, "\\(\\)\\[\\]\\{\\}"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("()[]{}")); @@ -491,7 +491,7 @@ test "stress: escaped special characters" { test "stress: escaped dot matches literal dot" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a\\.b"); + var regex = try Regex.compile(allocator, std.testing.io, "a\\.b"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("a.b")); @@ -500,7 +500,7 @@ test "stress: escaped dot matches literal dot" { test "stress: escaped backslash" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\\\"); + var regex = try Regex.compile(allocator, std.testing.io, "\\\\"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("\\")); @@ -511,7 +511,7 @@ test "stress: escaped backslash" { test "stress: match at end of long input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "needle"); + var regex = try Regex.compile(allocator, std.testing.io, "needle"); defer regex.deinit(); // Build a long haystack with needle at the very end @@ -531,7 +531,7 @@ test "stress: match at end of long input" { test "stress: no match in long input" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "needle"); + var regex = try Regex.compile(allocator, std.testing.io, "needle"); defer regex.deinit(); var buf: [1024]u8 = undefined; @@ -544,7 +544,7 @@ test "stress: no match in long input" { test "stress: multiline ^ matches after each newline" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "^\\w+", .{ .multiline = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\w+", .{ .multiline = true }); defer regex.deinit(); const matches = try regex.findAll(allocator, "hello\nworld\nfoo"); @@ -567,7 +567,7 @@ test "stress: dot does not match newline by default - find first line" { // Without multiline, $ only matches at end of input // So .+ matches "hello" but $ doesn't match at position 5 // Use multiline to match lines, or just test .+ without $ - var regex = try Regex.compile(allocator, ".+"); + var regex = try Regex.compile(allocator, std.testing.io, ".+"); defer regex.deinit(); if (try regex.find("hello\nworld")) |match| { @@ -582,7 +582,7 @@ test "stress: dot does not match newline by default - find first line" { test "stress: dot_all makes dot match newline" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "^.+$", .{ .dot_all = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^.+$", .{ .dot_all = true }); defer regex.deinit(); if (try regex.find("hello\nworld")) |match| { @@ -598,7 +598,7 @@ test "stress: dot_all makes dot match newline" { test "stress: case insensitive literal" { const allocator = std.testing.allocator; - var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true }); + var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true }); defer regex.deinit(); try std.testing.expect(try regex.isMatch("HELLO")); @@ -610,38 +610,38 @@ test "stress: case insensitive literal" { test "stress: unmatched closing paren" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "abc)"); + const result = Regex.compile(allocator, std.testing.io, "abc)"); try std.testing.expectError(RegexError.UnmatchedParenthesis, result); } test "stress: unmatched opening paren" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "(abc"); + const result = Regex.compile(allocator, std.testing.io, "(abc"); try std.testing.expectError(RegexError.UnexpectedCharacter, result); } test "stress: unmatched bracket" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "[abc"); + const result = Regex.compile(allocator, std.testing.io, "[abc"); try std.testing.expectError(RegexError.UnexpectedCharacter, result); } test "stress: quantifier star at start" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "*abc"); + const result = Regex.compile(allocator, std.testing.io, "*abc"); try std.testing.expectError(RegexError.UnexpectedCharacter, result); } test "stress: quantifier plus at start" { const allocator = std.testing.allocator; - const result = Regex.compile(allocator, "+abc"); + const result = Regex.compile(allocator, std.testing.io, "+abc"); try std.testing.expectError(RegexError.UnexpectedCharacter, result); } test "stress: empty character class" { const allocator = std.testing.allocator; // [] followed by ] - parser sees empty class then stray ] - if (Regex.compile(allocator, "[]")) |*r| { + if (Regex.compile(allocator, std.testing.io, "[]")) |*r| { var mut_r = r.*; mut_r.deinit(); } else |_| { @@ -653,7 +653,7 @@ test "stress: empty character class" { test "stress: greedy star matches longest" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "a.*b"); + var regex = try Regex.compile(allocator, std.testing.io, "a.*b"); defer regex.deinit(); if (try regex.find("aXXbYYb")) |match| { @@ -669,7 +669,7 @@ test "stress: greedy star matches longest" { test "stress: complex pattern with multiple features" { const allocator = std.testing.allocator; // Email-like pattern with captures - var regex = try Regex.compile(allocator, "([a-zA-Z0-9.]+)@([a-zA-Z0-9]+)\\.([a-zA-Z]{2,})"); + var regex = try Regex.compile(allocator, std.testing.io, "([a-zA-Z0-9.]+)@([a-zA-Z0-9]+)\\.([a-zA-Z]{2,})"); defer regex.deinit(); if (try regex.find("contact: user.name@example.com today")) |match| { @@ -692,7 +692,7 @@ test "stress: compile many patterns sequentially" { var i: usize = 0; while (i < 100) : (i += 1) { - var regex = try Regex.compile(allocator, "test\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "test\\d+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("test123")); } diff --git a/tests/string_anchors.zig b/tests/string_anchors.zig index 53d38cb..cb1a885 100644 --- a/tests/string_anchors.zig +++ b/tests/string_anchors.zig @@ -3,7 +3,7 @@ const Regex = @import("regex").Regex; test "string anchor: \\A start of text" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\Aabc"); + var regex = try Regex.compile(allocator, std.testing.io, "\\Aabc"); defer regex.deinit(); // Should match at the start of the text @@ -17,7 +17,7 @@ test "string anchor: \\A start of text" { test "string anchor: \\z end of text" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "abc\\z"); + var regex = try Regex.compile(allocator, std.testing.io, "abc\\z"); defer regex.deinit(); // Should match at the end of the text @@ -30,7 +30,7 @@ test "string anchor: \\z end of text" { test "string anchor: \\Z end of text (before optional newline)" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "abc\\Z"); + var regex = try Regex.compile(allocator, std.testing.io, "abc\\Z"); defer regex.deinit(); // Should match at the end of the text @@ -45,10 +45,10 @@ test "string anchor: \\Z end of text (before optional newline)" { test "string anchors: \\A vs ^" { const allocator = std.testing.allocator; - var regex_A = try Regex.compile(allocator, "\\Atest"); + var regex_A = try Regex.compile(allocator, std.testing.io, "\\Atest"); defer regex_A.deinit(); - var regex_caret = try Regex.compile(allocator, "^test"); + var regex_caret = try Regex.compile(allocator, std.testing.io, "^test"); defer regex_caret.deinit(); const input1 = "test"; @@ -68,7 +68,7 @@ test "string anchors: \\A vs ^" { test "string anchors: combined \\A and \\z" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\Aexact\\z"); + var regex = try Regex.compile(allocator, std.testing.io, "\\Aexact\\z"); defer regex.deinit(); // Should match only exact strings diff --git a/tests/thread_safety.zig b/tests/thread_safety.zig index 2e6ff09..38080e1 100644 --- a/tests/thread_safety.zig +++ b/tests/thread_safety.zig @@ -28,7 +28,7 @@ test "concurrent matching with same regex" { const allocator = testing.allocator; // Compile a regex once - var regex = try Regex.compile(allocator, "\\d+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex.deinit(); // Test inputs @@ -73,7 +73,7 @@ test "concurrent matching with same regex" { test "concurrent find operations" { const allocator = testing.allocator; - var regex = try Regex.compile(allocator, "[0-9]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[0-9]+"); defer regex.deinit(); const Worker = struct { @@ -114,7 +114,7 @@ test "concurrent find operations" { test "SharedRegex reference counting" { const allocator = testing.allocator; - var shared = try SharedRegex.init(allocator, "test"); + var shared = try SharedRegex.init(allocator, testing.io, "test"); defer shared.deinit(); // Acquire multiple references @@ -137,7 +137,7 @@ test "SharedRegex reference counting" { test "SharedRegex concurrent access" { const allocator = testing.allocator; - var shared = try SharedRegex.init(allocator, "\\d+"); + var shared = try SharedRegex.init(allocator, testing.io, "\\d+"); defer shared.deinit(); const Worker = struct { @@ -172,7 +172,7 @@ test "SharedRegex concurrent access" { test "RegexCache basic operations" { const allocator = testing.allocator; - var cache = RegexCache.init(allocator); + var cache = RegexCache.init(allocator, testing.io); defer cache.deinit(); // Get and cache a pattern @@ -195,7 +195,7 @@ test "thread-safe allocator with concurrent matching" { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - var regex = try Regex.compile(allocator, "test"); + var regex = try Regex.compile(allocator, std.testing.io, "test"); defer regex.deinit(); const Worker = struct { @@ -227,13 +227,13 @@ test "concurrent matching different patterns" { const allocator = gpa.allocator(); // Compile multiple patterns - var regex1 = try Regex.compile(allocator, "\\d+"); + var regex1 = try Regex.compile(allocator, std.testing.io, "\\d+"); defer regex1.deinit(); - var regex2 = try Regex.compile(allocator, "[a-z]+"); + var regex2 = try Regex.compile(allocator, std.testing.io, "[a-z]+"); defer regex2.deinit(); - var regex3 = try Regex.compile(allocator, "test"); + var regex3 = try Regex.compile(allocator, std.testing.io, "test"); defer regex3.deinit(); const Worker = struct { @@ -276,7 +276,7 @@ test "stress test: many threads many matches" { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - var regex = try Regex.compile(allocator, "test"); + var regex = try Regex.compile(allocator, std.testing.io, "test"); defer regex.deinit(); const Worker = struct { diff --git a/tests/utf8_unicode.zig b/tests/utf8_unicode.zig index 4627285..54e3a62 100644 --- a/tests/utf8_unicode.zig +++ b/tests/utf8_unicode.zig @@ -5,7 +5,7 @@ const Regex = @import("regex").Regex; test "UTF-8: literal matching" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "café"); + var regex = try Regex.compile(allocator, std.testing.io, "café"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("café")); @@ -14,7 +14,7 @@ test "UTF-8: literal matching" { test "UTF-8: emoji matching" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "Hello 👋 World"); + var regex = try Regex.compile(allocator, std.testing.io, "Hello 👋 World"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("Hello 👋 World")); @@ -22,7 +22,7 @@ test "UTF-8: emoji matching" { test "UTF-8: Chinese characters" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "你好"); + var regex = try Regex.compile(allocator, std.testing.io, "你好"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("你好")); @@ -31,7 +31,7 @@ test "UTF-8: Chinese characters" { test "UTF-8: mixed ASCII and Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "test-тест-テスト"); + var regex = try Regex.compile(allocator, std.testing.io, "test-тест-テスト"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("test-тест-テスト")); @@ -39,7 +39,7 @@ test "UTF-8: mixed ASCII and Unicode" { test "UTF-8: dot matches multi-byte character" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "c.fé"); + var regex = try Regex.compile(allocator, std.testing.io, "c.fé"); defer regex.deinit(); // Currently . matches one byte, not one character @@ -49,7 +49,7 @@ test "UTF-8: dot matches multi-byte character" { test "UTF-8: alternation with Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "hello|你好|こんにちは"); + var regex = try Regex.compile(allocator, std.testing.io, "hello|你好|こんにちは"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -60,7 +60,7 @@ test "UTF-8: alternation with Unicode" { test "UTF-8: character class range with multi-byte" { const allocator = std.testing.allocator; // Character classes currently only work with single-byte ASCII - var regex = try Regex.compile(allocator, "[a-z]+"); + var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("hello")); @@ -79,7 +79,7 @@ test "UTF-8: character class range with multi-byte" { test "UTF-8: quantifiers with Unicode literals" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "あ+"); + var regex = try Regex.compile(allocator, std.testing.io, "あ+"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("あ")); @@ -88,7 +88,7 @@ test "UTF-8: quantifiers with Unicode literals" { test "UTF-8: capture groups with Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(你好)(世界)"); + var regex = try Regex.compile(allocator, std.testing.io, "(你好)(世界)"); defer regex.deinit(); const result = try regex.find("你好世界"); @@ -106,7 +106,7 @@ test "UTF-8: capture groups with Unicode" { test "UTF-8: replacement with Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)"); + var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)"); defer regex.deinit(); // ASCII works @@ -117,7 +117,7 @@ test "UTF-8: replacement with Unicode" { test "UTF-8: anchors with Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^你好$"); + var regex = try Regex.compile(allocator, std.testing.io, "^你好$"); defer regex.deinit(); try std.testing.expect(try regex.isMatch("你好")); @@ -127,7 +127,7 @@ test "UTF-8: anchors with Unicode" { test "UTF-8: non-capturing groups with Unicode" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "(?:안녕|hello) (world|세계)"); + var regex = try Regex.compile(allocator, std.testing.io, "(?:안녕|hello) (world|세계)"); defer regex.deinit(); const result1 = try regex.find("hello world"); @@ -156,7 +156,7 @@ test "UTF-8: non-capturing groups with Unicode" { // Document current limitations test "UTF-8: known limitation - dot is byte-based" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "^.$"); + var regex = try Regex.compile(allocator, std.testing.io, "^.$"); defer regex.deinit(); // Single ASCII character @@ -170,7 +170,7 @@ test "UTF-8: known limitation - dot is byte-based" { test "UTF-8: known limitation - \\w is ASCII-only" { const allocator = std.testing.allocator; - var regex = try Regex.compile(allocator, "\\w+"); + var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); defer regex.deinit(); // ASCII word characters work