ReviewOS

also looking at this

zig-utils/zig-regex

Port to latest zig-master

#2
Closed armagidon-exception wants to merge main into main
53 files +1149 -1017
.github/workflows/release.ymlmodified+1-1
Changes to .github/workflows/release.yml
@@ -3,7 +3,7 @@ name: Release
33on:
44 push:
55 tags:
6 - 'v*'
6 - "v*"
77
88permissions:
99 contents: write
README.mdmodified+22-21
Changes to README.md
@@ -21,29 +21,29 @@ zig-regex is a regular expression engine for Zig featuring Thompson NFA construc
2121
2222### Core Regex Features
2323
24| Feature | Syntax | Status |
25|---------|--------|--------|
26| **Literals** | `abc`, `123` | Stable |
27| **Quantifiers** | `*`, `+`, `?`, `{n}`, `{m,n}` | Stable |
28| **Alternation** | `a\|b\|c` | Stable |
29| **Character Classes** | `\d`, `\w`, `\s`, `\D`, `\W`, `\S` | Stable |
30| **Custom Classes** | `[abc]`, `[a-z]`, `[^0-9]` | Stable |
31| **Anchors** | `^`, `$`, `\b`, `\B` | Stable |
32| **Wildcards** | `.` | Stable |
33| **Capturing Groups** | `(...)` | Stable |
34| **Named Groups** | `(?P<name>...)`, `(?<name>...)` | Stable |
35| **Non-capturing** | `(?:...)` | Stable |
36| **Lookahead** | `(?=...)`, `(?!...)` | Stable |
37| **Lookbehind** | `(?<=...)`, `(?<!...)` | Stable |
38| **Backreferences** | `\1`, `\2` | Stable |
39| **Case-insensitive** | `compileWithFlags(..., .{.case_insensitive = true})` | Stable |
40| **Multiline** | `compileWithFlags(..., .{.multiline = true})` | Stable |
41| **Dot-all** | `compileWithFlags(..., .{.dot_matches_newline = true})` | Stable |
42| **Escaping** | `\\`, `\.`, `\n`, `\t`, `\r` | Stable |
24| Feature | Syntax | Status |
25| --------------------- | ------------------------------------------------------- | ------ |
26| **Literals** | `abc`, `123` | Stable |
27| **Quantifiers** | `*`, `+`, `?`, `{n}`, `{m,n}` | Stable |
28| **Alternation** | `a\|b\|c` | Stable |
29| **Character Classes** | `\d`, `\w`, `\s`, `\D`, `\W`, `\S` | Stable |
30| **Custom Classes** | `[abc]`, `[a-z]`, `[^0-9]` | Stable |
31| **Anchors** | `^`, `$`, `\b`, `\B` | Stable |
32| **Wildcards** | `.` | Stable |
33| **Capturing Groups** | `(...)` | Stable |
34| **Named Groups** | `(?P<name>...)`, `(?<name>...)` | Stable |
35| **Non-capturing** | `(?:...)` | Stable |
36| **Lookahead** | `(?=...)`, `(?!...)` | Stable |
37| **Lookbehind** | `(?<=...)`, `(?<!...)` | Stable |
38| **Backreferences** | `\1`, `\2` | Stable |
39| **Case-insensitive** | `compileWithFlags(..., .{.case_insensitive = true})` | Stable |
40| **Multiline** | `compileWithFlags(..., .{.multiline = true})` | Stable |
41| **Dot-all** | `compileWithFlags(..., .{.dot_matches_newline = true})` | Stable |
42| **Escaping** | `\\`, `\.`, `\n`, `\t`, `\r` | Stable |
4343
4444### Advanced Features
4545
46- **Hybrid Execution Engine**: Automatically selects between Thompson NFA (O(n*m)) and optimized backtracking
46- **Hybrid Execution Engine**: Automatically selects between Thompson NFA (O(n\*m)) and optimized backtracking
4747- **AST Optimization**: Constant folding, dead code elimination, quantifier simplification
4848- **NFA Optimization**: Epsilon transition removal, state merging, transition optimization
4949- **Pattern Macros**: Composable, reusable pattern definitions
@@ -55,7 +55,7 @@ zig-regex is a regular expression engine for Zig featuring Thompson NFA construc
5555### Quality
5656
5757- **Zero Dependencies**: Only Zig standard library
58- **Linear Time Matching**: Thompson NFA guarantees O(n*m) worst-case
58- **Linear Time Matching**: Thompson NFA guarantees O(n\*m) worst-case
5959- **Memory Safety**: Full control via Zig allocators, no hidden allocations, zero leaks
6060- **500+ Tests**: Comprehensive test suite covering core features, edge cases, regressions, and stress tests
6161
formatting only · 7 hidden lines
Changes to README.md
@@ -241,6 +241,7 @@ MIT License - see [LICENSE](LICENSE) file for details. formatting only
241241## Acknowledgments
242242
243243Inspired by:
244
244245- Ken Thompson's NFA construction algorithm
245246- RE2 (Google's regex engine)
246247- Rust's regex crate
TODO.mdmodified+46-2
formatting only · 7 hidden lines
Changes to TODO.md
@@ -11,6 +11,7 @@ A modern, performant regex library for Zig 0.16+ formatting only
1111**Phases Completed:** 10 out of 11 (core + testing + docs + advanced features + fuzzing complete)
1212
1313### What Works Now:
14
1415- ✅ Complete lexer and parser for regex syntax
1516- ✅ Thompson NFA construction
1617- ✅ Thread-based NFA simulation with greedy matching
formatting only · 7 hidden lines
Changes to TODO.md
@@ -28,6 +29,7 @@ A modern, performant regex library for Zig 0.16+ formatting only
2829- ✅ Thread-safety utilities and documentation
2930
3031### Example Usage:
32
3133```zig
3234var regex = try Regex.compile(allocator, "\\d+");
3335defer regex.deinit();
formatting only · 7 hidden lines
Changes to TODO.md
@@ -39,6 +41,7 @@ if (try regex.find("Price: $123")) |match| { formatting only
3941## Project Overview
4042
4143Building a production-ready regular expression library for Zig that provides:
44
4245- Fast pattern matching using Thompson NFA construction
4346- Zero external dependencies (stdlib only)
4447- Full memory control with Zig allocators
formatting only · 7 hidden lines
Changes to TODO.md
@@ -51,6 +54,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
5154## Phase 1: Project Foundation ✅ COMPLETED
5255
5356### 1.1 Project Setup ✅
57
5458- [x] Initialize Zig project structure with `zig init`
5559- [x] Create `build.zig` with library and test targets
5660- [x] Create `build.zig.zon` with project metadata
formatting only · 7 hidden lines
Changes to TODO.md
@@ -60,6 +64,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
6064- [x] Create `.gitignore` for Zig projects
6165
6266### 1.2 Core Module Structure ✅
67
6368- [x] Create `src/regex.zig` as the main public API (385 lines)
6469- [x] Create `src/parser.zig` for regex pattern parsing (395 lines)
6570- [x] Create `src/ast.zig` for Abstract Syntax Tree representation (267 lines)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -69,6 +74,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
6974- [x] Create `src/common.zig` for shared types and utilities (174 lines)
7075
7176### 1.3 Documentation Foundation ✅
77
7278- [x] Set up documentation comments structure
7379- [x] Create `docs/ARCHITECTURE.md` explaining design decisions - **COMPLETED**
7480- [x] Create `docs/API.md` for API reference - **COMPLETED**
formatting only · 7 hidden lines
Changes to TODO.md
@@ -80,6 +86,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
8086## Phase 2: Parser & AST ✅ COMPLETED
8187
8288### 2.1 Lexer Implementation ✅
89
8390- [x] Implement tokenizer for regex patterns
8491- [x] Support basic literals (a-z, A-Z, 0-9)
8592- [x] Support special characters (`.`, `^`, `$`, etc.)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -89,6 +96,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
8996- [x] Implement proper error reporting with line/column info
9097
9198### 2.2 Parser Implementation ✅
99
92100- [x] Implement recursive descent parser
93101- [x] Handle operator precedence correctly
94102- [x] Support concatenation (implicit)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -103,6 +111,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
103111- [x] Add comprehensive error messages
104112
105113### 2.3 AST Construction ✅
114
106115- [x] Define AST node types (Literal, Alternation, Concatenation, etc.)
107116- [x] Implement AST builder from parser
108117- [x] Add AST validation pass
formatting only · 7 hidden lines
Changes to TODO.md
@@ -114,6 +123,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
114123## Phase 3: NFA Engine ✅ COMPLETED
115124
116125### 3.1 Thompson Construction ✅
126
117127- [x] Implement basic NFA data structure
118128- [x] Implement state and transition representations
119129- [x] Support epsilon (ε) transitions
Changes to TODO.md
@@ -121,11 +131,12 @@ Building a production-ready regular expression library for Zig that provides:
121131- [x] Handle literal characters
122132- [x] Handle concatenation
123133- [x] Handle alternation
124- [x] Handle Kleene star (*)
134- [x] Handle Kleene star (\*)
125135- [x] Handle plus (+) and optional (?)
126136- [x] Handle bounded repetition {m,n}
127137
128138### 3.2 NFA Optimization
139
129140- [x] Implement epsilon-closure computation
130141- [ ] Remove redundant epsilon transitions (future enhancement)
131142- [ ] Merge equivalent states (future enhancement)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -133,6 +144,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
133144- [ ] Add NFA visualization/debug output (future enhancement)
134145
135146### 3.3 NFA Simulation ✅
147
136148- [x] Implement basic NFA simulation engine (thread-based matching)
137149- [x] Support backtracking for complex patterns (via thread-based approach)
138150- [x] Track capture groups during matching
formatting only · 7 hidden lines
Changes to TODO.md
@@ -146,6 +158,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
146158## Phase 4: Pattern Matching API ✅ COMPLETED
147159
148160### 4.1 Core Matching Functions ✅
161
149162- [x] Implement `Regex.compile()` - compile pattern
150163- [x] Implement `Regex.deinit()` - cleanup
151164- [x] Implement `find()` - find first match
formatting only · 7 hidden lines
Changes to TODO.md
@@ -154,6 +167,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
154167- [x] Return match positions (start, end indices)
155168
156169### 4.2 Capture Groups ⚠️ Partial
170
157171- [x] Implement numbered capture groups `()`
158172- [x] Track capture group positions
159173- [x] Return captured substrings
formatting only · 7 hidden lines
Changes to TODO.md
@@ -162,6 +176,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
162176- [ ] Access captures by name (future enhancement)
163177
164178### 4.3 Advanced Matching ✅
179
165180- [x] Implement `replace()` - replace matches
166181- [x] Implement `replaceAll()` - replace all matches
167182- [ ] Support backreferences in replacement (future enhancement)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -174,6 +189,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
174189## Phase 5: Extended Regex Features
175190
176191### 5.1 Character Classes ✅
192
177193- [x] Support `\d` (digits)
178194- [x] Support `\D` (non-digits)
179195- [x] Support `\w` (word characters)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -185,6 +201,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
185201- [ ] Support POSIX character classes `[:alpha:]`, `[:digit:]`, etc. (future enhancement)
186202
187203### 5.2 Advanced Anchors & Boundaries ✅
204
188205- [x] Line anchors (`^`, `$`)
189206- [x] Word boundaries (`\b`, `\B`)
190207- [x] String anchors (`\A`, `\z`, `\Z`) - **COMPLETED** (fully implemented and tested)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -192,6 +209,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
192209- [ ] Lookbehind assertions `(?<=)`, `(?<!)` (future enhancement)
193210
194211### 5.3 Flags & Options ✅
212
195213- [x] Case-insensitive flag (i) - **COMPLETED**
196214- [x] Compile-time flag specification via `compileWithFlags()` - **COMPLETED**
197215- [x] Multiline flag (m) - **COMPLETED** (^ and $ respect multiline mode)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -205,6 +223,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
205223## Phase 6: Testing & Quality
206224
207225### 6.1 Unit Tests ✅ COMPLETED
226
208227- [x] Test lexer with various input patterns - **COMPLETED** (6+ lexer tests)
209228- [x] Test parser with valid/invalid regex - **COMPLETED** (10+ parser tests)
210229- [x] Test AST construction and validation - **COMPLETED** (4+ AST tests)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -217,6 +236,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
217236- [x] Test edge cases (empty strings, large inputs) - **COMPLETED** (dedicated edge_cases test file)
218237
219238### 6.2 Integration Tests ✅ COMPLETED
239
220240- [x] Test real-world regex patterns - **COMPLETED**
221241- [x] Test email validation patterns - **COMPLETED**
222242- [x] Test URL matching patterns - **COMPLETED**
formatting only · 7 hidden lines
Changes to TODO.md
@@ -231,6 +251,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
231251- [ ] Test error handling paths (future enhancement)
232252
233253### 6.3 Fuzzing & Property Tests ✅
254
234255- [x] Set up fuzzing infrastructure - **COMPLETED**
235256- [x] Fuzz lexer with random inputs - **COMPLETED** (fuzz.zig)
236257- [x] Fuzz parser with malformed patterns - **COMPLETED** (bad patterns test)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -239,6 +260,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
239260- [x] Test memory safety (no leaks) - **COMPLETED** (zero leaks detected)
240261
241262### 6.4 Compliance Tests
263
242264- [ ] Create test suite from regex standards
243265- [ ] Compare against PCRE test suite (where applicable)
244266- [ ] Test compatibility with common regex flavors
formatting only · 7 hidden lines
Changes to TODO.md
@@ -249,6 +271,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
249271## Phase 7: Performance Optimization
250272
251273### 7.1 Algorithm Optimization
274
252275- [ ] Profile hot paths in matching
253276- [ ] Optimize state transition lookup
254277- [ ] Implement DFA construction for static patterns
formatting only · 7 hidden lines
Changes to TODO.md
@@ -258,6 +281,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
258281- [ ] Cache compiled patterns
259282
260283### 7.2 Memory Optimization
284
261285- [ ] Minimize allocations during matching
262286- [ ] Use arena allocators where appropriate
263287- [ ] Implement copy-on-write for captures
formatting only · 7 hidden lines
Changes to TODO.md
@@ -265,6 +289,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
265289- [ ] Optimize AST/NFA memory layout
266290
267291### 7.3 Benchmarking ⚠️ Partial
292
268293- [x] Create benchmark suite - **COMPLETED**
269294- [x] Benchmark common patterns (literal, quantifiers, character classes) - **COMPLETED**
270295- [x] Benchmark case-insensitive matching - **COMPLETED**
formatting only · 7 hidden lines
Changes to TODO.md
@@ -278,6 +303,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
278303## Phase 8: Documentation & Examples ✅ COMPLETED
279304
280305### 8.1 API Documentation ✅ COMPLETED
306
281307- [x] Document all public functions with examples - **COMPLETED** (docs/API.md - 800+ lines)
282308- [x] Add parameter descriptions - **COMPLETED**
283309- [x] Document error conditions - **COMPLETED**
formatting only · 7 hidden lines
Changes to TODO.md
@@ -286,6 +312,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
286312- [ ] Generate docs with `zig build docs` (requires Zig docs infrastructure)
287313
288314### 8.2 User Guide ✅ COMPLETED
315
289316- [x] Write getting started guide - **COMPLETED** (README.md + docs/API.md)
290317- [x] Document pattern syntax - **COMPLETED** (docs/API.md Pattern Syntax section)
291318- [x] Document best practices - **COMPLETED** (docs/EXAMPLES.md Best Practices section)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -295,6 +322,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
295322- [ ] Create FAQ (future enhancement)
296323
297324### 8.3 Examples ✅ COMPLETED
325
298326- [x] Create basic usage example - **COMPLETED** (README.md + docs/EXAMPLES.md)
299327- [x] Create capture groups example - **COMPLETED** (docs/EXAMPLES.md)
300328- [x] Create replace/substitution example - **COMPLETED** (docs/EXAMPLES.md)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -307,6 +335,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
307335## Phase 9: Advanced Features
308336
309337### 9.1 Unicode Support
338
310339- [ ] Support UTF-8 input (Zig's default)
311340- [ ] Handle multi-byte characters correctly
312341- [ ] Support Unicode character classes
formatting only · 7 hidden lines
Changes to TODO.md
@@ -315,6 +344,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
315344- [ ] Handle normalization (if needed)
316345
317346### 9.2 Performance Features
347
318348- [ ] Implement lazy DFA construction
319349- [ ] Add memoization for repeated patterns
320350- [ ] Support compiled pattern serialization
formatting only · 7 hidden lines
Changes to TODO.md
@@ -322,6 +352,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
322352- [ ] Add SIMD optimizations (if applicable)
323353
324354### 9.3 Developer Features
355
325356- [ ] Implement regex debugging mode
326357- [ ] Add visualization of NFA/DFA
327358- [ ] Create regex playground/tester
formatting only · 7 hidden lines
Changes to TODO.md
@@ -333,6 +364,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
333364## Phase 10: Production Readiness
334365
335366### 10.1 Error Handling ✅
367
336368- [x] Comprehensive error types - **COMPLETED** (40+ error types defined)
337369- [x] Detailed error messages - **COMPLETED** (ErrorContext with formatting)
338370- [x] Recovery strategies where possible - **COMPLETED** (hints and suggestions)
Changes to TODO.md
@@ -340,12 +372,14 @@ Building a production-ready regular expression library for Zig that provides:
340372- [x] Panic-free API design - **COMPLETED** (all errors returned as values)
341373
342374### 10.2 Thread Safety ✅
375
343376- [x] Document thread-safety guarantees - **COMPLETED** (documented in LIMITATIONS.md)
344377- [x] Make compiled patterns thread-safe - **COMPLETED** (read-only operations are safe)
345378- [x] Support concurrent matching - **COMPLETED** (VM creates thread-local state)
346379- [x] Thread safety utilities - **COMPLETED** (RegexCache implementation available)
347380
348381### 10.3 API Stability
382
349383- [ ] Finalize public API surface
350384- [ ] Mark internal APIs clearly
351385- [ ] Version the API
formatting only · 7 hidden lines
Changes to TODO.md
@@ -353,6 +387,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
353387- [ ] Write upgrade guides
354388
355389### 10.4 Release Preparation
390
356391- [ ] Set up CI/CD pipeline
357392- [ ] Add pre-commit hooks
358393- [ ] Create release checklist
formatting only · 7 hidden lines
Changes to TODO.md
@@ -365,6 +400,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
365400## Phase 11: Community & Maintenance
366401
367402### 11.1 Community Building
403
368404- [ ] Create CONTRIBUTING.md
369405- [ ] Set up issue templates
370406- [ ] Set up PR templates
formatting only · 7 hidden lines
Changes to TODO.md
@@ -373,6 +409,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
373409- [ ] Announce on Zig forums
374410
375411### 11.2 Ongoing Maintenance
412
376413- [ ] Monitor issues and PRs
377414- [ ] Keep up with Zig language updates
378415- [ ] Update dependencies
formatting only · 7 hidden lines
Changes to TODO.md
@@ -385,6 +422,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
385422## Future Considerations
386423
387424### Potential Features
425
388426- [ ] Regex macros/composition
389427- [ ] Pattern compilation to native code
390428- [ ] C FFI for use in other languages
formatting only · 7 hidden lines
Changes to TODO.md
@@ -393,6 +431,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
393431- [ ] Regex lint/analysis tools / extreme narrow typing for user patterns
394432
395433### Research Topics
434
396435- [ ] Investigate derivative-based regex matching
397436- [ ] Explore SIMD/vector optimization opportunities
398437- [ ] Consider hybrid NFA/DFA approaches
formatting only · 7 hidden lines
Changes to TODO.md
@@ -404,6 +443,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
404443## Success Metrics
405444
406445### Quality Metrics
446
407447- [x] 90%+ test coverage - **ACHIEVED** (114+ tests, 100% pass rate)
408448- [x] Zero known memory leaks - **ACHIEVED** (all tests pass leak detection)
409449- [x] Zero known security vulnerabilities - **ACHIEVED** (safe memory management)
Changes to TODO.md
@@ -411,12 +451,14 @@ Building a production-ready regular expression library for Zig that provides:
411451- [ ] Pass compliance test suite (future - requires PCRE test suite integration)
412452
413453### Performance Metrics
414- [x] Linear time complexity for NFA matching - **ACHIEVED** (Thompson NFA with O(n*m))
454
455- [x] Linear time complexity for NFA matching - **ACHIEVED** (Thompson NFA with O(n\*m))
415456- [x] Reasonable memory usage - **ACHIEVED** (efficient allocator usage)
416457- [x] Fast compilation times - **ACHIEVED** (simple patterns compile quickly)
417458- [ ] Competitive with existing Zig regex libraries (future - requires benchmarking)
418459
419460### Adoption Metrics
461
420462- [ ] Used in at least 3 external projects (future)
421463- [ ] Positive community feedback (future - pending release)
422464- [ ] Active contributors beyond maintainers (future)
formatting only · 7 hidden lines
Changes to TODO.md
@@ -443,6 +485,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
443485## Recent Updates (2025-10-27)
444486
445487### Completed Features:
488
4464891. ✅ **Quantifiers `{m,n}`** - Full runtime support with comprehensive tests
4474902. ✅ **String Anchors** - `\A`, `\z`, `\Z` fully implemented and tested
4484913. ✅ **Multiline Flag** - `^` and `$` respect multiline mode
formatting only · 7 hidden lines
Changes to TODO.md
@@ -450,6 +493,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only
4504935. ✅ **Bug Fixes** - Fixed memory leaks in thread safety tests, updated Zig 0.15.1 APIs
451494
452495### Test Suite:
496
453497- **155+ tests** passing with 100% success rate
454498- New test files: `string_anchors.zig`, `multiline_dotall.zig`
455499- All memory leaks resolved
benchmarks/basic.zigmodified+10-10
Changes to benchmarks/basic.zig
@@ -27,7 +27,7 @@ const Benchmark = struct {
2727 }
2828};
2929
30pub fn main() !void {
30pub fn main(init: std.process.Init) !void {
3131 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
3232 defer _ = gpa.deinit();
3333 const allocator = gpa.allocator();
@@ -36,7 +36,7 @@ pub fn main() !void {
3636
3737 // Benchmark 1: Simple literal matching
3838 {
39 var regex = try Regex.compile(allocator, "hello");
39 var regex = try Regex.compile(allocator, init.io, "hello");
4040 defer regex.deinit();
4141 const input = "hello world";
4242
@@ -50,7 +50,7 @@ pub fn main() !void {
5050
5151 // Benchmark 2: Quantifier matching
5252 {
53 var regex = try Regex.compile(allocator, "a+b*c?");
53 var regex = try Regex.compile(allocator, init.io, "a+b*c?");
5454 defer regex.deinit();
5555 const input = "aaabbbbc";
5656
@@ -64,7 +64,7 @@ pub fn main() !void {
6464
6565 // Benchmark 3: Alternation
6666 {
67 var regex = try Regex.compile(allocator, "cat|dog|bird");
67 var regex = try Regex.compile(allocator, init.io, "cat|dog|bird");
6868 defer regex.deinit();
6969 const input = "dog";
7070
@@ -78,7 +78,7 @@ pub fn main() !void {
7878
7979 // Benchmark 4: Character class
8080 {
81 var regex = try Regex.compile(allocator, "[a-z]+");
81 var regex = try Regex.compile(allocator, init.io, "[a-z]+");
8282 defer regex.deinit();
8383 const input = "abcdefghijklmnop";
8484
@@ -92,7 +92,7 @@ pub fn main() !void {
9292
9393 // Benchmark 5: Digit matching
9494 {
95 var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}");
95 var regex = try Regex.compile(allocator, init.io, "\\d{3}-\\d{4}");
9696 defer regex.deinit();
9797 const input = "555-1234";
9898
@@ -106,7 +106,7 @@ pub fn main() !void {
106106
107107 // Benchmark 6: Email-like pattern
108108 {
109 var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+");
109 var regex = try Regex.compile(allocator, init.io, "\\w+@\\w+\\.\\w+");
110110 defer regex.deinit();
111111 const input = "user@example.com";
112112
@@ -120,7 +120,7 @@ pub fn main() !void {
120120
121121 // Benchmark 7: Find operation
122122 {
123 var regex = try Regex.compile(allocator, "\\d+");
123 var regex = try Regex.compile(allocator, init.io, "\\d+");
124124 defer regex.deinit();
125125 const input = "The price is $123 and tax is $45";
126126
@@ -137,7 +137,7 @@ pub fn main() !void {
137137
138138 // Benchmark 8: Replace operation
139139 {
140 var regex = try Regex.compile(allocator, "\\d+");
140 var regex = try Regex.compile(allocator, init.io, "\\d+");
141141 defer regex.deinit();
142142 const input = "Call 555-1234 or 555-5678";
143143
@@ -166,7 +166,7 @@ pub fn main() !void {
166166
167167 // Benchmark 10: Complex pattern
168168 {
169 var regex = try Regex.compile(allocator, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
169 var regex = try Regex.compile(allocator, init.io, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
170170 defer regex.deinit();
171171 const input = "test.user+tag@example.co.uk";
172172
benchmarks/prefix_optimization.zigmodified+24-27
Changes to benchmarks/prefix_optimization.zig
@@ -1,7 +1,9 @@
11const std = ;
2const Clock = std.Io.Clock;
23const Regex = .Regex;
34
4pub fn main() !void {
5pub fn main(init: std.process.Init) !void {
6 const io = init.io;
57 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
68 defer _ = gpa.deinit();
79 const allocator = gpa.allocator();
@@ -14,7 +16,7 @@ pub fn main() !void {
1416 // Test 1: Pattern with literal prefix (should use optimization)
1517 {
1618 std.debug.print("Test 1: Literal prefix optimization (FOUND_IT!)...\n", .{});
17 var regex = try Regex.compile(allocator, "FOUND_IT!");
19 var regex = try Regex.compile(allocator, io, "FOUND_IT!");
1820 defer regex.deinit();
1921
2022 // Print optimization info
@@ -24,8 +26,7 @@ pub fn main() !void {
2426 std.debug.print(" ✗ No literal prefix found\n", .{});
2527 }
2628
27 var timer = try std.time.Timer.start();
28 const start = timer.read();
29 const start = Clock.awake.now(io);
2930
3031 const iterations: usize = 10000;
3132 var i: usize = 0;
@@ -36,8 +37,8 @@ pub fn main() !void {
3637 }
3738 }
3839
39 const elapsed = timer.read() - start;
40 const avg_ns = elapsed / iterations;
40 const elapsed = start.untilNow(io, .awake).toNanoseconds();
41 const avg_ns = @divExact(elapsed, iterations);
4142 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
4243 iterations,
4344 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -48,7 +49,7 @@ pub fn main() !void {
4849 // Test 2: Pattern without useful prefix (no optimization) - using fewer iterations
4950 {
5051 std.debug.print("Test 2: No prefix optimization (.*FOUND)...\n", .{});
51 var regex = try Regex.compile(allocator, ".*FOUND");
52 var regex = try Regex.compile(allocator, init.io, ".*FOUND");
5253 defer regex.deinit();
5354
5455 if (regex.opt_info.literal_prefix) |prefix| {
@@ -57,8 +58,7 @@ pub fn main() !void {
5758 std.debug.print(" ✗ No literal prefix found (slower performance expected)\n", .{});
5859 }
5960
60 var timer = try std.time.Timer.start();
61 const start = timer.read();
61 const start = Clock.awake.now(init.io);
6262
6363 // Use fewer iterations since this is much slower without optimization
6464 const iterations: usize = 100;
@@ -70,8 +70,8 @@ pub fn main() !void {
7070 }
7171 }
7272
73 const elapsed = timer.read() - start;
74 const avg_ns = elapsed / iterations;
73 const elapsed = start.untilNow(io, .awake).toNanoseconds();
74 const avg_ns = @divExact(elapsed, iterations);
7575 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
7676 iterations,
7777 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -83,7 +83,7 @@ pub fn main() !void {
8383 {
8484 std.debug.print("Test 3: Prefix with complex suffix (hello.*world)...\n", .{});
8585 const text_with_hello = "blah blah " ** 50 ++ "hello there world";
86 var regex = try Regex.compile(allocator, "hello.*world");
86 var regex = try Regex.compile(allocator, init.io, "hello.*world");
8787 defer regex.deinit();
8888
8989 if (regex.opt_info.literal_prefix) |prefix| {
@@ -92,8 +92,7 @@ pub fn main() !void {
9292 std.debug.print(" ✗ No literal prefix found\n", .{});
9393 }
9494
95 var timer = try std.time.Timer.start();
96 const start = timer.read();
95 const start = Clock.awake.now(init.io);
9796
9897 const iterations: usize = 10000;
9998 var i: usize = 0;
@@ -104,8 +103,8 @@ pub fn main() !void {
104103 }
105104 }
106105
107 const elapsed = timer.read() - start;
108 const avg_ns = elapsed / iterations;
106 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
107 const avg_ns = @divExact(elapsed, iterations);
109108 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
110109 iterations,
111110 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -116,7 +115,7 @@ pub fn main() !void {
116115 // Test 4: Anchored patterns
117116 {
118117 std.debug.print("Test 4: Start-anchored pattern (^hello)...\n", .{});
119 var regex = try Regex.compile(allocator, "^hello");
118 var regex = try Regex.compile(allocator, init.io, "^hello");
120119 defer regex.deinit();
121120
122121 if (regex.opt_info.anchored_start) {
@@ -127,8 +126,7 @@ pub fn main() !void {
127126 }
128127
129128 const text = "hello world";
130 var timer = try std.time.Timer.start();
131 const start = timer.read();
129 const start = Clock.awake.now(init.io);
132130
133131 const iterations: usize = 10000;
134132 var i: usize = 0;
@@ -136,8 +134,8 @@ pub fn main() !void {
136134 _ = try regex.isMatch(text);
137135 }
138136
139 const elapsed = timer.read() - start;
140 const avg_ns = elapsed / iterations;
137 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
138 const avg_ns = @divExact(elapsed, iterations);
141139 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
142140 iterations,
143141 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -149,15 +147,14 @@ pub fn main() !void {
149147 {
150148 std.debug.print("Test 5: Email pattern (user@example.com)...\n", .{});
151149 const email_text = "Contact us at " ++ ("some filler text here. " ** 20) ++ "user@example.com for support";
152 var regex = try Regex.compile(allocator, "user@example.com");
150 var regex = try Regex.compile(allocator, init.io, "user@example.com");
153151 defer regex.deinit();
154152
155153 if (regex.opt_info.literal_prefix) |prefix| {
156 std.debug.print(" ✓ Using literal prefix: \"{s}\" (min_len={d})\n", .{prefix, regex.opt_info.min_length});
154 std.debug.print(" ✓ Using literal prefix: \"{s}\" (min_len={d})\n", .{ prefix, regex.opt_info.min_length });
157155 }
158156
159 var timer = try std.time.Timer.start();
160 const start = timer.read();
157 const start = Clock.awake.now(init.io);
161158
162159 const iterations: usize = 10000;
163160 var i: usize = 0;
@@ -168,8 +165,8 @@ pub fn main() !void {
168165 }
169166 }
170167
171 const elapsed = timer.read() - start;
172 const avg_ns = elapsed / iterations;
168 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
169 const avg_ns = @divExact(elapsed , iterations);
173170 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
174171 iterations,
175172 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
benchmarks/simple.zigmodified+19-21
Changes to benchmarks/simple.zig
@@ -1,8 +1,10 @@
11const std = ;
2const Clock = std.Io.Clock;
23const Regex = .Regex;
34
4pub fn main() !void {
5pub fn main(init: std.process.Init) !void {
56 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
7 const io = init.io;
68 defer _ = gpa.deinit();
79 const allocator = gpa.allocator();
810
@@ -11,11 +13,10 @@ pub fn main() !void {
1113 // Test 1: Literal matching
1214 {
1315 std.debug.print("Test 1: Literal matching...\n", .{});
14 var regex = try Regex.compile(allocator, "hello");
16 var regex = try Regex.compile(allocator, io, "hello");
1517 defer regex.deinit();
1618
17 var timer = try std.time.Timer.start();
18 const start = timer.read();
19 const start = Clock.awake.now(io);
1920
2021 const iterations: usize = 10000;
2122 var i: usize = 0;
@@ -23,8 +24,8 @@ pub fn main() !void {
2324 _ = try regex.isMatch("hello world");
2425 }
2526
26 const elapsed = timer.read() - start;
27 const avg_ns = elapsed / iterations;
27 const elapsed = start.untilNow(io, .awake).toNanoseconds();
28 const avg_ns = @divExact(elapsed, iterations);
2829 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
2930 iterations,
3031 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -35,11 +36,10 @@ pub fn main() !void {
3536 // Test 2: Quantifiers
3637 {
3738 std.debug.print("Test 2: Quantifiers (a+)...\n", .{});
38 var regex = try Regex.compile(allocator, "a+");
39 var regex = try Regex.compile(allocator, init.io, "a+");
3940 defer regex.deinit();
4041
41 var timer = try std.time.Timer.start();
42 const start = timer.read();
42 const start = Clock.awake.now(init.io);
4343
4444 const iterations: usize = 10000;
4545 var i: usize = 0;
@@ -47,8 +47,8 @@ pub fn main() !void {
4747 _ = try regex.isMatch("aaaa");
4848 }
4949
50 const elapsed = timer.read() - start;
51 const avg_ns = elapsed / iterations;
50 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
51 const avg_ns = @divExact(elapsed, iterations);
5252 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
5353 iterations,
5454 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -59,11 +59,10 @@ pub fn main() !void {
5959 // Test 3: Character classes
6060 {
6161 std.debug.print("Test 3: Digit matching (\\d+)...\n", .{});
62 var regex = try Regex.compile(allocator, "\\d+");
62 var regex = try Regex.compile(allocator, init.io, "\\d+");
6363 defer regex.deinit();
6464
65 var timer = try std.time.Timer.start();
66 const start = timer.read();
65 const start = Clock.awake.now(init.io);
6766
6867 const iterations: usize = 10000;
6968 var i: usize = 0;
@@ -71,8 +70,8 @@ pub fn main() !void {
7170 _ = try regex.isMatch("12345");
7271 }
7372
74 const elapsed = timer.read() - start;
75 const avg_ns = elapsed / iterations;
73 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
74 const avg_ns = @divExact(elapsed, iterations);
7675 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
7776 iterations,
7877 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@@ -83,11 +82,10 @@ pub fn main() !void {
8382 // Test 4: Case-insensitive
8483 {
8584 std.debug.print("Test 4: Case-insensitive matching...\n", .{});
86 var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
85 var regex = try Regex.compileWithFlags(allocator, init.io, "hello", .{ .case_insensitive = true });
8786 defer regex.deinit();
8887
89 var timer = try std.time.Timer.start();
90 const start = timer.read();
88 const start = Clock.awake.now(init.io);
9189
9290 const iterations: usize = 10000;
9391 var i: usize = 0;
@@ -95,8 +93,8 @@ pub fn main() !void {
9593 _ = try regex.isMatch("HELLO");
9694 }
9795
98 const elapsed = timer.read() - start;
99 const avg_ns = elapsed / iterations;
96 const elapsed = start.untilNow(init.io, .awake).toNanoseconds();
97 const avg_ns = @divExact(elapsed, iterations);
10098 std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
10199 iterations,
102100 @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
build.zigmodified+1-0
Changes to build.zig
@@ -39,6 +39,7 @@ pub fn build(b: *std.Build) void {
3939 // Later on we'll use this module as the root module of a test executable
4040 // which requires us to specify a target.
4141 .target = target,
42 .link_libc = true,
4243 });
4344
4445 // Here we define an executable. An executable needs to have a root module
docs/ADVANCED_FEATURES.mdmodified+11-15
Changes to docs/ADVANCED_FEATURES.md
@@ -386,35 +386,31 @@ zig build wasm
386386```javascript
387387// Load WASM module
388388const wasmModule = await WebAssembly.instantiateStreaming(
389 fetch('zig-regex.wasm')
389 fetch("zig-regex.wasm"),
390390);
391391
392const {
393 zig_regex_compile,
394 zig_regex_is_match,
395 zig_regex_free,
396 memory
397} = wasmModule.instance.exports;
392const { zig_regex_compile, zig_regex_is_match, zig_regex_free, memory } =
393 wasmModule.instance.exports;
398394
399395// Helper to convert JS string to WASM memory
400396function stringToWasm(str) {
401 const bytes = new TextEncoder().encode(str + '\0');
402 const ptr = allocate(bytes.length);
403 new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
404 return ptr;
397 const bytes = new TextEncoder().encode(str + "\0");
398 const ptr = allocate(bytes.length);
399 new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
400 return ptr;
405401}
406402
407403// Compile regex
408const pattern = stringToWasm('\\d+');
404const pattern = stringToWasm("\\d+");
409405const flags = 0; // No flags
410406const errorPtr = allocate(4);
411407const regex = zig_regex_compile(pattern, flags, errorPtr);
412408
413409// Test match
414const input = stringToWasm('hello123');
410const input = stringToWasm("hello123");
415411const isMatch = zig_regex_is_match(regex, input, errorPtr);
416412
417console.log('Match:', isMatch);
413console.log("Match:", isMatch);
418414
419415// Clean up
420416zig_regex_free(regex);
@@ -476,7 +472,7 @@ Warnings (2):
476472
477473The library automatically chooses between two engines:
478474
479- **Thompson NFA**: O(n*m) performance for basic patterns
475- **Thompson NFA**: O(n\*m) performance for basic patterns
480476- **Backtracking Engine**: Supports advanced features (lazy quantifiers, lookahead, lookbehind, backreferences)
481477
482478```zig
docs/API.mdmodified+21-0
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -49,6 +49,7 @@ pub const Match = struct { formatting only
4949```
5050
5151**Methods:**
52
5253- `deinit(allocator: std.mem.Allocator)` - Free capture group memory
5354
5455---
Changes to docs/API.md
@@ -64,12 +65,14 @@ pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex
6465```
6566
6667**Parameters:**
68
6769- `allocator` - Memory allocator for the regex and its internal structures
6870- `pattern` - The regex pattern string
6971
7072**Returns:** `Regex` or error
7173
7274**Errors:**
75
7376- `RegexError.EmptyPattern` - Pattern string is empty
7477- `RegexError.InvalidPattern` - Syntax error in pattern
7578- `RegexError.UnexpectedCharacter` - Invalid character in pattern
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -80,6 +83,7 @@ pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex formatting only
8083- `RegexError.UnmatchedBracket` - Unbalanced brackets
8184
8285**Example:**
86
8387```zig
8488const allocator = std.heap.page_allocator;
8589var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}");
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -99,6 +103,7 @@ pub fn compileWithFlags( formatting only
99103```
100104
101105**Parameters:**
106
102107- `allocator` - Memory allocator
103108- `pattern` - The regex pattern string
104109- `flags` - Compilation flags (see [Flags](#flags))
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -106,6 +111,7 @@ pub fn compileWithFlags( formatting only
106111**Returns:** `Regex` or error
107112
108113**Example:**
114
109115```zig
110116var regex = try Regex.compileWithFlags(
111117 allocator,
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -124,6 +130,7 @@ pub fn deinit(self: *Regex) void formatting only
124130```
125131
126132**Example:**
133
127134```zig
128135var regex = try Regex.compile(allocator, "pattern");
129136defer regex.deinit(); // Always call deinit when done
Changes to docs/API.md
@@ -142,11 +149,13 @@ pub fn isMatch(self: *const Regex, input: []const u8) !bool
142149```
143150
144151**Parameters:**
152
145153- `input` - The string to search in
146154
147155**Returns:** `true` if match found, `false` otherwise
148156
149157**Example:**
158
150159```zig
151160var regex = try Regex.compile(allocator, "\\d+");
152161defer regex.deinit();
@@ -169,11 +178,13 @@ pub fn find(self: *const Regex, input: []const u8) !?Match
169178```
170179
171180**Parameters:**
181
172182- `input` - The string to search in
173183
174184**Returns:** `Match` if found, `null` otherwise
175185
176186**Example:**
187
177188```zig
178189var regex = try Regex.compile(allocator, "\\d+");
179190defer regex.deinit();
@@ -200,12 +211,14 @@ pub fn findAll(
200211```
201212
202213**Parameters:**
214
203215- `allocator` - Allocator for the results array
204216- `input` - The string to search in
205217
206218**Returns:** Array of `Match` objects (caller owns, must free)
207219
208220**Example:**
221
209222```zig
210223var regex = try Regex.compile(allocator, "\\d+");
211224defer regex.deinit();
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -247,6 +260,7 @@ pub fn replace( formatting only
247260```
248261
249262**Parameters:**
263
250264- `allocator` - Allocator for the result string
251265- `input` - The input string
252266- `replacement` - The replacement string
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -254,6 +268,7 @@ pub fn replace( formatting only
254268**Returns:** New string with first match replaced (caller owns, must free)
255269
256270**Example:**
271
257272```zig
258273var regex = try Regex.compile(allocator, "\\d+");
259274defer regex.deinit();
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -278,6 +293,7 @@ pub fn replaceAll( formatting only
278293```
279294
280295**Parameters:**
296
281297- `allocator` - Allocator for the result string
282298- `input` - The input string
283299- `replacement` - The replacement string
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -285,6 +301,7 @@ pub fn replaceAll( formatting only
285301**Returns:** New string with all matches replaced (caller owns, must free)
286302
287303**Example:**
304
288305```zig
289306var regex = try Regex.compile(allocator, "\\d+");
290307defer regex.deinit();
Changes to docs/API.md
@@ -312,12 +329,14 @@ pub fn split(
312329```
313330
314331**Parameters:**
332
315333- `allocator` - Allocator for the results array
316334- `input` - The string to split
317335
318336**Returns:** Array of string slices (caller owns array, must free)
319337
320338**Example:**
339
321340```zig
322341var regex = try Regex.compile(allocator, ",");
323342defer regex.deinit();
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -357,6 +376,7 @@ pub const CompileFlags = packed struct { formatting only
357376When `true`, the pattern matches both uppercase and lowercase letters.
358377
359378**Example:**
379
360380```zig
361381var regex = try Regex.compileWithFlags(
362382 allocator,
formatting only · 7 hidden lines
Changes to docs/API.md
@@ -397,6 +417,7 @@ pub const RegexError = error{ formatting only
397417```
398418
399419**Error Handling Example:**
420
400421```zig
401422const regex = Regex.compile(allocator, "[invalid") catch |err| {
402423 switch (err) {
docs/ARCHITECTURE.mdmodified+68-24
Changes to docs/ARCHITECTURE.md
@@ -2,7 +2,7 @@
22
33## Overview
44
5The zig-regex library implements a regular expression engine using **Thompson's NFA construction** algorithm combined with **thread-based simulation** for pattern matching. This architecture provides linear time complexity O(n*m) for matching, where n is the input length and m is the pattern size.
5The zig-regex library implements a regular expression engine using **Thompson's NFA construction** algorithm combined with **thread-based simulation** for pattern matching. This architecture provides linear time complexity O(n\*m) for matching, where n is the input length and m is the pattern size.
66
77## Design Philosophy
88
@@ -55,9 +55,11 @@ The zig-regex library implements a regular expression engine using **Thompson's
5555## Module Breakdown
5656
5757### 1. common.zig (174 lines)
58
5859**Purpose:** Shared types and utilities
5960
6061**Key Components:**
62
6163- `CharRange` - Represents a character range (e.g., 'a'-'z')
6264- `CharClass` - Set of character ranges with negation support
6365- `CharClasses` - Predefined classes (\d, \w, \s, etc.)
@@ -65,13 +67,16 @@ The zig-regex library implements a regular expression engine using **Thompson's
6567- `Span` - Source position for error reporting
6668
6769**Design Notes:**
70
6871- Character classes use ranges for memory efficiency
6972- Negation handled at match time, not during construction
7073
7174### 2. ast.zig (267 lines)
75
7276**Purpose:** Abstract Syntax Tree representation
7377
7478**Node Types:**
79
7580```zig
7681pub const NodeType = enum {
7782 literal, // Single character 'a'
@@ -90,20 +95,24 @@ pub const NodeType = enum {
9095```
9196
9297**Key Features:**
98
9399- Tagged union for type-safe node data
94100- Recursive structure for nested expressions
95101- Memory management via allocator
96102- Span tracking for error messages
97103
98104**Design Notes:**
105
99106- Each node owns its children (tree ownership)
100107- `destroy()` recursively frees the entire tree
101108- Factory methods ensure proper initialization
102109
103110### 3. parser.zig (395 lines)
111
104112**Purpose:** Lexical analysis and parsing
105113
106114**Architecture:**
115
107116```
108117Lexer (Tokenizer)
109118 │
@@ -122,21 +131,25 @@ Parser (Recursive Descent)
122131```
123132
124133**Operator Precedence (high to low):**
134
1251351. Literals, groups, character classes
1262. Quantifiers (*, +, ?, {m,n})
1362. Quantifiers (\*, +, ?, {m,n})
1271373. Concatenation (implicit)
1281384. Alternation (|)
129139
130140**Design Notes:**
141
131142- Single-pass parsing
132143- Error recovery not implemented (fails fast)
133144- Lookahead limited to one token
134145- Character classes parsed separately
135146
136147### 4. compiler.zig (455 lines)
148
137149**Purpose:** NFA construction via Thompson's algorithm
138150
139151**NFA Components:**
152
140153```zig
141154pub const State = struct {
142155 id: StateId,
@@ -155,23 +168,26 @@ pub const Transition = struct {
155168
156169**Thompson Construction Rules:**
157170
158| Pattern | NFA Fragment |
159|---------|--------------|
160| Literal 'a' | `s0 --a--> s1` |
161| Concatenation 'ab' | `s0 --a--> s1 --ε--> s2 --b--> s3` |
171| Pattern | NFA Fragment |
172| ------------------ | ---------------------------------------------------------------------------- |
173| Literal 'a' | `s0 --a--> s1` |
174| Concatenation 'ab' | `s0 --a--> s1 --ε--> s2 --b--> s3` |
162175| Alternation 'a\|b' | `s0 --ε--> (s1 --a--> s2)<br>&nbsp;&nbsp;&nbsp;&nbsp;└--ε--> (s3 --b--> s4)` |
163| Star 'a*' | `s0 --ε--> s1 --a--> s2 --ε--> s1<br>&nbsp;&nbsp;&nbsp;&nbsp;└--ε--> accept` |
176| Star 'a\*' | `s0 --ε--> s1 --a--> s2 --ε--> s1<br>&nbsp;&nbsp;&nbsp;&nbsp;└--ε--> accept` |
164177
165178**Design Notes:**
179
166180- Each AST node compiles to a Fragment (start, accept states)
167181- Epsilon transitions used for control flow
168182- Greedy matching achieved through simulation order
169183- Capture groups marked on states, not transitions
170184
171185### 5. vm.zig (340 lines)
186
172187**Purpose:** NFA simulation engine
173188
174189**Thread-Based Simulation:**
190
175191```
176192Thread = {
177193 state: StateId,
@@ -190,29 +206,35 @@ Algorithm:
190206```
191207
192208**Key Features:**
209
193210- **Greedy Matching:** Continues matching to find longest match
194211- **Epsilon Closure:** Precomputed before consuming input
195212- **Capture Tracking:** Each thread maintains capture positions
196213- **No Backtracking:** All paths explored simultaneously
197214
198215**Time Complexity:**
199- O(n * m) where n = input length, m = number of states
200- Worst case: O(n * 2^p) where p = number of alternations
216
217- O(n \* m) where n = input length, m = number of states
218- Worst case: O(n \* 2^p) where p = number of alternations
201219
202220**Space Complexity:**
203- O(m * c) where m = states, c = capture groups
221
222- O(m \* c) where m = states, c = capture groups
204223- Thread list grows with state complexity
205224
206225**Design Notes:**
226
207227- Threads represent possible execution paths
208228- Visited states tracked to avoid infinite loops
209229- Anchors checked during epsilon closure
210230- Match preference: longer match > earlier match
211231
212232### 6. regex.zig (385 lines)
233
213234**Purpose:** Public API and convenience functions
214235
215236**API Surface:**
237
216238```zig
217239// Compilation
218240Regex.compile(allocator, pattern) -> Regex
@@ -230,15 +252,18 @@ regex.split(allocator, input) -> [][]const u8
230252```
231253
232254**Design Notes:**
255
233256- Immutable compiled patterns (can reuse)
234257- All allocations explicit and user-controlled
235258- Match results contain slices (no copies)
236259- Iterator pattern not yet implemented
237260
238261### 7. errors.zig (67 lines)
262
239263**Purpose:** Error types and reporting
240264
241265**Error Types:**
266
242267```zig
243268pub const RegexError = error{
244269 // Parse errors
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -260,6 +285,7 @@ pub const RegexError = error{ formatting only
260285```
261286
262287**Error Context:**
288
263289- Position tracking in pattern
264290- Human-readable messages
265291- Pointer to error location
Changes to docs/ARCHITECTURE.md
@@ -269,12 +295,14 @@ pub const RegexError = error{
269295### Thompson's NFA Construction
270296
271297**Why Thompson?**
298
2722991. Simple to implement
2733002. Guaranteed linear time matching
2743013. No catastrophic backtracking
2753024. Naturally supports alternation
276303
277304**How it works:**
305
2783061. Each regex operator has a standard NFA fragment pattern
2793072. Fragments compose recursively
2803083. Epsilon transitions connect fragments
@@ -283,16 +311,19 @@ pub const RegexError = error{
283311### Thread-Based Simulation
284312
285313**Why Threads?**
314
286315- Simulates NFA without building DFA (space efficient)
287316- Handles large state spaces
288317- Natural support for captures
289318
290319**Greedy Matching:**
320
291321- Continue simulation even after finding a match
292322- Keep longest match found
293323- Ensures `a*` matches "aaa" not ""
294324
295325**Epsilon Closure:**
326
296327- Precompute all states reachable via ε-transitions
297328- Check anchors during closure
298329- Avoids redundant state exploration
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -300,6 +331,7 @@ pub const RegexError = error{ formatting only
300331## Memory Management
301332
302333**Ownership Model:**
334
303335```
304336Regex
305337 └─ owns NFA
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -315,6 +347,7 @@ Matches formatting only
315347```
316348
317349**Allocation Points:**
350
3183511. Pattern string duplication
3193522. AST node creation (freed after compilation)
3203533. NFA states and transitions
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -322,6 +355,7 @@ Matches formatting only
3223555. Match results and captures
323356
324357**Guidelines:**
358
325359- User provides allocator for all operations
326360- No hidden global allocations
327361- Clear ownership semantics
Changes to docs/ARCHITECTURE.md
@@ -331,27 +365,28 @@ Matches
331365
332366### Time Complexity
333367
334| Operation | Complexity | Notes |
335|-----------|------------|-------|
336| Compile | O(p) | p = pattern length |
337| isMatch | O(n * m) | n = input, m = states |
338| find | O(n * m * k) | k = positions to try |
339| findAll | O(n * m) | Amortized |
340| replace | O(n + r) | r = replacement length |
341| split | O(n * m) | Plus allocation |
368| Operation | Complexity | Notes |
369| --------- | ------------ | ---------------------- |
370| Compile | O(p) | p = pattern length |
371| isMatch | O(n \* m) | n = input, m = states |
372| find | O(n _ m _ k) | k = positions to try |
373| findAll | O(n \* m) | Amortized |
374| replace | O(n + r) | r = replacement length |
375| split | O(n \* m) | Plus allocation |
342376
343377### Space Complexity
344378
345| Component | Complexity | Notes |
346|-----------|------------|-------|
347| AST | O(p) | Temporary |
348| NFA | O(p) | Persistent |
349| VM Threads | O(m * c) | c = captures |
350| Matches | O(r) | r = result count |
379| Component | Complexity | Notes |
380| ---------- | ---------- | ---------------- |
381| AST | O(p) | Temporary |
382| NFA | O(p) | Persistent |
383| VM Threads | O(m \* c) | c = captures |
384| Matches | O(r) | r = result count |
351385
352386### Optimization Opportunities
353387
354388**Not Yet Implemented:**
389
3553901. DFA construction for static patterns
3563912. State minimization
3573923. Literal prefix extraction
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -362,6 +397,7 @@ Matches formatting only
362397## Testing Strategy
363398
364399**Test Categories:**
400
3654011. **Unit Tests** - Each module tested independently
3664022. **Integration Tests** - End-to-end pattern matching
3674033. **Edge Cases** - Empty strings, boundary conditions
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -369,6 +405,7 @@ Matches formatting only
3694055. **Property Tests** - Invariants (future)
370406
371407**Current Coverage:**
408
372409- 35+ tests across all modules
373410- All basic features tested
374411- Edge cases covered
Changes to docs/ARCHITECTURE.md
@@ -377,18 +414,21 @@ Matches
377414## Future Enhancements
378415
379416### Phase 6: Quality & Testing
417
380418- Comprehensive fuzzing
381419- Property-based tests
382420- Performance regression tests
383421- Memory leak detection
384422
385423### Phase 7: Performance
424
386425- DFA construction option
387426- Literal prefix optimization
388427- State minimization
389428- Benchmark suite
390429
391430### Phase 8: Extended Features
431
392432- Named capture groups
393433- Backreferences
394434- Lookahead/lookbehind
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -396,6 +436,7 @@ Matches formatting only
396436- Unicode properties
397437
398438### Phase 9: API Improvements
439
399440- Iterator interface
400441- Streaming matches
401442- Partial matching
Changes to docs/ARCHITECTURE.md
@@ -404,10 +445,12 @@ Matches
404445## References
405446
406447**Algorithms:**
448
407449- Thompson, Ken (1968). "Regular Expression Search Algorithm"
408450- Cox, Russ (2007). "Regular Expression Matching Can Be Simple And Fast"
409451
410452**Implementations:**
453
411454- RE2 (Google) - Inspiration for architecture
412455- Rust regex crate - API design reference
413456- PCRE - Feature completeness reference
formatting only · 7 hidden lines
Changes to docs/ARCHITECTURE.md
@@ -415,6 +458,7 @@ Matches formatting only
415458## Contributing
416459
417460When modifying the architecture:
461
4184621. Maintain phase separation
4194632. Keep allocations explicit
4204643. Preserve linear time guarantees
docs/BENCHMARKS.mdmodified+26-21
Changes to docs/BENCHMARKS.md
@@ -32,12 +32,12 @@ All benchmarks run with 10,000 iterations unless otherwise noted.
3232
3333### Core Operations
3434
35| Pattern | Input | Avg Time | Ops/sec | Description |
36|---------|-------|----------|---------|-------------|
37| `hello` | `hello world` | ~6.87 µs | ~145,000 | Simple literal match |
38| `a+` | `aaaa` | ~5.50 µs | ~182,000 | Quantifier (one or more) |
39| `\d+` | `12345` | ~5.01 µs | ~200,000 | Digit character class |
40| `hello` (case-insensitive) | `HELLO` | ~4.62 µs | ~216,000 | Case-insensitive match |
35| Pattern | Input | Avg Time | Ops/sec | Description |
36| -------------------------- | ------------- | -------- | -------- | ------------------------ |
37| `hello` | `hello world` | ~6.87 µs | ~145,000 | Simple literal match |
38| `a+` | `aaaa` | ~5.50 µs | ~182,000 | Quantifier (one or more) |
39| `\d+` | `12345` | ~5.01 µs | ~200,000 | Digit character class |
40| `hello` (case-insensitive) | `HELLO` | ~4.62 µs | ~216,000 | Case-insensitive match |
4141
4242### Detailed Results
4343
@@ -65,24 +65,24 @@ Test 4: Case-insensitive matching...
6565
6666### Time Complexity
6767
68| Operation | Complexity | Notes |
69|-----------|------------|-------|
70| Compile | O(p) | p = pattern length |
71| isMatch | O(n × m) | n = input length, m = NFA states |
72| find | O(n × m × k) | k = starting positions attempted |
73| findAll | O(n × m) | Amortized over all matches |
74| replace | O(n + r) | r = replacement length |
75| replaceAll | O(n × m + r) | Includes matching cost |
76| split | O(n × m) | Plus array allocation |
68| Operation | Complexity | Notes |
69| ---------- | ------------ | -------------------------------- |
70| Compile | O(p) | p = pattern length |
71| isMatch | O(n × m) | n = input length, m = NFA states |
72| find | O(n × m × k) | k = starting positions attempted |
73| findAll | O(n × m) | Amortized over all matches |
74| replace | O(n + r) | r = replacement length |
75| replaceAll | O(n × m + r) | Includes matching cost |
76| split | O(n × m) | Plus array allocation |
7777
7878### Space Complexity
7979
80| Component | Complexity | Notes |
81|-----------|------------|-------|
82| Compiled Regex (NFA) | O(p) | Persistent storage |
83| VM Execution | O(m × c) | c = capture group count |
84| Match Results | O(r) | r = number of results |
85| Temporary (AST) | O(p) | Freed after compilation |
80| Component | Complexity | Notes |
81| -------------------- | ---------- | ----------------------- |
82| Compiled Regex (NFA) | O(p) | Persistent storage |
83| VM Execution | O(m × c) | c = capture group count |
84| Match Results | O(r) | r = number of results |
85| Temporary (AST) | O(p) | Freed after compilation |
8686
8787### Greedy Matching Performance
8888
@@ -255,11 +255,13 @@ Pattern: "\\d{3}-\\d{4}"
255255### vs. Backtracking Engines (PCRE, Python re)
256256
257257**Advantages:**
258
258259- ✅ **No catastrophic backtracking** - Always linear time
259260- ✅ **Predictable performance** - O(n×m) guaranteed
260261- ✅ **Memory safe** - No stack overflow on deep recursion
261262
262263**Trade-offs:**
264
263265- ❌ **Slower on simple patterns** - More overhead for simple cases
264266- ❌ **No backreferences** - Not currently supported
265267- ❌ **No look-around** - Not currently supported
@@ -267,10 +269,12 @@ Pattern: "\\d{3}-\\d{4}"
267269### vs. DFA Engines (RE2, ripgrep)
268270
269271**Similarities:**
272
270273- ✅ **Linear time** - Both guarantee O(n×m)
271274- ✅ **No backtracking** - Deterministic matching
272275
273276**Trade-offs:**
277
274278- ❌ **NFA simulation** - We don't build full DFA (saves memory)
275279- ❌ **Capture groups** - Our approach is simpler for captures
276280- ✅ **Memory usage** - Lower than full DFA construction
formatting only · 7 hidden lines
Changes to docs/BENCHMARKS.md
@@ -410,6 +414,7 @@ CSV splitting (100,000 rows): ~300ms total formatting only
410414```
411415
412416These are estimates. Actual performance depends on:
417
413418- Pattern complexity
414419- Input characteristics
415420- Hardware specifications
docs/WASM.mdmodified+70-72
Changes to docs/WASM.md
@@ -29,14 +29,11 @@ This will produce `regex.wasm` which can be loaded in browsers or Node.js.
2929
3030```javascript
3131// Load the WASM module
32const wasmModule = await WebAssembly.instantiateStreaming(
33 fetch('regex.wasm'),
34 {
35 env: {
36 // Provide any required imports
37 }
38 }
39);
32const wasmModule = await WebAssembly.instantiateStreaming(fetch("regex.wasm"), {
33 env: {
34 // Provide any required imports
35 },
36});
4037
4138// Use the exported functions
4239const exports = wasmModule.instance.exports;
@@ -48,7 +45,7 @@ const regex = exports.zig_regex_compile(pattern);
4845// Check for match
4946const input = "test 123";
5047const matches = exports.zig_regex_is_match(regex, input);
51console.log('Matches:', matches === 1);
48console.log("Matches:", matches === 1);
5249
5350// Clean up
5451exports.zig_regex_free(regex);
@@ -57,28 +54,29 @@ exports.zig_regex_free(regex);
5754### Node.js Usage
5855
5956```javascript
60const fs = require('fs');
61const path = require('path');
57const fs = require("fs");
58const path = require("path");
6259
6360// Load WASM file
64const wasmPath = path.join(__dirname, 'regex.wasm');
61const wasmPath = path.join(__dirname, "regex.wasm");
6562const wasmBuffer = fs.readFileSync(wasmPath);
6663
6764// Instantiate
6865WebAssembly.instantiate(wasmBuffer, {
69 env: {
70 // Provide imports if needed
71 }
72}).then(result => {
73 const { zig_regex_compile, zig_regex_is_match, zig_regex_free } = result.instance.exports;
66 env: {
67 // Provide imports if needed
68 },
69}).then((result) => {
70 const { zig_regex_compile, zig_regex_is_match, zig_regex_free } =
71 result.instance.exports;
7472
75 // Use the library
76 const regex = zig_regex_compile("hello.*world");
77 const matches = zig_regex_is_match(regex, "hello beautiful world");
73 // Use the library
74 const regex = zig_regex_compile("hello.*world");
75 const matches = zig_regex_is_match(regex, "hello beautiful world");
7876
79 console.log('Pattern matches:', matches === 1);
77 console.log("Pattern matches:", matches === 1);
8078
81 zig_regex_free(regex);
79 zig_regex_free(regex);
8280});
8381```
8482
@@ -89,43 +87,43 @@ Create a TypeScript wrapper for type safety:
8987```typescript
9088// regex.d.ts
9189export interface WasmRegex {
92 compile(pattern: string): number | null;
93 isMatch(regex: number, input: string): boolean;
94 find(regex: number, input: string): Match | null;
95 free(regex: number): void;
90 compile(pattern: string): number | null;
91 isMatch(regex: number, input: string): boolean;
92 find(regex: number, input: string): Match | null;
93 free(regex: number): void;
9694}
9795
9896export interface Match {
99 text: string;
100 start: number;
101 end: number;
97 text: string;
98 start: number;
99 end: number;
102100}
103101
104102// regex.ts
105103export class Regex {
106 private wasm: WasmRegex;
107 private handle: number;
108
109 constructor(wasm: WasmRegex, pattern: string) {
110 this.wasm = wasm;
111 const handle = wasm.compile(pattern);
112 if (handle === null) {
113 throw new Error('Failed to compile regex pattern');
114 }
115 this.handle = handle;
104 private wasm: WasmRegex;
105 private handle: number;
106
107 constructor(wasm: WasmRegex, pattern: string) {
108 this.wasm = wasm;
109 const handle = wasm.compile(pattern);
110 if (handle === null) {
111 throw new Error("Failed to compile regex pattern");
116112 }
113 this.handle = handle;
114 }
117115
118 isMatch(input: string): boolean {
119 return this.wasm.isMatch(this.handle, input);
120 }
116 isMatch(input: string): boolean {
117 return this.wasm.isMatch(this.handle, input);
118 }
121119
122 find(input: string): Match | null {
123 return this.wasm.find(this.handle, input);
124 }
120 find(input: string): Match | null {
121 return this.wasm.find(this.handle, input);
122 }
125123
126 free(): void {
127 this.wasm.free(this.handle);
128 }
124 free(): void {
125 this.wasm.free(this.handle);
126 }
129127}
130128```
131129
@@ -141,36 +139,36 @@ The WASM build uses the C allocator, which means:
141139
142140```javascript
143141class RegexManager {
144 constructor(wasmExports) {
145 this.exports = wasmExports;
146 this.regexes = new Map();
147 }
148
149 compile(pattern) {
150 const handle = this.exports.zig_regex_compile(pattern);
151 if (handle) {
152 this.regexes.set(handle, pattern);
153 }
154 return handle;
142 constructor(wasmExports) {
143 this.exports = wasmExports;
144 this.regexes = new Map();
145 }
146
147 compile(pattern) {
148 const handle = this.exports.zig_regex_compile(pattern);
149 if (handle) {
150 this.regexes.set(handle, pattern);
155151 }
152 return handle;
153 }
156154
157 isMatch(handle, input) {
158 return this.exports.zig_regex_is_match(handle, input) === 1;
159 }
155 isMatch(handle, input) {
156 return this.exports.zig_regex_is_match(handle, input) === 1;
157 }
160158
161 free(handle) {
162 if (this.regexes.has(handle)) {
163 this.exports.zig_regex_free(handle);
164 this.regexes.delete(handle);
165 }
159 free(handle) {
160 if (this.regexes.has(handle)) {
161 this.exports.zig_regex_free(handle);
162 this.regexes.delete(handle);
166163 }
164 }
167165
168 freeAll() {
169 for (const handle of this.regexes.keys()) {
170 this.exports.zig_regex_free(handle);
171 }
172 this.regexes.clear();
166 freeAll() {
167 for (const handle of this.regexes.keys()) {
168 this.exports.zig_regex_free(handle);
173169 }
170 this.regexes.clear();
171 }
174172}
175173```
176174
examples/advanced_features.zigmodified+6-6
Changes to examples/advanced_features.zig
@@ -6,7 +6,7 @@ const Composer = .Composer;
66const Lint = .Lint;
77const ComplexityAnalyzer = .ComplexityAnalyzer;
88
9pub fn main() !void {
9pub fn main(init: std.process.Init) !void {
1010 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
1111 defer _ = gpa.deinit();
1212 const allocator = gpa.allocator();
@@ -39,7 +39,7 @@ pub fn main() !void {
3939
4040 std.debug.print("Built pattern: {s}\n", .{pattern});
4141
42 var regex = try Regex.compile(allocator, pattern);
42 var regex = try Regex.compile(allocator, init.io, pattern);
4343 defer regex.deinit();
4444
4545 const test_emails = [_][]const u8{
@@ -78,7 +78,7 @@ pub fn main() !void {
7878 std.debug.print("Phone pattern: {s}\n\n", .{phone_pattern});
7979
8080 // Test phone number pattern
81 var phone_regex = try Regex.compile(allocator, phone_pattern);
81 var phone_regex = try Regex.compile(allocator, init.io, phone_pattern);
8282 defer phone_regex.deinit();
8383
8484 const test_phones = [_][]const u8{
@@ -116,7 +116,7 @@ pub fn main() !void {
116116
117117 std.debug.print("Composed pattern (OR): {s}\n", .{alternatives});
118118
119 var regex = try Regex.compile(allocator, alternatives);
119 var regex = try Regex.compile(allocator, init.io, alternatives);
120120 defer regex.deinit();
121121
122122 const test_strings = [_][]const u8{
@@ -232,7 +232,7 @@ pub fn main() !void {
232232 std.debug.print("Complexity: {s} (score: {d})\n\n", .{ @tagName(complexity.getLevel()), complexity.complexity_score });
233233
234234 // Test the pattern
235 var regex = try Regex.compile(allocator, url_pattern_built);
235 var regex = try Regex.compile(allocator, init.io, url_pattern_built);
236236 defer regex.deinit();
237237
238238 const test_urls = [_][]const u8{
@@ -305,7 +305,7 @@ pub fn main() !void {
305305
306306 std.debug.print("Built pattern: {s}\n\n", .{ipv4_pattern});
307307
308 var regex = try Regex.compile(allocator, ipv4_pattern);
308 var regex = try Regex.compile(allocator, init.io, ipv4_pattern);
309309 defer regex.deinit();
310310
311311 const test_ips = [_][]const u8{
examples/basic.zigmodified+6-6
Changes to examples/basic.zig
@@ -1,7 +1,7 @@
11const std = ;
22const Regex = .Regex;
33
4pub fn main() !void {
4pub fn main(init: std.process.Init) !void {
55 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
66 defer _ = gpa.deinit();
77 const allocator = gpa.allocator();
@@ -11,7 +11,7 @@ pub fn main() !void {
1111 // Example 1: Simple literal matching
1212 {
1313 std.debug.print("Example 1: Literal matching\n", .{});
14 var regex = try Regex.compile(allocator, "hello");
14 var regex = try Regex.compile(allocator, init.io, "hello");
1515 defer regex.deinit();
1616
1717 const matches = try regex.isMatch("hello");
@@ -22,7 +22,7 @@ pub fn main() !void {
2222 // Example 2: Alternation
2323 {
2424 std.debug.print("Example 2: Alternation (cat|dog)\n", .{});
25 var regex = try Regex.compile(allocator, "cat|dog");
25 var regex = try Regex.compile(allocator, init.io, "cat|dog");
2626 defer regex.deinit();
2727
2828 std.debug.print(" 'cat' matches: {}\n", .{try regex.isMatch("cat")});
@@ -34,7 +34,7 @@ pub fn main() !void {
3434 // Example 3: Star quantifier
3535 {
3636 std.debug.print("Example 3: Star quantifier (a*)\n", .{});
37 var regex = try Regex.compile(allocator, "a*");
37 var regex = try Regex.compile(allocator, init.io, "a*");
3838 defer regex.deinit();
3939
4040 std.debug.print(" '' matches: {}\n", .{try regex.isMatch("")});
@@ -46,7 +46,7 @@ pub fn main() !void {
4646 // Example 4: Finding matches
4747 {
4848 std.debug.print("Example 4: Finding matches\n", .{});
49 var regex = try Regex.compile(allocator, "world");
49 var regex = try Regex.compile(allocator,init.io, "world");
5050 defer regex.deinit();
5151
5252 if (try regex.find("hello world")) |match_result| {
@@ -60,7 +60,7 @@ pub fn main() !void {
6060 // Example 5: Replace
6161 {
6262 std.debug.print("Example 5: Replace\n", .{});
63 var regex = try Regex.compile(allocator, "world");
63 var regex = try Regex.compile(allocator, init.io, "world");
6464 defer regex.deinit();
6565
6666 const result = try regex.replace(allocator, "hello world", "Zig");
examples/debug_visualization.zigmodified+3-3
Changes to examples/debug_visualization.zig
@@ -4,7 +4,7 @@ const debug = .debug;
44const Parser = .parser.Parser;
55const Optimizer = .optimizer.Optimizer;
66
7pub fn main() !void {
7pub fn main(init: std.process.Init) !void {
88 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
99 defer _ = gpa.deinit();
1010 const allocator = gpa.allocator();
@@ -25,7 +25,7 @@ pub fn main() !void {
2525 std.debug.print("───────────────────────────────────────────────\n", .{});
2626
2727 // Example 1: Show optimization info
28 var regex = Regex.compile(allocator, pattern) catch |err| {
28 var regex = Regex.compile(allocator,init.io, pattern) catch |err| {
2929 std.debug.print(" ❌ Compilation error: {}\n\n", .{err});
3030 continue;
3131 };
@@ -88,7 +88,7 @@ pub fn main() !void {
8888 std.debug.print("───────────────────────────────────────────────\n", .{});
8989
9090 const test_pattern = "hello.*world";
91 var test_regex = try Regex.compile(allocator, test_pattern);
91 var test_regex = try Regex.compile(allocator,init.io, test_pattern);
9292 defer test_regex.deinit();
9393
9494 const test_inputs = [_][]const u8{
examples/error_handling.zigmodified+11-11
Changes to examples/error_handling.zig
@@ -4,7 +4,7 @@ const ErrorContext = .ErrorContext;
44const ErrorHelper = .ErrorHelper;
55const RegexError = .RegexError;
66
7pub fn main() !void {
7pub fn main(init: std.process.Init) !void {
88 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
99 defer _ = gpa.deinit();
1010 const allocator = gpa.allocator();
formatting only · 16 hidden lines
Changes to examples/error_handling.zig
@@ -13,11 +13,11 @@ pub fn main() !void { formatting only
1313
1414 // Example 1: Invalid patterns with helpful error messages
1515 const invalid_patterns = [_][]const u8{
16 "abc(def", // Unmatched parenthesis
17 "abc[def", // Unmatched bracket
18 "abc+(", // Invalid quantifier usage
19 "abc\\q", // Invalid escape sequence
20 "[z-a]", // Invalid character range
16 "abc(def", // Unmatched parenthesis
17 "abc[def", // Unmatched bracket
18 "abc+(", // Invalid quantifier usage
19 "abc\\q", // Invalid escape sequence
20 "[z-a]", // Invalid character range
2121 "(a(b(c(d(e(f(g(h(i(j(k(l(m(n(o(p(q", // Too many nested groups
2222 };
2323
Changes to examples/error_handling.zig
@@ -26,7 +26,7 @@ pub fn main() !void {
2626 std.debug.print("Testing pattern: \"{s}\"\n", .{pattern});
2727 std.debug.print("───────────────────────────────────────────────\n", .{});
2828
29 if (Regex.compile(allocator, pattern)) |regex_value| {
29 if (Regex.compile(allocator, init.io, pattern)) |regex_value| {
3030 var regex = regex_value;
3131 defer regex.deinit();
3232 std.debug.print("✓ Pattern compiled successfully (unexpected)\n\n", .{});
@@ -53,7 +53,7 @@ pub fn main() !void {
5353 const user_pattern = "hello|world";
5454 std.debug.print("User provided pattern: \"{s}\"\n", .{user_pattern});
5555
56 if (Regex.compile(allocator, user_pattern)) |regex_value| {
56 if (Regex.compile(allocator, init.io, user_pattern)) |regex_value| {
5757 var regex = regex_value;
5858 defer regex.deinit();
5959 std.debug.print("✓ Pattern compiled successfully\n", .{});
@@ -84,7 +84,7 @@ pub fn main() !void {
8484 const problematic_pattern = "abc[def";
8585 std.debug.print("Problematic pattern: \"{s}\"\n", .{problematic_pattern});
8686
87 if (Regex.compile(allocator, problematic_pattern)) |regex_value| {
87 if (Regex.compile(allocator, init.io, problematic_pattern)) |regex_value| {
8888 var regex = regex_value;
8989 regex.deinit();
9090 } else |err| {
@@ -95,7 +95,7 @@ pub fn main() !void {
9595 const fixed_pattern = "abc[def]";
9696 std.debug.print("Fixed pattern: \"{s}\"\n", .{fixed_pattern});
9797
98 if (Regex.compile(allocator, fixed_pattern)) |regex_value| {
98 if (Regex.compile(allocator, init.io, fixed_pattern)) |regex_value| {
9999 var regex = regex_value;
100100 defer regex.deinit();
101101 std.debug.print("✓ Fixed pattern compiles successfully!\n", .{});
@@ -119,7 +119,7 @@ pub fn main() !void {
119119
120120 for (patterns_to_validate) |pattern| {
121121 std.debug.print("Validating: \"{s}\" ... ", .{pattern});
122 if (Regex.compile(allocator, pattern)) |regex_value| {
122 if (Regex.compile(allocator, init.io, pattern)) |regex_value| {
123123 var regex = regex_value;
124124 regex.deinit();
125125 std.debug.print("✓ Valid\n", .{});
examples/profiling_example.zigmodified+20-20
Changes to examples/profiling_example.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33const Profiler = .Profiler;
44const ScopedTimer = .ScopedTimer;
55
6pub fn main() !void {
6pub fn main(init: std.process.Init) !void {
77 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
88 defer _ = gpa.deinit();
99 const allocator = gpa.allocator();
@@ -16,11 +16,11 @@ pub fn main() !void {
1616 std.debug.print("Example 1: Basic profiling\n", .{});
1717 std.debug.print("───────────────────────────────────────────────\n", .{});
1818
19 var profiler = Profiler.init(allocator, true);
19 var profiler = Profiler.init(allocator, init.io, true);
2020
2121 // Profile compilation
2222 profiler.startCompilation();
23 var regex = try Regex.compile(allocator, "hello.*world");
23 var regex = try Regex.compile(allocator, init.io, "hello.*world");
2424 profiler.endCompilation();
2525 defer regex.deinit();
2626
@@ -48,13 +48,13 @@ pub fn main() !void {
4848 std.debug.print("Example 2: Scoped timers\n", .{});
4949 std.debug.print("───────────────────────────────────────────────\n", .{});
5050
51 var profiler = Profiler.init(allocator, true);
51 var profiler = Profiler.init(allocator, init.io, true);
5252
5353 {
5454 var timer = ScopedTimer.init(&profiler, .compilation);
5555 defer timer.deinit();
5656
57 var regex = try Regex.compile(allocator, "[a-z]+@[a-z]+\\.[a-z]+");
57 var regex = try Regex.compile(allocator, init.io, "[a-z]+@[a-z]+\\.[a-z]+");
5858 regex.deinit();
5959 }
6060
@@ -69,19 +69,19 @@ pub fn main() !void {
6969 std.debug.print("───────────────────────────────────────────────\n", .{});
7070
7171 const patterns = [_][]const u8{
72 "hello", // Literal - should have prefix optimization
73 ".*hello", // No prefix - slower
74 "hello.*world", // Prefix optimization
72 "hello", // Literal - should have prefix optimization
73 ".*hello", // No prefix - slower
74 "hello.*world", // Prefix optimization
7575 };
7676
7777 const test_text = ("some random text " ** 10) ++ "hello world";
7878
7979 for (patterns) |pattern| {
80 var profiler = Profiler.init(allocator, true);
80 var profiler = Profiler.init(allocator, init.io, true);
8181
8282 std.debug.print("\nPattern: \"{s}\"\n", .{pattern});
8383
84 var regex = try Regex.compile(allocator, pattern);
84 var regex = try Regex.compile(allocator, init.io, pattern);
8585 defer regex.deinit();
8686
8787 // Run multiple matches
@@ -94,8 +94,8 @@ pub fn main() !void {
9494 }
9595
9696 const metrics = profiler.getMetrics();
97 const avg_ns = metrics.match_time_ns / iterations;
98 std.debug.print("Average match time: {d}μs ({d} iterations)\n", .{ avg_ns / 1000, iterations });
97 const avg_ns = @divExact(metrics.match_time_ns, iterations);
98 std.debug.print("Average match time: {d}μs ({d} iterations)\n", .{ @divExact(avg_ns, 1000), iterations });
9999 }
100100 }
101101
@@ -105,12 +105,12 @@ pub fn main() !void {
105105 std.debug.print("Example 4: Memory tracking\n", .{});
106106 std.debug.print("───────────────────────────────────────────────\n", .{});
107107
108 var profiler = Profiler.init(allocator, true);
108 var profiler = Profiler.init(allocator, init.io, true);
109109
110110 const complex_pattern = "(a|b)+c(d|e)*f";
111111 std.debug.print("Pattern: \"{s}\"\n", .{complex_pattern});
112112
113 var regex = try Regex.compile(allocator, complex_pattern);
113 var regex = try Regex.compile(allocator, init.io, complex_pattern);
114114 defer regex.deinit();
115115
116116 // Simulate memory tracking (in real implementation, this would be integrated)
@@ -131,9 +131,9 @@ pub fn main() !void {
131131 const test_pattern = "hello";
132132
133133 // Baseline
134 var baseline_profiler = Profiler.init(allocator, true);
134 var baseline_profiler = Profiler.init(allocator, init.io, true);
135135 {
136 var regex = try Regex.compile(allocator, baseline_pattern);
136 var regex = try Regex.compile(allocator, init.io, baseline_pattern);
137137 defer regex.deinit();
138138
139139 var i: usize = 0;
@@ -145,9 +145,9 @@ pub fn main() !void {
145145 }
146146
147147 // Test
148 var test_profiler = Profiler.init(allocator, true);
148 var test_profiler = Profiler.init(allocator, init.io, true);
149149 {
150 var regex = try Regex.compile(allocator, test_pattern);
150 var regex = try Regex.compile(allocator, init.io, test_pattern);
151151 defer regex.deinit();
152152
153153 var i: usize = 0;
@@ -162,8 +162,8 @@ pub fn main() !void {
162162 const test_time = test_profiler.getMetrics().match_time_ns;
163163 const diff_percent = @as(f64, @floatFromInt(test_time)) / @as(f64, @floatFromInt(baseline_time)) * 100.0 - 100.0;
164164
165 std.debug.print("\nBaseline time: {d}μs\n", .{baseline_time / 1000});
166 std.debug.print("Test time: {d}μs\n", .{test_time / 1000});
165 std.debug.print("\nBaseline time: {d}μs\n", .{@divExact(baseline_time, 1000)});
166 std.debug.print("Test time: {d}μs\n", .{@divExact(test_time, 1000)});
167167 std.debug.print("Difference: {d:.1}%\n", .{diff_percent});
168168
169169 if (diff_percent > 10.0) {
examples/thread_safety.zigmodified+18-18
Changes to examples/thread_safety.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33const SharedRegex = .SharedRegex;
44const RegexCache = .RegexCache;
55
6pub fn main() !void {
6pub fn main(init: std.process.Init) !void {
77 var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
88 defer _ = gpa.deinit();
99 const allocator = gpa.allocator();
@@ -17,7 +17,7 @@ pub fn main() !void {
1717 std.debug.print("───────────────────────────────────────────────\n", .{});
1818
1919 // Compile once
20 var regex = try Regex.compile(allocator, "\\d+");
20 var regex = try Regex.compile(allocator, init.io, "\\d+");
2121 defer regex.deinit();
2222
2323 const Worker = struct {
@@ -63,7 +63,7 @@ pub fn main() !void {
6363 std.debug.print("Example 2: SharedRegex with reference counting\n", .{});
6464 std.debug.print("───────────────────────────────────────────────\n", .{});
6565
66 var shared = try SharedRegex.init(allocator, "test");
66 var shared = try SharedRegex.init(allocator, init.io, "test");
6767 defer shared.deinit();
6868
6969 const Worker = struct {
@@ -103,9 +103,9 @@ pub fn main() !void {
103103 std.debug.print("───────────────────────────────────────────────\n", .{});
104104
105105 const Worker = struct {
106 fn run(alloc: std.mem.Allocator, thread_id: usize) void {
106 fn run(alloc: std.mem.Allocator, io: std.Io, thread_id: usize) void {
107107 // Each thread gets its own cache
108 var cache = RegexCache.init(alloc);
108 var cache = RegexCache.init(alloc, io);
109109 defer cache.deinit();
110110
111111 std.debug.print("Thread {d}: Created local cache\n", .{thread_id});
@@ -128,7 +128,7 @@ pub fn main() !void {
128128 var threads: [thread_count]std.Thread = undefined;
129129
130130 for (&threads, 0..) |*thread, i| {
131 thread.* = try std.Thread.spawn(.{}, Worker.run, .{ allocator, i });
131 thread.* = try std.Thread.spawn(.{}, Worker.run, .{ allocator, init.io, i });
132132 }
133133
134134 for (threads) |thread| {
@@ -144,12 +144,12 @@ pub fn main() !void {
144144 std.debug.print("Example 4: Performance with concurrent matching\n", .{});
145145 std.debug.print("───────────────────────────────────────────────\n", .{});
146146
147 var regex = try Regex.compile(allocator, "test");
147 var regex = try Regex.compile(allocator, init.io, "test");
148148 defer regex.deinit();
149149
150150 const Worker = struct {
151 fn run(r: *const Regex) usize {
152 var timer = std.time.Timer.start() catch return 0;
151 fn run(r: *const Regex) i64 {
152 const mark = std.Io.Clock.awake.now(r.io);
153153
154154 var count: usize = 0;
155155 var i: usize = 0;
@@ -159,8 +159,8 @@ pub fn main() !void {
159159 } else |_| {}
160160 }
161161
162 const elapsed_ms = timer.read() / 1_000_000;
163 return elapsed_ms;
162 const elapsed = mark.untilNow(r.io, std.Io.Clock.awake);
163 return elapsed.toMilliseconds();
164164 }
165165 };
166166
@@ -171,15 +171,15 @@ pub fn main() !void {
171171 // Parallel execution
172172 const thread_count = 4;
173173 var threads: [thread_count]std.Thread = undefined;
174 var times: [thread_count]usize = undefined;
174 var times: [thread_count]i64 = undefined;
175175
176176 const ParallelWorker = struct {
177 fn run(r: *const Regex, time_ptr: *usize) void {
177 fn run(r: *const Regex, time_ptr: *i64) void {
178178 time_ptr.* = Worker.run(r);
179179 }
180180 };
181181
182 var parallel_timer = try std.time.Timer.start();
182 var parallel_timer = std.Io.Clock.awake.now(init.io);
183183
184184 for (&threads, 0..) |*thread, i| {
185185 thread.* = try std.Thread.spawn(.{}, ParallelWorker.run, .{ &regex, &times[i] });
@@ -189,7 +189,7 @@ pub fn main() !void {
189189 thread.join();
190190 }
191191
192 const total_parallel = parallel_timer.read() / 1_000_000;
192 const total_parallel = @divExact(parallel_timer.untilNow(init.io, .awake).toNanoseconds(), 1_000_000);
193193
194194 std.debug.print("Parallel ({d} threads): 40,000 matches in {d}ms\n", .{ thread_count, total_parallel });
195195 std.debug.print("Individual thread times: ", .{});
@@ -206,13 +206,13 @@ pub fn main() !void {
206206 std.debug.print("Example 5: Multiple patterns with concurrent access\n", .{});
207207 std.debug.print("───────────────────────────────────────────────\n", .{});
208208
209 var email_regex = try Regex.compile(allocator, "[a-z]+@[a-z]+\\.[a-z]+");
209 var email_regex = try Regex.compile(allocator, init.io, "[a-z]+@[a-z]+\\.[a-z]+");
210210 defer email_regex.deinit();
211211
212 var phone_regex = try Regex.compile(allocator, "\\d{3}-\\d{4}");
212 var phone_regex = try Regex.compile(allocator, init.io, "\\d{3}-\\d{4}");
213213 defer phone_regex.deinit();
214214
215 var url_regex = try Regex.compile(allocator, "https?://");
215 var url_regex = try Regex.compile(allocator, init.io, "https?://");
216216 defer url_regex.deinit();
217217
218218 const Worker = struct {
src/advanced_features.zigmodifiedformatting only+4-4
Changes to src/advanced_features.zig
@@ -16,10 +16,10 @@ pub const AtomicGroupNode = struct {
1616/// Possessive Quantifiers: *+, ++, ?+, {n,m}+
1717/// Like greedy quantifiers but don't backtrack
1818pub const PossessiveQuantifier = enum {
19 star_possessive, // *+
20 plus_possessive, // ++
19 star_possessive, // *+
20 plus_possessive, // ++
2121 optional_possessive, // ?+
22 repeat_possessive, // {n,m}+
22 repeat_possessive, // {n,m}+
2323
2424 pub fn fromGreedy(_: bool) ?PossessiveQuantifier {
2525 // Helper to detect possessive syntax
@@ -74,7 +74,7 @@ pub const AdvancedNodeType = enum {
7474// Tests
7575test "atomic group creation" {
7676 const allocator = std.testing.allocator;
77
77
7878 // Create a simple child node (literal 'a')
7979 const child = try ast.Node.createLiteral(allocator, 'a', .{ .start = 0, .end = 1 });
8080 defer allocator.destroy(child);
src/backtrack.zigmodifiedformatting only+0-1
Changes to src/backtrack.zig
@@ -5,7 +5,6 @@ const common = ;
55/// Backtracking-based regex engine
66/// Supports: lazy quantifiers, lookahead/lookbehind, backreferences
77/// Trade-off: O(2^n) worst case, but supports features impossible in Thompson NFA
8
98/// Match result from backtracking engine
109pub const BacktrackMatch = struct {
1110 start: usize,
src/benchmark.zigmodifiedformatting only+2-2
Changes to src/benchmark.zig
@@ -26,7 +26,7 @@ pub fn benchmark(
2626 const avg_ns = elapsed_ns / iterations;
2727 const avg_us = avg_ns / 1000;
2828
29 std.debug.print("{s}: {d} iterations, avg {d}μs per match\n", .{name, iterations, avg_us});
29 std.debug.print("{s}: {d} iterations, avg {d}μs per match\n", .{ name, iterations, avg_us });
3030}
3131
3232pub fn benchmarkCompile(
@@ -47,5 +47,5 @@ pub fn benchmarkCompile(
4747 const avg_ns = elapsed_ns / iterations;
4848 const avg_us = avg_ns / 1000;
4949
50 std.debug.print("{s}: {d} compilations, avg {d}μs per compile\n", .{name, iterations, avg_us});
50 std.debug.print("{s}: {d} compilations, avg {d}μs per compile\n", .{ name, iterations, avg_us });
5151}
src/c_api.zigmodified+214-215
Changes to src/c_api.zig
@@ -1,215 +1,214 @@
1const std = ;
2const Regex = .Regex;
3const Match = .Match;
4
5/// C FFI for the Zig regex library
6///
7/// This module provides a C-compatible API for using the regex library
8/// from other languages (C, C++, Python via ctypes, etc.)
9///
10/// Example usage from C:
11/// ```c
12/// #include "zig_regex.h"
13///
14/// ZigRegex* regex = zig_regex_compile("\\d+", NULL);
15/// if (regex) {
16/// bool matches = zig_regex_is_match(regex, "test 123");
17/// printf("Matches: %d\n", matches);
18/// zig_regex_free(regex);
19/// }
20/// ```
21
22/// Opaque handle to a compiled regex
23pub const ZigRegex = opaque {};
24
25/// Opaque handle to a match result
26pub const ZigMatch = opaque {};
27
28/// Error codes
29pub const ZigRegexError = enum(c_int) {
30 ok = 0,
31 compile_error = -1,
32 invalid_pattern = -2,
33 out_of_memory = -3,
34 no_match = -4,
35 null_pointer = -5,
36};
37
38// Global allocator for C API (uses C allocator)
39var c_allocator = std.heap.c_allocator;
40
41/// Compile a regex pattern
42///
43/// Returns NULL on error. Call zig_regex_get_last_error() to get error details.
44/// The returned regex must be freed with zig_regex_free().
45export fn zig_regex_compile(pattern: [*:0]const u8) ?*ZigRegex {
46 const pattern_slice = std.mem.span(pattern);
47
48 const regex_ptr = c_allocator.create(Regex) catch return null;
49 regex_ptr.* = Regex.compile(c_allocator, pattern_slice) catch {
50 c_allocator.destroy(regex_ptr);
51 return null;
52 };
53
54 return @ptrCast(regex_ptr);
55}
56
57/// Free a compiled regex
58export fn zig_regex_free(regex: ?*ZigRegex) void {
59 if (regex) |r| {
60 const regex_ptr: *Regex = @ptrCast(@alignCast(r));
61 regex_ptr.deinit();
62 c_allocator.destroy(regex_ptr);
63 }
64}
65
66/// Check if a pattern matches a string
67///
68/// Returns 1 if matches, 0 if no match, -1 on error
69export fn zig_regex_is_match(regex: ?*ZigRegex, input: [*:0]const u8) c_int {
70 if (regex == null) return @intFromEnum(ZigRegexError.null_pointer);
71
72 const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?));
73 const input_slice = std.mem.span(input);
74
75 const result = regex_ptr.isMatch(input_slice) catch return -1;
76 return if (result) 1 else 0;
77}
78
79/// Find the first match in a string
80///
81/// Returns NULL if no match or on error.
82/// The returned match must be freed with zig_match_free().
83export fn zig_regex_find(regex: ?*ZigRegex, input: [*:0]const u8) ?*ZigMatch {
84 if (regex == null) return null;
85
86 const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?));
87 const input_slice = std.mem.span(input);
88
89 const maybe_match = regex_ptr.find(input_slice) catch return null;
90 if (maybe_match) |match| {
91 const match_ptr = c_allocator.create(Match) catch return null;
92 match_ptr.* = match;
93 return @ptrCast(match_ptr);
94 }
95
96 return null;
97}
98
99/// Get the matched substring
100///
101/// Returns NULL if match is NULL.
102/// The returned string is owned by the match and should not be freed separately.
103export fn zig_match_get_text(match: ?*ZigMatch) ?[*:0]const u8 {
104 if (match == null) return null;
105
106 const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
107 // Note: This assumes the slice is null-terminated, which may not always be true
108 // In production, you'd want to copy to a null-terminated buffer
109 return @ptrCast(match_ptr.slice.ptr);
110}
111
112/// Get the start position of a match
113export fn zig_match_get_start(match: ?*ZigMatch) c_int {
114 if (match == null) return -1;
115
116 const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
117 return @intCast(match_ptr.start);
118}
119
120/// Get the end position of a match
121export fn zig_match_get_end(match: ?*ZigMatch) c_int {
122 if (match == null) return -1;
123
124 const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
125 return @intCast(match_ptr.end);
126}
127
128/// Free a match result
129export fn zig_match_free(match: ?*ZigMatch) void {
130 if (match) |m| {
131 const match_ptr: *Match = @ptrCast(@alignCast(m));
132 var mut_match = match_ptr.*;
133 mut_match.deinit(c_allocator);
134 c_allocator.destroy(match_ptr);
135 }
136}
137
138/// Get version string
139export fn zig_regex_version() [*:0]const u8 {
140 return "0.1.0";
141}
142
143// Header file generator (for documentation)
144pub fn generateHeader(writer: anytype) !void {
145 try writer.writeAll(
146 \\#ifndef ZIG_REGEX_H
147 \\#define ZIG_REGEX_H
148 \\
149 \\#ifdef __cplusplus
150 \\extern "C" {
151 \\#endif
152 \\
153 \\#include <stddef.h>
154 \\#include <stdbool.h>
155 \\
156 \\/* Opaque types */
157 \\typedef struct ZigRegex ZigRegex;
158 \\typedef struct ZigMatch ZigMatch;
159 \\
160 \\/* Error codes */
161 \\typedef enum {
162 \\ ZIG_REGEX_OK = 0,
163 \\ ZIG_REGEX_COMPILE_ERROR = -1,
164 \\ ZIG_REGEX_INVALID_PATTERN = -2,
165 \\ ZIG_REGEX_OUT_OF_MEMORY = -3,
166 \\ ZIG_REGEX_NO_MATCH = -4,
167 \\ ZIG_REGEX_NULL_POINTER = -5,
168 \\} ZigRegexError;
169 \\
170 \\/* Compile a regex pattern */
171 \\ZigRegex* zig_regex_compile(const char* pattern);
172 \\
173 \\/* Free a compiled regex */
174 \\void zig_regex_free(ZigRegex* regex);
175 \\
176 \\/* Check if pattern matches string (returns 1=match, 0=no match, -1=error) */
177 \\int zig_regex_is_match(ZigRegex* regex, const char* input);
178 \\
179 \\/* Find first match in string (returns NULL if no match) */
180 \\ZigMatch* zig_regex_find(ZigRegex* regex, const char* input);
181 \\
182 \\/* Get matched text from match result */
183 \\const char* zig_match_get_text(ZigMatch* match);
184 \\
185 \\/* Get start position of match */
186 \\int zig_match_get_start(ZigMatch* match);
187 \\
188 \\/* Get end position of match */
189 \\int zig_match_get_end(ZigMatch* match);
190 \\
191 \\/* Free a match result */
192 \\void zig_match_free(ZigMatch* match);
193 \\
194 \\/* Get library version */
195 \\const char* zig_regex_version(void);
196 \\
197 \\#ifdef __cplusplus
198 \\}
199 \\#endif
200 \\
201 \\#endif /* ZIG_REGEX_H */
202 \\
203 );
204}
205
206test "C API header generation" {
207 var aw: std.Io.Writer.Allocating = .init(std.testing.allocator);
208 defer aw.deinit();
209
210 try generateHeader(&aw.writer);
211 var result = aw.toArrayList();
212 defer result.deinit(std.testing.allocator);
213 try std.testing.expect(result.items.len > 0);
214 try std.testing.expect(std.mem.indexOf(u8, result.items, "ZIG_REGEX_H") != null);
215}
1//const std = ;
2//const Regex = .Regex;
3//const Match = .Match;
4//
5///// C FFI for the Zig regex library
6/////
7///// This module provides a C-compatible API for using the regex library
8///// from other languages (C, C++, Python via ctypes, etc.)
9/////
10///// Example usage from C:
11///// ```c
12///// #include "zig_regex.h"
13/////
14///// ZigRegex* regex = zig_regex_compile("\\d+", NULL);
15///// if (regex) {
16///// bool matches = zig_regex_is_match(regex, "test 123");
17///// printf("Matches: %d\n", matches);
18///// zig_regex_free(regex);
19///// }
20///// ```
21///// Opaque handle to a compiled regex
22//pub const ZigRegex = opaque {};
23//
24///// Opaque handle to a match result
25//pub const ZigMatch = opaque {};
26//
27///// Error codes
28//pub const ZigRegexError = enum(c_int) {
29// ok = 0,
30// compile_error = -1,
31// invalid_pattern = -2,
32// out_of_memory = -3,
33// no_match = -4,
34// null_pointer = -5,
35//};
36//
37//// Global allocator for C API (uses C allocator)
38//var c_allocator = std.heap.c_allocator;
39//
40///// Compile a regex pattern
41/////
42///// Returns NULL on error. Call zig_regex_get_last_error() to get error details.
43///// The returned regex must be freed with zig_regex_free().
44//export fn zig_regex_compile(pattern: [*:0]const u8) ?*ZigRegex {
45// const pattern_slice = std.mem.span(pattern);
46//
47// const regex_ptr = c_allocator.create(Regex) catch return null;
48// regex_ptr.* = Regex.compile(c_allocator, pattern_slice) catch {
49// c_allocator.destroy(regex_ptr);
50// return null;
51// };
52//
53// return @ptrCast(regex_ptr);
54//}
55//
56///// Free a compiled regex
57//export fn zig_regex_free(regex: ?*ZigRegex) void {
58// if (regex) |r| {
59// const regex_ptr: *Regex = @ptrCast(@alignCast(r));
60// regex_ptr.deinit();
61// c_allocator.destroy(regex_ptr);
62// }
63//}
64//
65///// Check if a pattern matches a string
66/////
67///// Returns 1 if matches, 0 if no match, -1 on error
68//export fn zig_regex_is_match(regex: ?*ZigRegex, input: [*:0]const u8) c_int {
69// if (regex == null) return @intFromEnum(ZigRegexError.null_pointer);
70//
71// const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?));
72// const input_slice = std.mem.span(input);
73//
74// const result = regex_ptr.isMatch(input_slice) catch return -1;
75// return if (result) 1 else 0;
76//}
77//
78///// Find the first match in a string
79/////
80///// Returns NULL if no match or on error.
81///// The returned match must be freed with zig_match_free().
82//export fn zig_regex_find(regex: ?*ZigRegex, input: [*:0]const u8) ?*ZigMatch {
83// if (regex == null) return null;
84//
85// const regex_ptr: *const Regex = @ptrCast(@alignCast(regex.?));
86// const input_slice = std.mem.span(input);
87//
88// const maybe_match = regex_ptr.find(input_slice) catch return null;
89// if (maybe_match) |match| {
90// const match_ptr = c_allocator.create(Match) catch return null;
91// match_ptr.* = match;
92// return @ptrCast(match_ptr);
93// }
94//
95// return null;
96//}
97//
98///// Get the matched substring
99/////
100///// Returns NULL if match is NULL.
101///// The returned string is owned by the match and should not be freed separately.
102//export fn zig_match_get_text(match: ?*ZigMatch) ?[*:0]const u8 {
103// if (match == null) return null;
104//
105// const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
106// // Note: This assumes the slice is null-terminated, which may not always be true
107// // In production, you'd want to copy to a null-terminated buffer
108// return @ptrCast(match_ptr.slice.ptr);
109//}
110//
111///// Get the start position of a match
112//export fn zig_match_get_start(match: ?*ZigMatch) c_int {
113// if (match == null) return -1;
114//
115// const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
116// return @intCast(match_ptr.start);
117//}
118//
119///// Get the end position of a match
120//export fn zig_match_get_end(match: ?*ZigMatch) c_int {
121// if (match == null) return -1;
122//
123// const match_ptr: *const Match = @ptrCast(@alignCast(match.?));
124// return @intCast(match_ptr.end);
125//}
126//
127///// Free a match result
128//export fn zig_match_free(match: ?*ZigMatch) void {
129// if (match) |m| {
130// const match_ptr: *Match = @ptrCast(@alignCast(m));
131// var mut_match = match_ptr.*;
132// mut_match.deinit(c_allocator);
133// c_allocator.destroy(match_ptr);
134// }
135//}
136//
137///// Get version string
138//export fn zig_regex_version() [*:0]const u8 {
139// return "0.1.0";
140//}
141//
142//// Header file generator (for documentation)
143//pub fn generateHeader(writer: anytype) !void {
144// try writer.writeAll(
145// \\#ifndef ZIG_REGEX_H
146// \\#define ZIG_REGEX_H
147// \\
148// \\#ifdef __cplusplus
149// \\extern "C" {
150// \\#endif
151// \\
152// \\#include <stddef.h>
153// \\#include <stdbool.h>
154// \\
155// \\/* Opaque types */
156// \\typedef struct ZigRegex ZigRegex;
157// \\typedef struct ZigMatch ZigMatch;
158// \\
159// \\/* Error codes */
160// \\typedef enum {
161// \\ ZIG_REGEX_OK = 0,
162// \\ ZIG_REGEX_COMPILE_ERROR = -1,
163// \\ ZIG_REGEX_INVALID_PATTERN = -2,
164// \\ ZIG_REGEX_OUT_OF_MEMORY = -3,
165// \\ ZIG_REGEX_NO_MATCH = -4,
166// \\ ZIG_REGEX_NULL_POINTER = -5,
167// \\} ZigRegexError;
168// \\
169// \\/* Compile a regex pattern */
170// \\ZigRegex* zig_regex_compile(const char* pattern);
171// \\
172// \\/* Free a compiled regex */
173// \\void zig_regex_free(ZigRegex* regex);
174// \\
175// \\/* Check if pattern matches string (returns 1=match, 0=no match, -1=error) */
176// \\int zig_regex_is_match(ZigRegex* regex, const char* input);
177// \\
178// \\/* Find first match in string (returns NULL if no match) */
179// \\ZigMatch* zig_regex_find(ZigRegex* regex, const char* input);
180// \\
181// \\/* Get matched text from match result */
182// \\const char* zig_match_get_text(ZigMatch* match);
183// \\
184// \\/* Get start position of match */
185// \\int zig_match_get_start(ZigMatch* match);
186// \\
187// \\/* Get end position of match */
188// \\int zig_match_get_end(ZigMatch* match);
189// \\
190// \\/* Free a match result */
191// \\void zig_match_free(ZigMatch* match);
192// \\
193// \\/* Get library version */
194// \\const char* zig_regex_version(void);
195// \\
196// \\#ifdef __cplusplus
197// \\}
198// \\#endif
199// \\
200// \\#endif /* ZIG_REGEX_H */
201// \\
202// );
203//}
204//
205//test "C API header generation" {
206// var aw: std.Io.Writer.Allocating = .init(std.testing.allocator);
207// defer aw.deinit();
208//
209// try generateHeader(&aw.writer);
210// var result = aw.toArrayList();
211// defer result.deinit(std.testing.allocator);
212// try std.testing.expect(result.items.len > 0);
213// try std.testing.expect(std.mem.indexOf(u8, result.items, "ZIG_REGEX_H") != null);
214//}
src/compiler.zigmodifiedformatting only+1-1
Changes to src/compiler.zig
@@ -57,7 +57,7 @@ pub const Transition = struct {
5757 .data = .{ .char_class = .{
5858 .ranges = ranges_copy,
5959 .negated = class.negated,
60 }},
60 } },
6161 };
6262 }
6363
src/main.zigmodified+1-1
Changes to src/main.zig
@@ -106,7 +106,7 @@ pub fn main(init: std.process.Init) !void {
106106 break :blk stdin_alloc.?;
107107 };
108108
109 var re = regex.Regex.compileWithFlags(allocator, pattern, .{
109 var re = regex.Regex.compileWithFlags(allocator,io, pattern, .{
110110 .case_insensitive = case_insensitive,
111111 .multiline = multiline,
112112 }) catch |err| {
src/parser.zigmodified+4-6
Changes to src/parser.zig
@@ -657,10 +657,7 @@ pub const Parser = struct {
657657 .dollar => '$',
658658 .lbracket => '[', // Allow [ as literal (for non-POSIX cases)
659659 // These should not appear here
660 .rbracket, .caret, .backslash,
661 .escape_d, .escape_D, .escape_w, .escape_W,
662 .escape_s, .escape_S, .escape_b, .escape_B,
663 .escape_A, .escape_z, .escape_Z, .backref, .eof => null,
660 .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,
664661 };
665662 }
666663
@@ -685,7 +682,8 @@ pub const Parser = struct {
685682
686683 if (current_pos + 1 < self.lexer.input.len and
687684 self.lexer.input[current_pos] == '[' and
688 self.lexer.input[current_pos + 1] == ':') {
685 self.lexer.input[current_pos + 1] == ':')
686 {
689687
690688 // Find the closing :]
691689 var found_posix = false;
formatting only · 8 hidden lines
Changes to src/parser.zig
@@ -693,7 +691,7 @@ pub const Parser = struct { formatting only
693691 while (i + 1 < self.lexer.input.len) : (i += 1) {
694692 if (self.lexer.input[i] == ':' and self.lexer.input[i + 1] == ']') {
695693 // Found [:name:]
696 const class_name = self.lexer.input[current_pos + 2..i];
694 const class_name = self.lexer.input[current_pos + 2 .. i];
697695
698696 // Skip to the character AFTER ':]' which should be the outer ']' or more chars
699697 // We want the lexer to be positioned so that the NEXT token read will be correct
src/pattern_analyzer.zigmodifiedformatting only+1-2
Changes to src/pattern_analyzer.zig
@@ -324,7 +324,6 @@ pub const PatternAnalyzer = struct {
324324 }
325325
326326 fn nodesAreIdentical(self: *PatternAnalyzer, left: *ast.Node, right: *ast.Node) bool {
327
328327 if (left.node_type != right.node_type) return false;
329328
330329 return switch (left.node_type) {
@@ -337,7 +336,7 @@ pub const PatternAnalyzer = struct {
337336 const left_concat = left.data.concat;
338337 const right_concat = right.data.concat;
339338 return self.nodesAreIdentical(left_concat.left, right_concat.left) and
340 self.nodesAreIdentical(left_concat.right, right_concat.right);
339 self.nodesAreIdentical(left_concat.right, right_concat.right);
341340 },
342341 else => false,
343342 };
src/profiling.zigmodified+19-24
Changes to src/profiling.zig
@@ -1,15 +1,17 @@
11const std = ;
2const Clock = std.Io.Clock;
23
34/// Performance profiling and metrics tracking for regex operations
45pub const Profiler = struct {
56 allocator: std.mem.Allocator,
7 io: std.Io,
68 enabled: bool,
79 metrics: Metrics,
8 start_time: ?std.time.Instant,
10 start_time: ?std.Io.Timestamp,
911
1012 pub const Metrics = struct {
11 compilation_time_ns: u64 = 0,
12 match_time_ns: u64 = 0,
13 compilation_time_ns: i96 = 0,
14 match_time_ns: i96 = 0,
1315 match_count: usize = 0,
1416 nfa_states_created: usize = 0,
1517 backtrack_count: usize = 0,
@@ -25,13 +27,8 @@ pub const Profiler = struct {
2527
2628 pub fn format(
2729 self: Metrics,
28 comptime fmt: []const u8,
29 options: std.fmt.FormatOptions,
30 writer: anytype,
30 writer: *std.Io.Writer,
3131 ) !void {
32 _ = fmt;
33 _ = options;
34
3532 try writer.writeAll("\n=== Regex Performance Metrics ===\n");
3633 try writer.print("Compilation time: {d}μs\n", .{self.compilation_time_ns / 1000});
3734 try writer.print("Match time: {d}μs\n", .{self.match_time_ns / 1000});
@@ -49,41 +46,40 @@ pub const Profiler = struct {
4946 }
5047 };
5148
52 pub fn init(allocator: std.mem.Allocator, enabled: bool) Profiler {
49 pub fn init(allocator: std.mem.Allocator, io: std.Io, enabled: bool) Profiler {
5350 return .{
51 .io = io,
5452 .allocator = allocator,
5553 .enabled = enabled,
5654 .metrics = Metrics{},
57 .start_time = if (enabled) std.time.Instant.now() catch null else null,
55 .start_time = if (enabled) Clock.awake.now(io) else null,
5856 };
5957 }
6058
6159 /// Start timing a compilation phase
6260 pub fn startCompilation(self: *Profiler) void {
6361 if (!self.enabled) return;
64 self.start_time = std.time.Instant.now() catch null;
62 self.start_time = Clock.awake.now(self.io);
6563 }
6664
6765 /// End timing a compilation phase
6866 pub fn endCompilation(self: *Profiler) void {
6967 if (!self.enabled) return;
7068 const start = self.start_time orelse return;
71 const now = std.time.Instant.now() catch return;
72 self.metrics.compilation_time_ns += now.since(start);
69 self.metrics.compilation_time_ns += start.untilNow(self.io, Clock.awake).toNanoseconds();
7370 }
7471
7572 /// Start timing a match operation
7673 pub fn startMatch(self: *Profiler) void {
7774 if (!self.enabled) return;
78 self.start_time = std.time.Instant.now() catch null;
75 self.start_time = Clock.awake.now(self.io);
7976 }
8077
8178 /// End timing a match operation
8279 pub fn endMatch(self: *Profiler) void {
8380 if (!self.enabled) return;
8481 const start = self.start_time orelse return;
85 const now = std.time.Instant.now() catch return;
86 self.metrics.match_time_ns += now.since(start);
82 self.metrics.match_time_ns += start.untilNow(self.io, .awake).toNanoseconds();
8783 self.metrics.match_count += 1;
8884 }
8985
@@ -142,7 +138,7 @@ pub const Profiler = struct {
142138 /// Reset all metrics
143139 pub fn reset(self: *Profiler) void {
144140 self.metrics.reset();
145 self.start_time = if (self.enabled) std.time.Instant.now() catch null else null;
141 self.start_time = if (self.enabled) Clock.now(self.io) else null;
146142 }
147143
148144 /// Print metrics to stderr
@@ -156,7 +152,7 @@ pub const Profiler = struct {
156152pub const ScopedTimer = struct {
157153 profiler: *Profiler,
158154 timer_type: TimerType,
159 start: ?std.time.Instant,
155 start: ?std.Io.Timestamp,
160156
161157 pub const TimerType = enum {
162158 compilation,
@@ -167,15 +163,14 @@ pub const ScopedTimer = struct {
167163 return .{
168164 .profiler = profiler,
169165 .timer_type = timer_type,
170 .start = if (profiler.enabled) std.time.Instant.now() catch null else null,
166 .start = if (profiler.enabled) Clock.awake.now(profiler.io) else null,
171167 };
172168 }
173169
174170 pub fn deinit(self: *ScopedTimer) void {
175171 if (!self.profiler.enabled) return;
176172 const start = self.start orelse return;
177 const now = std.time.Instant.now() catch return;
178 const elapsed = now.since(start);
173 const elapsed = start.untilNow(self.profiler.io, Clock.awake).toNanoseconds();
179174 switch (self.timer_type) {
180175 .compilation => self.profiler.metrics.compilation_time_ns += elapsed,
181176 .match => {
@@ -188,7 +183,7 @@ pub const ScopedTimer = struct {
188183
189184test "profiler basic operations" {
190185 const allocator = std.testing.allocator;
191 var profiler = Profiler.init(allocator, true);
186 var profiler = Profiler.init(allocator, std.testing.io, true);
192187
193188 profiler.startCompilation();
194189 // Do some work (just loop to consume time)
@@ -210,7 +205,7 @@ test "profiler basic operations" {
210205
211206test "scoped timer" {
212207 const allocator = std.testing.allocator;
213 var profiler = Profiler.init(allocator, true);
208 var profiler = Profiler.init(allocator, std.testing.io, true);
214209
215210 {
216211 var timer = ScopedTimer.init(&profiler, .match);
src/regex.zigmodified+25-27
Changes to src/regex.zig
@@ -41,6 +41,7 @@ pub const EngineType = enum {
4141/// Main regex type - represents a compiled regular expression pattern
4242pub const Regex = struct {
4343 allocator: std.mem.Allocator,
44 io: std.Io,
4445 pattern: []const u8,
4546 nfa: compiler.NFA,
4647 backtrack_engine: ?backtrack.BacktrackEngine,
@@ -52,12 +53,12 @@ pub const Regex = struct {
5253 named_captures: std.StringHashMap(usize), // name -> capture_index mapping
5354
5455 /// Compile a regex pattern with default flags
55 pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex {
56 return compileWithFlags(allocator, pattern, .{});
56 pub fn compile(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8) !Regex {
57 return compileWithFlags(allocator, io, pattern, .{});
5758 }
5859
5960 /// Compile a regex pattern with custom flags
60 pub fn compileWithFlags(allocator: std.mem.Allocator, pattern: []const u8, flags: common.CompileFlags) !Regex {
61 pub fn compileWithFlags(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8, flags: common.CompileFlags) !Regex {
6162 if (pattern.len == 0) {
6263 return RegexError.EmptyPattern;
6364 }
@@ -92,12 +93,7 @@ pub const Regex = struct {
9293
9394 if (needs_backtracking) {
9495 // Use backtracking engine
95 var backtrack_engine = try backtrack.BacktrackEngine.init(
96 allocator,
97 tree.root,
98 tree.capture_count,
99 flags
100 );
96 var backtrack_engine = try backtrack.BacktrackEngine.init(allocator, tree.root, tree.capture_count, flags);
10197 errdefer backtrack_engine.deinit();
10298
10399 // Create a dummy NFA (not used)
@@ -106,6 +102,7 @@ pub const Regex = struct {
106102 _ = try dummy_nfa.addState();
107103
108104 return Regex{
105 .io = io,
109106 .allocator = allocator,
110107 .pattern = owned_pattern,
111108 .nfa = dummy_nfa,
@@ -126,6 +123,7 @@ pub const Regex = struct {
126123 _ = try comp.compile(&tree);
127124
128125 return Regex{
126 .io = io,
129127 .allocator = allocator,
130128 .pattern = owned_pattern,
131129 .nfa = comp.nfa,
formatting only · 13 hidden lines
Changes to src/regex.zig
@@ -676,11 +674,11 @@ fn requiresBacktracking(node: *ast.Node) bool { formatting only
676674 // Recursively check compound nodes
677675 .concat => {
678676 return requiresBacktracking(node.data.concat.left) or
679 requiresBacktracking(node.data.concat.right);
677 requiresBacktracking(node.data.concat.right);
680678 },
681679 .alternation => {
682680 return requiresBacktracking(node.data.alternation.left) or
683 requiresBacktracking(node.data.alternation.right);
681 requiresBacktracking(node.data.alternation.right);
684682 },
685683 .group => return requiresBacktracking(node.data.group.child),
686684
Changes to src/regex.zig
@@ -727,20 +725,20 @@ fn collectNamedCaptures(node: *ast.Node, map: *std.StringHashMap(usize)) !void {
727725
728726test "compile empty pattern" {
729727 const allocator = std.testing.allocator;
730 const result = Regex.compile(allocator, "");
728 const result = Regex.compile(allocator, std.testing.io, "");
731729 try std.testing.expectError(RegexError.EmptyPattern, result);
732730}
733731
734732test "compile basic pattern" {
735733 const allocator = std.testing.allocator;
736 var regex = try Regex.compile(allocator, "test");
734 var regex = try Regex.compile(allocator, std.testing.io, "test");
737735 defer regex.deinit();
738736 try std.testing.expectEqualStrings("test", regex.pattern);
739737}
740738
741739test "match literal" {
742740 const allocator = std.testing.allocator;
743 var regex = try Regex.compile(allocator, "hello");
741 var regex = try Regex.compile(allocator, std.testing.io, "hello");
744742 defer regex.deinit();
745743
746744 try std.testing.expect(try regex.isMatch("hello"));
@@ -749,7 +747,7 @@ test "match literal" {
749747
750748test "find literal" {
751749 const allocator = std.testing.allocator;
752 var regex = try Regex.compile(allocator, "world");
750 var regex = try Regex.compile(allocator, std.testing.io, "world");
753751 defer regex.deinit();
754752
755753 if (try regex.find("hello world")) |match_result| {
@@ -765,7 +763,7 @@ test "find literal" {
765763
766764test "alternation" {
767765 const allocator = std.testing.allocator;
768 var regex = try Regex.compile(allocator, "cat|dog");
766 var regex = try Regex.compile(allocator, std.testing.io, "cat|dog");
769767 defer regex.deinit();
770768
771769 try std.testing.expect(try regex.isMatch("cat"));
@@ -775,7 +773,7 @@ test "alternation" {
775773
776774test "star quantifier" {
777775 const allocator = std.testing.allocator;
778 var regex = try Regex.compile(allocator, "a*");
776 var regex = try Regex.compile(allocator, std.testing.io, "a*");
779777 defer regex.deinit();
780778
781779 try std.testing.expect(try regex.isMatch(""));
@@ -785,7 +783,7 @@ test "star quantifier" {
785783
786784test "plus quantifier" {
787785 const allocator = std.testing.allocator;
788 var regex = try Regex.compile(allocator, "a+");
786 var regex = try Regex.compile(allocator, std.testing.io, "a+");
789787 defer regex.deinit();
790788
791789 try std.testing.expect(!try regex.isMatch(""));
@@ -795,7 +793,7 @@ test "plus quantifier" {
795793
796794test "optional quantifier" {
797795 const allocator = std.testing.allocator;
798 var regex = try Regex.compile(allocator, "a?");
796 var regex = try Regex.compile(allocator, std.testing.io, "a?");
799797 defer regex.deinit();
800798
801799 try std.testing.expect(try regex.isMatch(""));
@@ -804,7 +802,7 @@ test "optional quantifier" {
804802
805803test "dot wildcard" {
806804 const allocator = std.testing.allocator;
807 var regex = try Regex.compile(allocator, "a.c");
805 var regex = try Regex.compile(allocator, std.testing.io, "a.c");
808806 defer regex.deinit();
809807
810808 try std.testing.expect(try regex.isMatch("abc"));
@@ -814,7 +812,7 @@ test "dot wildcard" {
814812
815813test "character class \\d" {
816814 const allocator = std.testing.allocator;
817 var regex = try Regex.compile(allocator, "\\d+");
815 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
818816 defer regex.deinit();
819817
820818 if (try regex.find("abc123def")) |match_result| {
@@ -828,7 +826,7 @@ test "character class \\d" {
828826
829827test "replace" {
830828 const allocator = std.testing.allocator;
831 var regex = try Regex.compile(allocator, "world");
829 var regex = try Regex.compile(allocator, std.testing.io, "world");
832830 defer regex.deinit();
833831
834832 const result = try regex.replace(allocator, "hello world", "Zig");
@@ -839,7 +837,7 @@ test "replace" {
839837
840838test "replace all" {
841839 const allocator = std.testing.allocator;
842 var regex = try Regex.compile(allocator, "a");
840 var regex = try Regex.compile(allocator, std.testing.io, "a");
843841 defer regex.deinit();
844842
845843 const result = try regex.replaceAll(allocator, "banana", "o");
@@ -850,7 +848,7 @@ test "replace all" {
850848
851849test "split" {
852850 const allocator = std.testing.allocator;
853 var regex = try Regex.compile(allocator, ",");
851 var regex = try Regex.compile(allocator, std.testing.io, ",");
854852 defer regex.deinit();
855853
856854 const parts = try regex.split(allocator, "a,b,c");
@@ -866,7 +864,7 @@ test "compile rejects dangerous nested quantifiers" {
866864 const allocator = std.testing.allocator;
867865
868866 // This pattern should be rejected as critical risk
869 const result = Regex.compile(allocator, "(a+)+");
867 const result = Regex.compile(allocator, std.testing.io, "(a+)+");
870868 try std.testing.expectError(RegexError.PatternTooComplex, result);
871869}
872870
@@ -874,7 +872,7 @@ test "compile rejects nested stars" {
874872 const allocator = std.testing.allocator;
875873
876874 // This pattern should be rejected
877 const result = Regex.compile(allocator, "(a*)*");
875 const result = Regex.compile(allocator, std.testing.io, "(a*)*");
878876 try std.testing.expectError(RegexError.PatternTooComplex, result);
879877}
880878
@@ -882,6 +880,6 @@ test "compile accepts safe complex patterns" {
882880 const allocator = std.testing.allocator;
883881
884882 // This pattern should be accepted (medium risk is OK)
885 var regex = try Regex.compile(allocator, "a+b*c?d{2,5}");
883 var regex = try Regex.compile(allocator, std.testing.io, "a+b*c?d{2,5}");
886884 defer regex.deinit();
887885}
src/root.zigmodified+1-1
Changes to src/root.zig
@@ -8,7 +8,7 @@ const std = ;
88/// Example usage:
99/// ```zig
1010/// const Regex = .Regex;
11/// const regex = try Regex.compile(allocator, "\\d+");
11/// const regex = try Regex.compile(allocator, init.io, "\\d+");
1212/// defer regex.deinit();
1313/// if (try regex.find("abc123")) |match| {
1414/// std.debug.print("Found: {s}\n", .{match.slice});
src/thread_safety.zigmodified+7-5
Changes to src/thread_safety.zig
@@ -43,7 +43,7 @@ const std = ;
4343/// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
4444/// const allocator = gpa.allocator();
4545///
46/// const regex = try Regex.compile(allocator, "\\d+");
46/// const regex = try Regex.compile(allocator, init.io, "\\d+");
4747/// defer regex.deinit();
4848///
4949/// // Thread 1
@@ -115,12 +115,12 @@ pub fn SharedRegex(comptime Regex: type) type {
115115 allocator: std.mem.Allocator,
116116
117117 /// Initialize a shared regex pattern
118 pub fn init(allocator: std.mem.Allocator, pattern: []const u8) !*Self {
118 pub fn init(allocator: std.mem.Allocator, io: std.Io, pattern: []const u8) !*Self {
119119 const self = try allocator.create(Self);
120120 errdefer allocator.destroy(self);
121121
122122 self.* = .{
123 .regex = try Regex.compile(allocator, pattern),
123 .regex = try Regex.compile(allocator, io, pattern),
124124 .ref_count = std.atomic.Value(usize).init(1),
125125 .allocator = allocator,
126126 };
@@ -198,11 +198,13 @@ pub fn RegexCache(comptime Regex: type) type {
198198 };
199199
200200 cache: std.StringHashMap(Regex),
201 io: std.Io,
201202 allocator: std.mem.Allocator,
202203 initialized: bool = false,
203204
204 pub fn init(allocator: std.mem.Allocator) Self {
205 pub fn init(allocator: std.mem.Allocator, io: std.Io) Self {
205206 return .{
207 .io = io,
206208 .cache = std.StringHashMap(Regex).init(allocator),
207209 .allocator = allocator,
208210 .initialized = true,
@@ -229,7 +231,7 @@ pub fn RegexCache(comptime Regex: type) type {
229231 }
230232
231233 // Not in cache, compile and store
232 const regex = try Regex.compile(self.allocator, pattern);
234 const regex = try Regex.compile(self.allocator,self.io, pattern);
233235 errdefer {
234236 var mut_regex = regex;
235237 mut_regex.deinit();
src/unicode.zigmodified+95-58
Changes to src/unicode.zig
@@ -261,62 +261,99 @@ test "UTF-8 encoding" {
261261/// Unicode property names for \p{Property} matching
262262pub const UnicodeProperty = enum {
263263 // General categories (short & long forms)
264 Letter, L,
265 Lowercase_Letter, Ll,
266 Uppercase_Letter, Lu,
267 Titlecase_Letter, Lt,
268 Modifier_Letter, Lm,
269 Other_Letter, Lo,
270
271 Mark, M,
272 Nonspacing_Mark, Mn,
273 Spacing_Mark, Mc,
274 Enclosing_Mark, Me,
275
276 Number, N,
277 Decimal_Number, Nd,
278 Letter_Number, Nl,
279 Other_Number, No,
280
281 Punctuation, P,
282 Connector_Punctuation, Pc,
283 Dash_Punctuation, Pd,
284 Open_Punctuation, Ps,
285 Close_Punctuation, Pe,
286 Initial_Punctuation, Pi,
287 Final_Punctuation, Pf,
288 Other_Punctuation, Po,
289
290 Symbol, S,
291 Math_Symbol, Sm,
292 Currency_Symbol, Sc,
293 Modifier_Symbol, Sk,
294 Other_Symbol, So,
295
296 Separator, Z,
297 Space_Separator, Zs,
298 Line_Separator, Zl,
299 Paragraph_Separator, Zp,
300
301 Other, C,
302 Control, Cc,
303 Format, Cf,
304 Surrogate, Cs,
305 Private_Use, Co,
306 Not_Assigned, Cn,
264 Letter,
265 L,
266 Lowercase_Letter,
267 Ll,
268 Uppercase_Letter,
269 Lu,
270 Titlecase_Letter,
271 Lt,
272 Modifier_Letter,
273 Lm,
274 Other_Letter,
275 Lo,
276
277 Mark,
278 M,
279 Nonspacing_Mark,
280 Mn,
281 Spacing_Mark,
282 Mc,
283 Enclosing_Mark,
284 Me,
285
286 Number,
287 N,
288 Decimal_Number,
289 Nd,
290 Letter_Number,
291 Nl,
292 Other_Number,
293 No,
294
295 Punctuation,
296 P,
297 Connector_Punctuation,
298 Pc,
299 Dash_Punctuation,
300 Pd,
301 Open_Punctuation,
302 Ps,
303 Close_Punctuation,
304 Pe,
305 Initial_Punctuation,
306 Pi,
307 Final_Punctuation,
308 Pf,
309 Other_Punctuation,
310 Po,
311
312 Symbol,
313 S,
314 Math_Symbol,
315 Sm,
316 Currency_Symbol,
317 Sc,
318 Modifier_Symbol,
319 Sk,
320 Other_Symbol,
321 So,
322
323 Separator,
324 Z,
325 Space_Separator,
326 Zs,
327 Line_Separator,
328 Zl,
329 Paragraph_Separator,
330 Zp,
331
332 Other,
333 C,
334 Control,
335 Cc,
336 Format,
337 Cf,
338 Surrogate,
339 Cs,
340 Private_Use,
341 Co,
342 Not_Assigned,
343 Cn,
307344
308345 pub fn fromString(s: []const u8) ?UnicodeProperty {
309346 const map = std.StaticStringMap(UnicodeProperty).initComptime(.{
310 .{ "Letter", .Letter }, .{ "L", .L },
347 .{ "Letter", .Letter }, .{ "L", .L },
311348 .{ "Lowercase_Letter", .Lowercase_Letter }, .{ "Ll", .Ll },
312349 .{ "Uppercase_Letter", .Uppercase_Letter }, .{ "Lu", .Lu },
313 .{ "Number", .Number }, .{ "N", .N },
314 .{ "Decimal_Number", .Decimal_Number }, .{ "Nd", .Nd },
315 .{ "Punctuation", .Punctuation }, .{ "P", .P },
316 .{ "Symbol", .Symbol }, .{ "S", .S },
317 .{ "Separator", .Separator }, .{ "Z", .Z },
318 .{ "Space_Separator", .Space_Separator }, .{ "Zs", .Zs },
319 .{ "Control", .Control }, .{ "Cc", .Cc },
350 .{ "Number", .Number }, .{ "N", .N },
351 .{ "Decimal_Number", .Decimal_Number }, .{ "Nd", .Nd },
352 .{ "Punctuation", .Punctuation }, .{ "P", .P },
353 .{ "Symbol", .Symbol }, .{ "S", .S },
354 .{ "Separator", .Separator }, .{ "Z", .Z },
355 .{ "Space_Separator", .Space_Separator }, .{ "Zs", .Zs },
356 .{ "Control", .Control }, .{ "Cc", .Cc },
320357 });
321358 return map.get(s);
322359 }
formatting only · 8 hidden lines
Changes to src/unicode.zig
@@ -368,7 +405,7 @@ test "Unicode property matching" { formatting only
368405// Edge case tests
369406test "unicode: invalid UTF-8 sequences" {
370407 // Truncated multi-byte sequence
371 const truncated = [_]u8{ 0xC3 }; // Should be 2 bytes but only 1
408 const truncated = [_]u8{0xC3}; // Should be 2 bytes but only 1
372409 try std.testing.expectError(error.InvalidUtf8, decodeUtf8(&truncated));
373410
374411 // Invalid continuation byte
formatting only · 18 hidden lines
Changes to src/unicode.zig
@@ -432,12 +469,12 @@ test "unicode: round-trip encoding/decoding" { formatting only
432469
433470 // Test various codepoints can be encoded and decoded back
434471 const test_codepoints = [_]Codepoint{
435 0x0000, 0x007F, // ASCII boundaries
436 0x0080, 0x07FF, // 2-byte boundaries
437 0x0800, 0xFFFF, // 3-byte boundaries
438 0x10000, 0x10FFFF, // 4-byte boundaries
439 'a', 'Z', '0', '9', // Common ASCII
440 0x00E9, 0x20AC, // Common non-ASCII
472 0x0000, 0x007F, // ASCII boundaries
473 0x0080, 0x07FF, // 2-byte boundaries
474 0x0800, 0xFFFF, // 3-byte boundaries
475 0x10000, 0x10FFFF, // 4-byte boundaries
476 'a', 'Z', '0', '9', // Common ASCII
477 0x00E9, 0x20AC, // Common non-ASCII
441478 };
442479
443480 for (test_codepoints) |cp| {
tests/advanced_edge_cases.zigmodified+24-24
Changes to tests/advanced_edge_cases.zig
@@ -10,7 +10,7 @@ const RegexError = .RegexError;
1010
1111test "advanced: positive lookahead basic" {
1212 const allocator = std.testing.allocator;
13 var regex = try Regex.compile(allocator, "foo(?=bar)");
13 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)");
1414 defer regex.deinit();
1515
1616 // "foobar" should match "foo" (lookahead is zero-width)
@@ -28,7 +28,7 @@ test "advanced: positive lookahead basic" {
2828
2929test "advanced: negative lookahead basic" {
3030 const allocator = std.testing.allocator;
31 var regex = try Regex.compile(allocator, "foo(?!bar)");
31 var regex = try Regex.compile(allocator, std.testing.io, "foo(?!bar)");
3232 defer regex.deinit();
3333
3434 // "foobaz" should match "foo" (bar not following)
@@ -41,7 +41,7 @@ test "advanced: negative lookahead basic" {
4141
4242test "advanced: lookahead is zero-width" {
4343 const allocator = std.testing.allocator;
44 var regex = try Regex.compile(allocator, "foo(?=bar)bar");
44 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)bar");
4545 defer regex.deinit();
4646
4747 // Lookahead doesn't consume, so "bar" after it should match the same "bar"
@@ -52,7 +52,7 @@ test "advanced: lookahead is zero-width" {
5252
5353test "advanced: positive lookbehind" {
5454 const allocator = std.testing.allocator;
55 var regex = try Regex.compile(allocator, "(?<=@)\\w+");
55 var regex = try Regex.compile(allocator, std.testing.io, "(?<=@)\\w+");
5656 defer regex.deinit();
5757
5858 if (try regex.find("user@domain")) |match| {
@@ -66,7 +66,7 @@ test "advanced: positive lookbehind" {
6666
6767test "advanced: negative lookbehind" {
6868 const allocator = std.testing.allocator;
69 var regex = try Regex.compile(allocator, "(?<!\\d)\\w+");
69 var regex = try Regex.compile(allocator, std.testing.io, "(?<!\\d)\\w+");
7070 defer regex.deinit();
7171
7272 if (try regex.find("abc")) |match| {
@@ -82,7 +82,7 @@ test "advanced: negative lookbehind" {
8282
8383test "advanced: backreference repeated word" {
8484 const allocator = std.testing.allocator;
85 var regex = try Regex.compile(allocator, "\\b(\\w+)\\s+\\1\\b");
85 var regex = try Regex.compile(allocator, std.testing.io, "\\b(\\w+)\\s+\\1\\b");
8686 defer regex.deinit();
8787
8888 try std.testing.expect(try regex.isMatch("the the"));
@@ -92,7 +92,7 @@ test "advanced: backreference repeated word" {
9292
9393test "advanced: backreference HTML tag matching" {
9494 const allocator = std.testing.allocator;
95 var regex = try Regex.compile(allocator, "<(\\w+)>.*</\\1>");
95 var regex = try Regex.compile(allocator, std.testing.io, "<(\\w+)>.*</\\1>");
9696 defer regex.deinit();
9797
9898 try std.testing.expect(try regex.isMatch("<b>bold</b>"));
@@ -104,7 +104,7 @@ test "advanced: backreference HTML tag matching" {
104104
105105test "advanced: non-capturing group doesn't affect capture numbering" {
106106 const allocator = std.testing.allocator;
107 var regex = try Regex.compile(allocator, "(?:abc)(def)");
107 var regex = try Regex.compile(allocator, std.testing.io, "(?:abc)(def)");
108108 defer regex.deinit();
109109
110110 if (try regex.find("abcdef")) |match| {
@@ -121,7 +121,7 @@ test "advanced: non-capturing group doesn't affect capture numbering" {
121121
122122test "advanced: non-capturing with alternation" {
123123 const allocator = std.testing.allocator;
124 var regex = try Regex.compile(allocator, "(?:cat|dog)fish");
124 var regex = try Regex.compile(allocator, std.testing.io, "(?:cat|dog)fish");
125125 defer regex.deinit();
126126
127127 try std.testing.expect(try regex.isMatch("catfish"));
@@ -131,7 +131,7 @@ test "advanced: non-capturing with alternation" {
131131
132132test "advanced: mixed capturing and non-capturing" {
133133 const allocator = std.testing.allocator;
134 var regex = try Regex.compile(allocator, "(\\w+)(?:\\s+)(\\w+)");
134 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)(?:\\s+)(\\w+)");
135135 defer regex.deinit();
136136
137137 if (try regex.find("hello world")) |match| {
@@ -149,7 +149,7 @@ test "advanced: mixed capturing and non-capturing" {
149149
150150test "advanced: named capture python style" {
151151 const allocator = std.testing.allocator;
152 var regex = try Regex.compile(allocator, "(?P<year>\\d{4})-(?P<month>\\d{2})");
152 var regex = try Regex.compile(allocator, std.testing.io, "(?P<year>\\d{4})-(?P<month>\\d{2})");
153153 defer regex.deinit();
154154
155155 if (try regex.find("date: 2024-01 ok")) |match| {
@@ -166,7 +166,7 @@ test "advanced: named capture python style" {
166166
167167test "advanced: named capture angle bracket style" {
168168 const allocator = std.testing.allocator;
169 var regex = try Regex.compile(allocator, "(?<first>\\w+)\\s(?<last>\\w+)");
169 var regex = try Regex.compile(allocator, std.testing.io, "(?<first>\\w+)\\s(?<last>\\w+)");
170170 defer regex.deinit();
171171
172172 if (try regex.find("John Doe")) |match| {
@@ -184,7 +184,7 @@ test "advanced: named capture angle bracket style" {
184184
185185test "advanced: anchored pattern with captures" {
186186 const allocator = std.testing.allocator;
187 var regex = try Regex.compile(allocator, "^(\\w+)\\s+(\\w+)$");
187 var regex = try Regex.compile(allocator, std.testing.io, "^(\\w+)\\s+(\\w+)$");
188188 defer regex.deinit();
189189
190190 if (try regex.find("hello world")) |match| {
@@ -202,7 +202,7 @@ test "advanced: anchored pattern with captures" {
202202
203203test "advanced: case insensitive find with captures" {
204204 const allocator = std.testing.allocator;
205 var regex = try Regex.compileWithFlags(allocator, "(hello) (world)", .{ .case_insensitive = true });
205 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "(hello) (world)", .{ .case_insensitive = true });
206206 defer regex.deinit();
207207
208208 if (try regex.find("HELLO WORLD")) |match| {
@@ -221,7 +221,7 @@ test "advanced: case insensitive find with captures" {
221221
222222test "advanced: IPv4 address pattern" {
223223 const allocator = std.testing.allocator;
224 var regex = try Regex.compile(allocator, "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
224 var regex = try Regex.compile(allocator, std.testing.io, "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
225225 defer regex.deinit();
226226
227227 try std.testing.expect(try regex.isMatch("192.168.1.1"));
@@ -231,7 +231,7 @@ test "advanced: IPv4 address pattern" {
231231
232232test "advanced: hex color pattern" {
233233 const allocator = std.testing.allocator;
234 var regex = try Regex.compile(allocator, "#[0-9a-fA-F]{6}");
234 var regex = try Regex.compile(allocator, std.testing.io, "#[0-9a-fA-F]{6}");
235235 defer regex.deinit();
236236
237237 try std.testing.expect(try regex.isMatch("#ff0000"));
@@ -241,7 +241,7 @@ test "advanced: hex color pattern" {
241241
242242test "advanced: semver pattern" {
243243 const allocator = std.testing.allocator;
244 var regex = try Regex.compile(allocator, "(\\d+)\\.(\\d+)\\.(\\d+)");
244 var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)\\.(\\d+)\\.(\\d+)");
245245 defer regex.deinit();
246246
247247 if (try regex.find("version 1.2.3-beta")) |match| {
@@ -259,7 +259,7 @@ test "advanced: semver pattern" {
259259
260260test "advanced: log line pattern" {
261261 const allocator = std.testing.allocator;
262 var regex = try Regex.compile(allocator, "\\[(\\w+)\\]\\s+(.+)");
262 var regex = try Regex.compile(allocator, std.testing.io, "\\[(\\w+)\\]\\s+(.+)");
263263 defer regex.deinit();
264264
265265 if (try regex.find("[ERROR] Something went wrong")) |match| {
@@ -275,7 +275,7 @@ test "advanced: log line pattern" {
275275
276276test "advanced: URL extraction" {
277277 const allocator = std.testing.allocator;
278 var regex = try Regex.compile(allocator, "https?://[a-zA-Z0-9._/-]+");
278 var regex = try Regex.compile(allocator, std.testing.io, "https?://[a-zA-Z0-9._/-]+");
279279 defer regex.deinit();
280280
281281 if (try regex.find("visit http://example.com/path today")) |match| {
@@ -291,7 +291,7 @@ test "advanced: URL extraction" {
291291
292292test "advanced: iterator over empty result" {
293293 const allocator = std.testing.allocator;
294 var regex = try Regex.compile(allocator, "xyz");
294 var regex = try Regex.compile(allocator, std.testing.io, "xyz");
295295 defer regex.deinit();
296296
297297 var iter = regex.iterator("hello world");
@@ -301,7 +301,7 @@ test "advanced: iterator over empty result" {
301301
302302test "advanced: iterator collects all matches" {
303303 const allocator = std.testing.allocator;
304 var regex = try Regex.compile(allocator, "\\d+");
304 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
305305 defer regex.deinit();
306306
307307 var iter = regex.iterator("a1b23c456");
@@ -320,7 +320,7 @@ test "advanced: iterator collects all matches" {
320320
321321test "advanced: multiline with captures" {
322322 const allocator = std.testing.allocator;
323 var regex = try Regex.compileWithFlags(allocator, "^(\\w+):", .{ .multiline = true });
323 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^(\\w+):", .{ .multiline = true });
324324 defer regex.deinit();
325325
326326 const matches = try regex.findAll(allocator, "key1: val\nkey2: val");
@@ -343,7 +343,7 @@ test "advanced: multiline with captures" {
343343
344344test "advanced: match at very start of input" {
345345 const allocator = std.testing.allocator;
346 var regex = try Regex.compile(allocator, "\\d+");
346 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
347347 defer regex.deinit();
348348
349349 if (try regex.find("123abc")) |match| {
@@ -358,7 +358,7 @@ test "advanced: match at very start of input" {
358358
359359test "advanced: match consuming entire input" {
360360 const allocator = std.testing.allocator;
361 var regex = try Regex.compile(allocator, "\\w+");
361 var regex = try Regex.compile(allocator, std.testing.io, "\\w+");
362362 defer regex.deinit();
363363
364364 if (try regex.find("hello")) |match| {
tests/backreferences.zigmodified+23-23
Changes to tests/backreferences.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "backreference: simple capture group replacement" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "(\\w+)");
8 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)");
99 defer regex.deinit();
1010
1111 const result = try regex.replace(allocator, "hello", "$1!");
@@ -16,7 +16,7 @@ test "backreference: simple capture group replacement" {
1616
1717test "backreference: swap two words" {
1818 const allocator = std.testing.allocator;
19 var regex = try Regex.compile(allocator, "(\\w+) (\\w+)");
19 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) (\\w+)");
2020 defer regex.deinit();
2121
2222 const result = try regex.replace(allocator, "hello world", "$2 $1");
@@ -27,7 +27,7 @@ test "backreference: swap two words" {
2727
2828test "backreference: repeat capture" {
2929 const allocator = std.testing.allocator;
30 var regex = try Regex.compile(allocator, "(\\w+)");
30 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)");
3131 defer regex.deinit();
3232
3333 const result = try regex.replace(allocator, "test", "$1-$1");
@@ -38,7 +38,7 @@ test "backreference: repeat capture" {
3838
3939test "backreference: multiple captures" {
4040 const allocator = std.testing.allocator;
41 var regex = try Regex.compile(allocator, "(\\d+)-(\\d+)-(\\d+)");
41 var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)-(\\d+)-(\\d+)");
4242 defer regex.deinit();
4343
4444 const result = try regex.replace(allocator, "2025-10-26", "$3/$2/$1");
@@ -49,7 +49,7 @@ test "backreference: multiple captures" {
4949
5050test "backreference: escaped dollar sign" {
5151 const allocator = std.testing.allocator;
52 var regex = try Regex.compile(allocator, "(\\d+)");
52 var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)");
5353 defer regex.deinit();
5454
5555 const result = try regex.replace(allocator, "100", "$$$1");
@@ -60,7 +60,7 @@ test "backreference: escaped dollar sign" {
6060
6161test "backreference: replaceAll with captures" {
6262 const allocator = std.testing.allocator;
63 var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)");
63 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)");
6464 defer regex.deinit();
6565
6666 const result = try regex.replaceAll(allocator, "user@example and admin@test", "$1 at $2");
@@ -71,7 +71,7 @@ test "backreference: replaceAll with captures" {
7171
7272test "backreference: extract and format phone numbers" {
7373 const allocator = std.testing.allocator;
74 var regex = try Regex.compile(allocator, "(\\d{3})-(\\d{3})-(\\d{4})");
74 var regex = try Regex.compile(allocator, std.testing.io, "(\\d{3})-(\\d{3})-(\\d{4})");
7575 defer regex.deinit();
7676
7777 const result = try regex.replace(allocator, "555-123-4567", "($1) $2-$3");
@@ -82,7 +82,7 @@ test "backreference: extract and format phone numbers" {
8282
8383test "backreference: reformat dates" {
8484 const allocator = std.testing.allocator;
85 var regex = try Regex.compile(allocator, "(\\d{4})-(\\d{2})-(\\d{2})");
85 var regex = try Regex.compile(allocator, std.testing.io, "(\\d{4})-(\\d{2})-(\\d{2})");
8686 defer regex.deinit();
8787
8888 const result = try regex.replaceAll(allocator, "2025-10-26 and 2024-12-31", "$2/$3/$1");
@@ -93,7 +93,7 @@ test "backreference: reformat dates" {
9393
9494test "backreference: wrap matches in tags" {
9595 const allocator = std.testing.allocator;
96 var regex = try Regex.compile(allocator, "(\\w+)");
96 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)");
9797 defer regex.deinit();
9898
9999 const result = try regex.replaceAll(allocator, "hello world", "<b>$1</b>");
@@ -104,7 +104,7 @@ test "backreference: wrap matches in tags" {
104104
105105test "backreference: invalid capture index" {
106106 const allocator = std.testing.allocator;
107 var regex = try Regex.compile(allocator, "(\\w+)");
107 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)");
108108 defer regex.deinit();
109109
110110 // Only one capture group, $2 should be treated as literal
@@ -116,7 +116,7 @@ test "backreference: invalid capture index" {
116116
117117test "backreference: nested captures" {
118118 const allocator = std.testing.allocator;
119 var regex = try Regex.compile(allocator, "((\\w+)@(\\w+))");
119 var regex = try Regex.compile(allocator, std.testing.io, "((\\w+)@(\\w+))");
120120 defer regex.deinit();
121121
122122 const result = try regex.replace(allocator, "user@example.com", "Email: $1 (user=$2, domain=$3)");
@@ -127,7 +127,7 @@ test "backreference: nested captures" {
127127
128128test "backreference: quote words" {
129129 const allocator = std.testing.allocator;
130 var regex = try Regex.compile(allocator, "\\b(\\w+)\\b");
130 var regex = try Regex.compile(allocator, std.testing.io, "\\b(\\w+)\\b");
131131 defer regex.deinit();
132132
133133 const result = try regex.replaceAll(allocator, "hello world", "'$1'");
@@ -138,7 +138,7 @@ test "backreference: quote words" {
138138
139139test "backreference: transform case context" {
140140 const allocator = std.testing.allocator;
141 var regex = try Regex.compile(allocator, "(Mr|Mrs|Ms) (\\w+)");
141 var regex = try Regex.compile(allocator, std.testing.io, "(Mr|Mrs|Ms) (\\w+)");
142142 defer regex.deinit();
143143
144144 const result = try regex.replace(allocator, "Hello Mr Smith", "$1. $2");
@@ -151,7 +151,7 @@ test "backreference: transform case context" {
151151
152152test "pattern backreference: basic \\1" {
153153 const allocator = std.testing.allocator;
154 var regex = try Regex.compile(allocator, "(\\w+) \\1");
154 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1");
155155 defer regex.deinit();
156156
157157 // Should match repeated words
@@ -171,7 +171,7 @@ test "pattern backreference: basic \\1" {
171171
172172test "pattern backreference: multiple captures" {
173173 const allocator = std.testing.allocator;
174 var regex = try Regex.compile(allocator, "(\\w+) (\\w+) \\1 \\2");
174 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) (\\w+) \\1 \\2");
175175 defer regex.deinit();
176176
177177 try std.testing.expect(try regex.isMatch("foo bar foo bar"));
@@ -191,7 +191,7 @@ test "pattern backreference: multiple captures" {
191191
192192test "pattern backreference: with quantifiers" {
193193 const allocator = std.testing.allocator;
194 var regex = try Regex.compile(allocator, "(\\d+)\\+\\1");
194 var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)\\+\\1");
195195 defer regex.deinit();
196196
197197 // Match patterns like "5+5", "123+123"
@@ -202,7 +202,7 @@ test "pattern backreference: with quantifiers" {
202202
203203test "pattern backreference: HTML tag matching" {
204204 const allocator = std.testing.allocator;
205 var regex = try Regex.compile(allocator, "<(\\w+)>.*</\\1>");
205 var regex = try Regex.compile(allocator, std.testing.io, "<(\\w+)>.*</\\1>");
206206 defer regex.deinit();
207207
208208 // Match matching HTML tags
@@ -213,7 +213,7 @@ test "pattern backreference: HTML tag matching" {
213213
214214test "pattern backreference: case sensitive" {
215215 const allocator = std.testing.allocator;
216 var regex = try Regex.compile(allocator, "(\\w+) \\1");
216 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1");
217217 defer regex.deinit();
218218
219219 // Backreferences should be case sensitive
@@ -223,7 +223,7 @@ test "pattern backreference: case sensitive" {
223223
224224test "pattern backreference: nested groups" {
225225 const allocator = std.testing.allocator;
226 var regex = try Regex.compile(allocator, "((\\w)\\w)\\2");
226 var regex = try Regex.compile(allocator, std.testing.io, "((\\w)\\w)\\2");
227227 defer regex.deinit();
228228
229229 // \\2 refers to second group (single char), so "aba" should match:
@@ -246,7 +246,7 @@ test "pattern backreference: nested groups" {
246246
247247test "pattern backreference: with alternation" {
248248 const allocator = std.testing.allocator;
249 var regex = try Regex.compile(allocator, "(a|b)\\1");
249 var regex = try Regex.compile(allocator, std.testing.io, "(a|b)\\1");
250250 defer regex.deinit();
251251
252252 try std.testing.expect(try regex.isMatch("aa"));
@@ -257,7 +257,7 @@ test "pattern backreference: with alternation" {
257257
258258test "pattern backreference: multiple in same pattern" {
259259 const allocator = std.testing.allocator;
260 var regex = try Regex.compile(allocator, "(\\w)\\1\\1");
260 var regex = try Regex.compile(allocator, std.testing.io, "(\\w)\\1\\1");
261261 defer regex.deinit();
262262
263263 // Match three of the same character
@@ -269,7 +269,7 @@ test "pattern backreference: multiple in same pattern" {
269269
270270test "pattern backreference: findAll" {
271271 const allocator = std.testing.allocator;
272 var regex = try Regex.compile(allocator, "(\\w+) \\1");
272 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1");
273273 defer regex.deinit();
274274
275275 const matches = try regex.findAll(allocator, "foo foo bar bar baz qux");
@@ -288,7 +288,7 @@ test "pattern backreference: findAll" {
288288
289289test "pattern backreference: quoted strings" {
290290 const allocator = std.testing.allocator;
291 var regex = try Regex.compile(allocator, "(['\"]).*\\1");
291 var regex = try Regex.compile(allocator, std.testing.io, "(['\"]).*\\1");
292292 defer regex.deinit();
293293
294294 // Match quoted strings with same quote type
tests/case_insensitive.zigmodified+9-9
Changes to tests/case_insensitive.zig
@@ -4,7 +4,7 @@ const common = .common;
44
55test "case insensitive: simple literal" {
66 const allocator = std.testing.allocator;
7 var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
7 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true });
88 defer regex.deinit();
99
1010 try std.testing.expect(try regex.isMatch("hello"));
@@ -16,7 +16,7 @@ test "case insensitive: simple literal" {
1616
1717test "case insensitive: with quantifiers" {
1818 const allocator = std.testing.allocator;
19 var regex = try Regex.compileWithFlags(allocator, "a+b*", .{ .case_insensitive = true });
19 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "a+b*", .{ .case_insensitive = true });
2020 defer regex.deinit();
2121
2222 try std.testing.expect(try regex.isMatch("aaa"));
@@ -29,7 +29,7 @@ test "case insensitive: with quantifiers" {
2929
3030test "case insensitive: mixed with alternation" {
3131 const allocator = std.testing.allocator;
32 var regex = try Regex.compileWithFlags(allocator, "cat|dog", .{ .case_insensitive = true });
32 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "cat|dog", .{ .case_insensitive = true });
3333 defer regex.deinit();
3434
3535 try std.testing.expect(try regex.isMatch("cat"));
@@ -42,7 +42,7 @@ test "case insensitive: mixed with alternation" {
4242
4343test "case insensitive: find in text" {
4444 const allocator = std.testing.allocator;
45 var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
45 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true });
4646 defer regex.deinit();
4747
4848 if (try regex.find("Say HELLO world")) |match| {
@@ -56,7 +56,7 @@ test "case insensitive: find in text" {
5656
5757test "case insensitive: with anchors" {
5858 const allocator = std.testing.allocator;
59 var regex = try Regex.compileWithFlags(allocator, "^hello$", .{ .case_insensitive = true });
59 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^hello$", .{ .case_insensitive = true });
6060 defer regex.deinit();
6161
6262 try std.testing.expect(try regex.isMatch("hello"));
@@ -68,7 +68,7 @@ test "case insensitive: with anchors" {
6868
6969test "case insensitive: replace" {
7070 const allocator = std.testing.allocator;
71 var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
71 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true });
7272 defer regex.deinit();
7373
7474 const result = try regex.replace(allocator, "Say HELLO world", "hi");
@@ -79,7 +79,7 @@ test "case insensitive: replace" {
7979
8080test "case insensitive: replaceAll" {
8181 const allocator = std.testing.allocator;
82 var regex = try Regex.compileWithFlags(allocator, "a", .{ .case_insensitive = true });
82 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "a", .{ .case_insensitive = true });
8383 defer regex.deinit();
8484
8585 const result = try regex.replaceAll(allocator, "AaAa", "X");
@@ -90,7 +90,7 @@ test "case insensitive: replaceAll" {
9090
9191test "case insensitive: disabled by default" {
9292 const allocator = std.testing.allocator;
93 var regex = try Regex.compile(allocator, "hello");
93 var regex = try Regex.compile(allocator, std.testing.io, "hello");
9494 defer regex.deinit();
9595
9696 try std.testing.expect(try regex.isMatch("hello"));
@@ -100,7 +100,7 @@ test "case insensitive: disabled by default" {
100100
101101test "case insensitive: with numbers and special chars" {
102102 const allocator = std.testing.allocator;
103 var regex = try Regex.compileWithFlags(allocator, "test123", .{ .case_insensitive = true });
103 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "test123", .{ .case_insensitive = true });
104104 defer regex.deinit();
105105
106106 try std.testing.expect(try regex.isMatch("test123"));
tests/comprehensive.zigmodified+14-14
Changes to tests/comprehensive.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33
44test "comprehensive: email-like pattern" {
55 const allocator = std.testing.allocator;
6 var regex = try Regex.compile(allocator, "\\w+@\\w+");
6 var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+");
77 defer regex.deinit();
88
99 try std.testing.expect(try regex.isMatch("user@domain"));
@@ -12,7 +12,7 @@ test "comprehensive: email-like pattern" {
1212
1313test "comprehensive: phone number pattern" {
1414 const allocator = std.testing.allocator;
15 var regex = try Regex.compile(allocator, "\\d+-\\d+");
15 var regex = try Regex.compile(allocator, std.testing.io, "\\d+-\\d+");
1616 defer regex.deinit();
1717
1818 if (try regex.find("Call 555-1234 now")) |match| {
@@ -26,7 +26,7 @@ test "comprehensive: phone number pattern" {
2626
2727test "comprehensive: complex alternation" {
2828 const allocator = std.testing.allocator;
29 var regex = try Regex.compile(allocator, "cat|dog|bird");
29 var regex = try Regex.compile(allocator, std.testing.io, "cat|dog|bird");
3030 defer regex.deinit();
3131
3232 try std.testing.expect(try regex.isMatch("cat"));
@@ -37,7 +37,7 @@ test "comprehensive: complex alternation" {
3737
3838test "comprehensive: nested grouping" {
3939 const allocator = std.testing.allocator;
40 var regex = try Regex.compile(allocator, "(a(b)c)");
40 var regex = try Regex.compile(allocator, std.testing.io, "(a(b)c)");
4141 defer regex.deinit();
4242
4343 try std.testing.expect(try regex.isMatch("abc"));
@@ -46,7 +46,7 @@ test "comprehensive: nested grouping" {
4646
4747test "comprehensive: multiple quantifiers" {
4848 const allocator = std.testing.allocator;
49 var regex = try Regex.compile(allocator, "a+b*c?");
49 var regex = try Regex.compile(allocator, std.testing.io, "a+b*c?");
5050 defer regex.deinit();
5151
5252 try std.testing.expect(try regex.isMatch("a"));
@@ -57,7 +57,7 @@ test "comprehensive: multiple quantifiers" {
5757
5858test "comprehensive: start and end anchors" {
5959 const allocator = std.testing.allocator;
60 var regex = try Regex.compile(allocator, "^hello$");
60 var regex = try Regex.compile(allocator, std.testing.io, "^hello$");
6161 defer regex.deinit();
6262
6363 try std.testing.expect(try regex.isMatch("hello"));
@@ -67,7 +67,7 @@ test "comprehensive: start and end anchors" {
6767
6868test "comprehensive: character class ranges" {
6969 const allocator = std.testing.allocator;
70 var regex = try Regex.compile(allocator, "[a-z]+");
70 var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+");
7171 defer regex.deinit();
7272
7373 if (try regex.find("ABC123xyz")) |match| {
@@ -81,7 +81,7 @@ test "comprehensive: character class ranges" {
8181
8282test "comprehensive: negated character class" {
8383 const allocator = std.testing.allocator;
84 var regex = try Regex.compile(allocator, "[^0-9]+");
84 var regex = try Regex.compile(allocator, std.testing.io, "[^0-9]+");
8585 defer regex.deinit();
8686
8787 if (try regex.find("123abc456")) |match| {
@@ -95,7 +95,7 @@ test "comprehensive: negated character class" {
9595
9696test "comprehensive: findAll with multiple matches" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "\\d+");
98 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
9999 defer regex.deinit();
100100
101101 const matches = try regex.findAll(allocator, "10 cats and 20 dogs");
@@ -114,7 +114,7 @@ test "comprehensive: findAll with multiple matches" {
114114
115115test "comprehensive: replaceAll multiple occurrences" {
116116 const allocator = std.testing.allocator;
117 var regex = try Regex.compile(allocator, "\\d+");
117 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
118118 defer regex.deinit();
119119
120120 const result = try regex.replaceAll(allocator, "I have 2 cats and 3 dogs", "many");
@@ -125,7 +125,7 @@ test "comprehensive: replaceAll multiple occurrences" {
125125
126126test "comprehensive: split by whitespace pattern" {
127127 const allocator = std.testing.allocator;
128 var regex = try Regex.compile(allocator, "\\s+");
128 var regex = try Regex.compile(allocator, std.testing.io, "\\s+");
129129 defer regex.deinit();
130130
131131 const parts = try regex.split(allocator, "hello world test");
@@ -139,7 +139,7 @@ test "comprehensive: split by whitespace pattern" {
139139
140140test "comprehensive: dot matches any character" {
141141 const allocator = std.testing.allocator;
142 var regex = try Regex.compile(allocator, "a.c");
142 var regex = try Regex.compile(allocator, std.testing.io, "a.c");
143143 defer regex.deinit();
144144
145145 try std.testing.expect(try regex.isMatch("abc"));
@@ -150,7 +150,7 @@ test "comprehensive: dot matches any character" {
150150
151151test "comprehensive: escape special characters" {
152152 const allocator = std.testing.allocator;
153 var regex = try Regex.compile(allocator, "\\$\\d+\\.\\d+");
153 var regex = try Regex.compile(allocator, std.testing.io, "\\$\\d+\\.\\d+");
154154 defer regex.deinit();
155155
156156 if (try regex.find("Price: $19.99")) |match| {
@@ -164,7 +164,7 @@ test "comprehensive: escape special characters" {
164164
165165test "comprehensive: word boundary" {
166166 const allocator = std.testing.allocator;
167 var regex = try Regex.compile(allocator, "\\bword\\b");
167 var regex = try Regex.compile(allocator, std.testing.io, "\\bword\\b");
168168 defer regex.deinit();
169169
170170 if (try regex.find("a word here")) |match| {
tests/edge_cases.zigmodified+25-25
Changes to tests/edge_cases.zig
@@ -5,7 +5,7 @@ const RegexError = .RegexError;
55// Edge case: Empty input
66test "edge: empty input with star" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "a*");
8 var regex = try Regex.compile(allocator, std.testing.io, "a*");
99 defer regex.deinit();
1010
1111 try std.testing.expect(try regex.isMatch(""));
@@ -13,7 +13,7 @@ test "edge: empty input with star" {
1313
1414test "edge: empty input with plus should fail" {
1515 const allocator = std.testing.allocator;
16 var regex = try Regex.compile(allocator, "a+");
16 var regex = try Regex.compile(allocator, std.testing.io, "a+");
1717 defer regex.deinit();
1818
1919 try std.testing.expect(!try regex.isMatch(""));
@@ -22,7 +22,7 @@ test "edge: empty input with plus should fail" {
2222// Edge case: Single character patterns
2323test "edge: single character literal" {
2424 const allocator = std.testing.allocator;
25 var regex = try Regex.compile(allocator, "x");
25 var regex = try Regex.compile(allocator, std.testing.io, "x");
2626 defer regex.deinit();
2727
2828 try std.testing.expect(try regex.isMatch("x"));
@@ -33,7 +33,7 @@ test "edge: single character literal" {
3333// Edge case: Nested quantifiers behavior
3434test "edge: star after plus" {
3535 const allocator = std.testing.allocator;
36 var regex = try Regex.compile(allocator, "a+b*");
36 var regex = try Regex.compile(allocator, std.testing.io, "a+b*");
3737 defer regex.deinit();
3838
3939 try std.testing.expect(try regex.isMatch("a"));
@@ -44,7 +44,7 @@ test "edge: star after plus" {
4444// Edge case: Multiple alternations
4545test "edge: multiple alternations" {
4646 const allocator = std.testing.allocator;
47 var regex = try Regex.compile(allocator, "a|b|c|d");
47 var regex = try Regex.compile(allocator, std.testing.io, "a|b|c|d");
4848 defer regex.deinit();
4949
5050 try std.testing.expect(try regex.isMatch("a"));
@@ -57,7 +57,7 @@ test "edge: multiple alternations" {
5757// Edge case: Alternation with empty branch
5858test "edge: alternation with quantifiers" {
5959 const allocator = std.testing.allocator;
60 var regex = try Regex.compile(allocator, "a*|b+");
60 var regex = try Regex.compile(allocator, std.testing.io, "a*|b+");
6161 defer regex.deinit();
6262
6363 try std.testing.expect(try regex.isMatch(""));
@@ -68,7 +68,7 @@ test "edge: alternation with quantifiers" {
6868// Edge case: Complex nested groups
6969test "edge: nested groups" {
7070 const allocator = std.testing.allocator;
71 var regex = try Regex.compile(allocator, "((a))");
71 var regex = try Regex.compile(allocator, std.testing.io, "((a))");
7272 defer regex.deinit();
7373
7474 try std.testing.expect(try regex.isMatch("a"));
@@ -77,7 +77,7 @@ test "edge: nested groups" {
7777// Edge case: Character class edge cases
7878test "edge: empty character class range" {
7979 const allocator = std.testing.allocator;
80 var regex = try Regex.compile(allocator, "[a]");
80 var regex = try Regex.compile(allocator, std.testing.io, "[a]");
8181 defer regex.deinit();
8282
8383 try std.testing.expect(try regex.isMatch("a"));
@@ -86,7 +86,7 @@ test "edge: empty character class range" {
8686
8787test "edge: character class with single range" {
8888 const allocator = std.testing.allocator;
89 var regex = try Regex.compile(allocator, "[a-a]");
89 var regex = try Regex.compile(allocator, std.testing.io, "[a-a]");
9090 defer regex.deinit();
9191
9292 try std.testing.expect(try regex.isMatch("a"));
@@ -95,7 +95,7 @@ test "edge: character class with single range" {
9595
9696test "edge: negated character class matching nothing in range" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "[^a-z]");
98 var regex = try Regex.compile(allocator, std.testing.io, "[^a-z]");
9999 defer regex.deinit();
100100
101101 try std.testing.expect(try regex.isMatch("A"));
@@ -106,7 +106,7 @@ test "edge: negated character class matching nothing in range" {
106106// Edge case: Anchors at different positions
107107test "edge: start anchor not at beginning" {
108108 const allocator = std.testing.allocator;
109 var regex = try Regex.compile(allocator, "a^b");
109 var regex = try Regex.compile(allocator, std.testing.io, "a^b");
110110 defer regex.deinit();
111111
112112 // This should not match anything since ^ is not at the start
@@ -115,7 +115,7 @@ test "edge: start anchor not at beginning" {
115115
116116test "edge: end anchor not at end" {
117117 const allocator = std.testing.allocator;
118 var regex = try Regex.compile(allocator, "a$b");
118 var regex = try Regex.compile(allocator, std.testing.io, "a$b");
119119 defer regex.deinit();
120120
121121 // This should not match anything since $ is not at the end
@@ -125,7 +125,7 @@ test "edge: end anchor not at end" {
125125// Edge case: Dot matching
126126test "edge: dot with quantifiers" {
127127 const allocator = std.testing.allocator;
128 var regex = try Regex.compile(allocator, ".*");
128 var regex = try Regex.compile(allocator, std.testing.io, ".*");
129129 defer regex.deinit();
130130
131131 try std.testing.expect(try regex.isMatch(""));
@@ -136,7 +136,7 @@ test "edge: dot with quantifiers" {
136136// Edge case: Escape sequences
137137test "edge: escaped backslash" {
138138 const allocator = std.testing.allocator;
139 var regex = try Regex.compile(allocator, "\\\\");
139 var regex = try Regex.compile(allocator, std.testing.io, "\\\\");
140140 defer regex.deinit();
141141
142142 try std.testing.expect(try regex.isMatch("\\"));
@@ -145,7 +145,7 @@ test "edge: escaped backslash" {
145145
146146test "edge: escaped dot" {
147147 const allocator = std.testing.allocator;
148 var regex = try Regex.compile(allocator, "\\.");
148 var regex = try Regex.compile(allocator, std.testing.io, "\\.");
149149 defer regex.deinit();
150150
151151 try std.testing.expect(try regex.isMatch("."));
@@ -155,7 +155,7 @@ test "edge: escaped dot" {
155155// Edge case: findAll edge cases
156156test "edge: findAll with no matches" {
157157 const allocator = std.testing.allocator;
158 var regex = try Regex.compile(allocator, "x");
158 var regex = try Regex.compile(allocator, std.testing.io, "x");
159159 defer regex.deinit();
160160
161161 const matches = try regex.findAll(allocator, "aaabbb");
@@ -166,7 +166,7 @@ test "edge: findAll with no matches" {
166166
167167test "edge: findAll with overlapping potential matches" {
168168 const allocator = std.testing.allocator;
169 var regex = try Regex.compile(allocator, "a+");
169 var regex = try Regex.compile(allocator, std.testing.io, "a+");
170170 defer regex.deinit();
171171
172172 const matches = try regex.findAll(allocator, "aaa");
@@ -186,7 +186,7 @@ test "edge: findAll with overlapping potential matches" {
186186// Edge case: replace edge cases
187187test "edge: replace with empty string" {
188188 const allocator = std.testing.allocator;
189 var regex = try Regex.compile(allocator, "a");
189 var regex = try Regex.compile(allocator, std.testing.io, "a");
190190 defer regex.deinit();
191191
192192 const result = try regex.replace(allocator, "banana", "");
@@ -197,7 +197,7 @@ test "edge: replace with empty string" {
197197
198198test "edge: replace in empty string" {
199199 const allocator = std.testing.allocator;
200 var regex = try Regex.compile(allocator, "a");
200 var regex = try Regex.compile(allocator, std.testing.io, "a");
201201 defer regex.deinit();
202202
203203 const result = try regex.replace(allocator, "", "x");
@@ -209,7 +209,7 @@ test "edge: replace in empty string" {
209209// Edge case: split edge cases
210210test "edge: split empty string" {
211211 const allocator = std.testing.allocator;
212 var regex = try Regex.compile(allocator, ",");
212 var regex = try Regex.compile(allocator, std.testing.io, ",");
213213 defer regex.deinit();
214214
215215 const parts = try regex.split(allocator, "");
@@ -221,7 +221,7 @@ test "edge: split empty string" {
221221
222222test "edge: split with no delimiter found" {
223223 const allocator = std.testing.allocator;
224 var regex = try Regex.compile(allocator, ",");
224 var regex = try Regex.compile(allocator, std.testing.io, ",");
225225 defer regex.deinit();
226226
227227 const parts = try regex.split(allocator, "hello");
@@ -234,7 +234,7 @@ test "edge: split with no delimiter found" {
234234// Edge case: Word boundaries
235235test "edge: word boundary at start" {
236236 const allocator = std.testing.allocator;
237 var regex = try Regex.compile(allocator, "\\bword");
237 var regex = try Regex.compile(allocator, std.testing.io, "\\bword");
238238 defer regex.deinit();
239239
240240 if (try regex.find("word here")) |match| {
@@ -248,7 +248,7 @@ test "edge: word boundary at start" {
248248
249249test "edge: word boundary at end" {
250250 const allocator = std.testing.allocator;
251 var regex = try Regex.compile(allocator, "word\\b");
251 var regex = try Regex.compile(allocator, std.testing.io, "word\\b");
252252 defer regex.deinit();
253253
254254 if (try regex.find("a word")) |match| {
@@ -263,7 +263,7 @@ test "edge: word boundary at end" {
263263// Edge case: Character classes with special chars
264264test "edge: whitespace character class" {
265265 const allocator = std.testing.allocator;
266 var regex = try Regex.compile(allocator, "\\s+");
266 var regex = try Regex.compile(allocator, std.testing.io, "\\s+");
267267 defer regex.deinit();
268268
269269 if (try regex.find("hello world")) |match| {
@@ -277,7 +277,7 @@ test "edge: whitespace character class" {
277277
278278test "edge: digit character class at boundaries" {
279279 const allocator = std.testing.allocator;
280 var regex = try Regex.compile(allocator, "^\\d+$");
280 var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$");
281281 defer regex.deinit();
282282
283283 try std.testing.expect(try regex.isMatch("123"));
tests/fuzz.zigmodified+14-14
formatting only · 8 hidden lines
Changes to tests/fuzz.zig
@@ -21,7 +21,7 @@ test "fuzz: random valid patterns" { formatting only
2121 const char_choice = random.intRangeAtMost(u8, 0, 4);
2222 pattern_buf[j] = switch (char_choice) {
2323 0 => 'a' + random.intRangeAtMost(u8, 0, 25), // a-z
24 1 => '0' + random.intRangeAtMost(u8, 0, 9), // 0-9
24 1 => '0' + random.intRangeAtMost(u8, 0, 9), // 0-9
2525 2 => '.', // wildcard
2626 3 => '*', // quantifier
2727 else => '+', // quantifier
formatting only · 30 hidden lines
Changes to tests/fuzz.zig
@@ -129,18 +129,18 @@ test "fuzz: malformed patterns should return errors" { formatting only
129129 const allocator = std.testing.allocator;
130130
131131 const bad_patterns = [_][]const u8{
132 "(abc", // Unmatched paren
133 "abc)", // Unmatched paren
134 "[abc", // Unmatched bracket
135 "abc]", // Unmatched bracket
136 "*", // Quantifier without target
137 "+abc", // Quantifier without target
138 "?abc", // Quantifier without target
139 "{2,3}", // Quantifier without target
140 "\\", // Incomplete escape
141 "[z-a]", // Invalid range
142 "{-1,5}", // Invalid repetition
143 "{5,2}", // Invalid repetition (min > max)
132 "(abc", // Unmatched paren
133 "abc)", // Unmatched paren
134 "[abc", // Unmatched bracket
135 "abc]", // Unmatched bracket
136 "*", // Quantifier without target
137 "+abc", // Quantifier without target
138 "?abc", // Quantifier without target
139 "{2,3}", // Quantifier without target
140 "\\", // Incomplete escape
141 "[z-a]", // Invalid range
142 "{-1,5}", // Invalid repetition
143 "{5,2}", // Invalid repetition (min > max)
144144 };
145145
146146 for (bad_patterns) |pattern| {
Changes to tests/fuzz.zig
@@ -180,7 +180,7 @@ test "fuzz: edge cases with empty strings" {
180180test "fuzz: long input strings" {
181181 const allocator = std.testing.allocator;
182182
183 var regex = try Regex.compile(allocator, "a*b+c?");
183 var regex = try Regex.compile(allocator, std.testing.io, "a*b+c?");
184184 defer regex.deinit();
185185
186186 // Create progressively longer inputs
tests/integration.zigmodified+22-22
Changes to tests/integration.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55test "integration: email validation - basic pattern" {
66 const allocator = std.testing.allocator;
77 // Simplified email pattern: word@word.word
8 var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+");
8 var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+\\.\\w+");
99 defer regex.deinit();
1010
1111 try std.testing.expect(try regex.isMatch("user@example.com"));
@@ -16,7 +16,7 @@ test "integration: email validation - basic pattern" {
1616
1717test "integration: email validation - extract from text" {
1818 const allocator = std.testing.allocator;
19 var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+");
19 var regex = try Regex.compile(allocator, std.testing.io, "\\w+@\\w+\\.\\w+");
2020 defer regex.deinit();
2121
2222 const text = "Contact support@example.com for help";
@@ -33,7 +33,7 @@ test "integration: email validation - extract from text" {
3333test "integration: URL extraction from text" {
3434 const allocator = std.testing.allocator;
3535 // Simplified: just match http:// or https:// followed by word.word
36 var regex = try Regex.compile(allocator, "https://\\w+\\.\\w+");
36 var regex = try Regex.compile(allocator, std.testing.io, "https://\\w+\\.\\w+");
3737 defer regex.deinit();
3838
3939 const text = "Visit https://example.com for more info.";
@@ -49,7 +49,7 @@ test "integration: URL extraction from text" {
4949// Phone Number Tests
5050test "integration: US phone number validation" {
5151 const allocator = std.testing.allocator;
52 var regex = try Regex.compile(allocator, "^\\d{3}-\\d{3}-\\d{4}$");
52 var regex = try Regex.compile(allocator, std.testing.io, "^\\d{3}-\\d{3}-\\d{4}$");
5353 defer regex.deinit();
5454
5555 try std.testing.expect(try regex.isMatch("555-123-4567"));
@@ -60,7 +60,7 @@ test "integration: US phone number validation" {
6060
6161test "integration: extract phone numbers from text" {
6262 const allocator = std.testing.allocator;
63 var regex = try Regex.compile(allocator, "\\d{3}-\\d{3}-\\d{4}");
63 var regex = try Regex.compile(allocator, std.testing.io, "\\d{3}-\\d{3}-\\d{4}");
6464 defer regex.deinit();
6565
6666 const text = "Call 555-123-4567 or 800-555-9876 for support.";
@@ -81,7 +81,7 @@ test "integration: extract phone numbers from text" {
8181// Date/Time Patterns
8282test "integration: ISO date format" {
8383 const allocator = std.testing.allocator;
84 var regex = try Regex.compile(allocator, "^\\d{4}-\\d{2}-\\d{2}$");
84 var regex = try Regex.compile(allocator, std.testing.io, "^\\d{4}-\\d{2}-\\d{2}$");
8585 defer regex.deinit();
8686
8787 try std.testing.expect(try regex.isMatch("2025-01-26"));
@@ -92,7 +92,7 @@ test "integration: ISO date format" {
9292
9393test "integration: time format HH:MM:SS" {
9494 const allocator = std.testing.allocator;
95 var regex = try Regex.compile(allocator, "^\\d{2}:\\d{2}:\\d{2}$");
95 var regex = try Regex.compile(allocator, std.testing.io, "^\\d{2}:\\d{2}:\\d{2}$");
9696 defer regex.deinit();
9797
9898 try std.testing.expect(try regex.isMatch("14:30:45"));
@@ -106,7 +106,7 @@ test "integration: time format HH:MM:SS" {
106106test "integration: extract log levels" {
107107 const allocator = std.testing.allocator;
108108 // Match INFO specifically (alternation with 4 options seems to cause overflow)
109 var regex = try Regex.compile(allocator, "INFO");
109 var regex = try Regex.compile(allocator, std.testing.io, "INFO");
110110 defer regex.deinit();
111111
112112 try std.testing.expect(try regex.isMatch("[INFO] Application started"));
@@ -115,7 +115,7 @@ test "integration: extract log levels" {
115115
116116test "integration: parse log timestamp and message" {
117117 const allocator = std.testing.allocator;
118 var regex = try Regex.compile(allocator, "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
118 var regex = try Regex.compile(allocator, std.testing.io, "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
119119 defer regex.deinit();
120120
121121 const log = "[2025-01-26 14:30:45] User login successful";
@@ -132,7 +132,7 @@ test "integration: parse log timestamp and message" {
132132test "integration: IPv4 address matching" {
133133 const allocator = std.testing.allocator;
134134 // Simplified pattern (doesn't validate ranges)
135 var regex = try Regex.compile(allocator, "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
135 var regex = try Regex.compile(allocator, std.testing.io, "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$");
136136 defer regex.deinit();
137137
138138 try std.testing.expect(try regex.isMatch("192.168.1.1"));
@@ -145,7 +145,7 @@ test "integration: IPv4 address matching" {
145145test "integration: password strength - minimum length" {
146146 const allocator = std.testing.allocator;
147147 // Check if string has at least 8 characters (simplified - just check length differently)
148 var regex = try Regex.compile(allocator, ".{8}");
148 var regex = try Regex.compile(allocator, std.testing.io, ".{8}");
149149 defer regex.deinit();
150150
151151 try std.testing.expect(try regex.isMatch("password123")); // has 8+ chars
@@ -155,7 +155,7 @@ test "integration: password strength - minimum length" {
155155
156156test "integration: password contains digit" {
157157 const allocator = std.testing.allocator;
158 var regex = try Regex.compile(allocator, "\\d");
158 var regex = try Regex.compile(allocator, std.testing.io, "\\d");
159159 defer regex.deinit();
160160
161161 try std.testing.expect(try regex.isMatch("password1"));
@@ -167,7 +167,7 @@ test "integration: password contains digit" {
167167test "integration: username alphanumeric with underscores" {
168168 const allocator = std.testing.allocator;
169169 // Simplified - removed anchors and large quantifier bound to avoid crash
170 var regex = try Regex.compile(allocator, "[a-zA-Z0-9_]{3}");
170 var regex = try Regex.compile(allocator, std.testing.io, "[a-zA-Z0-9_]{3}");
171171 defer regex.deinit();
172172
173173 try std.testing.expect(try regex.isMatch("user123"));
@@ -179,7 +179,7 @@ test "integration: username alphanumeric with underscores" {
179179// Hashtag Extraction
180180test "integration: extract hashtags from text" {
181181 const allocator = std.testing.allocator;
182 var regex = try Regex.compile(allocator, "#\\w+");
182 var regex = try Regex.compile(allocator, std.testing.io, "#\\w+");
183183 defer regex.deinit();
184184
185185 const tweet = "Loving #Zig programming! #opensource #systems";
@@ -202,7 +202,7 @@ test "integration: extract hashtags from text" {
202202test "integration: match HTML tags" {
203203 const allocator = std.testing.allocator;
204204 // Match opening tags only (no closing tags with /)
205 var regex = try Regex.compile(allocator, "<[a-z]+>");
205 var regex = try Regex.compile(allocator, std.testing.io, "<[a-z]+>");
206206 defer regex.deinit();
207207
208208 const html = "<div>content<span>text";
@@ -223,7 +223,7 @@ test "integration: match HTML tags" {
223223// CSV Parsing
224224test "integration: split CSV line" {
225225 const allocator = std.testing.allocator;
226 var regex = try Regex.compile(allocator, ",");
226 var regex = try Regex.compile(allocator, std.testing.io, ",");
227227 defer regex.deinit();
228228
229229 const csv = "John,Doe,30,Engineer";
@@ -240,7 +240,7 @@ test "integration: split CSV line" {
240240// Whitespace Normalization
241241test "integration: replace multiple spaces with single space" {
242242 const allocator = std.testing.allocator;
243 var regex = try Regex.compile(allocator, "\\s+");
243 var regex = try Regex.compile(allocator, std.testing.io, "\\s+");
244244 defer regex.deinit();
245245
246246 const text = "This has too many spaces";
@@ -254,7 +254,7 @@ test "integration: replace multiple spaces with single space" {
254254test "integration: extract markdown links" {
255255 const allocator = std.testing.allocator;
256256 // Simplified pattern - just find one link
257 var regex = try Regex.compile(allocator, "\\[\\w+\\]\\(https://\\w+\\.\\w+\\)");
257 var regex = try Regex.compile(allocator, std.testing.io, "\\[\\w+\\]\\(https://\\w+\\.\\w+\\)");
258258 defer regex.deinit();
259259
260260 const markdown = "Check [Zig](https://ziglang.org) for more info";
@@ -270,7 +270,7 @@ test "integration: extract markdown links" {
270270// Version Number Extraction
271271test "integration: semver version numbers" {
272272 const allocator = std.testing.allocator;
273 var regex = try Regex.compile(allocator, "\\d+\\.\\d+\\.\\d+");
273 var regex = try Regex.compile(allocator, std.testing.io, "\\d+\\.\\d+\\.\\d+");
274274 defer regex.deinit();
275275
276276 const text = "Version 1.2.3 released. Upgrade from 1.0.0 required.";
@@ -291,7 +291,7 @@ test "integration: semver version numbers" {
291291// Hex Color Codes
292292test "integration: match hex color codes" {
293293 const allocator = std.testing.allocator;
294 var regex = try Regex.compile(allocator, "#[0-9a-fA-F]{6}");
294 var regex = try Regex.compile(allocator, std.testing.io, "#[0-9a-fA-F]{6}");
295295 defer regex.deinit();
296296
297297 const css = "color: #FF5733; background: #FFFFFF;";
@@ -312,7 +312,7 @@ test "integration: match hex color codes" {
312312// Code Comment Extraction
313313test "integration: extract single-line comments" {
314314 const allocator = std.testing.allocator;
315 var regex = try Regex.compile(allocator, "//.*");
315 var regex = try Regex.compile(allocator, std.testing.io, "//.*");
316316 defer regex.deinit();
317317
318318 const code = "const x = 5; // This is a comment";
@@ -328,7 +328,7 @@ test "integration: extract single-line comments" {
328328// Environment Variable Format
329329test "integration: match environment variables" {
330330 const allocator = std.testing.allocator;
331 var regex = try Regex.compile(allocator, "\\$[A-Z_]+");
331 var regex = try Regex.compile(allocator, std.testing.io, "\\$[A-Z_]+");
332332 defer regex.deinit();
333333
334334 const script = "export PATH=$HOME/bin:$PATH";
tests/iterator.zigmodified+9-9
Changes to tests/iterator.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "iterator: basic iteration" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "\\d+");
8 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
99 defer regex.deinit();
1010
1111 const text = "123 abc 456 def 789";
@@ -29,7 +29,7 @@ test "iterator: basic iteration" {
2929
3030test "iterator: empty input" {
3131 const allocator = std.testing.allocator;
32 var regex = try Regex.compile(allocator, "\\d+");
32 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
3333 defer regex.deinit();
3434
3535 const text = "";
@@ -41,7 +41,7 @@ test "iterator: empty input" {
4141
4242test "iterator: no matches" {
4343 const allocator = std.testing.allocator;
44 var regex = try Regex.compile(allocator, "\\d+");
44 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
4545 defer regex.deinit();
4646
4747 const text = "abc def";
@@ -53,7 +53,7 @@ test "iterator: no matches" {
5353
5454test "iterator: reset functionality" {
5555 const allocator = std.testing.allocator;
56 var regex = try Regex.compile(allocator, "\\w+");
56 var regex = try Regex.compile(allocator, std.testing.io, "\\w+");
5757 defer regex.deinit();
5858
5959 const text = "hello world";
@@ -72,7 +72,7 @@ test "iterator: reset functionality" {
7272
7373test "iterator: captures in iteration" {
7474 const allocator = std.testing.allocator;
75 var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)");
75 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)");
7676 defer regex.deinit();
7777
7878 const text = "user@example and admin@test";
@@ -94,7 +94,7 @@ test "iterator: captures in iteration" {
9494
9595test "iterator: large input streaming" {
9696 const allocator = std.testing.allocator;
97 var regex = try Regex.compile(allocator, "\\d+");
97 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
9898 defer regex.deinit();
9999
100100 // Simulate processing large input without loading all matches
@@ -115,7 +115,7 @@ test "iterator: large input streaming" {
115115
116116test "iterator: manual loop pattern" {
117117 const allocator = std.testing.allocator;
118 var regex = try Regex.compile(allocator, "[a-z]+");
118 var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+");
119119 defer regex.deinit();
120120
121121 const text = "hello world test";
@@ -146,7 +146,7 @@ test "iterator: manual loop pattern" {
146146
147147test "iterator: memory efficiency" {
148148 const allocator = std.testing.allocator;
149 var regex = try Regex.compile(allocator, "\\w+");
149 var regex = try Regex.compile(allocator, std.testing.io, "\\w+");
150150 defer regex.deinit();
151151
152152 // Iterator should not allocate all matches at once
@@ -171,7 +171,7 @@ test "iterator: memory efficiency" {
171171
172172test "iterator: overlapping matches prevention" {
173173 const allocator = std.testing.allocator;
174 var regex = try Regex.compile(allocator, "\\w\\w");
174 var regex = try Regex.compile(allocator, std.testing.io, "\\w\\w");
175175 defer regex.deinit();
176176
177177 const text = "abcd";
tests/lazy_quantifiers.zigmodified+23-23
Changes to tests/lazy_quantifiers.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "lazy star: a*? matches minimal" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "a*?b");
8 var regex = try Regex.compile(allocator, std.testing.io, "a*?b");
99 defer regex.deinit();
1010
1111 // Greedy would match "aaab", lazy should match "b"
@@ -22,7 +22,7 @@ test "lazy star vs greedy star" {
2222 const allocator = std.testing.allocator;
2323
2424 // Greedy star
25 var greedy = try Regex.compile(allocator, "a*b");
25 var greedy = try Regex.compile(allocator, std.testing.io, "a*b");
2626 defer greedy.deinit();
2727
2828 if (try greedy.find("aaab")) |match| {
@@ -35,7 +35,7 @@ test "lazy star vs greedy star" {
3535 }
3636
3737 // Lazy star
38 var lazy = try Regex.compile(allocator, "a*?b");
38 var lazy = try Regex.compile(allocator, std.testing.io, "a*?b");
3939 defer lazy.deinit();
4040
4141 if (try lazy.find("aaab")) |match| {
@@ -50,7 +50,7 @@ test "lazy star vs greedy star" {
5050
5151test "lazy plus: a+? matches minimal (at least one)" {
5252 const allocator = std.testing.allocator;
53 var regex = try Regex.compile(allocator, "a+?b");
53 var regex = try Regex.compile(allocator, std.testing.io, "a+?b");
5454 defer regex.deinit();
5555
5656 // Lazy plus must match at least one 'a'
@@ -67,7 +67,7 @@ test "lazy plus vs greedy plus" {
6767 const allocator = std.testing.allocator;
6868
6969 // Greedy plus
70 var greedy = try Regex.compile(allocator, "a+b");
70 var greedy = try Regex.compile(allocator, std.testing.io, "a+b");
7171 defer greedy.deinit();
7272
7373 if (try greedy.find("aaab")) |match| {
@@ -80,7 +80,7 @@ test "lazy plus vs greedy plus" {
8080 }
8181
8282 // Lazy plus
83 var lazy = try Regex.compile(allocator, "a+?b");
83 var lazy = try Regex.compile(allocator, std.testing.io, "a+?b");
8484 defer lazy.deinit();
8585
8686 if (try lazy.find("aaab")) |match| {
@@ -95,7 +95,7 @@ test "lazy plus vs greedy plus" {
9595
9696test "lazy optional: a?? matches minimal (zero)" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "a??b");
98 var regex = try Regex.compile(allocator, std.testing.io, "a??b");
9999 defer regex.deinit();
100100
101101 // Lazy optional prefers zero matches
@@ -112,7 +112,7 @@ test "lazy optional vs greedy optional" {
112112 const allocator = std.testing.allocator;
113113
114114 // Greedy optional
115 var greedy = try Regex.compile(allocator, "a?b");
115 var greedy = try Regex.compile(allocator, std.testing.io, "a?b");
116116 defer greedy.deinit();
117117
118118 if (try greedy.find("ab")) |match| {
@@ -125,7 +125,7 @@ test "lazy optional vs greedy optional" {
125125 }
126126
127127 // Lazy optional
128 var lazy = try Regex.compile(allocator, "a??b");
128 var lazy = try Regex.compile(allocator, std.testing.io, "a??b");
129129 defer lazy.deinit();
130130
131131 if (try lazy.find("ab")) |match| {
@@ -140,7 +140,7 @@ test "lazy optional vs greedy optional" {
140140
141141test "lazy repeat: a{2,4}? matches minimal" {
142142 const allocator = std.testing.allocator;
143 var regex = try Regex.compile(allocator, "a{2,4}?b");
143 var regex = try Regex.compile(allocator, std.testing.io, "a{2,4}?b");
144144 defer regex.deinit();
145145
146146 // Lazy repeat matches minimum (2 'a's)
@@ -157,7 +157,7 @@ test "lazy repeat vs greedy repeat" {
157157 const allocator = std.testing.allocator;
158158
159159 // Greedy repeat
160 var greedy = try Regex.compile(allocator, "a{2,4}b");
160 var greedy = try Regex.compile(allocator, std.testing.io, "a{2,4}b");
161161 defer greedy.deinit();
162162
163163 if (try greedy.find("aaaaab")) |match| {
@@ -170,7 +170,7 @@ test "lazy repeat vs greedy repeat" {
170170 }
171171
172172 // Lazy repeat
173 var lazy = try Regex.compile(allocator, "a{2,4}?b");
173 var lazy = try Regex.compile(allocator, std.testing.io, "a{2,4}?b");
174174 defer lazy.deinit();
175175
176176 if (try lazy.find("aaaaab")) |match| {
@@ -185,7 +185,7 @@ test "lazy repeat vs greedy repeat" {
185185
186186test "lazy quantifier in alternation" {
187187 const allocator = std.testing.allocator;
188 var regex = try Regex.compile(allocator, "a*?b|c");
188 var regex = try Regex.compile(allocator, std.testing.io, "a*?b|c");
189189 defer regex.deinit();
190190
191191 try std.testing.expect(try regex.isMatch("b"));
@@ -195,7 +195,7 @@ test "lazy quantifier in alternation" {
195195
196196test "multiple lazy quantifiers" {
197197 const allocator = std.testing.allocator;
198 var regex = try Regex.compile(allocator, "a*?b+?c");
198 var regex = try Regex.compile(allocator, std.testing.io, "a*?b+?c");
199199 defer regex.deinit();
200200
201201 if (try regex.find("aaabbbbc")) |match| {
@@ -210,7 +210,7 @@ test "multiple lazy quantifiers" {
210210
211211test "lazy quantifier with character class" {
212212 const allocator = std.testing.allocator;
213 var regex = try Regex.compile(allocator, "[a-z]+?\\d");
213 var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+?\\d");
214214 defer regex.deinit();
215215
216216 if (try regex.find("abc123")) |match| {
@@ -225,7 +225,7 @@ test "lazy quantifier with character class" {
225225
226226test "lazy star with dot" {
227227 const allocator = std.testing.allocator;
228 var regex = try Regex.compile(allocator, ".*?x");
228 var regex = try Regex.compile(allocator, std.testing.io, ".*?x");
229229 defer regex.deinit();
230230
231231 if (try regex.find("abcxyz")) |match| {
@@ -240,7 +240,7 @@ test "lazy star with dot" {
240240
241241test "lazy quantifier in capture group" {
242242 const allocator = std.testing.allocator;
243 var regex = try Regex.compile(allocator, "(a+?)b");
243 var regex = try Regex.compile(allocator, std.testing.io, "(a+?)b");
244244 defer regex.deinit();
245245
246246 if (try regex.find("aaab")) |match| {
@@ -258,7 +258,7 @@ test "lazy quantifier in capture group" {
258258
259259test "lazy repeat {n,}?" {
260260 const allocator = std.testing.allocator;
261 var regex = try Regex.compile(allocator, "a{2,}?b");
261 var regex = try Regex.compile(allocator, std.testing.io, "a{2,}?b");
262262 defer regex.deinit();
263263
264264 if (try regex.find("aaaaab")) |match| {
@@ -274,7 +274,7 @@ test "lazy repeat {n,}?" {
274274test "lazy quantifier backtracking" {
275275 const allocator = std.testing.allocator;
276276 // Even though lazy, it must backtrack if necessary to match
277 var regex = try Regex.compile(allocator, "a*?aab");
277 var regex = try Regex.compile(allocator, std.testing.io, "a*?aab");
278278 defer regex.deinit();
279279
280280 if (try regex.find("aaab")) |match| {
@@ -289,7 +289,7 @@ test "lazy quantifier backtracking" {
289289
290290test "nested lazy quantifiers" {
291291 const allocator = std.testing.allocator;
292 var regex = try Regex.compile(allocator, "(a*?b+?)*?c");
292 var regex = try Regex.compile(allocator, std.testing.io, "(a*?b+?)*?c");
293293 defer regex.deinit();
294294
295295 try std.testing.expect(try regex.isMatch("c"));
@@ -299,7 +299,7 @@ test "nested lazy quantifiers" {
299299
300300test "lazy quantifier at end of pattern" {
301301 const allocator = std.testing.allocator;
302 var regex = try Regex.compile(allocator, "a+?");
302 var regex = try Regex.compile(allocator, std.testing.io, "a+?");
303303 defer regex.deinit();
304304
305305 if (try regex.find("aaa")) |match| {
@@ -318,10 +318,10 @@ test "lazy vs greedy performance comparison" {
318318 const input = "a" ** 100 ++ "b";
319319
320320 // Both should match, but with different lengths
321 var greedy = try Regex.compile(allocator, "a*b");
321 var greedy = try Regex.compile(allocator, std.testing.io, "a*b");
322322 defer greedy.deinit();
323323
324 var lazy = try Regex.compile(allocator, "a*?b");
324 var lazy = try Regex.compile(allocator, std.testing.io, "a*?b");
325325 defer lazy.deinit();
326326
327327 // Greedy matches all 100 'a's + 'b'
tests/lookahead.zigmodified+11-11
Changes to tests/lookahead.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33
44test "positive lookahead: basic (?=...)" {
55 const allocator = std.testing.allocator;
6 var regex = try Regex.compile(allocator, "foo(?=bar)");
6 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)");
77 defer regex.deinit();
88
99 // Should match "foo" only when followed by "bar"
@@ -24,7 +24,7 @@ test "positive lookahead: basic (?=...)" {
2424
2525test "positive lookahead: at end of string" {
2626 const allocator = std.testing.allocator;
27 var regex = try Regex.compile(allocator, "foo(?=bar)bar");
27 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar)bar");
2828 defer regex.deinit();
2929
3030 try std.testing.expect(try regex.isMatch("foobar"));
@@ -40,7 +40,7 @@ test "positive lookahead: at end of string" {
4040
4141test "positive lookahead: with alternation" {
4242 const allocator = std.testing.allocator;
43 var regex = try Regex.compile(allocator, "foo(?=bar|baz)");
43 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar|baz)");
4444 defer regex.deinit();
4545
4646 try std.testing.expect(try regex.isMatch("foobar"));
@@ -50,7 +50,7 @@ test "positive lookahead: with alternation" {
5050
5151test "negative lookahead: basic (?!...)" {
5252 const allocator = std.testing.allocator;
53 var regex = try Regex.compile(allocator, "foo(?!bar)");
53 var regex = try Regex.compile(allocator, std.testing.io, "foo(?!bar)");
5454 defer regex.deinit();
5555
5656 // Should match "foo" when NOT followed by "bar"
@@ -68,7 +68,7 @@ test "negative lookahead: basic (?!...)" {
6868
6969test "negative lookahead: with character class" {
7070 const allocator = std.testing.allocator;
71 var regex = try Regex.compile(allocator, "\\d+(?!\\d)");
71 var regex = try Regex.compile(allocator, std.testing.io, "\\d+(?!\\d)");
7272 defer regex.deinit();
7373
7474 // Should match digits not followed by another digit
@@ -84,7 +84,7 @@ test "negative lookahead: with character class" {
8484test "multiple lookaheads" {
8585 const allocator = std.testing.allocator;
8686 // Password validation: at least one digit AND at least one letter
87 var regex = try Regex.compile(allocator, "(?=.*\\d)(?=.*[a-z]).+");
87 var regex = try Regex.compile(allocator, std.testing.io, "(?=.*\\d)(?=.*[a-z]).+");
8888 defer regex.deinit();
8989
9090 try std.testing.expect(try regex.isMatch("abc123"));
@@ -95,7 +95,7 @@ test "multiple lookaheads" {
9595
9696test "lookahead in alternation" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "(?=foo)|(?=bar)");
98 var regex = try Regex.compile(allocator, std.testing.io, "(?=foo)|(?=bar)");
9999 defer regex.deinit();
100100
101101 try std.testing.expect(try regex.isMatch("foo"));
@@ -104,7 +104,7 @@ test "lookahead in alternation" {
104104
105105test "lookahead with quantifiers" {
106106 const allocator = std.testing.allocator;
107 var regex = try Regex.compile(allocator, "a+(?=b+)");
107 var regex = try Regex.compile(allocator, std.testing.io, "a+(?=b+)");
108108 defer regex.deinit();
109109
110110 if (try regex.find("aaabbb")) |match| {
@@ -118,7 +118,7 @@ test "lookahead with quantifiers" {
118118
119119test "nested lookahead" {
120120 const allocator = std.testing.allocator;
121 var regex = try Regex.compile(allocator, "foo(?=bar(?=baz))");
121 var regex = try Regex.compile(allocator, std.testing.io, "foo(?=bar(?=baz))");
122122 defer regex.deinit();
123123
124124 try std.testing.expect(try regex.isMatch("foobarbaz"));
@@ -127,7 +127,7 @@ test "nested lookahead" {
127127
128128test "lookahead: zero-width assertion" {
129129 const allocator = std.testing.allocator;
130 var regex = try Regex.compile(allocator, "(?=a)a");
130 var regex = try Regex.compile(allocator, std.testing.io, "(?=a)a");
131131 defer regex.deinit();
132132
133133 if (try regex.find("a")) |match| {
@@ -144,7 +144,7 @@ test "lookahead: zero-width assertion" {
144144
145145test "lookahead with capture groups" {
146146 const allocator = std.testing.allocator;
147 var regex = try Regex.compile(allocator, "(\\w+)(?= world)");
147 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)(?= world)");
148148 defer regex.deinit();
149149
150150 if (try regex.find("hello world")) |match| {
tests/lookbehind.zigmodified+11-11
Changes to tests/lookbehind.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33
44test "positive lookbehind: basic (?<=...)" {
55 const allocator = std.testing.allocator;
6 var regex = try Regex.compile(allocator, "(?<=foo)bar");
6 var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo)bar");
77 defer regex.deinit();
88
99 // Should match "bar" only when preceded by "foo"
@@ -25,7 +25,7 @@ test "positive lookbehind: basic (?<=...)" {
2525
2626test "positive lookbehind: at start of pattern" {
2727 const allocator = std.testing.allocator;
28 var regex = try Regex.compile(allocator, "(?<=foo)bar");
28 var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo)bar");
2929 defer regex.deinit();
3030
3131 try std.testing.expect(try regex.isMatch("foobar"));
@@ -34,7 +34,7 @@ test "positive lookbehind: at start of pattern" {
3434
3535test "positive lookbehind: with alternation" {
3636 const allocator = std.testing.allocator;
37 var regex = try Regex.compile(allocator, "(?<=foo|baz)bar");
37 var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo|baz)bar");
3838 defer regex.deinit();
3939
4040 try std.testing.expect(try regex.isMatch("foobar"));
@@ -44,7 +44,7 @@ test "positive lookbehind: with alternation" {
4444
4545test "negative lookbehind: basic (?<!...)" {
4646 const allocator = std.testing.allocator;
47 var regex = try Regex.compile(allocator, "(?<!foo)bar");
47 var regex = try Regex.compile(allocator, std.testing.io, "(?<!foo)bar");
4848 defer regex.deinit();
4949
5050 // Should match "bar" when NOT preceded by "foo"
@@ -62,7 +62,7 @@ test "negative lookbehind: basic (?<!...)" {
6262
6363test "negative lookbehind: at string start" {
6464 const allocator = std.testing.allocator;
65 var regex = try Regex.compile(allocator, "(?<!x)bar");
65 var regex = try Regex.compile(allocator, std.testing.io, "(?<!x)bar");
6666 defer regex.deinit();
6767
6868 // "bar" at start should match (nothing before it, so not preceded by 'x')
@@ -78,7 +78,7 @@ test "negative lookbehind: at string start" {
7878
7979test "lookbehind with literal characters" {
8080 const allocator = std.testing.allocator;
81 var regex = try Regex.compile(allocator, "(?<=\\$)\\d+");
81 var regex = try Regex.compile(allocator, std.testing.io, "(?<=\\$)\\d+");
8282 defer regex.deinit();
8383
8484 // Match digits preceded by dollar sign
@@ -95,7 +95,7 @@ test "lookbehind with literal characters" {
9595
9696test "lookbehind: zero-width assertion" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "(?<=a)b");
98 var regex = try Regex.compile(allocator, std.testing.io, "(?<=a)b");
9999 defer regex.deinit();
100100
101101 if (try regex.find("ab")) |match| {
@@ -112,7 +112,7 @@ test "lookbehind: zero-width assertion" {
112112
113113test "combined lookahead and lookbehind" {
114114 const allocator = std.testing.allocator;
115 var regex = try Regex.compile(allocator, "(?<=foo)bar(?=baz)");
115 var regex = try Regex.compile(allocator, std.testing.io, "(?<=foo)bar(?=baz)");
116116 defer regex.deinit();
117117
118118 try std.testing.expect(try regex.isMatch("foobarbaz"));
@@ -132,7 +132,7 @@ test "combined lookahead and lookbehind" {
132132
133133test "lookbehind with capture groups" {
134134 const allocator = std.testing.allocator;
135 var regex = try Regex.compile(allocator, "(?<=@)(\\w+)");
135 var regex = try Regex.compile(allocator, std.testing.io, "(?<=@)(\\w+)");
136136 defer regex.deinit();
137137
138138 if (try regex.find("@username")) |match| {
@@ -148,7 +148,7 @@ test "lookbehind with capture groups" {
148148
149149test "multiple lookbehinds" {
150150 const allocator = std.testing.allocator;
151 var regex = try Regex.compile(allocator, "(?<=a)(?<=ab)c");
151 var regex = try Regex.compile(allocator, std.testing.io, "(?<=a)(?<=ab)c");
152152 defer regex.deinit();
153153
154154 try std.testing.expect(try regex.isMatch("abc"));
@@ -157,7 +157,7 @@ test "multiple lookbehinds" {
157157
158158test "lookbehind in findAll" {
159159 const allocator = std.testing.allocator;
160 var regex = try Regex.compile(allocator, "(?<=\\s)\\w+");
160 var regex = try Regex.compile(allocator, std.testing.io, "(?<=\\s)\\w+");
161161 defer regex.deinit();
162162
163163 const matches = try regex.findAll(allocator, "hello world foo bar");
tests/multiline_dotall.zigmodified+12-12
Changes to tests/multiline_dotall.zig
@@ -6,11 +6,11 @@ test "multiline flag: ^ matches after newlines" {
66 const allocator = std.testing.allocator;
77
88 // Without multiline flag
9 var regex_single = try Regex.compileWithFlags(allocator, "^test", .{ .multiline = false });
9 var regex_single = try Regex.compileWithFlags(allocator, std.testing.io, "^test", .{ .multiline = false });
1010 defer regex_single.deinit();
1111
1212 // With multiline flag
13 var regex_multi = try Regex.compileWithFlags(allocator, "^test", .{ .multiline = true });
13 var regex_multi = try Regex.compileWithFlags(allocator, std.testing.io, "^test", .{ .multiline = true });
1414 defer regex_multi.deinit();
1515
1616 const input = "line1\ntest";
@@ -26,11 +26,11 @@ test "multiline flag: $ matches before newlines" {
2626 const allocator = std.testing.allocator;
2727
2828 // Without multiline flag
29 var regex_single = try Regex.compileWithFlags(allocator, "test$", .{ .multiline = false });
29 var regex_single = try Regex.compileWithFlags(allocator, std.testing.io, "test$", .{ .multiline = false });
3030 defer regex_single.deinit();
3131
3232 // With multiline flag
33 var regex_multi = try Regex.compileWithFlags(allocator, "test$", .{ .multiline = true });
33 var regex_multi = try Regex.compileWithFlags(allocator, std.testing.io, "test$", .{ .multiline = true });
3434 defer regex_multi.deinit();
3535
3636 const input = "test\nline2";
@@ -45,7 +45,7 @@ test "multiline flag: $ matches before newlines" {
4545test "multiline flag: complex pattern" {
4646 const allocator = std.testing.allocator;
4747
48 var regex = try Regex.compileWithFlags(allocator, "^\\w+$", .{ .multiline = true });
48 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\w+$", .{ .multiline = true });
4949 defer regex.deinit();
5050
5151 // Should match each line
@@ -57,11 +57,11 @@ test "dot-all flag: . matches newlines" {
5757 const allocator = std.testing.allocator;
5858
5959 // Without dot-all flag
60 var regex_normal = try Regex.compileWithFlags(allocator, "a.b", .{ .dot_all = false });
60 var regex_normal = try Regex.compileWithFlags(allocator, std.testing.io, "a.b", .{ .dot_all = false });
6161 defer regex_normal.deinit();
6262
6363 // With dot-all flag
64 var regex_dotall = try Regex.compileWithFlags(allocator, "a.b", .{ .dot_all = true });
64 var regex_dotall = try Regex.compileWithFlags(allocator, std.testing.io, "a.b", .{ .dot_all = true });
6565 defer regex_dotall.deinit();
6666
6767 const input_with_newline = "a\nb";
@@ -80,11 +80,11 @@ test "dot-all flag: .* matches across lines" {
8080 const allocator = std.testing.allocator;
8181
8282 // Without dot-all flag
83 var regex_normal = try Regex.compileWithFlags(allocator, "start.*end", .{ .dot_all = false });
83 var regex_normal = try Regex.compileWithFlags(allocator, std.testing.io, "start.*end", .{ .dot_all = false });
8484 defer regex_normal.deinit();
8585
8686 // With dot-all flag
87 var regex_dotall = try Regex.compileWithFlags(allocator, "start.*end", .{ .dot_all = true });
87 var regex_dotall = try Regex.compileWithFlags(allocator, std.testing.io, "start.*end", .{ .dot_all = true });
8888 defer regex_dotall.deinit();
8989
9090 const input = "start\nmiddle\nend";
@@ -99,7 +99,7 @@ test "dot-all flag: .* matches across lines" {
9999test "combined multiline and dot-all flags" {
100100 const allocator = std.testing.allocator;
101101
102 var regex = try Regex.compileWithFlags(allocator, "^.*$", .{
102 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^.*$", .{
103103 .multiline = true,
104104 .dot_all = true,
105105 });
@@ -112,7 +112,7 @@ test "combined multiline and dot-all flags" {
112112test "multiline flag: multiple matches with findAll" {
113113 const allocator = std.testing.allocator;
114114
115 var regex = try Regex.compileWithFlags(allocator, "^\\d+", .{ .multiline = true });
115 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\d+", .{ .multiline = true });
116116 defer regex.deinit();
117117
118118 const matches = try regex.findAll(allocator, "123\n456\n789");
@@ -135,7 +135,7 @@ test "default flags: multiline and dot-all are false" {
135135 const allocator = std.testing.allocator;
136136
137137 // Default compile should have multiline=false and dot_all=false
138 var regex = try Regex.compile(allocator, "^test$");
138 var regex = try Regex.compile(allocator, std.testing.io, "^test$");
139139 defer regex.deinit();
140140
141141 // Should not match with newlines
tests/named_captures.zigmodified+12-12
Changes to tests/named_captures.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "named group: Python style (?P<name>...)" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "(?P<word>\\w+)");
8 var regex = try Regex.compile(allocator, std.testing.io, "(?P<word>\\w+)");
99 defer regex.deinit();
1010
1111 if (try regex.find("hello")) |match| {
@@ -33,7 +33,7 @@ test "named group: Python style (?P<name>...)" {
3333
3434test "named group: .NET/Perl style (?<name>...)" {
3535 const allocator = std.testing.allocator;
36 var regex = try Regex.compile(allocator, "(?<digit>\\d+)");
36 var regex = try Regex.compile(allocator, std.testing.io, "(?<digit>\\d+)");
3737 defer regex.deinit();
3838
3939 if (try regex.find("123")) |match| {
@@ -52,7 +52,7 @@ test "named group: .NET/Perl style (?<name>...)" {
5252
5353test "named group: multiple named groups" {
5454 const allocator = std.testing.allocator;
55 var regex = try Regex.compile(allocator, "(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})");
55 var regex = try Regex.compile(allocator, std.testing.io, "(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})");
5656 defer regex.deinit();
5757
5858 if (try regex.find("2025-10-27")) |match| {
@@ -79,7 +79,7 @@ test "named group: multiple named groups" {
7979
8080test "named group: mixed with numbered groups" {
8181 const allocator = std.testing.allocator;
82 var regex = try Regex.compile(allocator, "(\\w+)@(?P<domain>\\w+\\.\\w+)");
82 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(?P<domain>\\w+\\.\\w+)");
8383 defer regex.deinit();
8484
8585 if (try regex.find("user@example.com")) |match| {
@@ -100,7 +100,7 @@ test "named group: mixed with numbered groups" {
100100
101101test "named group: non-existent name returns null" {
102102 const allocator = std.testing.allocator;
103 var regex = try Regex.compile(allocator, "(?P<word>\\w+)");
103 var regex = try Regex.compile(allocator, std.testing.io, "(?P<word>\\w+)");
104104 defer regex.deinit();
105105
106106 if (try regex.find("hello")) |match| {
@@ -119,7 +119,7 @@ test "named group: non-existent name returns null" {
119119
120120test "named group: with alternation" {
121121 const allocator = std.testing.allocator;
122 var regex = try Regex.compile(allocator, "(?P<protocol>http|https)://(?P<host>[^/]+)");
122 var regex = try Regex.compile(allocator, std.testing.io, "(?P<protocol>http|https)://(?P<host>[^/]+)");
123123 defer regex.deinit();
124124
125125 if (try regex.find("https://example.com")) |match| {
@@ -141,7 +141,7 @@ test "named group: with alternation" {
141141
142142test "named group: with quantifiers" {
143143 const allocator = std.testing.allocator;
144 var regex = try Regex.compile(allocator, "(?P<tag><[^>]+>)(?P<content>.*)");
144 var regex = try Regex.compile(allocator, std.testing.io, "(?P<tag><[^>]+>)(?P<content>.*)");
145145 defer regex.deinit();
146146
147147 if (try regex.find("<div>hello world</div>")) |match| {
@@ -163,7 +163,7 @@ test "named group: with quantifiers" {
163163
164164test "named group: nested groups" {
165165 const allocator = std.testing.allocator;
166 var regex = try Regex.compile(allocator, "(?P<outer>(inner))");
166 var regex = try Regex.compile(allocator, std.testing.io, "(?P<outer>(inner))");
167167 defer regex.deinit();
168168
169169 if (try regex.find("inner")) |match| {
@@ -186,7 +186,7 @@ test "named group: nested groups" {
186186
187187test "named group: with non-capturing group" {
188188 const allocator = std.testing.allocator;
189 var regex = try Regex.compile(allocator, "(?:prefix-)?(?P<name>\\w+)");
189 var regex = try Regex.compile(allocator, std.testing.io, "(?:prefix-)?(?P<name>\\w+)");
190190 defer regex.deinit();
191191
192192 if (try regex.find("prefix-test")) |match| {
@@ -206,7 +206,7 @@ test "named group: with non-capturing group" {
206206
207207test "named group: email parser example" {
208208 const allocator = std.testing.allocator;
209 var regex = try Regex.compile(allocator, "(?P<user>[^@]+)@(?P<domain>[^@]+)");
209 var regex = try Regex.compile(allocator, std.testing.io, "(?P<user>[^@]+)@(?P<domain>[^@]+)");
210210 defer regex.deinit();
211211
212212 if (try regex.find("john.doe@example.com")) |match| {
@@ -228,7 +228,7 @@ test "named group: email parser example" {
228228
229229test "named group: IP address parser" {
230230 const allocator = std.testing.allocator;
231 var regex = try Regex.compile(allocator, "(?P<a>\\d+)\\.(?P<b>\\d+)\\.(?P<c>\\d+)\\.(?P<d>\\d+)");
231 var regex = try Regex.compile(allocator, std.testing.io, "(?P<a>\\d+)\\.(?P<b>\\d+)\\.(?P<c>\\d+)\\.(?P<d>\\d+)");
232232 defer regex.deinit();
233233
234234 if (try regex.find("192.168.1.1")) |match| {
@@ -256,7 +256,7 @@ test "named group: IP address parser" {
256256
257257test "named group: findAll preserves names" {
258258 const allocator = std.testing.allocator;
259 var regex = try Regex.compile(allocator, "(?P<num>\\d+)");
259 var regex = try Regex.compile(allocator, std.testing.io, "(?P<num>\\d+)");
260260 defer regex.deinit();
261261
262262 var matches = try regex.findAll(allocator, "123 456 789");
tests/non_capturing_groups.zigmodified+16-16
Changes to tests/non_capturing_groups.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "non-capturing: basic non-capturing group" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "(?:\\d+)");
8 var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)");
99 defer regex.deinit();
1010
1111 const result = try regex.find("123");
@@ -22,7 +22,7 @@ test "non-capturing: basic non-capturing group" {
2222
2323test "non-capturing: mixed capturing and non-capturing" {
2424 const allocator = std.testing.allocator;
25 var regex = try Regex.compile(allocator, "(\\w+)@(?:\\w+)\\.(\\w+)");
25 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(?:\\w+)\\.(\\w+)");
2626 defer regex.deinit();
2727
2828 const result = try regex.find("user@example.com");
@@ -41,7 +41,7 @@ test "non-capturing: mixed capturing and non-capturing" {
4141
4242test "non-capturing: alternation in non-capturing group" {
4343 const allocator = std.testing.allocator;
44 var regex = try Regex.compile(allocator, "(?:cat|dog) (\\w+)");
44 var regex = try Regex.compile(allocator, std.testing.io, "(?:cat|dog) (\\w+)");
4545 defer regex.deinit();
4646
4747 const result1 = try regex.find("cat food");
@@ -69,7 +69,7 @@ test "non-capturing: alternation in non-capturing group" {
6969
7070test "non-capturing: nested non-capturing groups" {
7171 const allocator = std.testing.allocator;
72 var regex = try Regex.compile(allocator, "(?:(?:a|b)(?:c|d))");
72 var regex = try Regex.compile(allocator, std.testing.io, "(?:(?:a|b)(?:c|d))");
7373 defer regex.deinit();
7474
7575 try std.testing.expect(try regex.isMatch("ac"));
@@ -82,7 +82,7 @@ test "non-capturing: nested non-capturing groups" {
8282test "non-capturing: capture numbering with non-capturing groups" {
8383 const allocator = std.testing.allocator;
8484 // (a) (?:b) (c) - should have captures $1=a, $2=c (not $1=a, $2=b, $3=c)
85 var regex = try Regex.compile(allocator, "(a)(?:b)(c)");
85 var regex = try Regex.compile(allocator, std.testing.io, "(a)(?:b)(c)");
8686 defer regex.deinit();
8787
8888 const result = try regex.find("abc");
@@ -100,7 +100,7 @@ test "non-capturing: capture numbering with non-capturing groups" {
100100
101101test "non-capturing: with quantifiers" {
102102 const allocator = std.testing.allocator;
103 var regex = try Regex.compile(allocator, "(?:\\d+)+");
103 var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)+");
104104 defer regex.deinit();
105105
106106 try std.testing.expect(try regex.isMatch("123"));
@@ -110,7 +110,7 @@ test "non-capturing: with quantifiers" {
110110test "non-capturing: complex pattern" {
111111 const allocator = std.testing.allocator;
112112 // Simplified pattern - removed [\w.] which has parsing issues
113 var regex = try Regex.compile(allocator, "(?:https?://)?([a-z]+)(?:/\\w+)*");
113 var regex = try Regex.compile(allocator, std.testing.io, "(?:https?://)?([a-z]+)(?:/\\w+)*");
114114 defer regex.deinit();
115115
116116 const result1 = try regex.find("http://example/path/to/page");
@@ -138,7 +138,7 @@ test "non-capturing: complex pattern" {
138138
139139test "non-capturing: in replacement" {
140140 const allocator = std.testing.allocator;
141 var regex = try Regex.compile(allocator, "(?:Mr|Mrs|Ms) (\\w+)");
141 var regex = try Regex.compile(allocator, std.testing.io, "(?:Mr|Mrs|Ms) (\\w+)");
142142 defer regex.deinit();
143143
144144 const result = try regex.replace(allocator, "Hello Mr Smith", "$1");
@@ -149,7 +149,7 @@ test "non-capturing: in replacement" {
149149
150150test "non-capturing: multiple non-capturing with one capturing" {
151151 const allocator = std.testing.allocator;
152 var regex = try Regex.compile(allocator, "(?:a)(?:b)(c)(?:d)");
152 var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(?:b)(c)(?:d)");
153153 defer regex.deinit();
154154
155155 const result = try regex.find("abcd");
@@ -166,7 +166,7 @@ test "non-capturing: multiple non-capturing with one capturing" {
166166
167167test "non-capturing: with character classes" {
168168 const allocator = std.testing.allocator;
169 var regex = try Regex.compile(allocator, "(?:[a-z]+)@(\\w+)");
169 var regex = try Regex.compile(allocator, std.testing.io, "(?:[a-z]+)@(\\w+)");
170170 defer regex.deinit();
171171
172172 const result = try regex.find("user@example");
@@ -183,7 +183,7 @@ test "non-capturing: with character classes" {
183183
184184test "non-capturing: nested capturing inside non-capturing" {
185185 const allocator = std.testing.allocator;
186 var regex = try Regex.compile(allocator, "(?:(\\d+):(\\d+))");
186 var regex = try Regex.compile(allocator, std.testing.io, "(?:(\\d+):(\\d+))");
187187 defer regex.deinit();
188188
189189 const result = try regex.find("12:30");
@@ -201,7 +201,7 @@ test "non-capturing: nested capturing inside non-capturing" {
201201
202202test "non-capturing: all groups non-capturing" {
203203 const allocator = std.testing.allocator;
204 var regex = try Regex.compile(allocator, "(?:a)(?:b)(?:c)");
204 var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(?:b)(?:c)");
205205 defer regex.deinit();
206206
207207 const result = try regex.find("abc");
@@ -218,7 +218,7 @@ test "non-capturing: all groups non-capturing" {
218218
219219test "non-capturing: with anchors" {
220220 const allocator = std.testing.allocator;
221 var regex = try Regex.compile(allocator, "^(?:hello|hi) (\\w+)$");
221 var regex = try Regex.compile(allocator, std.testing.io, "^(?:hello|hi) (\\w+)$");
222222 defer regex.deinit();
223223
224224 const result = try regex.find("hello world");
@@ -237,7 +237,7 @@ test "non-capturing: with anchors" {
237237
238238test "non-capturing: replaceAll with non-capturing groups" {
239239 const allocator = std.testing.allocator;
240 var regex = try Regex.compile(allocator, "(?:the|a) (\\w+)");
240 var regex = try Regex.compile(allocator, std.testing.io, "(?:the|a) (\\w+)");
241241 defer regex.deinit();
242242
243243 const result = try regex.replaceAll(allocator, "the cat and a dog", "$1");
@@ -248,7 +248,7 @@ test "non-capturing: replaceAll with non-capturing groups" {
248248
249249test "non-capturing: empty non-capturing group" {
250250 const allocator = std.testing.allocator;
251 var regex = try Regex.compile(allocator, "a(?:)b");
251 var regex = try Regex.compile(allocator, std.testing.io, "a(?:)b");
252252 defer regex.deinit();
253253
254254 try std.testing.expect(try regex.isMatch("ab"));
@@ -258,7 +258,7 @@ test "non-capturing: verify capture count" {
258258 const allocator = std.testing.allocator;
259259
260260 // Pattern with 3 non-capturing and 2 capturing groups
261 var regex = try Regex.compile(allocator, "(?:a)(b)(?:c)(d)(?:e)");
261 var regex = try Regex.compile(allocator, std.testing.io, "(?:a)(b)(?:c)(d)(?:e)");
262262 defer regex.deinit();
263263
264264 // Should only have 2 captures, not 5
tests/parser_compiler_edge_cases.zigmodified+35-35
Changes to tests/parser_compiler_edge_cases.zig
@@ -10,13 +10,13 @@ const RegexError = .RegexError;
1010
1111test "parser: quantifier ? at start is rejected" {
1212 const allocator = std.testing.allocator;
13 const result = Regex.compile(allocator, "?abc");
13 const result = Regex.compile(allocator, std.testing.io, "?abc");
1414 try std.testing.expectError(RegexError.UnexpectedCharacter, result);
1515}
1616
1717test "parser: nested empty groups" {
1818 const allocator = std.testing.allocator;
19 var regex = try Regex.compile(allocator, "(())");
19 var regex = try Regex.compile(allocator, std.testing.io, "(())");
2020 defer regex.deinit();
2121 // Should compile without error
2222 try std.testing.expect(try regex.isMatch(""));
@@ -25,7 +25,7 @@ test "parser: nested empty groups" {
2525test "parser: deeply nested groups" {
2626 const allocator = std.testing.allocator;
2727 // 20 levels of nesting - should be within limits
28 var regex = try Regex.compile(allocator, "((((((((((((((((((((a))))))))))))))))))))");
28 var regex = try Regex.compile(allocator, std.testing.io, "((((((((((((((((((((a))))))))))))))))))))");
2929 defer regex.deinit();
3030 try std.testing.expect(try regex.isMatch("a"));
3131}
@@ -33,20 +33,20 @@ test "parser: deeply nested groups" {
3333test "parser: consecutive quantifiers rejected by analyzer" {
3434 const allocator = std.testing.allocator;
3535 // a** parses as (a*)* which is rejected as nested quantifiers
36 const result = Regex.compile(allocator, "a**");
36 const result = Regex.compile(allocator, std.testing.io, "a**");
3737 try std.testing.expectError(RegexError.PatternTooComplex, result);
3838}
3939
4040test "parser: quantifier on quantifier rejected by analyzer" {
4141 const allocator = std.testing.allocator;
4242 // a+* parses as (a+)* which is rejected as nested quantifiers
43 const result = Regex.compile(allocator, "a+*");
43 const result = Regex.compile(allocator, std.testing.io, "a+*");
4444 try std.testing.expectError(RegexError.PatternTooComplex, result);
4545}
4646
4747test "parser: empty alternation branch is valid" {
4848 const allocator = std.testing.allocator;
49 var regex = try Regex.compile(allocator, "a|");
49 var regex = try Regex.compile(allocator, std.testing.io, "a|");
5050 defer regex.deinit();
5151 // "a|" means "a or empty string" - should match empty
5252 try std.testing.expect(try regex.isMatch("a"));
@@ -55,7 +55,7 @@ test "parser: empty alternation branch is valid" {
5555
5656test "parser: leading pipe alternation" {
5757 const allocator = std.testing.allocator;
58 var regex = try Regex.compile(allocator, "|a");
58 var regex = try Regex.compile(allocator, std.testing.io, "|a");
5959 defer regex.deinit();
6060 // "|a" means "empty string or a"
6161 try std.testing.expect(try regex.isMatch(""));
@@ -66,7 +66,7 @@ test "parser: leading pipe alternation" {
6666
6767test "parser: {0} means zero occurrences" {
6868 const allocator = std.testing.allocator;
69 var regex = try Regex.compile(allocator, "^a{0}b$");
69 var regex = try Regex.compile(allocator, std.testing.io, "^a{0}b$");
7070 defer regex.deinit();
7171
7272 try std.testing.expect(try regex.isMatch("b"));
@@ -75,7 +75,7 @@ test "parser: {0} means zero occurrences" {
7575
7676test "parser: {1} means exactly once" {
7777 const allocator = std.testing.allocator;
78 var regex = try Regex.compile(allocator, "^a{1}$");
78 var regex = try Regex.compile(allocator, std.testing.io, "^a{1}$");
7979 defer regex.deinit();
8080
8181 try std.testing.expect(try regex.isMatch("a"));
@@ -86,7 +86,7 @@ test "parser: {1} means exactly once" {
8686test "parser: large but valid quantifier" {
8787 const allocator = std.testing.allocator;
8888 // {0,100} should be valid
89 var regex = try Regex.compile(allocator, "a{0,100}");
89 var regex = try Regex.compile(allocator, std.testing.io, "a{0,100}");
9090 defer regex.deinit();
9191 try std.testing.expect(try regex.isMatch(""));
9292}
@@ -95,7 +95,7 @@ test "parser: large but valid quantifier" {
9595
9696test "parser: character class with escaped ]" {
9797 const allocator = std.testing.allocator;
98 var regex = try Regex.compile(allocator, "[\\]]");
98 var regex = try Regex.compile(allocator, std.testing.io, "[\\]]");
9999 defer regex.deinit();
100100
101101 try std.testing.expect(try regex.isMatch("]"));
@@ -105,7 +105,7 @@ test "parser: character class with escaped ]" {
105105test "parser: character class with hyphen between ranges" {
106106 const allocator = std.testing.allocator;
107107 // Hyphen at start is literal, not range
108 var regex = try Regex.compile(allocator, "[-az]");
108 var regex = try Regex.compile(allocator, std.testing.io, "[-az]");
109109 defer regex.deinit();
110110
111111 try std.testing.expect(try regex.isMatch("-"));
@@ -116,7 +116,7 @@ test "parser: character class with hyphen between ranges" {
116116
117117test "parser: negated class with range" {
118118 const allocator = std.testing.allocator;
119 var regex = try Regex.compile(allocator, "^[^0-9]+$");
119 var regex = try Regex.compile(allocator, std.testing.io, "^[^0-9]+$");
120120 defer regex.deinit();
121121
122122 try std.testing.expect(try regex.isMatch("abc"));
@@ -128,7 +128,7 @@ test "parser: negated class with range" {
128128
129129test "parser: tab and newline escapes" {
130130 const allocator = std.testing.allocator;
131 var regex = try Regex.compile(allocator, "\\t");
131 var regex = try Regex.compile(allocator, std.testing.io, "\\t");
132132 defer regex.deinit();
133133
134134 try std.testing.expect(try regex.isMatch("\t"));
@@ -137,7 +137,7 @@ test "parser: tab and newline escapes" {
137137
138138test "parser: carriage return escape" {
139139 const allocator = std.testing.allocator;
140 var regex = try Regex.compile(allocator, "\\r\\n");
140 var regex = try Regex.compile(allocator, std.testing.io, "\\r\\n");
141141 defer regex.deinit();
142142
143143 try std.testing.expect(try regex.isMatch("\r\n"));
@@ -145,7 +145,7 @@ test "parser: carriage return escape" {
145145
146146test "parser: escaped caret and dollar" {
147147 const allocator = std.testing.allocator;
148 var regex = try Regex.compile(allocator, "\\^\\$");
148 var regex = try Regex.compile(allocator, std.testing.io, "\\^\\$");
149149 defer regex.deinit();
150150
151151 try std.testing.expect(try regex.isMatch("^$"));
@@ -156,7 +156,7 @@ test "parser: escaped caret and dollar" {
156156
157157test "compiler: alternation with quantifiers" {
158158 const allocator = std.testing.allocator;
159 var regex = try Regex.compile(allocator, "^(ab+|cd*)e$");
159 var regex = try Regex.compile(allocator, std.testing.io, "^(ab+|cd*)e$");
160160 defer regex.deinit();
161161
162162 try std.testing.expect(try regex.isMatch("abe"));
@@ -169,7 +169,7 @@ test "compiler: alternation with quantifiers" {
169169
170170test "compiler: character class in alternation" {
171171 const allocator = std.testing.allocator;
172 var regex = try Regex.compile(allocator, "^([0-9]+|[a-z]+)$");
172 var regex = try Regex.compile(allocator, std.testing.io, "^([0-9]+|[a-z]+)$");
173173 defer regex.deinit();
174174
175175 try std.testing.expect(try regex.isMatch("123"));
@@ -180,7 +180,7 @@ test "compiler: character class in alternation" {
180180
181181test "compiler: optional group" {
182182 const allocator = std.testing.allocator;
183 var regex = try Regex.compile(allocator, "^colou?r$");
183 var regex = try Regex.compile(allocator, std.testing.io, "^colou?r$");
184184 defer regex.deinit();
185185
186186 try std.testing.expect(try regex.isMatch("color"));
@@ -190,7 +190,7 @@ test "compiler: optional group" {
190190
191191test "compiler: optional group with parentheses" {
192192 const allocator = std.testing.allocator;
193 var regex = try Regex.compile(allocator, "^(un)?happy$");
193 var regex = try Regex.compile(allocator, std.testing.io, "^(un)?happy$");
194194 defer regex.deinit();
195195
196196 try std.testing.expect(try regex.isMatch("happy"));
@@ -200,7 +200,7 @@ test "compiler: optional group with parentheses" {
200200
201201test "compiler: multiple capture groups" {
202202 const allocator = std.testing.allocator;
203 var regex = try Regex.compile(allocator, "(\\d{4})-(\\d{2})-(\\d{2})");
203 var regex = try Regex.compile(allocator, std.testing.io, "(\\d{4})-(\\d{2})-(\\d{2})");
204204 defer regex.deinit();
205205
206206 if (try regex.find("Today is 2024-01-15!")) |match| {
@@ -220,7 +220,7 @@ test "compiler: multiple capture groups" {
220220
221221test "compiler: anchored pattern only tries position 0" {
222222 const allocator = std.testing.allocator;
223 var regex = try Regex.compile(allocator, "^abc");
223 var regex = try Regex.compile(allocator, std.testing.io, "^abc");
224224 defer regex.deinit();
225225
226226 try std.testing.expect(try regex.isMatch("abcdef"));
@@ -230,7 +230,7 @@ test "compiler: anchored pattern only tries position 0" {
230230
231231test "compiler: end-anchored pattern" {
232232 const allocator = std.testing.allocator;
233 var regex = try Regex.compile(allocator, "abc$");
233 var regex = try Regex.compile(allocator, std.testing.io, "abc$");
234234 defer regex.deinit();
235235
236236 if (try regex.find("xyzabc")) |match| {
@@ -244,7 +244,7 @@ test "compiler: end-anchored pattern" {
244244
245245test "compiler: both anchors" {
246246 const allocator = std.testing.allocator;
247 var regex = try Regex.compile(allocator, "^exact$");
247 var regex = try Regex.compile(allocator, std.testing.io, "^exact$");
248248 defer regex.deinit();
249249
250250 try std.testing.expect(try regex.isMatch("exact"));
@@ -257,13 +257,13 @@ test "compiler: both anchors" {
257257
258258test "compiler: password strength check" {
259259 const allocator = std.testing.allocator;
260 var has_digit = try Regex.compile(allocator, "\\d");
260 var has_digit = try Regex.compile(allocator, std.testing.io, "\\d");
261261 defer has_digit.deinit();
262 var has_lower = try Regex.compile(allocator, "[a-z]");
262 var has_lower = try Regex.compile(allocator, std.testing.io, "[a-z]");
263263 defer has_lower.deinit();
264 var has_upper = try Regex.compile(allocator, "[A-Z]");
264 var has_upper = try Regex.compile(allocator, std.testing.io, "[A-Z]");
265265 defer has_upper.deinit();
266 var min_length = try Regex.compile(allocator, ".{8,}");
266 var min_length = try Regex.compile(allocator, std.testing.io, ".{8,}");
267267 defer min_length.deinit();
268268
269269 const password = "MyP4ssword";
@@ -280,7 +280,7 @@ test "compiler: password strength check" {
280280
281281test "compiler: CSV field extraction" {
282282 const allocator = std.testing.allocator;
283 var regex = try Regex.compile(allocator, "[^,]+");
283 var regex = try Regex.compile(allocator, std.testing.io, "[^,]+");
284284 defer regex.deinit();
285285
286286 const matches = try regex.findAll(allocator, "foo,bar,baz");
@@ -300,7 +300,7 @@ test "compiler: CSV field extraction" {
300300
301301test "compiler: whitespace normalization via replaceAll" {
302302 const allocator = std.testing.allocator;
303 var regex = try Regex.compile(allocator, "\\s+");
303 var regex = try Regex.compile(allocator, std.testing.io, "\\s+");
304304 defer regex.deinit();
305305
306306 const result = try regex.replaceAll(allocator, " hello world ", " ");
@@ -312,7 +312,7 @@ test "compiler: whitespace normalization via replaceAll" {
312312
313313test "compiler: pattern with only ^ anchor" {
314314 const allocator = std.testing.allocator;
315 var regex = try Regex.compile(allocator, "^");
315 var regex = try Regex.compile(allocator, std.testing.io, "^");
316316 defer regex.deinit();
317317
318318 // ^ matches at position 0 of any string (matches zero-width)
@@ -322,7 +322,7 @@ test "compiler: pattern with only ^ anchor" {
322322
323323test "compiler: pattern with only $ anchor" {
324324 const allocator = std.testing.allocator;
325 var regex = try Regex.compile(allocator, "$");
325 var regex = try Regex.compile(allocator, std.testing.io, "$");
326326 defer regex.deinit();
327327
328328 // $ matches at end of any string (zero-width)
@@ -334,7 +334,7 @@ test "compiler: pattern with only $ anchor" {
334334
335335test "compiler: dot plus" {
336336 const allocator = std.testing.allocator;
337 var regex = try Regex.compile(allocator, "^.+$");
337 var regex = try Regex.compile(allocator, std.testing.io, "^.+$");
338338 defer regex.deinit();
339339
340340 try std.testing.expect(try regex.isMatch("a"));
@@ -344,7 +344,7 @@ test "compiler: dot plus" {
344344
345345test "compiler: dot question" {
346346 const allocator = std.testing.allocator;
347 var regex = try Regex.compile(allocator, "^.?$");
347 var regex = try Regex.compile(allocator, std.testing.io, "^.?$");
348348 defer regex.deinit();
349349
350350 try std.testing.expect(try regex.isMatch(""));
@@ -354,7 +354,7 @@ test "compiler: dot question" {
354354
355355test "compiler: dot repeat" {
356356 const allocator = std.testing.allocator;
357 var regex = try Regex.compile(allocator, "^.{3}$");
357 var regex = try Regex.compile(allocator, std.testing.io, "^.{3}$");
358358 defer regex.deinit();
359359
360360 try std.testing.expect(try regex.isMatch("abc"));
tests/posix_classes.zigmodified+18-18
Changes to tests/posix_classes.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "posix: [:alpha:] matches letters" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "[[:alpha:]]+");
8 var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+");
99 defer regex.deinit();
1010
1111 try std.testing.expect(try regex.isMatch("hello"));
@@ -17,7 +17,7 @@ test "posix: [:alpha:] matches letters" {
1717
1818test "posix: [:digit:] matches digits" {
1919 const allocator = std.testing.allocator;
20 var regex = try Regex.compile(allocator, "[[:digit:]]+");
20 var regex = try Regex.compile(allocator, std.testing.io, "[[:digit:]]+");
2121 defer regex.deinit();
2222
2323 try std.testing.expect(try regex.isMatch("123"));
@@ -29,7 +29,7 @@ test "posix: [:digit:] matches digits" {
2929
3030test "posix: [:alnum:] matches alphanumeric" {
3131 const allocator = std.testing.allocator;
32 var regex = try Regex.compile(allocator, "[[:alnum:]]+");
32 var regex = try Regex.compile(allocator, std.testing.io, "[[:alnum:]]+");
3333 defer regex.deinit();
3434
3535 try std.testing.expect(try regex.isMatch("hello123"));
@@ -41,7 +41,7 @@ test "posix: [:alnum:] matches alphanumeric" {
4141
4242test "posix: [:space:] matches whitespace" {
4343 const allocator = std.testing.allocator;
44 var regex = try Regex.compile(allocator, "[[:space:]]+");
44 var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+");
4545 defer regex.deinit();
4646
4747 try std.testing.expect(try regex.isMatch(" "));
@@ -53,7 +53,7 @@ test "posix: [:space:] matches whitespace" {
5353
5454test "posix: [:upper:] matches uppercase" {
5555 const allocator = std.testing.allocator;
56 var regex = try Regex.compile(allocator, "[[:upper:]]+");
56 var regex = try Regex.compile(allocator, std.testing.io, "[[:upper:]]+");
5757 defer regex.deinit();
5858
5959 try std.testing.expect(try regex.isMatch("HELLO"));
@@ -65,7 +65,7 @@ test "posix: [:upper:] matches uppercase" {
6565
6666test "posix: [:lower:] matches lowercase" {
6767 const allocator = std.testing.allocator;
68 var regex = try Regex.compile(allocator, "[[:lower:]]+");
68 var regex = try Regex.compile(allocator, std.testing.io, "[[:lower:]]+");
6969 defer regex.deinit();
7070
7171 try std.testing.expect(try regex.isMatch("hello"));
@@ -77,7 +77,7 @@ test "posix: [:lower:] matches lowercase" {
7777
7878test "posix: [:xdigit:] matches hex digits" {
7979 const allocator = std.testing.allocator;
80 var regex = try Regex.compile(allocator, "[[:xdigit:]]+");
80 var regex = try Regex.compile(allocator, std.testing.io, "[[:xdigit:]]+");
8181 defer regex.deinit();
8282
8383 try std.testing.expect(try regex.isMatch("0123456789"));
@@ -90,7 +90,7 @@ test "posix: [:xdigit:] matches hex digits" {
9090
9191test "posix: [:punct:] matches punctuation" {
9292 const allocator = std.testing.allocator;
93 var regex = try Regex.compile(allocator, "[[:punct:]]+");
93 var regex = try Regex.compile(allocator, std.testing.io, "[[:punct:]]+");
9494 defer regex.deinit();
9595
9696 try std.testing.expect(try regex.isMatch("!@#$%"));
@@ -102,7 +102,7 @@ test "posix: [:punct:] matches punctuation" {
102102
103103test "posix: [:blank:] matches space and tab" {
104104 const allocator = std.testing.allocator;
105 var regex = try Regex.compile(allocator, "[[:blank:]]+");
105 var regex = try Regex.compile(allocator, std.testing.io, "[[:blank:]]+");
106106 defer regex.deinit();
107107
108108 try std.testing.expect(try regex.isMatch(" "));
@@ -114,7 +114,7 @@ test "posix: [:blank:] matches space and tab" {
114114
115115test "posix: [:print:] matches printable characters" {
116116 const allocator = std.testing.allocator;
117 var regex = try Regex.compile(allocator, "[[:print:]]+");
117 var regex = try Regex.compile(allocator, std.testing.io, "[[:print:]]+");
118118 defer regex.deinit();
119119
120120 try std.testing.expect(try regex.isMatch("Hello World!"));
@@ -124,7 +124,7 @@ test "posix: [:print:] matches printable characters" {
124124
125125test "posix: [:graph:] matches visible characters" {
126126 const allocator = std.testing.allocator;
127 var regex = try Regex.compile(allocator, "[[:graph:]]+");
127 var regex = try Regex.compile(allocator, std.testing.io, "[[:graph:]]+");
128128 defer regex.deinit();
129129
130130 try std.testing.expect(try regex.isMatch("Hello"));
@@ -136,7 +136,7 @@ test "posix: [:graph:] matches visible characters" {
136136
137137test "posix: [:cntrl:] matches control characters" {
138138 const allocator = std.testing.allocator;
139 var regex = try Regex.compile(allocator, "[[:cntrl:]]");
139 var regex = try Regex.compile(allocator, std.testing.io, "[[:cntrl:]]");
140140 defer regex.deinit();
141141
142142 try std.testing.expect(try regex.isMatch("\x00"));
@@ -149,7 +149,7 @@ test "posix: [:cntrl:] matches control characters" {
149149
150150test "posix: combined with regular characters" {
151151 const allocator = std.testing.allocator;
152 var regex = try Regex.compile(allocator, "[[:alpha:]0-9_]+");
152 var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]0-9_]+");
153153 defer regex.deinit();
154154
155155 try std.testing.expect(try regex.isMatch("hello123"));
@@ -159,7 +159,7 @@ test "posix: combined with regular characters" {
159159
160160test "posix: multiple POSIX classes in one bracket" {
161161 const allocator = std.testing.allocator;
162 var regex = try Regex.compile(allocator, "[[:alpha:][:digit:]]+");
162 var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:][:digit:]]+");
163163 defer regex.deinit();
164164
165165 try std.testing.expect(try regex.isMatch("abc123"));
@@ -169,7 +169,7 @@ test "posix: multiple POSIX classes in one bracket" {
169169
170170test "posix: extract words with [:alpha:]" {
171171 const allocator = std.testing.allocator;
172 var regex = try Regex.compile(allocator, "[[:alpha:]]+");
172 var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+");
173173 defer regex.deinit();
174174
175175 const text = "Hello World 123";
@@ -189,7 +189,7 @@ test "posix: extract words with [:alpha:]" {
189189
190190test "posix: extract hex numbers with [:xdigit:]" {
191191 const allocator = std.testing.allocator;
192 var regex = try Regex.compile(allocator, "0x[[:xdigit:]]+");
192 var regex = try Regex.compile(allocator, std.testing.io, "0x[[:xdigit:]]+");
193193 defer regex.deinit();
194194
195195 const text = "Colors: 0xFF5733 and 0xABCDEF";
@@ -209,7 +209,7 @@ test "posix: extract hex numbers with [:xdigit:]" {
209209
210210test "posix: validate identifier with [:alnum:]" {
211211 const allocator = std.testing.allocator;
212 var regex = try Regex.compile(allocator, "^[[:alpha:]_][[:alnum:]_]*$");
212 var regex = try Regex.compile(allocator, std.testing.io, "^[[:alpha:]_][[:alnum:]_]*$");
213213 defer regex.deinit();
214214
215215 try std.testing.expect(try regex.isMatch("valid_identifier"));
@@ -221,7 +221,7 @@ test "posix: validate identifier with [:alnum:]" {
221221
222222test "posix: split on whitespace with [:space:]" {
223223 const allocator = std.testing.allocator;
224 var regex = try Regex.compile(allocator, "[[:space:]]+");
224 var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+");
225225 defer regex.deinit();
226226
227227 const text = "one\ttwo\nthree four";
tests/quantifiers.zigmodified+9-9
Changes to tests/quantifiers.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33
44test "quantifier: exactly n times {3}" {
55 const allocator = std.testing.allocator;
6 var regex = try Regex.compile(allocator, "a{3}");
6 var regex = try Regex.compile(allocator, std.testing.io, "a{3}");
77 defer regex.deinit();
88
99 try std.testing.expect(!try regex.isMatch("aa"));
@@ -13,7 +13,7 @@ test "quantifier: exactly n times {3}" {
1313
1414test "quantifier: range {2,4}" {
1515 const allocator = std.testing.allocator;
16 var regex = try Regex.compile(allocator, "a{2,4}");
16 var regex = try Regex.compile(allocator, std.testing.io, "a{2,4}");
1717 defer regex.deinit();
1818
1919 try std.testing.expect(!try regex.isMatch("a"));
@@ -25,7 +25,7 @@ test "quantifier: range {2,4}" {
2525
2626test "quantifier: at least n {2,}" {
2727 const allocator = std.testing.allocator;
28 var regex = try Regex.compile(allocator, "a{2,}");
28 var regex = try Regex.compile(allocator, std.testing.io, "a{2,}");
2929 defer regex.deinit();
3030
3131 try std.testing.expect(!try regex.isMatch("a"));
@@ -36,7 +36,7 @@ test "quantifier: at least n {2,}" {
3636
3737test "quantifier: {0,1} equivalent to ?" {
3838 const allocator = std.testing.allocator;
39 var regex = try Regex.compile(allocator, "a{0,1}");
39 var regex = try Regex.compile(allocator, std.testing.io, "a{0,1}");
4040 defer regex.deinit();
4141
4242 try std.testing.expect(try regex.isMatch(""));
@@ -46,7 +46,7 @@ test "quantifier: {0,1} equivalent to ?" {
4646
4747test "quantifier: {1,} equivalent to +" {
4848 const allocator = std.testing.allocator;
49 var regex = try Regex.compile(allocator, "a{1,}");
49 var regex = try Regex.compile(allocator, std.testing.io, "a{1,}");
5050 defer regex.deinit();
5151
5252 try std.testing.expect(!try regex.isMatch(""));
@@ -56,7 +56,7 @@ test "quantifier: {1,} equivalent to +" {
5656
5757test "quantifier: {0,} equivalent to *" {
5858 const allocator = std.testing.allocator;
59 var regex = try Regex.compile(allocator, "a{0,}");
59 var regex = try Regex.compile(allocator, std.testing.io, "a{0,}");
6060 defer regex.deinit();
6161
6262 try std.testing.expect(try regex.isMatch(""));
@@ -66,7 +66,7 @@ test "quantifier: {0,} equivalent to *" {
6666
6767test "quantifier: complex pattern with {m,n}" {
6868 const allocator = std.testing.allocator;
69 var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}");
69 var regex = try Regex.compile(allocator, std.testing.io, "\\d{3}-\\d{4}");
7070 defer regex.deinit();
7171
7272 if (try regex.find("Call 555-1234 now")) |match| {
@@ -80,7 +80,7 @@ test "quantifier: complex pattern with {m,n}" {
8080
8181test "quantifier: multiple bounded quantifiers" {
8282 const allocator = std.testing.allocator;
83 var regex = try Regex.compile(allocator, "a{2}b{3}");
83 var regex = try Regex.compile(allocator, std.testing.io, "a{2}b{3}");
8484 defer regex.deinit();
8585
8686 try std.testing.expect(!try regex.isMatch("ab"));
@@ -91,7 +91,7 @@ test "quantifier: multiple bounded quantifiers" {
9191
9292test "quantifier: {m,n} with character class" {
9393 const allocator = std.testing.allocator;
94 var regex = try Regex.compile(allocator, "[a-z]{3,5}");
94 var regex = try Regex.compile(allocator, std.testing.io, "[a-z]{3,5}");
9595 defer regex.deinit();
9696
9797 try std.testing.expect(!try regex.isMatch("ab"));
tests/regression.zigmodified+17-17
Changes to tests/regression.zig
@@ -10,7 +10,7 @@ const RegexError = .RegexError;
1010
1111test "regression: $0 replacement expands to full match" {
1212 const allocator = std.testing.allocator;
13 var regex = try Regex.compile(allocator, "\\d+");
13 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
1414 defer regex.deinit();
1515
1616 const result = try regex.replace(allocator, "abc 123 def", "[$0]");
@@ -20,7 +20,7 @@ test "regression: $0 replacement expands to full match" {
2020
2121test "regression: $0 replacement with captures" {
2222 const allocator = std.testing.allocator;
23 var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)");
23 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)");
2424 defer regex.deinit();
2525
2626 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" {
3030
3131test "regression: $0 in replaceAll" {
3232 const allocator = std.testing.allocator;
33 var regex = try Regex.compile(allocator, "\\w+");
33 var regex = try Regex.compile(allocator, std.testing.io, "\\w+");
3434 defer regex.deinit();
3535
3636 const result = try regex.replaceAll(allocator, "a b c", "[$0]");
@@ -42,7 +42,7 @@ test "regression: $0 in replaceAll" {
4242
4343test "regression: case-insensitive backreference" {
4444 const allocator = std.testing.allocator;
45 var regex = try Regex.compileWithFlags(allocator, "(\\w+) \\1", .{ .case_insensitive = true });
45 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "(\\w+) \\1", .{ .case_insensitive = true });
4646 defer regex.deinit();
4747
4848 try std.testing.expect(try regex.isMatch("hello HELLO"));
@@ -52,7 +52,7 @@ test "regression: case-insensitive backreference" {
5252
5353test "regression: case-sensitive backreference rejects case mismatch" {
5454 const allocator = std.testing.allocator;
55 var regex = try Regex.compile(allocator, "(\\w+) \\1");
55 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1");
5656 defer regex.deinit();
5757
5858 try std.testing.expect(!try regex.isMatch("hello HELLO"));
@@ -63,19 +63,19 @@ test "regression: case-sensitive backreference rejects case mismatch" {
6363
6464test "regression: {10,5} is rejected as invalid" {
6565 const allocator = std.testing.allocator;
66 const result = Regex.compile(allocator, "a{10,5}");
66 const result = Regex.compile(allocator, std.testing.io, "a{10,5}");
6767 try std.testing.expectError(RegexError.InvalidQuantifier, result);
6868}
6969
7070test "regression: {5,3} is rejected" {
7171 const allocator = std.testing.allocator;
72 const result = Regex.compile(allocator, "x{5,3}");
72 const result = Regex.compile(allocator, std.testing.io, "x{5,3}");
7373 try std.testing.expectError(RegexError.InvalidQuantifier, result);
7474}
7575
7676test "regression: {3,3} is accepted (min == max)" {
7777 const allocator = std.testing.allocator;
78 var regex = try Regex.compile(allocator, "a{3,3}");
78 var regex = try Regex.compile(allocator, std.testing.io, "a{3,3}");
7979 defer regex.deinit();
8080
8181 try std.testing.expect(try regex.isMatch("aaa"));
@@ -86,7 +86,7 @@ test "regression: {3,3} is accepted (min == max)" {
8686
8787test "regression: {0,3} matches 0 to 3 occurrences" {
8888 const allocator = std.testing.allocator;
89 var regex = try Regex.compile(allocator, "^a{0,3}$");
89 var regex = try Regex.compile(allocator, std.testing.io, "^a{0,3}$");
9090 defer regex.deinit();
9191
9292 try std.testing.expect(try regex.isMatch(""));
@@ -98,9 +98,9 @@ test "regression: {0,3} matches 0 to 3 occurrences" {
9898
9999test "regression: {0,1} is equivalent to ?" {
100100 const allocator = std.testing.allocator;
101 var regex1 = try Regex.compile(allocator, "^ab{0,1}c$");
101 var regex1 = try Regex.compile(allocator, std.testing.io, "^ab{0,1}c$");
102102 defer regex1.deinit();
103 var regex2 = try Regex.compile(allocator, "^ab?c$");
103 var regex2 = try Regex.compile(allocator, std.testing.io, "^ab?c$");
104104 defer regex2.deinit();
105105
106106 const inputs = [_][]const u8{ "ac", "abc", "abbc", "" };
@@ -111,7 +111,7 @@ test "regression: {0,1} is equivalent to ?" {
111111
112112test "regression: {0,0} matches empty only" {
113113 const allocator = std.testing.allocator;
114 var regex = try Regex.compile(allocator, "^a{0,0}b$");
114 var regex = try Regex.compile(allocator, std.testing.io, "^a{0,0}b$");
115115 defer regex.deinit();
116116
117117 try std.testing.expect(try regex.isMatch("b"));
@@ -123,7 +123,7 @@ test "regression: {0,0} matches empty only" {
123123test "regression: prefix optimization finds match after false prefix start" {
124124 const allocator = std.testing.allocator;
125125 // Pattern with prefix "ab" - but first "ab" at position 0 doesn't complete the full match
126 var regex = try Regex.compile(allocator, "abc\\d+");
126 var regex = try Regex.compile(allocator, std.testing.io, "abc\\d+");
127127 defer regex.deinit();
128128
129129 // "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" {
141141test "regression: backtrack step counter resets between positions" {
142142 const allocator = std.testing.allocator;
143143 // This pattern uses backtracking via backreference.
144 var regex = try Regex.compile(allocator, "(\\w+) \\1");
144 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+) \\1");
145145 defer regex.deinit();
146146
147147 // Verify basic backreference works
@@ -161,7 +161,7 @@ test "regression: backtrack step counter resets between positions" {
161161
162162test "regression: deinit on regex that was never used" {
163163 const allocator = std.testing.allocator;
164 var regex = try Regex.compile(allocator, "test");
164 var regex = try Regex.compile(allocator, std.testing.io, "test");
165165 regex.deinit();
166166 // Should not crash - just testing that deinit works without any find/match calls
167167}
@@ -169,12 +169,12 @@ test "regression: deinit on regex that was never used" {
169169test "regression: double pattern compile and deinit" {
170170 const allocator = std.testing.allocator;
171171 {
172 var regex = try Regex.compile(allocator, "abc");
172 var regex = try Regex.compile(allocator, std.testing.io, "abc");
173173 defer regex.deinit();
174174 try std.testing.expect(try regex.isMatch("abc"));
175175 }
176176 {
177 var regex = try Regex.compile(allocator, "def");
177 var regex = try Regex.compile(allocator, std.testing.io, "def");
178178 defer regex.deinit();
179179 try std.testing.expect(try regex.isMatch("def"));
180180 }
tests/stress_edge_cases.zigmodified+58-58
Changes to tests/stress_edge_cases.zig
@@ -11,7 +11,7 @@ const RegexError = .RegexError;
1111test "stress: empty alternation branch" {
1212 const allocator = std.testing.allocator;
1313 // Pattern like (|a) should match empty string or "a"
14 var regex = try Regex.compile(allocator, "^(|a)$");
14 var regex = try Regex.compile(allocator, std.testing.io, "^(|a)$");
1515 defer regex.deinit();
1616
1717 try std.testing.expect(try regex.isMatch(""));
@@ -21,7 +21,7 @@ test "stress: empty alternation branch" {
2121
2222test "stress: pattern that only matches empty string" {
2323 const allocator = std.testing.allocator;
24 var regex = try Regex.compile(allocator, "^$");
24 var regex = try Regex.compile(allocator, std.testing.io, "^$");
2525 defer regex.deinit();
2626
2727 try std.testing.expect(try regex.isMatch(""));
@@ -31,7 +31,7 @@ test "stress: pattern that only matches empty string" {
3131
3232test "stress: dot star matches everything" {
3333 const allocator = std.testing.allocator;
34 var regex = try Regex.compile(allocator, ".*");
34 var regex = try Regex.compile(allocator, std.testing.io, ".*");
3535 defer regex.deinit();
3636
3737 try std.testing.expect(try regex.isMatch(""));
@@ -41,7 +41,7 @@ test "stress: dot star matches everything" {
4141
4242test "stress: single dot" {
4343 const allocator = std.testing.allocator;
44 var regex = try Regex.compile(allocator, "^.$");
44 var regex = try Regex.compile(allocator, std.testing.io, "^.$");
4545 defer regex.deinit();
4646
4747 try std.testing.expect(try regex.isMatch("a"));
@@ -55,7 +55,7 @@ test "stress: single dot" {
5555
5656test "stress: {1,1} is same as no quantifier" {
5757 const allocator = std.testing.allocator;
58 var regex = try Regex.compile(allocator, "^a{1,1}$");
58 var regex = try Regex.compile(allocator, std.testing.io, "^a{1,1}$");
5959 defer regex.deinit();
6060
6161 try std.testing.expect(try regex.isMatch("a"));
@@ -65,7 +65,7 @@ test "stress: {1,1} is same as no quantifier" {
6565
6666test "stress: {2,} matches 2 or more" {
6767 const allocator = std.testing.allocator;
68 var regex = try Regex.compile(allocator, "^a{2,}$");
68 var regex = try Regex.compile(allocator, std.testing.io, "^a{2,}$");
6969 defer regex.deinit();
7070
7171 try std.testing.expect(!try regex.isMatch(""));
@@ -77,7 +77,7 @@ test "stress: {2,} matches 2 or more" {
7777
7878test "stress: exact repeat {5}" {
7979 const allocator = std.testing.allocator;
80 var regex = try Regex.compile(allocator, "^x{5}$");
80 var regex = try Regex.compile(allocator, std.testing.io, "^x{5}$");
8181 defer regex.deinit();
8282
8383 try std.testing.expect(!try regex.isMatch("xxxx"));
@@ -87,7 +87,7 @@ test "stress: exact repeat {5}" {
8787
8888test "stress: mixed quantifiers in sequence" {
8989 const allocator = std.testing.allocator;
90 var regex = try Regex.compile(allocator, "^a+b*c?d{2}$");
90 var regex = try Regex.compile(allocator, std.testing.io, "^a+b*c?d{2}$");
9191 defer regex.deinit();
9292
9393 try std.testing.expect(try regex.isMatch("add"));
@@ -101,7 +101,7 @@ test "stress: mixed quantifiers in sequence" {
101101
102102test "stress: character class with hyphen at start" {
103103 const allocator = std.testing.allocator;
104 var regex = try Regex.compile(allocator, "^[-abc]+$");
104 var regex = try Regex.compile(allocator, std.testing.io, "^[-abc]+$");
105105 defer regex.deinit();
106106
107107 try std.testing.expect(try regex.isMatch("-"));
@@ -112,7 +112,7 @@ test "stress: character class with hyphen at start" {
112112
113113test "stress: character class with hyphen at end" {
114114 const allocator = std.testing.allocator;
115 var regex = try Regex.compile(allocator, "^[abc-]+$");
115 var regex = try Regex.compile(allocator, std.testing.io, "^[abc-]+$");
116116 defer regex.deinit();
117117
118118 try std.testing.expect(try regex.isMatch("-"));
@@ -121,7 +121,7 @@ test "stress: character class with hyphen at end" {
121121
122122test "stress: negated character class" {
123123 const allocator = std.testing.allocator;
124 var regex = try Regex.compile(allocator, "^[^abc]+$");
124 var regex = try Regex.compile(allocator, std.testing.io, "^[^abc]+$");
125125 defer regex.deinit();
126126
127127 try std.testing.expect(try regex.isMatch("xyz"));
@@ -132,7 +132,7 @@ test "stress: negated character class" {
132132
133133test "stress: character class with single char range" {
134134 const allocator = std.testing.allocator;
135 var regex = try Regex.compile(allocator, "^[a-a]+$");
135 var regex = try Regex.compile(allocator, std.testing.io, "^[a-a]+$");
136136 defer regex.deinit();
137137
138138 try std.testing.expect(try regex.isMatch("a"));
@@ -142,7 +142,7 @@ test "stress: character class with single char range" {
142142
143143test "stress: overlapping ranges in character class" {
144144 const allocator = std.testing.allocator;
145 var regex = try Regex.compile(allocator, "^[a-zA-Z]+$");
145 var regex = try Regex.compile(allocator, std.testing.io, "^[a-zA-Z]+$");
146146 defer regex.deinit();
147147
148148 try std.testing.expect(try regex.isMatch("hello"));
@@ -154,20 +154,20 @@ test "stress: overlapping ranges in character class" {
154154test "stress: shorthand classes \\d \\w \\s" {
155155 const allocator = std.testing.allocator;
156156 {
157 var regex = try Regex.compile(allocator, "^\\d+$");
157 var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$");
158158 defer regex.deinit();
159159 try std.testing.expect(try regex.isMatch("12345"));
160160 try std.testing.expect(!try regex.isMatch("abc"));
161161 try std.testing.expect(!try regex.isMatch(""));
162162 }
163163 {
164 var regex = try Regex.compile(allocator, "^\\w+$");
164 var regex = try Regex.compile(allocator, std.testing.io, "^\\w+$");
165165 defer regex.deinit();
166166 try std.testing.expect(try regex.isMatch("hello_123"));
167167 try std.testing.expect(!try regex.isMatch("hello world"));
168168 }
169169 {
170 var regex = try Regex.compile(allocator, "^\\s+$");
170 var regex = try Regex.compile(allocator, std.testing.io, "^\\s+$");
171171 defer regex.deinit();
172172 try std.testing.expect(try regex.isMatch(" "));
173173 try std.testing.expect(try regex.isMatch("\t\n"));
@@ -178,19 +178,19 @@ test "stress: shorthand classes \\d \\w \\s" {
178178test "stress: negated shorthand classes \\D \\W \\S" {
179179 const allocator = std.testing.allocator;
180180 {
181 var regex = try Regex.compile(allocator, "^\\D+$");
181 var regex = try Regex.compile(allocator, std.testing.io, "^\\D+$");
182182 defer regex.deinit();
183183 try std.testing.expect(try regex.isMatch("abc"));
184184 try std.testing.expect(!try regex.isMatch("123"));
185185 }
186186 {
187 var regex = try Regex.compile(allocator, "^\\W+$");
187 var regex = try Regex.compile(allocator, std.testing.io, "^\\W+$");
188188 defer regex.deinit();
189189 try std.testing.expect(try regex.isMatch(" !@#"));
190190 try std.testing.expect(!try regex.isMatch("abc"));
191191 }
192192 {
193 var regex = try Regex.compile(allocator, "^\\S+$");
193 var regex = try Regex.compile(allocator, std.testing.io, "^\\S+$");
194194 defer regex.deinit();
195195 try std.testing.expect(try regex.isMatch("abc"));
196196 try std.testing.expect(!try regex.isMatch("a b"));
@@ -201,7 +201,7 @@ test "stress: negated shorthand classes \\D \\W \\S" {
201201
202202test "stress: ^ only matches at start" {
203203 const allocator = std.testing.allocator;
204 var regex = try Regex.compile(allocator, "^abc");
204 var regex = try Regex.compile(allocator, std.testing.io, "^abc");
205205 defer regex.deinit();
206206
207207 try std.testing.expect(try regex.isMatch("abcdef"));
@@ -210,7 +210,7 @@ test "stress: ^ only matches at start" {
210210
211211test "stress: $ only matches at end" {
212212 const allocator = std.testing.allocator;
213 var regex = try Regex.compile(allocator, "abc$");
213 var regex = try Regex.compile(allocator, std.testing.io, "abc$");
214214 defer regex.deinit();
215215
216216 try std.testing.expect(try regex.isMatch("xyzabc"));
@@ -219,7 +219,7 @@ test "stress: $ only matches at end" {
219219
220220test "stress: ^$ on empty string" {
221221 const allocator = std.testing.allocator;
222 var regex = try Regex.compile(allocator, "^$");
222 var regex = try Regex.compile(allocator, std.testing.io, "^$");
223223 defer regex.deinit();
224224
225225 try std.testing.expect(try regex.isMatch(""));
@@ -228,7 +228,7 @@ test "stress: ^$ on empty string" {
228228
229229test "stress: word boundary \\b" {
230230 const allocator = std.testing.allocator;
231 var regex = try Regex.compile(allocator, "\\bword\\b");
231 var regex = try Regex.compile(allocator, std.testing.io, "\\bword\\b");
232232 defer regex.deinit();
233233
234234 try std.testing.expect(try regex.isMatch("a word here"));
@@ -239,7 +239,7 @@ test "stress: word boundary \\b" {
239239
240240test "stress: non-word boundary \\B" {
241241 const allocator = std.testing.allocator;
242 var regex = try Regex.compile(allocator, "\\Bword\\B");
242 var regex = try Regex.compile(allocator, std.testing.io, "\\Bword\\B");
243243 defer regex.deinit();
244244
245245 try std.testing.expect(try regex.isMatch("swordwords"));
@@ -251,7 +251,7 @@ test "stress: non-word boundary \\B" {
251251
252252test "stress: alternation with different lengths" {
253253 const allocator = std.testing.allocator;
254 var regex = try Regex.compile(allocator, "^(a|bb|ccc)$");
254 var regex = try Regex.compile(allocator, std.testing.io, "^(a|bb|ccc)$");
255255 defer regex.deinit();
256256
257257 try std.testing.expect(try regex.isMatch("a"));
@@ -263,7 +263,7 @@ test "stress: alternation with different lengths" {
263263
264264test "stress: nested alternation" {
265265 const allocator = std.testing.allocator;
266 var regex = try Regex.compile(allocator, "^(a(b|c)d|efg)$");
266 var regex = try Regex.compile(allocator, std.testing.io, "^(a(b|c)d|efg)$");
267267 defer regex.deinit();
268268
269269 try std.testing.expect(try regex.isMatch("abd"));
@@ -275,7 +275,7 @@ test "stress: nested alternation" {
275275
276276test "stress: NFA finds longest match for alternation" {
277277 const allocator = std.testing.allocator;
278 var regex = try Regex.compile(allocator, "cat|catfish");
278 var regex = try Regex.compile(allocator, std.testing.io, "cat|catfish");
279279 defer regex.deinit();
280280
281281 if (try regex.find("catfish")) |match| {
@@ -292,7 +292,7 @@ test "stress: NFA finds longest match for alternation" {
292292
293293test "stress: capture group with alternation - only one branch matches" {
294294 const allocator = std.testing.allocator;
295 var regex = try Regex.compile(allocator, "(a)|(b)");
295 var regex = try Regex.compile(allocator, std.testing.io, "(a)|(b)");
296296 defer regex.deinit();
297297
298298 if (try regex.find("b")) |match| {
@@ -306,7 +306,7 @@ test "stress: capture group with alternation - only one branch matches" {
306306
307307test "stress: nested capture groups" {
308308 const allocator = std.testing.allocator;
309 var regex = try Regex.compile(allocator, "((a)(b))");
309 var regex = try Regex.compile(allocator, std.testing.io, "((a)(b))");
310310 defer regex.deinit();
311311
312312 if (try regex.find("ab")) |match| {
@@ -325,7 +325,7 @@ test "stress: nested capture groups" {
325325
326326test "stress: capture with quantifier" {
327327 const allocator = std.testing.allocator;
328 var regex = try Regex.compile(allocator, "(a)+");
328 var regex = try Regex.compile(allocator, std.testing.io, "(a)+");
329329 defer regex.deinit();
330330
331331 if (try regex.find("aaa")) |match| {
@@ -341,7 +341,7 @@ test "stress: capture with quantifier" {
341341
342342test "stress: findAll with no matches" {
343343 const allocator = std.testing.allocator;
344 var regex = try Regex.compile(allocator, "xyz");
344 var regex = try Regex.compile(allocator, std.testing.io, "xyz");
345345 defer regex.deinit();
346346
347347 const matches = try regex.findAll(allocator, "abc def ghi");
@@ -358,7 +358,7 @@ test "stress: findAll with no matches" {
358358
359359test "stress: findAll adjacent matches" {
360360 const allocator = std.testing.allocator;
361 var regex = try Regex.compile(allocator, "\\d");
361 var regex = try Regex.compile(allocator, std.testing.io, "\\d");
362362 defer regex.deinit();
363363
364364 const matches = try regex.findAll(allocator, "123");
@@ -378,7 +378,7 @@ test "stress: findAll adjacent matches" {
378378
379379test "stress: findAll non-overlapping" {
380380 const allocator = std.testing.allocator;
381 var regex = try Regex.compile(allocator, "aa");
381 var regex = try Regex.compile(allocator, std.testing.io, "aa");
382382 defer regex.deinit();
383383
384384 const matches = try regex.findAll(allocator, "aaaa");
@@ -398,7 +398,7 @@ test "stress: findAll non-overlapping" {
398398
399399test "stress: replace with no match returns input" {
400400 const allocator = std.testing.allocator;
401 var regex = try Regex.compile(allocator, "xyz");
401 var regex = try Regex.compile(allocator, std.testing.io, "xyz");
402402 defer regex.deinit();
403403
404404 const result = try regex.replace(allocator, "hello world", "!");
@@ -408,7 +408,7 @@ test "stress: replace with no match returns input" {
408408
409409test "stress: replace with empty replacement" {
410410 const allocator = std.testing.allocator;
411 var regex = try Regex.compile(allocator, "\\s+");
411 var regex = try Regex.compile(allocator, std.testing.io, "\\s+");
412412 defer regex.deinit();
413413
414414 const result = try regex.replace(allocator, "hello world", "");
@@ -418,7 +418,7 @@ test "stress: replace with empty replacement" {
418418
419419test "stress: replaceAll removes all matches" {
420420 const allocator = std.testing.allocator;
421 var regex = try Regex.compile(allocator, "\\d");
421 var regex = try Regex.compile(allocator, std.testing.io, "\\d");
422422 defer regex.deinit();
423423
424424 const result = try regex.replaceAll(allocator, "a1b2c3", "");
@@ -428,7 +428,7 @@ test "stress: replaceAll removes all matches" {
428428
429429test "stress: replace with $$ escaped dollar" {
430430 const allocator = std.testing.allocator;
431 var regex = try Regex.compile(allocator, "price");
431 var regex = try Regex.compile(allocator, std.testing.io, "price");
432432 defer regex.deinit();
433433
434434 const result = try regex.replace(allocator, "the price is", "$$5");
@@ -440,7 +440,7 @@ test "stress: replace with $$ escaped dollar" {
440440
441441test "stress: split with no match returns whole string" {
442442 const allocator = std.testing.allocator;
443 var regex = try Regex.compile(allocator, ",");
443 var regex = try Regex.compile(allocator, std.testing.io, ",");
444444 defer regex.deinit();
445445
446446 const parts = try regex.split(allocator, "hello");
@@ -452,7 +452,7 @@ test "stress: split with no match returns whole string" {
452452
453453test "stress: split on every character" {
454454 const allocator = std.testing.allocator;
455 var regex = try Regex.compile(allocator, ",");
455 var regex = try Regex.compile(allocator, std.testing.io, ",");
456456 defer regex.deinit();
457457
458458 const parts = try regex.split(allocator, "a,b,c");
@@ -466,7 +466,7 @@ test "stress: split on every character" {
466466
467467test "stress: split with regex separator" {
468468 const allocator = std.testing.allocator;
469 var regex = try Regex.compile(allocator, "\\s*,\\s*");
469 var regex = try Regex.compile(allocator, std.testing.io, "\\s*,\\s*");
470470 defer regex.deinit();
471471
472472 const parts = try regex.split(allocator, "a , b , c");
@@ -482,7 +482,7 @@ test "stress: split with regex separator" {
482482
483483test "stress: escaped special characters" {
484484 const allocator = std.testing.allocator;
485 var regex = try Regex.compile(allocator, "\\(\\)\\[\\]\\{\\}");
485 var regex = try Regex.compile(allocator, std.testing.io, "\\(\\)\\[\\]\\{\\}");
486486 defer regex.deinit();
487487
488488 try std.testing.expect(try regex.isMatch("()[]{}"));
@@ -491,7 +491,7 @@ test "stress: escaped special characters" {
491491
492492test "stress: escaped dot matches literal dot" {
493493 const allocator = std.testing.allocator;
494 var regex = try Regex.compile(allocator, "a\\.b");
494 var regex = try Regex.compile(allocator, std.testing.io, "a\\.b");
495495 defer regex.deinit();
496496
497497 try std.testing.expect(try regex.isMatch("a.b"));
@@ -500,7 +500,7 @@ test "stress: escaped dot matches literal dot" {
500500
501501test "stress: escaped backslash" {
502502 const allocator = std.testing.allocator;
503 var regex = try Regex.compile(allocator, "\\\\");
503 var regex = try Regex.compile(allocator, std.testing.io, "\\\\");
504504 defer regex.deinit();
505505
506506 try std.testing.expect(try regex.isMatch("\\"));
@@ -511,7 +511,7 @@ test "stress: escaped backslash" {
511511
512512test "stress: match at end of long input" {
513513 const allocator = std.testing.allocator;
514 var regex = try Regex.compile(allocator, "needle");
514 var regex = try Regex.compile(allocator, std.testing.io, "needle");
515515 defer regex.deinit();
516516
517517 // Build a long haystack with needle at the very end
@@ -531,7 +531,7 @@ test "stress: match at end of long input" {
531531
532532test "stress: no match in long input" {
533533 const allocator = std.testing.allocator;
534 var regex = try Regex.compile(allocator, "needle");
534 var regex = try Regex.compile(allocator, std.testing.io, "needle");
535535 defer regex.deinit();
536536
537537 var buf: [1024]u8 = undefined;
@@ -544,7 +544,7 @@ test "stress: no match in long input" {
544544
545545test "stress: multiline ^ matches after each newline" {
546546 const allocator = std.testing.allocator;
547 var regex = try Regex.compileWithFlags(allocator, "^\\w+", .{ .multiline = true });
547 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\w+", .{ .multiline = true });
548548 defer regex.deinit();
549549
550550 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" {
567567 // Without multiline, $ only matches at end of input
568568 // So .+ matches "hello" but $ doesn't match at position 5
569569 // Use multiline to match lines, or just test .+ without $
570 var regex = try Regex.compile(allocator, ".+");
570 var regex = try Regex.compile(allocator, std.testing.io, ".+");
571571 defer regex.deinit();
572572
573573 if (try regex.find("hello\nworld")) |match| {
@@ -582,7 +582,7 @@ test "stress: dot does not match newline by default - find first line" {
582582
583583test "stress: dot_all makes dot match newline" {
584584 const allocator = std.testing.allocator;
585 var regex = try Regex.compileWithFlags(allocator, "^.+$", .{ .dot_all = true });
585 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^.+$", .{ .dot_all = true });
586586 defer regex.deinit();
587587
588588 if (try regex.find("hello\nworld")) |match| {
@@ -598,7 +598,7 @@ test "stress: dot_all makes dot match newline" {
598598
599599test "stress: case insensitive literal" {
600600 const allocator = std.testing.allocator;
601 var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
601 var regex = try Regex.compileWithFlags(allocator, std.testing.io, "hello", .{ .case_insensitive = true });
602602 defer regex.deinit();
603603
604604 try std.testing.expect(try regex.isMatch("HELLO"));
@@ -610,38 +610,38 @@ test "stress: case insensitive literal" {
610610
611611test "stress: unmatched closing paren" {
612612 const allocator = std.testing.allocator;
613 const result = Regex.compile(allocator, "abc)");
613 const result = Regex.compile(allocator, std.testing.io, "abc)");
614614 try std.testing.expectError(RegexError.UnmatchedParenthesis, result);
615615}
616616
617617test "stress: unmatched opening paren" {
618618 const allocator = std.testing.allocator;
619 const result = Regex.compile(allocator, "(abc");
619 const result = Regex.compile(allocator, std.testing.io, "(abc");
620620 try std.testing.expectError(RegexError.UnexpectedCharacter, result);
621621}
622622
623623test "stress: unmatched bracket" {
624624 const allocator = std.testing.allocator;
625 const result = Regex.compile(allocator, "[abc");
625 const result = Regex.compile(allocator, std.testing.io, "[abc");
626626 try std.testing.expectError(RegexError.UnexpectedCharacter, result);
627627}
628628
629629test "stress: quantifier star at start" {
630630 const allocator = std.testing.allocator;
631 const result = Regex.compile(allocator, "*abc");
631 const result = Regex.compile(allocator, std.testing.io, "*abc");
632632 try std.testing.expectError(RegexError.UnexpectedCharacter, result);
633633}
634634
635635test "stress: quantifier plus at start" {
636636 const allocator = std.testing.allocator;
637 const result = Regex.compile(allocator, "+abc");
637 const result = Regex.compile(allocator, std.testing.io, "+abc");
638638 try std.testing.expectError(RegexError.UnexpectedCharacter, result);
639639}
640640
641641test "stress: empty character class" {
642642 const allocator = std.testing.allocator;
643643 // [] followed by ] - parser sees empty class then stray ]
644 if (Regex.compile(allocator, "[]")) |*r| {
644 if (Regex.compile(allocator, std.testing.io, "[]")) |*r| {
645645 var mut_r = r.*;
646646 mut_r.deinit();
647647 } else |_| {
@@ -653,7 +653,7 @@ test "stress: empty character class" {
653653
654654test "stress: greedy star matches longest" {
655655 const allocator = std.testing.allocator;
656 var regex = try Regex.compile(allocator, "a.*b");
656 var regex = try Regex.compile(allocator, std.testing.io, "a.*b");
657657 defer regex.deinit();
658658
659659 if (try regex.find("aXXbYYb")) |match| {
@@ -669,7 +669,7 @@ test "stress: greedy star matches longest" {
669669test "stress: complex pattern with multiple features" {
670670 const allocator = std.testing.allocator;
671671 // Email-like pattern with captures
672 var regex = try Regex.compile(allocator, "([a-zA-Z0-9.]+)@([a-zA-Z0-9]+)\\.([a-zA-Z]{2,})");
672 var regex = try Regex.compile(allocator, std.testing.io, "([a-zA-Z0-9.]+)@([a-zA-Z0-9]+)\\.([a-zA-Z]{2,})");
673673 defer regex.deinit();
674674
675675 if (try regex.find("contact: user.name@example.com today")) |match| {
@@ -692,7 +692,7 @@ test "stress: compile many patterns sequentially" {
692692
693693 var i: usize = 0;
694694 while (i < 100) : (i += 1) {
695 var regex = try Regex.compile(allocator, "test\\d+");
695 var regex = try Regex.compile(allocator, std.testing.io, "test\\d+");
696696 defer regex.deinit();
697697 try std.testing.expect(try regex.isMatch("test123"));
698698 }
tests/string_anchors.zigmodified+6-6
Changes to tests/string_anchors.zig
@@ -3,7 +3,7 @@ const Regex = .Regex;
33
44test "string anchor: \\A start of text" {
55 const allocator = std.testing.allocator;
6 var regex = try Regex.compile(allocator, "\\Aabc");
6 var regex = try Regex.compile(allocator, std.testing.io, "\\Aabc");
77 defer regex.deinit();
88
99 // Should match at the start of the text
@@ -17,7 +17,7 @@ test "string anchor: \\A start of text" {
1717
1818test "string anchor: \\z end of text" {
1919 const allocator = std.testing.allocator;
20 var regex = try Regex.compile(allocator, "abc\\z");
20 var regex = try Regex.compile(allocator, std.testing.io, "abc\\z");
2121 defer regex.deinit();
2222
2323 // Should match at the end of the text
@@ -30,7 +30,7 @@ test "string anchor: \\z end of text" {
3030
3131test "string anchor: \\Z end of text (before optional newline)" {
3232 const allocator = std.testing.allocator;
33 var regex = try Regex.compile(allocator, "abc\\Z");
33 var regex = try Regex.compile(allocator, std.testing.io, "abc\\Z");
3434 defer regex.deinit();
3535
3636 // Should match at the end of the text
@@ -45,10 +45,10 @@ test "string anchor: \\Z end of text (before optional newline)" {
4545test "string anchors: \\A vs ^" {
4646 const allocator = std.testing.allocator;
4747
48 var regex_A = try Regex.compile(allocator, "\\Atest");
48 var regex_A = try Regex.compile(allocator, std.testing.io, "\\Atest");
4949 defer regex_A.deinit();
5050
51 var regex_caret = try Regex.compile(allocator, "^test");
51 var regex_caret = try Regex.compile(allocator, std.testing.io, "^test");
5252 defer regex_caret.deinit();
5353
5454 const input1 = "test";
@@ -68,7 +68,7 @@ test "string anchors: \\A vs ^" {
6868
6969test "string anchors: combined \\A and \\z" {
7070 const allocator = std.testing.allocator;
71 var regex = try Regex.compile(allocator, "\\Aexact\\z");
71 var regex = try Regex.compile(allocator, std.testing.io, "\\Aexact\\z");
7272 defer regex.deinit();
7373
7474 // Should match only exact strings
tests/thread_safety.zigmodified+10-10
Changes to tests/thread_safety.zig
@@ -28,7 +28,7 @@ test "concurrent matching with same regex" {
2828 const allocator = testing.allocator;
2929
3030 // Compile a regex once
31 var regex = try Regex.compile(allocator, "\\d+");
31 var regex = try Regex.compile(allocator, std.testing.io, "\\d+");
3232 defer regex.deinit();
3333
3434 // Test inputs
@@ -73,7 +73,7 @@ test "concurrent matching with same regex" {
7373test "concurrent find operations" {
7474 const allocator = testing.allocator;
7575
76 var regex = try Regex.compile(allocator, "[0-9]+");
76 var regex = try Regex.compile(allocator, std.testing.io, "[0-9]+");
7777 defer regex.deinit();
7878
7979 const Worker = struct {
@@ -114,7 +114,7 @@ test "concurrent find operations" {
114114test "SharedRegex reference counting" {
115115 const allocator = testing.allocator;
116116
117 var shared = try SharedRegex.init(allocator, "test");
117 var shared = try SharedRegex.init(allocator, testing.io, "test");
118118 defer shared.deinit();
119119
120120 // Acquire multiple references
@@ -137,7 +137,7 @@ test "SharedRegex reference counting" {
137137test "SharedRegex concurrent access" {
138138 const allocator = testing.allocator;
139139
140 var shared = try SharedRegex.init(allocator, "\\d+");
140 var shared = try SharedRegex.init(allocator, testing.io, "\\d+");
141141 defer shared.deinit();
142142
143143 const Worker = struct {
@@ -172,7 +172,7 @@ test "SharedRegex concurrent access" {
172172test "RegexCache basic operations" {
173173 const allocator = testing.allocator;
174174
175 var cache = RegexCache.init(allocator);
175 var cache = RegexCache.init(allocator, testing.io);
176176 defer cache.deinit();
177177
178178 // Get and cache a pattern
@@ -195,7 +195,7 @@ test "thread-safe allocator with concurrent matching" {
195195 defer _ = gpa.deinit();
196196 const allocator = gpa.allocator();
197197
198 var regex = try Regex.compile(allocator, "test");
198 var regex = try Regex.compile(allocator, std.testing.io, "test");
199199 defer regex.deinit();
200200
201201 const Worker = struct {
@@ -227,13 +227,13 @@ test "concurrent matching different patterns" {
227227 const allocator = gpa.allocator();
228228
229229 // Compile multiple patterns
230 var regex1 = try Regex.compile(allocator, "\\d+");
230 var regex1 = try Regex.compile(allocator, std.testing.io, "\\d+");
231231 defer regex1.deinit();
232232
233 var regex2 = try Regex.compile(allocator, "[a-z]+");
233 var regex2 = try Regex.compile(allocator, std.testing.io, "[a-z]+");
234234 defer regex2.deinit();
235235
236 var regex3 = try Regex.compile(allocator, "test");
236 var regex3 = try Regex.compile(allocator, std.testing.io, "test");
237237 defer regex3.deinit();
238238
239239 const Worker = struct {
@@ -276,7 +276,7 @@ test "stress test: many threads many matches" {
276276 defer _ = gpa.deinit();
277277 const allocator = gpa.allocator();
278278
279 var regex = try Regex.compile(allocator, "test");
279 var regex = try Regex.compile(allocator, std.testing.io, "test");
280280 defer regex.deinit();
281281
282282 const Worker = struct {
tests/utf8_unicode.zigmodified+14-14
Changes to tests/utf8_unicode.zig
@@ -5,7 +5,7 @@ const Regex = .Regex;
55
66test "UTF-8: literal matching" {
77 const allocator = std.testing.allocator;
8 var regex = try Regex.compile(allocator, "café");
8 var regex = try Regex.compile(allocator, std.testing.io, "café");
99 defer regex.deinit();
1010
1111 try std.testing.expect(try regex.isMatch("café"));
@@ -14,7 +14,7 @@ test "UTF-8: literal matching" {
1414
1515test "UTF-8: emoji matching" {
1616 const allocator = std.testing.allocator;
17 var regex = try Regex.compile(allocator, "Hello 👋 World");
17 var regex = try Regex.compile(allocator, std.testing.io, "Hello 👋 World");
1818 defer regex.deinit();
1919
2020 try std.testing.expect(try regex.isMatch("Hello 👋 World"));
@@ -22,7 +22,7 @@ test "UTF-8: emoji matching" {
2222
2323test "UTF-8: Chinese characters" {
2424 const allocator = std.testing.allocator;
25 var regex = try Regex.compile(allocator, "你好");
25 var regex = try Regex.compile(allocator, std.testing.io, "你好");
2626 defer regex.deinit();
2727
2828 try std.testing.expect(try regex.isMatch("你好"));
@@ -31,7 +31,7 @@ test "UTF-8: Chinese characters" {
3131
3232test "UTF-8: mixed ASCII and Unicode" {
3333 const allocator = std.testing.allocator;
34 var regex = try Regex.compile(allocator, "test-тест-テスト");
34 var regex = try Regex.compile(allocator, std.testing.io, "test-тест-テスト");
3535 defer regex.deinit();
3636
3737 try std.testing.expect(try regex.isMatch("test-тест-テスト"));
@@ -39,7 +39,7 @@ test "UTF-8: mixed ASCII and Unicode" {
3939
4040test "UTF-8: dot matches multi-byte character" {
4141 const allocator = std.testing.allocator;
42 var regex = try Regex.compile(allocator, "c.fé");
42 var regex = try Regex.compile(allocator, std.testing.io, "c.fé");
4343 defer regex.deinit();
4444
4545 // Currently . matches one byte, not one character
@@ -49,7 +49,7 @@ test "UTF-8: dot matches multi-byte character" {
4949
5050test "UTF-8: alternation with Unicode" {
5151 const allocator = std.testing.allocator;
52 var regex = try Regex.compile(allocator, "hello|你好|こんにちは");
52 var regex = try Regex.compile(allocator, std.testing.io, "hello|你好|こんにちは");
5353 defer regex.deinit();
5454
5555 try std.testing.expect(try regex.isMatch("hello"));
@@ -60,7 +60,7 @@ test "UTF-8: alternation with Unicode" {
6060test "UTF-8: character class range with multi-byte" {
6161 const allocator = std.testing.allocator;
6262 // Character classes currently only work with single-byte ASCII
63 var regex = try Regex.compile(allocator, "[a-z]+");
63 var regex = try Regex.compile(allocator, std.testing.io, "[a-z]+");
6464 defer regex.deinit();
6565
6666 try std.testing.expect(try regex.isMatch("hello"));
@@ -79,7 +79,7 @@ test "UTF-8: character class range with multi-byte" {
7979
8080test "UTF-8: quantifiers with Unicode literals" {
8181 const allocator = std.testing.allocator;
82 var regex = try Regex.compile(allocator, "あ+");
82 var regex = try Regex.compile(allocator, std.testing.io, "あ+");
8383 defer regex.deinit();
8484
8585 try std.testing.expect(try regex.isMatch("あ"));
@@ -88,7 +88,7 @@ test "UTF-8: quantifiers with Unicode literals" {
8888
8989test "UTF-8: capture groups with Unicode" {
9090 const allocator = std.testing.allocator;
91 var regex = try Regex.compile(allocator, "(你好)(世界)");
91 var regex = try Regex.compile(allocator, std.testing.io, "(你好)(世界)");
9292 defer regex.deinit();
9393
9494 const result = try regex.find("你好世界");
@@ -106,7 +106,7 @@ test "UTF-8: capture groups with Unicode" {
106106
107107test "UTF-8: replacement with Unicode" {
108108 const allocator = std.testing.allocator;
109 var regex = try Regex.compile(allocator, "(\\w+)@(\\w+)");
109 var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)@(\\w+)");
110110 defer regex.deinit();
111111
112112 // ASCII works
@@ -117,7 +117,7 @@ test "UTF-8: replacement with Unicode" {
117117
118118test "UTF-8: anchors with Unicode" {
119119 const allocator = std.testing.allocator;
120 var regex = try Regex.compile(allocator, "^你好$");
120 var regex = try Regex.compile(allocator, std.testing.io, "^你好$");
121121 defer regex.deinit();
122122
123123 try std.testing.expect(try regex.isMatch("你好"));
@@ -127,7 +127,7 @@ test "UTF-8: anchors with Unicode" {
127127
128128test "UTF-8: non-capturing groups with Unicode" {
129129 const allocator = std.testing.allocator;
130 var regex = try Regex.compile(allocator, "(?:안녕|hello) (world|세계)");
130 var regex = try Regex.compile(allocator, std.testing.io, "(?:안녕|hello) (world|세계)");
131131 defer regex.deinit();
132132
133133 const result1 = try regex.find("hello world");
@@ -156,7 +156,7 @@ test "UTF-8: non-capturing groups with Unicode" {
156156// Document current limitations
157157test "UTF-8: known limitation - dot is byte-based" {
158158 const allocator = std.testing.allocator;
159 var regex = try Regex.compile(allocator, "^.$");
159 var regex = try Regex.compile(allocator, std.testing.io, "^.$");
160160 defer regex.deinit();
161161
162162 // Single ASCII character
@@ -170,7 +170,7 @@ test "UTF-8: known limitation - dot is byte-based" {
170170
171171test "UTF-8: known limitation - \\w is ASCII-only" {
172172 const allocator = std.testing.allocator;
173 var regex = try Regex.compile(allocator, "\\w+");
173 var regex = try Regex.compile(allocator, std.testing.io, "\\w+");
174174 defer regex.deinit();
175175
176176 // ASCII word characters work