also looking at this
Port to latest zig-master
#2
53 files
+1149
-1017
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -21,29 +21,29 @@ zig-regex is a regular expression engine for Zig featuring Thompson NFA construc | ||
| 21 | 21 | |
| 22 | 22 | ### Core Regex Features |
| 23 | 23 | |
| 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 | | |
| 43 | 43 | |
| 44 | 44 | ### Advanced Features |
| 45 | 45 | |
| 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 | |
| 47 | 47 | - **AST Optimization**: Constant folding, dead code elimination, quantifier simplification |
| 48 | 48 | - **NFA Optimization**: Epsilon transition removal, state merging, transition optimization |
| 49 | 49 | - **Pattern Macros**: Composable, reusable pattern definitions |
| @@ -55,7 +55,7 @@ zig-regex is a regular expression engine for Zig featuring Thompson NFA construc | ||
| 55 | 55 | ### Quality |
| 56 | 56 | |
| 57 | 57 | - **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 | |
| 59 | 59 | - **Memory Safety**: Full control via Zig allocators, no hidden allocations, zero leaks |
| 60 | 60 | - **500+ Tests**: Comprehensive test suite covering core features, edge cases, regressions, and stress tests |
| 61 | 61 | |
| @@ -241,6 +241,7 @@ MIT License - see [LICENSE](LICENSE) file for details. formatting only | ||
| 241 | 241 | ## Acknowledgments |
| 242 | 242 | |
| 243 | 243 | Inspired by: |
| 244 | ||
| 244 | 245 | - Ken Thompson's NFA construction algorithm |
| 245 | 246 | - RE2 (Google's regex engine) |
| 246 | 247 | - Rust's regex crate |
| @@ -11,6 +11,7 @@ A modern, performant regex library for Zig 0.16+ formatting only | ||
| 11 | 11 | **Phases Completed:** 10 out of 11 (core + testing + docs + advanced features + fuzzing complete) |
| 12 | 12 | |
| 13 | 13 | ### What Works Now: |
| 14 | ||
| 14 | 15 | - ✅ Complete lexer and parser for regex syntax |
| 15 | 16 | - ✅ Thompson NFA construction |
| 16 | 17 | - ✅ Thread-based NFA simulation with greedy matching |
| @@ -28,6 +29,7 @@ A modern, performant regex library for Zig 0.16+ formatting only | ||
| 28 | 29 | - ✅ Thread-safety utilities and documentation |
| 29 | 30 | |
| 30 | 31 | ### Example Usage: |
| 32 | ||
| 31 | 33 | ```zig |
| 32 | 34 | var regex = try Regex.compile(allocator, "\\d+"); |
| 33 | 35 | defer regex.deinit(); |
| @@ -39,6 +41,7 @@ if (try regex.find("Price: $123")) |match| { formatting only | ||
| 39 | 41 | ## Project Overview |
| 40 | 42 | |
| 41 | 43 | Building a production-ready regular expression library for Zig that provides: |
| 44 | ||
| 42 | 45 | - Fast pattern matching using Thompson NFA construction |
| 43 | 46 | - Zero external dependencies (stdlib only) |
| 44 | 47 | - Full memory control with Zig allocators |
| @@ -51,6 +54,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 51 | 54 | ## Phase 1: Project Foundation ✅ COMPLETED |
| 52 | 55 | |
| 53 | 56 | ### 1.1 Project Setup ✅ |
| 57 | ||
| 54 | 58 | - [x] Initialize Zig project structure with `zig init` |
| 55 | 59 | - [x] Create `build.zig` with library and test targets |
| 56 | 60 | - [x] Create `build.zig.zon` with project metadata |
| @@ -60,6 +64,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 60 | 64 | - [x] Create `.gitignore` for Zig projects |
| 61 | 65 | |
| 62 | 66 | ### 1.2 Core Module Structure ✅ |
| 67 | ||
| 63 | 68 | - [x] Create `src/regex.zig` as the main public API (385 lines) |
| 64 | 69 | - [x] Create `src/parser.zig` for regex pattern parsing (395 lines) |
| 65 | 70 | - [x] Create `src/ast.zig` for Abstract Syntax Tree representation (267 lines) |
| @@ -69,6 +74,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 69 | 74 | - [x] Create `src/common.zig` for shared types and utilities (174 lines) |
| 70 | 75 | |
| 71 | 76 | ### 1.3 Documentation Foundation ✅ |
| 77 | ||
| 72 | 78 | - [x] Set up documentation comments structure |
| 73 | 79 | - [x] Create `docs/ARCHITECTURE.md` explaining design decisions - **COMPLETED** |
| 74 | 80 | - [x] Create `docs/API.md` for API reference - **COMPLETED** |
| @@ -80,6 +86,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 80 | 86 | ## Phase 2: Parser & AST ✅ COMPLETED |
| 81 | 87 | |
| 82 | 88 | ### 2.1 Lexer Implementation ✅ |
| 89 | ||
| 83 | 90 | - [x] Implement tokenizer for regex patterns |
| 84 | 91 | - [x] Support basic literals (a-z, A-Z, 0-9) |
| 85 | 92 | - [x] Support special characters (`.`, `^`, `$`, etc.) |
| @@ -89,6 +96,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 89 | 96 | - [x] Implement proper error reporting with line/column info |
| 90 | 97 | |
| 91 | 98 | ### 2.2 Parser Implementation ✅ |
| 99 | ||
| 92 | 100 | - [x] Implement recursive descent parser |
| 93 | 101 | - [x] Handle operator precedence correctly |
| 94 | 102 | - [x] Support concatenation (implicit) |
| @@ -103,6 +111,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 103 | 111 | - [x] Add comprehensive error messages |
| 104 | 112 | |
| 105 | 113 | ### 2.3 AST Construction ✅ |
| 114 | ||
| 106 | 115 | - [x] Define AST node types (Literal, Alternation, Concatenation, etc.) |
| 107 | 116 | - [x] Implement AST builder from parser |
| 108 | 117 | - [x] Add AST validation pass |
| @@ -114,6 +123,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 114 | 123 | ## Phase 3: NFA Engine ✅ COMPLETED |
| 115 | 124 | |
| 116 | 125 | ### 3.1 Thompson Construction ✅ |
| 126 | ||
| 117 | 127 | - [x] Implement basic NFA data structure |
| 118 | 128 | - [x] Implement state and transition representations |
| 119 | 129 | - [x] Support epsilon (ε) transitions |
| @@ -121,11 +131,12 @@ Building a production-ready regular expression library for Zig that provides: | ||
| 121 | 131 | - [x] Handle literal characters |
| 122 | 132 | - [x] Handle concatenation |
| 123 | 133 | - [x] Handle alternation |
| 124 | - [x] Handle Kleene star (*) | |
| 134 | - [x] Handle Kleene star (\*) | |
| 125 | 135 | - [x] Handle plus (+) and optional (?) |
| 126 | 136 | - [x] Handle bounded repetition {m,n} |
| 127 | 137 | |
| 128 | 138 | ### 3.2 NFA Optimization |
| 139 | ||
| 129 | 140 | - [x] Implement epsilon-closure computation |
| 130 | 141 | - [ ] Remove redundant epsilon transitions (future enhancement) |
| 131 | 142 | - [ ] Merge equivalent states (future enhancement) |
| @@ -133,6 +144,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 133 | 144 | - [ ] Add NFA visualization/debug output (future enhancement) |
| 134 | 145 | |
| 135 | 146 | ### 3.3 NFA Simulation ✅ |
| 147 | ||
| 136 | 148 | - [x] Implement basic NFA simulation engine (thread-based matching) |
| 137 | 149 | - [x] Support backtracking for complex patterns (via thread-based approach) |
| 138 | 150 | - [x] Track capture groups during matching |
| @@ -146,6 +158,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 146 | 158 | ## Phase 4: Pattern Matching API ✅ COMPLETED |
| 147 | 159 | |
| 148 | 160 | ### 4.1 Core Matching Functions ✅ |
| 161 | ||
| 149 | 162 | - [x] Implement `Regex.compile()` - compile pattern |
| 150 | 163 | - [x] Implement `Regex.deinit()` - cleanup |
| 151 | 164 | - [x] Implement `find()` - find first match |
| @@ -154,6 +167,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 154 | 167 | - [x] Return match positions (start, end indices) |
| 155 | 168 | |
| 156 | 169 | ### 4.2 Capture Groups ⚠️ Partial |
| 170 | ||
| 157 | 171 | - [x] Implement numbered capture groups `()` |
| 158 | 172 | - [x] Track capture group positions |
| 159 | 173 | - [x] Return captured substrings |
| @@ -162,6 +176,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 162 | 176 | - [ ] Access captures by name (future enhancement) |
| 163 | 177 | |
| 164 | 178 | ### 4.3 Advanced Matching ✅ |
| 179 | ||
| 165 | 180 | - [x] Implement `replace()` - replace matches |
| 166 | 181 | - [x] Implement `replaceAll()` - replace all matches |
| 167 | 182 | - [ ] Support backreferences in replacement (future enhancement) |
| @@ -174,6 +189,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 174 | 189 | ## Phase 5: Extended Regex Features |
| 175 | 190 | |
| 176 | 191 | ### 5.1 Character Classes ✅ |
| 192 | ||
| 177 | 193 | - [x] Support `\d` (digits) |
| 178 | 194 | - [x] Support `\D` (non-digits) |
| 179 | 195 | - [x] Support `\w` (word characters) |
| @@ -185,6 +201,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 185 | 201 | - [ ] Support POSIX character classes `[:alpha:]`, `[:digit:]`, etc. (future enhancement) |
| 186 | 202 | |
| 187 | 203 | ### 5.2 Advanced Anchors & Boundaries ✅ |
| 204 | ||
| 188 | 205 | - [x] Line anchors (`^`, `$`) |
| 189 | 206 | - [x] Word boundaries (`\b`, `\B`) |
| 190 | 207 | - [x] String anchors (`\A`, `\z`, `\Z`) - **COMPLETED** (fully implemented and tested) |
| @@ -192,6 +209,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 192 | 209 | - [ ] Lookbehind assertions `(?<=)`, `(?<!)` (future enhancement) |
| 193 | 210 | |
| 194 | 211 | ### 5.3 Flags & Options ✅ |
| 212 | ||
| 195 | 213 | - [x] Case-insensitive flag (i) - **COMPLETED** |
| 196 | 214 | - [x] Compile-time flag specification via `compileWithFlags()` - **COMPLETED** |
| 197 | 215 | - [x] Multiline flag (m) - **COMPLETED** (^ and $ respect multiline mode) |
| @@ -205,6 +223,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 205 | 223 | ## Phase 6: Testing & Quality |
| 206 | 224 | |
| 207 | 225 | ### 6.1 Unit Tests ✅ COMPLETED |
| 226 | ||
| 208 | 227 | - [x] Test lexer with various input patterns - **COMPLETED** (6+ lexer tests) |
| 209 | 228 | - [x] Test parser with valid/invalid regex - **COMPLETED** (10+ parser tests) |
| 210 | 229 | - [x] Test AST construction and validation - **COMPLETED** (4+ AST tests) |
| @@ -217,6 +236,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 217 | 236 | - [x] Test edge cases (empty strings, large inputs) - **COMPLETED** (dedicated edge_cases test file) |
| 218 | 237 | |
| 219 | 238 | ### 6.2 Integration Tests ✅ COMPLETED |
| 239 | ||
| 220 | 240 | - [x] Test real-world regex patterns - **COMPLETED** |
| 221 | 241 | - [x] Test email validation patterns - **COMPLETED** |
| 222 | 242 | - [x] Test URL matching patterns - **COMPLETED** |
| @@ -231,6 +251,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 231 | 251 | - [ ] Test error handling paths (future enhancement) |
| 232 | 252 | |
| 233 | 253 | ### 6.3 Fuzzing & Property Tests ✅ |
| 254 | ||
| 234 | 255 | - [x] Set up fuzzing infrastructure - **COMPLETED** |
| 235 | 256 | - [x] Fuzz lexer with random inputs - **COMPLETED** (fuzz.zig) |
| 236 | 257 | - [x] Fuzz parser with malformed patterns - **COMPLETED** (bad patterns test) |
| @@ -239,6 +260,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 239 | 260 | - [x] Test memory safety (no leaks) - **COMPLETED** (zero leaks detected) |
| 240 | 261 | |
| 241 | 262 | ### 6.4 Compliance Tests |
| 263 | ||
| 242 | 264 | - [ ] Create test suite from regex standards |
| 243 | 265 | - [ ] Compare against PCRE test suite (where applicable) |
| 244 | 266 | - [ ] Test compatibility with common regex flavors |
| @@ -249,6 +271,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 249 | 271 | ## Phase 7: Performance Optimization |
| 250 | 272 | |
| 251 | 273 | ### 7.1 Algorithm Optimization |
| 274 | ||
| 252 | 275 | - [ ] Profile hot paths in matching |
| 253 | 276 | - [ ] Optimize state transition lookup |
| 254 | 277 | - [ ] Implement DFA construction for static patterns |
| @@ -258,6 +281,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 258 | 281 | - [ ] Cache compiled patterns |
| 259 | 282 | |
| 260 | 283 | ### 7.2 Memory Optimization |
| 284 | ||
| 261 | 285 | - [ ] Minimize allocations during matching |
| 262 | 286 | - [ ] Use arena allocators where appropriate |
| 263 | 287 | - [ ] Implement copy-on-write for captures |
| @@ -265,6 +289,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 265 | 289 | - [ ] Optimize AST/NFA memory layout |
| 266 | 290 | |
| 267 | 291 | ### 7.3 Benchmarking ⚠️ Partial |
| 292 | ||
| 268 | 293 | - [x] Create benchmark suite - **COMPLETED** |
| 269 | 294 | - [x] Benchmark common patterns (literal, quantifiers, character classes) - **COMPLETED** |
| 270 | 295 | - [x] Benchmark case-insensitive matching - **COMPLETED** |
| @@ -278,6 +303,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 278 | 303 | ## Phase 8: Documentation & Examples ✅ COMPLETED |
| 279 | 304 | |
| 280 | 305 | ### 8.1 API Documentation ✅ COMPLETED |
| 306 | ||
| 281 | 307 | - [x] Document all public functions with examples - **COMPLETED** (docs/API.md - 800+ lines) |
| 282 | 308 | - [x] Add parameter descriptions - **COMPLETED** |
| 283 | 309 | - [x] Document error conditions - **COMPLETED** |
| @@ -286,6 +312,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 286 | 312 | - [ ] Generate docs with `zig build docs` (requires Zig docs infrastructure) |
| 287 | 313 | |
| 288 | 314 | ### 8.2 User Guide ✅ COMPLETED |
| 315 | ||
| 289 | 316 | - [x] Write getting started guide - **COMPLETED** (README.md + docs/API.md) |
| 290 | 317 | - [x] Document pattern syntax - **COMPLETED** (docs/API.md Pattern Syntax section) |
| 291 | 318 | - [x] Document best practices - **COMPLETED** (docs/EXAMPLES.md Best Practices section) |
| @@ -295,6 +322,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 295 | 322 | - [ ] Create FAQ (future enhancement) |
| 296 | 323 | |
| 297 | 324 | ### 8.3 Examples ✅ COMPLETED |
| 325 | ||
| 298 | 326 | - [x] Create basic usage example - **COMPLETED** (README.md + docs/EXAMPLES.md) |
| 299 | 327 | - [x] Create capture groups example - **COMPLETED** (docs/EXAMPLES.md) |
| 300 | 328 | - [x] Create replace/substitution example - **COMPLETED** (docs/EXAMPLES.md) |
| @@ -307,6 +335,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 307 | 335 | ## Phase 9: Advanced Features |
| 308 | 336 | |
| 309 | 337 | ### 9.1 Unicode Support |
| 338 | ||
| 310 | 339 | - [ ] Support UTF-8 input (Zig's default) |
| 311 | 340 | - [ ] Handle multi-byte characters correctly |
| 312 | 341 | - [ ] Support Unicode character classes |
| @@ -315,6 +344,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 315 | 344 | - [ ] Handle normalization (if needed) |
| 316 | 345 | |
| 317 | 346 | ### 9.2 Performance Features |
| 347 | ||
| 318 | 348 | - [ ] Implement lazy DFA construction |
| 319 | 349 | - [ ] Add memoization for repeated patterns |
| 320 | 350 | - [ ] Support compiled pattern serialization |
| @@ -322,6 +352,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 322 | 352 | - [ ] Add SIMD optimizations (if applicable) |
| 323 | 353 | |
| 324 | 354 | ### 9.3 Developer Features |
| 355 | ||
| 325 | 356 | - [ ] Implement regex debugging mode |
| 326 | 357 | - [ ] Add visualization of NFA/DFA |
| 327 | 358 | - [ ] Create regex playground/tester |
| @@ -333,6 +364,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 333 | 364 | ## Phase 10: Production Readiness |
| 334 | 365 | |
| 335 | 366 | ### 10.1 Error Handling ✅ |
| 367 | ||
| 336 | 368 | - [x] Comprehensive error types - **COMPLETED** (40+ error types defined) |
| 337 | 369 | - [x] Detailed error messages - **COMPLETED** (ErrorContext with formatting) |
| 338 | 370 | - [x] Recovery strategies where possible - **COMPLETED** (hints and suggestions) |
| @@ -340,12 +372,14 @@ Building a production-ready regular expression library for Zig that provides: | ||
| 340 | 372 | - [x] Panic-free API design - **COMPLETED** (all errors returned as values) |
| 341 | 373 | |
| 342 | 374 | ### 10.2 Thread Safety ✅ |
| 375 | ||
| 343 | 376 | - [x] Document thread-safety guarantees - **COMPLETED** (documented in LIMITATIONS.md) |
| 344 | 377 | - [x] Make compiled patterns thread-safe - **COMPLETED** (read-only operations are safe) |
| 345 | 378 | - [x] Support concurrent matching - **COMPLETED** (VM creates thread-local state) |
| 346 | 379 | - [x] Thread safety utilities - **COMPLETED** (RegexCache implementation available) |
| 347 | 380 | |
| 348 | 381 | ### 10.3 API Stability |
| 382 | ||
| 349 | 383 | - [ ] Finalize public API surface |
| 350 | 384 | - [ ] Mark internal APIs clearly |
| 351 | 385 | - [ ] Version the API |
| @@ -353,6 +387,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 353 | 387 | - [ ] Write upgrade guides |
| 354 | 388 | |
| 355 | 389 | ### 10.4 Release Preparation |
| 390 | ||
| 356 | 391 | - [ ] Set up CI/CD pipeline |
| 357 | 392 | - [ ] Add pre-commit hooks |
| 358 | 393 | - [ ] Create release checklist |
| @@ -365,6 +400,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 365 | 400 | ## Phase 11: Community & Maintenance |
| 366 | 401 | |
| 367 | 402 | ### 11.1 Community Building |
| 403 | ||
| 368 | 404 | - [ ] Create CONTRIBUTING.md |
| 369 | 405 | - [ ] Set up issue templates |
| 370 | 406 | - [ ] Set up PR templates |
| @@ -373,6 +409,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 373 | 409 | - [ ] Announce on Zig forums |
| 374 | 410 | |
| 375 | 411 | ### 11.2 Ongoing Maintenance |
| 412 | ||
| 376 | 413 | - [ ] Monitor issues and PRs |
| 377 | 414 | - [ ] Keep up with Zig language updates |
| 378 | 415 | - [ ] Update dependencies |
| @@ -385,6 +422,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 385 | 422 | ## Future Considerations |
| 386 | 423 | |
| 387 | 424 | ### Potential Features |
| 425 | ||
| 388 | 426 | - [ ] Regex macros/composition |
| 389 | 427 | - [ ] Pattern compilation to native code |
| 390 | 428 | - [ ] C FFI for use in other languages |
| @@ -393,6 +431,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 393 | 431 | - [ ] Regex lint/analysis tools / extreme narrow typing for user patterns |
| 394 | 432 | |
| 395 | 433 | ### Research Topics |
| 434 | ||
| 396 | 435 | - [ ] Investigate derivative-based regex matching |
| 397 | 436 | - [ ] Explore SIMD/vector optimization opportunities |
| 398 | 437 | - [ ] Consider hybrid NFA/DFA approaches |
| @@ -404,6 +443,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 404 | 443 | ## Success Metrics |
| 405 | 444 | |
| 406 | 445 | ### Quality Metrics |
| 446 | ||
| 407 | 447 | - [x] 90%+ test coverage - **ACHIEVED** (114+ tests, 100% pass rate) |
| 408 | 448 | - [x] Zero known memory leaks - **ACHIEVED** (all tests pass leak detection) |
| 409 | 449 | - [x] Zero known security vulnerabilities - **ACHIEVED** (safe memory management) |
| @@ -411,12 +451,14 @@ Building a production-ready regular expression library for Zig that provides: | ||
| 411 | 451 | - [ ] Pass compliance test suite (future - requires PCRE test suite integration) |
| 412 | 452 | |
| 413 | 453 | ### 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)) | |
| 415 | 456 | - [x] Reasonable memory usage - **ACHIEVED** (efficient allocator usage) |
| 416 | 457 | - [x] Fast compilation times - **ACHIEVED** (simple patterns compile quickly) |
| 417 | 458 | - [ ] Competitive with existing Zig regex libraries (future - requires benchmarking) |
| 418 | 459 | |
| 419 | 460 | ### Adoption Metrics |
| 461 | ||
| 420 | 462 | - [ ] Used in at least 3 external projects (future) |
| 421 | 463 | - [ ] Positive community feedback (future - pending release) |
| 422 | 464 | - [ ] Active contributors beyond maintainers (future) |
| @@ -443,6 +485,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 443 | 485 | ## Recent Updates (2025-10-27) |
| 444 | 486 | |
| 445 | 487 | ### Completed Features: |
| 488 | ||
| 446 | 489 | 1. ✅ **Quantifiers `{m,n}`** - Full runtime support with comprehensive tests |
| 447 | 490 | 2. ✅ **String Anchors** - `\A`, `\z`, `\Z` fully implemented and tested |
| 448 | 491 | 3. ✅ **Multiline Flag** - `^` and `$` respect multiline mode |
| @@ -450,6 +493,7 @@ Building a production-ready regular expression library for Zig that provides: formatting only | ||
| 450 | 493 | 5. ✅ **Bug Fixes** - Fixed memory leaks in thread safety tests, updated Zig 0.15.1 APIs |
| 451 | 494 | |
| 452 | 495 | ### Test Suite: |
| 496 | ||
| 453 | 497 | - **155+ tests** passing with 100% success rate |
| 454 | 498 | - New test files: `string_anchors.zig`, `multiline_dotall.zig` |
| 455 | 499 | - All memory leaks resolved |
| @@ -27,7 +27,7 @@ const Benchmark = struct { | ||
| 27 | 27 | } |
| 28 | 28 | }; |
| 29 | 29 | |
| 30 | pub fn main() !void { | |
| 30 | pub fn main(init: std.process.Init) !void { | |
| 31 | 31 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 32 | 32 | defer _ = gpa.deinit(); |
| 33 | 33 | const allocator = gpa.allocator(); |
| @@ -36,7 +36,7 @@ pub fn main() !void { | ||
| 36 | 36 | |
| 37 | 37 | // Benchmark 1: Simple literal matching |
| 38 | 38 | { |
| 39 | var regex = try Regex.compile(allocator, "hello"); | |
| 39 | var regex = try Regex.compile(allocator, init.io, "hello"); | |
| 40 | 40 | defer regex.deinit(); |
| 41 | 41 | const input = "hello world"; |
| 42 | 42 | |
| @@ -50,7 +50,7 @@ pub fn main() !void { | ||
| 50 | 50 | |
| 51 | 51 | // Benchmark 2: Quantifier matching |
| 52 | 52 | { |
| 53 | var regex = try Regex.compile(allocator, "a+b*c?"); | |
| 53 | var regex = try Regex.compile(allocator, init.io, "a+b*c?"); | |
| 54 | 54 | defer regex.deinit(); |
| 55 | 55 | const input = "aaabbbbc"; |
| 56 | 56 | |
| @@ -64,7 +64,7 @@ pub fn main() !void { | ||
| 64 | 64 | |
| 65 | 65 | // Benchmark 3: Alternation |
| 66 | 66 | { |
| 67 | var regex = try Regex.compile(allocator, "cat|dog|bird"); | |
| 67 | var regex = try Regex.compile(allocator, init.io, "cat|dog|bird"); | |
| 68 | 68 | defer regex.deinit(); |
| 69 | 69 | const input = "dog"; |
| 70 | 70 | |
| @@ -78,7 +78,7 @@ pub fn main() !void { | ||
| 78 | 78 | |
| 79 | 79 | // Benchmark 4: Character class |
| 80 | 80 | { |
| 81 | var regex = try Regex.compile(allocator, "[a-z]+"); | |
| 81 | var regex = try Regex.compile(allocator, init.io, "[a-z]+"); | |
| 82 | 82 | defer regex.deinit(); |
| 83 | 83 | const input = "abcdefghijklmnop"; |
| 84 | 84 | |
| @@ -92,7 +92,7 @@ pub fn main() !void { | ||
| 92 | 92 | |
| 93 | 93 | // Benchmark 5: Digit matching |
| 94 | 94 | { |
| 95 | var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}"); | |
| 95 | var regex = try Regex.compile(allocator, init.io, "\\d{3}-\\d{4}"); | |
| 96 | 96 | defer regex.deinit(); |
| 97 | 97 | const input = "555-1234"; |
| 98 | 98 | |
| @@ -106,7 +106,7 @@ pub fn main() !void { | ||
| 106 | 106 | |
| 107 | 107 | // Benchmark 6: Email-like pattern |
| 108 | 108 | { |
| 109 | var regex = try Regex.compile(allocator, "\\w+@\\w+\\.\\w+"); | |
| 109 | var regex = try Regex.compile(allocator, init.io, "\\w+@\\w+\\.\\w+"); | |
| 110 | 110 | defer regex.deinit(); |
| 111 | 111 | const input = "user@example.com"; |
| 112 | 112 | |
| @@ -120,7 +120,7 @@ pub fn main() !void { | ||
| 120 | 120 | |
| 121 | 121 | // Benchmark 7: Find operation |
| 122 | 122 | { |
| 123 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 123 | var regex = try Regex.compile(allocator, init.io, "\\d+"); | |
| 124 | 124 | defer regex.deinit(); |
| 125 | 125 | const input = "The price is $123 and tax is $45"; |
| 126 | 126 | |
| @@ -137,7 +137,7 @@ pub fn main() !void { | ||
| 137 | 137 | |
| 138 | 138 | // Benchmark 8: Replace operation |
| 139 | 139 | { |
| 140 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 140 | var regex = try Regex.compile(allocator, init.io, "\\d+"); | |
| 141 | 141 | defer regex.deinit(); |
| 142 | 142 | const input = "Call 555-1234 or 555-5678"; |
| 143 | 143 | |
| @@ -166,7 +166,7 @@ pub fn main() !void { | ||
| 166 | 166 | |
| 167 | 167 | // Benchmark 10: Complex pattern |
| 168 | 168 | { |
| 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,}$"); | |
| 170 | 170 | defer regex.deinit(); |
| 171 | 171 | const input = "test.user+tag@example.co.uk"; |
| 172 | 172 | |
| @@ -1,7 +1,9 @@ | ||
| 1 | 1 | const std = ; |
| 2 | const Clock = std.Io.Clock; | |
| 2 | 3 | const Regex = .Regex; |
| 3 | 4 | |
| 4 | pub fn main() !void { | |
| 5 | pub fn main(init: std.process.Init) !void { | |
| 6 | const io = init.io; | |
| 5 | 7 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 6 | 8 | defer _ = gpa.deinit(); |
| 7 | 9 | const allocator = gpa.allocator(); |
| @@ -14,7 +16,7 @@ pub fn main() !void { | ||
| 14 | 16 | // Test 1: Pattern with literal prefix (should use optimization) |
| 15 | 17 | { |
| 16 | 18 | 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!"); | |
| 18 | 20 | defer regex.deinit(); |
| 19 | 21 | |
| 20 | 22 | // Print optimization info |
| @@ -24,8 +26,7 @@ pub fn main() !void { | ||
| 24 | 26 | std.debug.print(" ✗ No literal prefix found\n", .{}); |
| 25 | 27 | } |
| 26 | 28 | |
| 27 | var timer = try std.time.Timer.start(); | |
| 28 | const start = timer.read(); | |
| 29 | const start = Clock.awake.now(io); | |
| 29 | 30 | |
| 30 | 31 | const iterations: usize = 10000; |
| 31 | 32 | var i: usize = 0; |
| @@ -36,8 +37,8 @@ pub fn main() !void { | ||
| 36 | 37 | } |
| 37 | 38 | } |
| 38 | 39 | |
| 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); | |
| 41 | 42 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 42 | 43 | iterations, |
| 43 | 44 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -48,7 +49,7 @@ pub fn main() !void { | ||
| 48 | 49 | // Test 2: Pattern without useful prefix (no optimization) - using fewer iterations |
| 49 | 50 | { |
| 50 | 51 | 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"); | |
| 52 | 53 | defer regex.deinit(); |
| 53 | 54 | |
| 54 | 55 | if (regex.opt_info.literal_prefix) |prefix| { |
| @@ -57,8 +58,7 @@ pub fn main() !void { | ||
| 57 | 58 | std.debug.print(" ✗ No literal prefix found (slower performance expected)\n", .{}); |
| 58 | 59 | } |
| 59 | 60 | |
| 60 | var timer = try std.time.Timer.start(); | |
| 61 | const start = timer.read(); | |
| 61 | const start = Clock.awake.now(init.io); | |
| 62 | 62 | |
| 63 | 63 | // Use fewer iterations since this is much slower without optimization |
| 64 | 64 | const iterations: usize = 100; |
| @@ -70,8 +70,8 @@ pub fn main() !void { | ||
| 70 | 70 | } |
| 71 | 71 | } |
| 72 | 72 | |
| 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); | |
| 75 | 75 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 76 | 76 | iterations, |
| 77 | 77 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -83,7 +83,7 @@ pub fn main() !void { | ||
| 83 | 83 | { |
| 84 | 84 | std.debug.print("Test 3: Prefix with complex suffix (hello.*world)...\n", .{}); |
| 85 | 85 | 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"); | |
| 87 | 87 | defer regex.deinit(); |
| 88 | 88 | |
| 89 | 89 | if (regex.opt_info.literal_prefix) |prefix| { |
| @@ -92,8 +92,7 @@ pub fn main() !void { | ||
| 92 | 92 | std.debug.print(" ✗ No literal prefix found\n", .{}); |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | var timer = try std.time.Timer.start(); | |
| 96 | const start = timer.read(); | |
| 95 | const start = Clock.awake.now(init.io); | |
| 97 | 96 | |
| 98 | 97 | const iterations: usize = 10000; |
| 99 | 98 | var i: usize = 0; |
| @@ -104,8 +103,8 @@ pub fn main() !void { | ||
| 104 | 103 | } |
| 105 | 104 | } |
| 106 | 105 | |
| 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); | |
| 109 | 108 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 110 | 109 | iterations, |
| 111 | 110 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -116,7 +115,7 @@ pub fn main() !void { | ||
| 116 | 115 | // Test 4: Anchored patterns |
| 117 | 116 | { |
| 118 | 117 | 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"); | |
| 120 | 119 | defer regex.deinit(); |
| 121 | 120 | |
| 122 | 121 | if (regex.opt_info.anchored_start) { |
| @@ -127,8 +126,7 @@ pub fn main() !void { | ||
| 127 | 126 | } |
| 128 | 127 | |
| 129 | 128 | 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); | |
| 132 | 130 | |
| 133 | 131 | const iterations: usize = 10000; |
| 134 | 132 | var i: usize = 0; |
| @@ -136,8 +134,8 @@ pub fn main() !void { | ||
| 136 | 134 | _ = try regex.isMatch(text); |
| 137 | 135 | } |
| 138 | 136 | |
| 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); | |
| 141 | 139 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 142 | 140 | iterations, |
| 143 | 141 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -149,15 +147,14 @@ pub fn main() !void { | ||
| 149 | 147 | { |
| 150 | 148 | std.debug.print("Test 5: Email pattern (user@example.com)...\n", .{}); |
| 151 | 149 | 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"); | |
| 153 | 151 | defer regex.deinit(); |
| 154 | 152 | |
| 155 | 153 | 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 }); | |
| 157 | 155 | } |
| 158 | 156 | |
| 159 | var timer = try std.time.Timer.start(); | |
| 160 | const start = timer.read(); | |
| 157 | const start = Clock.awake.now(init.io); | |
| 161 | 158 | |
| 162 | 159 | const iterations: usize = 10000; |
| 163 | 160 | var i: usize = 0; |
| @@ -168,8 +165,8 @@ pub fn main() !void { | ||
| 168 | 165 | } |
| 169 | 166 | } |
| 170 | 167 | |
| 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); | |
| 173 | 170 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 174 | 171 | iterations, |
| 175 | 172 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -1,8 +1,10 @@ | ||
| 1 | 1 | const std = ; |
| 2 | const Clock = std.Io.Clock; | |
| 2 | 3 | const Regex = .Regex; |
| 3 | 4 | |
| 4 | pub fn main() !void { | |
| 5 | pub fn main(init: std.process.Init) !void { | |
| 5 | 6 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 7 | const io = init.io; | |
| 6 | 8 | defer _ = gpa.deinit(); |
| 7 | 9 | const allocator = gpa.allocator(); |
| 8 | 10 | |
| @@ -11,11 +13,10 @@ pub fn main() !void { | ||
| 11 | 13 | // Test 1: Literal matching |
| 12 | 14 | { |
| 13 | 15 | 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"); | |
| 15 | 17 | defer regex.deinit(); |
| 16 | 18 | |
| 17 | var timer = try std.time.Timer.start(); | |
| 18 | const start = timer.read(); | |
| 19 | const start = Clock.awake.now(io); | |
| 19 | 20 | |
| 20 | 21 | const iterations: usize = 10000; |
| 21 | 22 | var i: usize = 0; |
| @@ -23,8 +24,8 @@ pub fn main() !void { | ||
| 23 | 24 | _ = try regex.isMatch("hello world"); |
| 24 | 25 | } |
| 25 | 26 | |
| 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); | |
| 28 | 29 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 29 | 30 | iterations, |
| 30 | 31 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -35,11 +36,10 @@ pub fn main() !void { | ||
| 35 | 36 | // Test 2: Quantifiers |
| 36 | 37 | { |
| 37 | 38 | 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+"); | |
| 39 | 40 | defer regex.deinit(); |
| 40 | 41 | |
| 41 | var timer = try std.time.Timer.start(); | |
| 42 | const start = timer.read(); | |
| 42 | const start = Clock.awake.now(init.io); | |
| 43 | 43 | |
| 44 | 44 | const iterations: usize = 10000; |
| 45 | 45 | var i: usize = 0; |
| @@ -47,8 +47,8 @@ pub fn main() !void { | ||
| 47 | 47 | _ = try regex.isMatch("aaaa"); |
| 48 | 48 | } |
| 49 | 49 | |
| 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); | |
| 52 | 52 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 53 | 53 | iterations, |
| 54 | 54 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -59,11 +59,10 @@ pub fn main() !void { | ||
| 59 | 59 | // Test 3: Character classes |
| 60 | 60 | { |
| 61 | 61 | 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+"); | |
| 63 | 63 | defer regex.deinit(); |
| 64 | 64 | |
| 65 | var timer = try std.time.Timer.start(); | |
| 66 | const start = timer.read(); | |
| 65 | const start = Clock.awake.now(init.io); | |
| 67 | 66 | |
| 68 | 67 | const iterations: usize = 10000; |
| 69 | 68 | var i: usize = 0; |
| @@ -71,8 +70,8 @@ pub fn main() !void { | ||
| 71 | 70 | _ = try regex.isMatch("12345"); |
| 72 | 71 | } |
| 73 | 72 | |
| 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); | |
| 76 | 75 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 77 | 76 | iterations, |
| 78 | 77 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -83,11 +82,10 @@ pub fn main() !void { | ||
| 83 | 82 | // Test 4: Case-insensitive |
| 84 | 83 | { |
| 85 | 84 | 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 }); | |
| 87 | 86 | defer regex.deinit(); |
| 88 | 87 | |
| 89 | var timer = try std.time.Timer.start(); | |
| 90 | const start = timer.read(); | |
| 88 | const start = Clock.awake.now(init.io); | |
| 91 | 89 | |
| 92 | 90 | const iterations: usize = 10000; |
| 93 | 91 | var i: usize = 0; |
| @@ -95,8 +93,8 @@ pub fn main() !void { | ||
| 95 | 93 | _ = try regex.isMatch("HELLO"); |
| 96 | 94 | } |
| 97 | 95 | |
| 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); | |
| 100 | 98 | std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{ |
| 101 | 99 | iterations, |
| 102 | 100 | @as(f64, @floatFromInt(elapsed)) / 1_000_000.0, |
| @@ -39,6 +39,7 @@ pub fn build(b: *std.Build) void { | ||
| 39 | 39 | // Later on we'll use this module as the root module of a test executable |
| 40 | 40 | // which requires us to specify a target. |
| 41 | 41 | .target = target, |
| 42 | .link_libc = true, | |
| 42 | 43 | }); |
| 43 | 44 | |
| 44 | 45 | // Here we define an executable. An executable needs to have a root module |
| @@ -386,35 +386,31 @@ zig build wasm | ||
| 386 | 386 | ```javascript |
| 387 | 387 | // Load WASM module |
| 388 | 388 | const wasmModule = await WebAssembly.instantiateStreaming( |
| 389 | fetch('zig-regex.wasm') | |
| 389 | fetch("zig-regex.wasm"), | |
| 390 | 390 | ); |
| 391 | 391 | |
| 392 | const { | |
| 393 | zig_regex_compile, | |
| 394 | zig_regex_is_match, | |
| 395 | zig_regex_free, | |
| 396 | memory | |
| 397 | } = wasmModule.instance.exports; | |
| 392 | const { zig_regex_compile, zig_regex_is_match, zig_regex_free, memory } = | |
| 393 | wasmModule.instance.exports; | |
| 398 | 394 | |
| 399 | 395 | // Helper to convert JS string to WASM memory |
| 400 | 396 | function 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; | |
| 405 | 401 | } |
| 406 | 402 | |
| 407 | 403 | // Compile regex |
| 408 | const pattern = stringToWasm('\\d+'); | |
| 404 | const pattern = stringToWasm("\\d+"); | |
| 409 | 405 | const flags = 0; // No flags |
| 410 | 406 | const errorPtr = allocate(4); |
| 411 | 407 | const regex = zig_regex_compile(pattern, flags, errorPtr); |
| 412 | 408 | |
| 413 | 409 | // Test match |
| 414 | const input = stringToWasm('hello123'); | |
| 410 | const input = stringToWasm("hello123"); | |
| 415 | 411 | const isMatch = zig_regex_is_match(regex, input, errorPtr); |
| 416 | 412 | |
| 417 | console.log('Match:', isMatch); | |
| 413 | console.log("Match:", isMatch); | |
| 418 | 414 | |
| 419 | 415 | // Clean up |
| 420 | 416 | zig_regex_free(regex); |
| @@ -476,7 +472,7 @@ Warnings (2): | ||
| 476 | 472 | |
| 477 | 473 | The library automatically chooses between two engines: |
| 478 | 474 | |
| 479 | - **Thompson NFA**: O(n*m) performance for basic patterns | |
| 475 | - **Thompson NFA**: O(n\*m) performance for basic patterns | |
| 480 | 476 | - **Backtracking Engine**: Supports advanced features (lazy quantifiers, lookahead, lookbehind, backreferences) |
| 481 | 477 | |
| 482 | 478 | ```zig |
| @@ -49,6 +49,7 @@ pub const Match = struct { formatting only | ||
| 49 | 49 | ``` |
| 50 | 50 | |
| 51 | 51 | **Methods:** |
| 52 | ||
| 52 | 53 | - `deinit(allocator: std.mem.Allocator)` - Free capture group memory |
| 53 | 54 | |
| 54 | 55 | --- |
| @@ -64,12 +65,14 @@ pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex | ||
| 64 | 65 | ``` |
| 65 | 66 | |
| 66 | 67 | **Parameters:** |
| 68 | ||
| 67 | 69 | - `allocator` - Memory allocator for the regex and its internal structures |
| 68 | 70 | - `pattern` - The regex pattern string |
| 69 | 71 | |
| 70 | 72 | **Returns:** `Regex` or error |
| 71 | 73 | |
| 72 | 74 | **Errors:** |
| 75 | ||
| 73 | 76 | - `RegexError.EmptyPattern` - Pattern string is empty |
| 74 | 77 | - `RegexError.InvalidPattern` - Syntax error in pattern |
| 75 | 78 | - `RegexError.UnexpectedCharacter` - Invalid character in pattern |
| @@ -80,6 +83,7 @@ pub fn compile(allocator: std.mem.Allocator, pattern: []const u8) !Regex formatting only | ||
| 80 | 83 | - `RegexError.UnmatchedBracket` - Unbalanced brackets |
| 81 | 84 | |
| 82 | 85 | **Example:** |
| 86 | ||
| 83 | 87 | ```zig |
| 84 | 88 | const allocator = std.heap.page_allocator; |
| 85 | 89 | var regex = try Regex.compile(allocator, "\\d{3}-\\d{4}"); |
| @@ -99,6 +103,7 @@ pub fn compileWithFlags( formatting only | ||
| 99 | 103 | ``` |
| 100 | 104 | |
| 101 | 105 | **Parameters:** |
| 106 | ||
| 102 | 107 | - `allocator` - Memory allocator |
| 103 | 108 | - `pattern` - The regex pattern string |
| 104 | 109 | - `flags` - Compilation flags (see [Flags](#flags)) |
| @@ -106,6 +111,7 @@ pub fn compileWithFlags( formatting only | ||
| 106 | 111 | **Returns:** `Regex` or error |
| 107 | 112 | |
| 108 | 113 | **Example:** |
| 114 | ||
| 109 | 115 | ```zig |
| 110 | 116 | var regex = try Regex.compileWithFlags( |
| 111 | 117 | allocator, |
| @@ -124,6 +130,7 @@ pub fn deinit(self: *Regex) void formatting only | ||
| 124 | 130 | ``` |
| 125 | 131 | |
| 126 | 132 | **Example:** |
| 133 | ||
| 127 | 134 | ```zig |
| 128 | 135 | var regex = try Regex.compile(allocator, "pattern"); |
| 129 | 136 | defer regex.deinit(); // Always call deinit when done |
| @@ -142,11 +149,13 @@ pub fn isMatch(self: *const Regex, input: []const u8) !bool | ||
| 142 | 149 | ``` |
| 143 | 150 | |
| 144 | 151 | **Parameters:** |
| 152 | ||
| 145 | 153 | - `input` - The string to search in |
| 146 | 154 | |
| 147 | 155 | **Returns:** `true` if match found, `false` otherwise |
| 148 | 156 | |
| 149 | 157 | **Example:** |
| 158 | ||
| 150 | 159 | ```zig |
| 151 | 160 | var regex = try Regex.compile(allocator, "\\d+"); |
| 152 | 161 | defer regex.deinit(); |
| @@ -169,11 +178,13 @@ pub fn find(self: *const Regex, input: []const u8) !?Match | ||
| 169 | 178 | ``` |
| 170 | 179 | |
| 171 | 180 | **Parameters:** |
| 181 | ||
| 172 | 182 | - `input` - The string to search in |
| 173 | 183 | |
| 174 | 184 | **Returns:** `Match` if found, `null` otherwise |
| 175 | 185 | |
| 176 | 186 | **Example:** |
| 187 | ||
| 177 | 188 | ```zig |
| 178 | 189 | var regex = try Regex.compile(allocator, "\\d+"); |
| 179 | 190 | defer regex.deinit(); |
| @@ -200,12 +211,14 @@ pub fn findAll( | ||
| 200 | 211 | ``` |
| 201 | 212 | |
| 202 | 213 | **Parameters:** |
| 214 | ||
| 203 | 215 | - `allocator` - Allocator for the results array |
| 204 | 216 | - `input` - The string to search in |
| 205 | 217 | |
| 206 | 218 | **Returns:** Array of `Match` objects (caller owns, must free) |
| 207 | 219 | |
| 208 | 220 | **Example:** |
| 221 | ||
| 209 | 222 | ```zig |
| 210 | 223 | var regex = try Regex.compile(allocator, "\\d+"); |
| 211 | 224 | defer regex.deinit(); |
| @@ -247,6 +260,7 @@ pub fn replace( formatting only | ||
| 247 | 260 | ``` |
| 248 | 261 | |
| 249 | 262 | **Parameters:** |
| 263 | ||
| 250 | 264 | - `allocator` - Allocator for the result string |
| 251 | 265 | - `input` - The input string |
| 252 | 266 | - `replacement` - The replacement string |
| @@ -254,6 +268,7 @@ pub fn replace( formatting only | ||
| 254 | 268 | **Returns:** New string with first match replaced (caller owns, must free) |
| 255 | 269 | |
| 256 | 270 | **Example:** |
| 271 | ||
| 257 | 272 | ```zig |
| 258 | 273 | var regex = try Regex.compile(allocator, "\\d+"); |
| 259 | 274 | defer regex.deinit(); |
| @@ -278,6 +293,7 @@ pub fn replaceAll( formatting only | ||
| 278 | 293 | ``` |
| 279 | 294 | |
| 280 | 295 | **Parameters:** |
| 296 | ||
| 281 | 297 | - `allocator` - Allocator for the result string |
| 282 | 298 | - `input` - The input string |
| 283 | 299 | - `replacement` - The replacement string |
| @@ -285,6 +301,7 @@ pub fn replaceAll( formatting only | ||
| 285 | 301 | **Returns:** New string with all matches replaced (caller owns, must free) |
| 286 | 302 | |
| 287 | 303 | **Example:** |
| 304 | ||
| 288 | 305 | ```zig |
| 289 | 306 | var regex = try Regex.compile(allocator, "\\d+"); |
| 290 | 307 | defer regex.deinit(); |
| @@ -312,12 +329,14 @@ pub fn split( | ||
| 312 | 329 | ``` |
| 313 | 330 | |
| 314 | 331 | **Parameters:** |
| 332 | ||
| 315 | 333 | - `allocator` - Allocator for the results array |
| 316 | 334 | - `input` - The string to split |
| 317 | 335 | |
| 318 | 336 | **Returns:** Array of string slices (caller owns array, must free) |
| 319 | 337 | |
| 320 | 338 | **Example:** |
| 339 | ||
| 321 | 340 | ```zig |
| 322 | 341 | var regex = try Regex.compile(allocator, ","); |
| 323 | 342 | defer regex.deinit(); |
| @@ -357,6 +376,7 @@ pub const CompileFlags = packed struct { formatting only | ||
| 357 | 376 | When `true`, the pattern matches both uppercase and lowercase letters. |
| 358 | 377 | |
| 359 | 378 | **Example:** |
| 379 | ||
| 360 | 380 | ```zig |
| 361 | 381 | var regex = try Regex.compileWithFlags( |
| 362 | 382 | allocator, |
| @@ -397,6 +417,7 @@ pub const RegexError = error{ formatting only | ||
| 397 | 417 | ``` |
| 398 | 418 | |
| 399 | 419 | **Error Handling Example:** |
| 420 | ||
| 400 | 421 | ```zig |
| 401 | 422 | const regex = Regex.compile(allocator, "[invalid") catch |err| { |
| 402 | 423 | switch (err) { |
| @@ -2,7 +2,7 @@ | ||
| 2 | 2 | |
| 3 | 3 | ## Overview |
| 4 | 4 | |
| 5 | The 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. | |
| 5 | The 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. | |
| 6 | 6 | |
| 7 | 7 | ## Design Philosophy |
| 8 | 8 | |
| @@ -55,9 +55,11 @@ The zig-regex library implements a regular expression engine using **Thompson's | ||
| 55 | 55 | ## Module Breakdown |
| 56 | 56 | |
| 57 | 57 | ### 1. common.zig (174 lines) |
| 58 | ||
| 58 | 59 | **Purpose:** Shared types and utilities |
| 59 | 60 | |
| 60 | 61 | **Key Components:** |
| 62 | ||
| 61 | 63 | - `CharRange` - Represents a character range (e.g., 'a'-'z') |
| 62 | 64 | - `CharClass` - Set of character ranges with negation support |
| 63 | 65 | - `CharClasses` - Predefined classes (\d, \w, \s, etc.) |
| @@ -65,13 +67,16 @@ The zig-regex library implements a regular expression engine using **Thompson's | ||
| 65 | 67 | - `Span` - Source position for error reporting |
| 66 | 68 | |
| 67 | 69 | **Design Notes:** |
| 70 | ||
| 68 | 71 | - Character classes use ranges for memory efficiency |
| 69 | 72 | - Negation handled at match time, not during construction |
| 70 | 73 | |
| 71 | 74 | ### 2. ast.zig (267 lines) |
| 75 | ||
| 72 | 76 | **Purpose:** Abstract Syntax Tree representation |
| 73 | 77 | |
| 74 | 78 | **Node Types:** |
| 79 | ||
| 75 | 80 | ```zig |
| 76 | 81 | pub const NodeType = enum { |
| 77 | 82 | literal, // Single character 'a' |
| @@ -90,20 +95,24 @@ pub const NodeType = enum { | ||
| 90 | 95 | ``` |
| 91 | 96 | |
| 92 | 97 | **Key Features:** |
| 98 | ||
| 93 | 99 | - Tagged union for type-safe node data |
| 94 | 100 | - Recursive structure for nested expressions |
| 95 | 101 | - Memory management via allocator |
| 96 | 102 | - Span tracking for error messages |
| 97 | 103 | |
| 98 | 104 | **Design Notes:** |
| 105 | ||
| 99 | 106 | - Each node owns its children (tree ownership) |
| 100 | 107 | - `destroy()` recursively frees the entire tree |
| 101 | 108 | - Factory methods ensure proper initialization |
| 102 | 109 | |
| 103 | 110 | ### 3. parser.zig (395 lines) |
| 111 | ||
| 104 | 112 | **Purpose:** Lexical analysis and parsing |
| 105 | 113 | |
| 106 | 114 | **Architecture:** |
| 115 | ||
| 107 | 116 | ``` |
| 108 | 117 | Lexer (Tokenizer) |
| 109 | 118 | │ |
| @@ -122,21 +131,25 @@ Parser (Recursive Descent) | ||
| 122 | 131 | ``` |
| 123 | 132 | |
| 124 | 133 | **Operator Precedence (high to low):** |
| 134 | ||
| 125 | 135 | 1. Literals, groups, character classes |
| 126 | 2. Quantifiers (*, +, ?, {m,n}) | |
| 136 | 2. Quantifiers (\*, +, ?, {m,n}) | |
| 127 | 137 | 3. Concatenation (implicit) |
| 128 | 138 | 4. Alternation (|) |
| 129 | 139 | |
| 130 | 140 | **Design Notes:** |
| 141 | ||
| 131 | 142 | - Single-pass parsing |
| 132 | 143 | - Error recovery not implemented (fails fast) |
| 133 | 144 | - Lookahead limited to one token |
| 134 | 145 | - Character classes parsed separately |
| 135 | 146 | |
| 136 | 147 | ### 4. compiler.zig (455 lines) |
| 148 | ||
| 137 | 149 | **Purpose:** NFA construction via Thompson's algorithm |
| 138 | 150 | |
| 139 | 151 | **NFA Components:** |
| 152 | ||
| 140 | 153 | ```zig |
| 141 | 154 | pub const State = struct { |
| 142 | 155 | id: StateId, |
| @@ -155,23 +168,26 @@ pub const Transition = struct { | ||
| 155 | 168 | |
| 156 | 169 | **Thompson Construction Rules:** |
| 157 | 170 | |
| 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` | | |
| 162 | 175 | | Alternation 'a\|b' | `s0 --ε--> (s1 --a--> s2)<br> └--ε--> (s3 --b--> s4)` | |
| 163 | | Star 'a*' | `s0 --ε--> s1 --a--> s2 --ε--> s1<br> └--ε--> accept` | | |
| 176 | | Star 'a\*' | `s0 --ε--> s1 --a--> s2 --ε--> s1<br> └--ε--> accept` | | |
| 164 | 177 | |
| 165 | 178 | **Design Notes:** |
| 179 | ||
| 166 | 180 | - Each AST node compiles to a Fragment (start, accept states) |
| 167 | 181 | - Epsilon transitions used for control flow |
| 168 | 182 | - Greedy matching achieved through simulation order |
| 169 | 183 | - Capture groups marked on states, not transitions |
| 170 | 184 | |
| 171 | 185 | ### 5. vm.zig (340 lines) |
| 186 | ||
| 172 | 187 | **Purpose:** NFA simulation engine |
| 173 | 188 | |
| 174 | 189 | **Thread-Based Simulation:** |
| 190 | ||
| 175 | 191 | ``` |
| 176 | 192 | Thread = { |
| 177 | 193 | state: StateId, |
| @@ -190,29 +206,35 @@ Algorithm: | ||
| 190 | 206 | ``` |
| 191 | 207 | |
| 192 | 208 | **Key Features:** |
| 209 | ||
| 193 | 210 | - **Greedy Matching:** Continues matching to find longest match |
| 194 | 211 | - **Epsilon Closure:** Precomputed before consuming input |
| 195 | 212 | - **Capture Tracking:** Each thread maintains capture positions |
| 196 | 213 | - **No Backtracking:** All paths explored simultaneously |
| 197 | 214 | |
| 198 | 215 | **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 | |
| 201 | 219 | |
| 202 | 220 | **Space Complexity:** |
| 203 | - O(m * c) where m = states, c = capture groups | |
| 221 | ||
| 222 | - O(m \* c) where m = states, c = capture groups | |
| 204 | 223 | - Thread list grows with state complexity |
| 205 | 224 | |
| 206 | 225 | **Design Notes:** |
| 226 | ||
| 207 | 227 | - Threads represent possible execution paths |
| 208 | 228 | - Visited states tracked to avoid infinite loops |
| 209 | 229 | - Anchors checked during epsilon closure |
| 210 | 230 | - Match preference: longer match > earlier match |
| 211 | 231 | |
| 212 | 232 | ### 6. regex.zig (385 lines) |
| 233 | ||
| 213 | 234 | **Purpose:** Public API and convenience functions |
| 214 | 235 | |
| 215 | 236 | **API Surface:** |
| 237 | ||
| 216 | 238 | ```zig |
| 217 | 239 | // Compilation |
| 218 | 240 | Regex.compile(allocator, pattern) -> Regex |
| @@ -230,15 +252,18 @@ regex.split(allocator, input) -> [][]const u8 | ||
| 230 | 252 | ``` |
| 231 | 253 | |
| 232 | 254 | **Design Notes:** |
| 255 | ||
| 233 | 256 | - Immutable compiled patterns (can reuse) |
| 234 | 257 | - All allocations explicit and user-controlled |
| 235 | 258 | - Match results contain slices (no copies) |
| 236 | 259 | - Iterator pattern not yet implemented |
| 237 | 260 | |
| 238 | 261 | ### 7. errors.zig (67 lines) |
| 262 | ||
| 239 | 263 | **Purpose:** Error types and reporting |
| 240 | 264 | |
| 241 | 265 | **Error Types:** |
| 266 | ||
| 242 | 267 | ```zig |
| 243 | 268 | pub const RegexError = error{ |
| 244 | 269 | // Parse errors |
| @@ -260,6 +285,7 @@ pub const RegexError = error{ formatting only | ||
| 260 | 285 | ``` |
| 261 | 286 | |
| 262 | 287 | **Error Context:** |
| 288 | ||
| 263 | 289 | - Position tracking in pattern |
| 264 | 290 | - Human-readable messages |
| 265 | 291 | - Pointer to error location |
| @@ -269,12 +295,14 @@ pub const RegexError = error{ | ||
| 269 | 295 | ### Thompson's NFA Construction |
| 270 | 296 | |
| 271 | 297 | **Why Thompson?** |
| 298 | ||
| 272 | 299 | 1. Simple to implement |
| 273 | 300 | 2. Guaranteed linear time matching |
| 274 | 301 | 3. No catastrophic backtracking |
| 275 | 302 | 4. Naturally supports alternation |
| 276 | 303 | |
| 277 | 304 | **How it works:** |
| 305 | ||
| 278 | 306 | 1. Each regex operator has a standard NFA fragment pattern |
| 279 | 307 | 2. Fragments compose recursively |
| 280 | 308 | 3. Epsilon transitions connect fragments |
| @@ -283,16 +311,19 @@ pub const RegexError = error{ | ||
| 283 | 311 | ### Thread-Based Simulation |
| 284 | 312 | |
| 285 | 313 | **Why Threads?** |
| 314 | ||
| 286 | 315 | - Simulates NFA without building DFA (space efficient) |
| 287 | 316 | - Handles large state spaces |
| 288 | 317 | - Natural support for captures |
| 289 | 318 | |
| 290 | 319 | **Greedy Matching:** |
| 320 | ||
| 291 | 321 | - Continue simulation even after finding a match |
| 292 | 322 | - Keep longest match found |
| 293 | 323 | - Ensures `a*` matches "aaa" not "" |
| 294 | 324 | |
| 295 | 325 | **Epsilon Closure:** |
| 326 | ||
| 296 | 327 | - Precompute all states reachable via ε-transitions |
| 297 | 328 | - Check anchors during closure |
| 298 | 329 | - Avoids redundant state exploration |
| @@ -300,6 +331,7 @@ pub const RegexError = error{ formatting only | ||
| 300 | 331 | ## Memory Management |
| 301 | 332 | |
| 302 | 333 | **Ownership Model:** |
| 334 | ||
| 303 | 335 | ``` |
| 304 | 336 | Regex |
| 305 | 337 | └─ owns NFA |
| @@ -315,6 +347,7 @@ Matches formatting only | ||
| 315 | 347 | ``` |
| 316 | 348 | |
| 317 | 349 | **Allocation Points:** |
| 350 | ||
| 318 | 351 | 1. Pattern string duplication |
| 319 | 352 | 2. AST node creation (freed after compilation) |
| 320 | 353 | 3. NFA states and transitions |
| @@ -322,6 +355,7 @@ Matches formatting only | ||
| 322 | 355 | 5. Match results and captures |
| 323 | 356 | |
| 324 | 357 | **Guidelines:** |
| 358 | ||
| 325 | 359 | - User provides allocator for all operations |
| 326 | 360 | - No hidden global allocations |
| 327 | 361 | - Clear ownership semantics |
| @@ -331,27 +365,28 @@ Matches | ||
| 331 | 365 | |
| 332 | 366 | ### Time Complexity |
| 333 | 367 | |
| 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 | | |
| 342 | 376 | |
| 343 | 377 | ### Space Complexity |
| 344 | 378 | |
| 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 | | |
| 351 | 385 | |
| 352 | 386 | ### Optimization Opportunities |
| 353 | 387 | |
| 354 | 388 | **Not Yet Implemented:** |
| 389 | ||
| 355 | 390 | 1. DFA construction for static patterns |
| 356 | 391 | 2. State minimization |
| 357 | 392 | 3. Literal prefix extraction |
| @@ -362,6 +397,7 @@ Matches formatting only | ||
| 362 | 397 | ## Testing Strategy |
| 363 | 398 | |
| 364 | 399 | **Test Categories:** |
| 400 | ||
| 365 | 401 | 1. **Unit Tests** - Each module tested independently |
| 366 | 402 | 2. **Integration Tests** - End-to-end pattern matching |
| 367 | 403 | 3. **Edge Cases** - Empty strings, boundary conditions |
| @@ -369,6 +405,7 @@ Matches formatting only | ||
| 369 | 405 | 5. **Property Tests** - Invariants (future) |
| 370 | 406 | |
| 371 | 407 | **Current Coverage:** |
| 408 | ||
| 372 | 409 | - 35+ tests across all modules |
| 373 | 410 | - All basic features tested |
| 374 | 411 | - Edge cases covered |
| @@ -377,18 +414,21 @@ Matches | ||
| 377 | 414 | ## Future Enhancements |
| 378 | 415 | |
| 379 | 416 | ### Phase 6: Quality & Testing |
| 417 | ||
| 380 | 418 | - Comprehensive fuzzing |
| 381 | 419 | - Property-based tests |
| 382 | 420 | - Performance regression tests |
| 383 | 421 | - Memory leak detection |
| 384 | 422 | |
| 385 | 423 | ### Phase 7: Performance |
| 424 | ||
| 386 | 425 | - DFA construction option |
| 387 | 426 | - Literal prefix optimization |
| 388 | 427 | - State minimization |
| 389 | 428 | - Benchmark suite |
| 390 | 429 | |
| 391 | 430 | ### Phase 8: Extended Features |
| 431 | ||
| 392 | 432 | - Named capture groups |
| 393 | 433 | - Backreferences |
| 394 | 434 | - Lookahead/lookbehind |
| @@ -396,6 +436,7 @@ Matches formatting only | ||
| 396 | 436 | - Unicode properties |
| 397 | 437 | |
| 398 | 438 | ### Phase 9: API Improvements |
| 439 | ||
| 399 | 440 | - Iterator interface |
| 400 | 441 | - Streaming matches |
| 401 | 442 | - Partial matching |
| @@ -404,10 +445,12 @@ Matches | ||
| 404 | 445 | ## References |
| 405 | 446 | |
| 406 | 447 | **Algorithms:** |
| 448 | ||
| 407 | 449 | - Thompson, Ken (1968). "Regular Expression Search Algorithm" |
| 408 | 450 | - Cox, Russ (2007). "Regular Expression Matching Can Be Simple And Fast" |
| 409 | 451 | |
| 410 | 452 | **Implementations:** |
| 453 | ||
| 411 | 454 | - RE2 (Google) - Inspiration for architecture |
| 412 | 455 | - Rust regex crate - API design reference |
| 413 | 456 | - PCRE - Feature completeness reference |
| @@ -415,6 +458,7 @@ Matches formatting only | ||
| 415 | 458 | ## Contributing |
| 416 | 459 | |
| 417 | 460 | When modifying the architecture: |
| 461 | ||
| 418 | 462 | 1. Maintain phase separation |
| 419 | 463 | 2. Keep allocations explicit |
| 420 | 464 | 3. Preserve linear time guarantees |
| @@ -32,12 +32,12 @@ All benchmarks run with 10,000 iterations unless otherwise noted. | ||
| 32 | 32 | |
| 33 | 33 | ### Core Operations |
| 34 | 34 | |
| 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 | | |
| 41 | 41 | |
| 42 | 42 | ### Detailed Results |
| 43 | 43 | |
| @@ -65,24 +65,24 @@ Test 4: Case-insensitive matching... | ||
| 65 | 65 | |
| 66 | 66 | ### Time Complexity |
| 67 | 67 | |
| 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 | | |
| 77 | 77 | |
| 78 | 78 | ### Space Complexity |
| 79 | 79 | |
| 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 | | |
| 86 | 86 | |
| 87 | 87 | ### Greedy Matching Performance |
| 88 | 88 | |
| @@ -255,11 +255,13 @@ Pattern: "\\d{3}-\\d{4}" | ||
| 255 | 255 | ### vs. Backtracking Engines (PCRE, Python re) |
| 256 | 256 | |
| 257 | 257 | **Advantages:** |
| 258 | ||
| 258 | 259 | - ✅ **No catastrophic backtracking** - Always linear time |
| 259 | 260 | - ✅ **Predictable performance** - O(n×m) guaranteed |
| 260 | 261 | - ✅ **Memory safe** - No stack overflow on deep recursion |
| 261 | 262 | |
| 262 | 263 | **Trade-offs:** |
| 264 | ||
| 263 | 265 | - ❌ **Slower on simple patterns** - More overhead for simple cases |
| 264 | 266 | - ❌ **No backreferences** - Not currently supported |
| 265 | 267 | - ❌ **No look-around** - Not currently supported |
| @@ -267,10 +269,12 @@ Pattern: "\\d{3}-\\d{4}" | ||
| 267 | 269 | ### vs. DFA Engines (RE2, ripgrep) |
| 268 | 270 | |
| 269 | 271 | **Similarities:** |
| 272 | ||
| 270 | 273 | - ✅ **Linear time** - Both guarantee O(n×m) |
| 271 | 274 | - ✅ **No backtracking** - Deterministic matching |
| 272 | 275 | |
| 273 | 276 | **Trade-offs:** |
| 277 | ||
| 274 | 278 | - ❌ **NFA simulation** - We don't build full DFA (saves memory) |
| 275 | 279 | - ❌ **Capture groups** - Our approach is simpler for captures |
| 276 | 280 | - ✅ **Memory usage** - Lower than full DFA construction |
| @@ -410,6 +414,7 @@ CSV splitting (100,000 rows): ~300ms total formatting only | ||
| 410 | 414 | ``` |
| 411 | 415 | |
| 412 | 416 | These are estimates. Actual performance depends on: |
| 417 | ||
| 413 | 418 | - Pattern complexity |
| 414 | 419 | - Input characteristics |
| 415 | 420 | - Hardware specifications |
| @@ -29,14 +29,11 @@ This will produce `regex.wasm` which can be loaded in browsers or Node.js. | ||
| 29 | 29 | |
| 30 | 30 | ```javascript |
| 31 | 31 | // Load the WASM module |
| 32 | const wasmModule = await WebAssembly.instantiateStreaming( | |
| 33 | fetch('regex.wasm'), | |
| 34 | { | |
| 35 | env: { | |
| 36 | // Provide any required imports | |
| 37 | } | |
| 38 | } | |
| 39 | ); | |
| 32 | const wasmModule = await WebAssembly.instantiateStreaming(fetch("regex.wasm"), { | |
| 33 | env: { | |
| 34 | // Provide any required imports | |
| 35 | }, | |
| 36 | }); | |
| 40 | 37 | |
| 41 | 38 | // Use the exported functions |
| 42 | 39 | const exports = wasmModule.instance.exports; |
| @@ -48,7 +45,7 @@ const regex = exports.zig_regex_compile(pattern); | ||
| 48 | 45 | // Check for match |
| 49 | 46 | const input = "test 123"; |
| 50 | 47 | const matches = exports.zig_regex_is_match(regex, input); |
| 51 | console.log('Matches:', matches === 1); | |
| 48 | console.log("Matches:", matches === 1); | |
| 52 | 49 | |
| 53 | 50 | // Clean up |
| 54 | 51 | exports.zig_regex_free(regex); |
| @@ -57,28 +54,29 @@ exports.zig_regex_free(regex); | ||
| 57 | 54 | ### Node.js Usage |
| 58 | 55 | |
| 59 | 56 | ```javascript |
| 60 | const fs = require('fs'); | |
| 61 | const path = require('path'); | |
| 57 | const fs = require("fs"); | |
| 58 | const path = require("path"); | |
| 62 | 59 | |
| 63 | 60 | // Load WASM file |
| 64 | const wasmPath = path.join(__dirname, 'regex.wasm'); | |
| 61 | const wasmPath = path.join(__dirname, "regex.wasm"); | |
| 65 | 62 | const wasmBuffer = fs.readFileSync(wasmPath); |
| 66 | 63 | |
| 67 | 64 | // Instantiate |
| 68 | 65 | WebAssembly.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; | |
| 74 | 72 | |
| 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"); | |
| 78 | 76 | |
| 79 | console.log('Pattern matches:', matches === 1); | |
| 77 | console.log("Pattern matches:", matches === 1); | |
| 80 | 78 | |
| 81 | zig_regex_free(regex); | |
| 79 | zig_regex_free(regex); | |
| 82 | 80 | }); |
| 83 | 81 | ``` |
| 84 | 82 | |
| @@ -89,43 +87,43 @@ Create a TypeScript wrapper for type safety: | ||
| 89 | 87 | ```typescript |
| 90 | 88 | // regex.d.ts |
| 91 | 89 | export 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; | |
| 96 | 94 | } |
| 97 | 95 | |
| 98 | 96 | export interface Match { |
| 99 | text: string; | |
| 100 | start: number; | |
| 101 | end: number; | |
| 97 | text: string; | |
| 98 | start: number; | |
| 99 | end: number; | |
| 102 | 100 | } |
| 103 | 101 | |
| 104 | 102 | // regex.ts |
| 105 | 103 | export 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"); | |
| 116 | 112 | } |
| 113 | this.handle = handle; | |
| 114 | } | |
| 117 | 115 | |
| 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 | } | |
| 121 | 119 | |
| 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 | } | |
| 125 | 123 | |
| 126 | free(): void { | |
| 127 | this.wasm.free(this.handle); | |
| 128 | } | |
| 124 | free(): void { | |
| 125 | this.wasm.free(this.handle); | |
| 126 | } | |
| 129 | 127 | } |
| 130 | 128 | ``` |
| 131 | 129 | |
| @@ -141,36 +139,36 @@ The WASM build uses the C allocator, which means: | ||
| 141 | 139 | |
| 142 | 140 | ```javascript |
| 143 | 141 | class 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); | |
| 155 | 151 | } |
| 152 | return handle; | |
| 153 | } | |
| 156 | 154 | |
| 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 | } | |
| 160 | 158 | |
| 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); | |
| 166 | 163 | } |
| 164 | } | |
| 167 | 165 | |
| 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); | |
| 173 | 169 | } |
| 170 | this.regexes.clear(); | |
| 171 | } | |
| 174 | 172 | } |
| 175 | 173 | ``` |
| 176 | 174 | |
| @@ -6,7 +6,7 @@ const Composer = .Composer; | ||
| 6 | 6 | const Lint = .Lint; |
| 7 | 7 | const ComplexityAnalyzer = .ComplexityAnalyzer; |
| 8 | 8 | |
| 9 | pub fn main() !void { | |
| 9 | pub fn main(init: std.process.Init) !void { | |
| 10 | 10 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 11 | 11 | defer _ = gpa.deinit(); |
| 12 | 12 | const allocator = gpa.allocator(); |
| @@ -39,7 +39,7 @@ pub fn main() !void { | ||
| 39 | 39 | |
| 40 | 40 | std.debug.print("Built pattern: {s}\n", .{pattern}); |
| 41 | 41 | |
| 42 | var regex = try Regex.compile(allocator, pattern); | |
| 42 | var regex = try Regex.compile(allocator, init.io, pattern); | |
| 43 | 43 | defer regex.deinit(); |
| 44 | 44 | |
| 45 | 45 | const test_emails = [_][]const u8{ |
| @@ -78,7 +78,7 @@ pub fn main() !void { | ||
| 78 | 78 | std.debug.print("Phone pattern: {s}\n\n", .{phone_pattern}); |
| 79 | 79 | |
| 80 | 80 | // 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); | |
| 82 | 82 | defer phone_regex.deinit(); |
| 83 | 83 | |
| 84 | 84 | const test_phones = [_][]const u8{ |
| @@ -116,7 +116,7 @@ pub fn main() !void { | ||
| 116 | 116 | |
| 117 | 117 | std.debug.print("Composed pattern (OR): {s}\n", .{alternatives}); |
| 118 | 118 | |
| 119 | var regex = try Regex.compile(allocator, alternatives); | |
| 119 | var regex = try Regex.compile(allocator, init.io, alternatives); | |
| 120 | 120 | defer regex.deinit(); |
| 121 | 121 | |
| 122 | 122 | const test_strings = [_][]const u8{ |
| @@ -232,7 +232,7 @@ pub fn main() !void { | ||
| 232 | 232 | std.debug.print("Complexity: {s} (score: {d})\n\n", .{ @tagName(complexity.getLevel()), complexity.complexity_score }); |
| 233 | 233 | |
| 234 | 234 | // 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); | |
| 236 | 236 | defer regex.deinit(); |
| 237 | 237 | |
| 238 | 238 | const test_urls = [_][]const u8{ |
| @@ -305,7 +305,7 @@ pub fn main() !void { | ||
| 305 | 305 | |
| 306 | 306 | std.debug.print("Built pattern: {s}\n\n", .{ipv4_pattern}); |
| 307 | 307 | |
| 308 | var regex = try Regex.compile(allocator, ipv4_pattern); | |
| 308 | var regex = try Regex.compile(allocator, init.io, ipv4_pattern); | |
| 309 | 309 | defer regex.deinit(); |
| 310 | 310 | |
| 311 | 311 | const test_ips = [_][]const u8{ |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | const std = ; |
| 2 | 2 | const Regex = .Regex; |
| 3 | 3 | |
| 4 | pub fn main() !void { | |
| 4 | pub fn main(init: std.process.Init) !void { | |
| 5 | 5 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 6 | 6 | defer _ = gpa.deinit(); |
| 7 | 7 | const allocator = gpa.allocator(); |
| @@ -11,7 +11,7 @@ pub fn main() !void { | ||
| 11 | 11 | // Example 1: Simple literal matching |
| 12 | 12 | { |
| 13 | 13 | 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"); | |
| 15 | 15 | defer regex.deinit(); |
| 16 | 16 | |
| 17 | 17 | const matches = try regex.isMatch("hello"); |
| @@ -22,7 +22,7 @@ pub fn main() !void { | ||
| 22 | 22 | // Example 2: Alternation |
| 23 | 23 | { |
| 24 | 24 | 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"); | |
| 26 | 26 | defer regex.deinit(); |
| 27 | 27 | |
| 28 | 28 | std.debug.print(" 'cat' matches: {}\n", .{try regex.isMatch("cat")}); |
| @@ -34,7 +34,7 @@ pub fn main() !void { | ||
| 34 | 34 | // Example 3: Star quantifier |
| 35 | 35 | { |
| 36 | 36 | 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*"); | |
| 38 | 38 | defer regex.deinit(); |
| 39 | 39 | |
| 40 | 40 | std.debug.print(" '' matches: {}\n", .{try regex.isMatch("")}); |
| @@ -46,7 +46,7 @@ pub fn main() !void { | ||
| 46 | 46 | // Example 4: Finding matches |
| 47 | 47 | { |
| 48 | 48 | 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"); | |
| 50 | 50 | defer regex.deinit(); |
| 51 | 51 | |
| 52 | 52 | if (try regex.find("hello world")) |match_result| { |
| @@ -60,7 +60,7 @@ pub fn main() !void { | ||
| 60 | 60 | // Example 5: Replace |
| 61 | 61 | { |
| 62 | 62 | 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"); | |
| 64 | 64 | defer regex.deinit(); |
| 65 | 65 | |
| 66 | 66 | const result = try regex.replace(allocator, "hello world", "Zig"); |
| @@ -4,7 +4,7 @@ const debug = .debug; | ||
| 4 | 4 | const Parser = .parser.Parser; |
| 5 | 5 | const Optimizer = .optimizer.Optimizer; |
| 6 | 6 | |
| 7 | pub fn main() !void { | |
| 7 | pub fn main(init: std.process.Init) !void { | |
| 8 | 8 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 9 | 9 | defer _ = gpa.deinit(); |
| 10 | 10 | const allocator = gpa.allocator(); |
| @@ -25,7 +25,7 @@ pub fn main() !void { | ||
| 25 | 25 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 26 | 26 | |
| 27 | 27 | // 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| { | |
| 29 | 29 | std.debug.print(" ❌ Compilation error: {}\n\n", .{err}); |
| 30 | 30 | continue; |
| 31 | 31 | }; |
| @@ -88,7 +88,7 @@ pub fn main() !void { | ||
| 88 | 88 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 89 | 89 | |
| 90 | 90 | 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); | |
| 92 | 92 | defer test_regex.deinit(); |
| 93 | 93 | |
| 94 | 94 | const test_inputs = [_][]const u8{ |
| @@ -4,7 +4,7 @@ const ErrorContext = .ErrorContext; | ||
| 4 | 4 | const ErrorHelper = .ErrorHelper; |
| 5 | 5 | const RegexError = .RegexError; |
| 6 | 6 | |
| 7 | pub fn main() !void { | |
| 7 | pub fn main(init: std.process.Init) !void { | |
| 8 | 8 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 9 | 9 | defer _ = gpa.deinit(); |
| 10 | 10 | const allocator = gpa.allocator(); |
| @@ -13,11 +13,11 @@ pub fn main() !void { formatting only | ||
| 13 | 13 | |
| 14 | 14 | // Example 1: Invalid patterns with helpful error messages |
| 15 | 15 | 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 | |
| 21 | 21 | "(a(b(c(d(e(f(g(h(i(j(k(l(m(n(o(p(q", // Too many nested groups |
| 22 | 22 | }; |
| 23 | 23 | |
| @@ -26,7 +26,7 @@ pub fn main() !void { | ||
| 26 | 26 | std.debug.print("Testing pattern: \"{s}\"\n", .{pattern}); |
| 27 | 27 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 28 | 28 | |
| 29 | if (Regex.compile(allocator, pattern)) |regex_value| { | |
| 29 | if (Regex.compile(allocator, init.io, pattern)) |regex_value| { | |
| 30 | 30 | var regex = regex_value; |
| 31 | 31 | defer regex.deinit(); |
| 32 | 32 | std.debug.print("✓ Pattern compiled successfully (unexpected)\n\n", .{}); |
| @@ -53,7 +53,7 @@ pub fn main() !void { | ||
| 53 | 53 | const user_pattern = "hello|world"; |
| 54 | 54 | std.debug.print("User provided pattern: \"{s}\"\n", .{user_pattern}); |
| 55 | 55 | |
| 56 | if (Regex.compile(allocator, user_pattern)) |regex_value| { | |
| 56 | if (Regex.compile(allocator, init.io, user_pattern)) |regex_value| { | |
| 57 | 57 | var regex = regex_value; |
| 58 | 58 | defer regex.deinit(); |
| 59 | 59 | std.debug.print("✓ Pattern compiled successfully\n", .{}); |
| @@ -84,7 +84,7 @@ pub fn main() !void { | ||
| 84 | 84 | const problematic_pattern = "abc[def"; |
| 85 | 85 | std.debug.print("Problematic pattern: \"{s}\"\n", .{problematic_pattern}); |
| 86 | 86 | |
| 87 | if (Regex.compile(allocator, problematic_pattern)) |regex_value| { | |
| 87 | if (Regex.compile(allocator, init.io, problematic_pattern)) |regex_value| { | |
| 88 | 88 | var regex = regex_value; |
| 89 | 89 | regex.deinit(); |
| 90 | 90 | } else |err| { |
| @@ -95,7 +95,7 @@ pub fn main() !void { | ||
| 95 | 95 | const fixed_pattern = "abc[def]"; |
| 96 | 96 | std.debug.print("Fixed pattern: \"{s}\"\n", .{fixed_pattern}); |
| 97 | 97 | |
| 98 | if (Regex.compile(allocator, fixed_pattern)) |regex_value| { | |
| 98 | if (Regex.compile(allocator, init.io, fixed_pattern)) |regex_value| { | |
| 99 | 99 | var regex = regex_value; |
| 100 | 100 | defer regex.deinit(); |
| 101 | 101 | std.debug.print("✓ Fixed pattern compiles successfully!\n", .{}); |
| @@ -119,7 +119,7 @@ pub fn main() !void { | ||
| 119 | 119 | |
| 120 | 120 | for (patterns_to_validate) |pattern| { |
| 121 | 121 | std.debug.print("Validating: \"{s}\" ... ", .{pattern}); |
| 122 | if (Regex.compile(allocator, pattern)) |regex_value| { | |
| 122 | if (Regex.compile(allocator, init.io, pattern)) |regex_value| { | |
| 123 | 123 | var regex = regex_value; |
| 124 | 124 | regex.deinit(); |
| 125 | 125 | std.debug.print("✓ Valid\n", .{}); |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | const Profiler = .Profiler; |
| 4 | 4 | const ScopedTimer = .ScopedTimer; |
| 5 | 5 | |
| 6 | pub fn main() !void { | |
| 6 | pub fn main(init: std.process.Init) !void { | |
| 7 | 7 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 8 | 8 | defer _ = gpa.deinit(); |
| 9 | 9 | const allocator = gpa.allocator(); |
| @@ -16,11 +16,11 @@ pub fn main() !void { | ||
| 16 | 16 | std.debug.print("Example 1: Basic profiling\n", .{}); |
| 17 | 17 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 18 | 18 | |
| 19 | var profiler = Profiler.init(allocator, true); | |
| 19 | var profiler = Profiler.init(allocator, init.io, true); | |
| 20 | 20 | |
| 21 | 21 | // Profile compilation |
| 22 | 22 | profiler.startCompilation(); |
| 23 | var regex = try Regex.compile(allocator, "hello.*world"); | |
| 23 | var regex = try Regex.compile(allocator, init.io, "hello.*world"); | |
| 24 | 24 | profiler.endCompilation(); |
| 25 | 25 | defer regex.deinit(); |
| 26 | 26 | |
| @@ -48,13 +48,13 @@ pub fn main() !void { | ||
| 48 | 48 | std.debug.print("Example 2: Scoped timers\n", .{}); |
| 49 | 49 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 50 | 50 | |
| 51 | var profiler = Profiler.init(allocator, true); | |
| 51 | var profiler = Profiler.init(allocator, init.io, true); | |
| 52 | 52 | |
| 53 | 53 | { |
| 54 | 54 | var timer = ScopedTimer.init(&profiler, .compilation); |
| 55 | 55 | defer timer.deinit(); |
| 56 | 56 | |
| 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]+"); | |
| 58 | 58 | regex.deinit(); |
| 59 | 59 | } |
| 60 | 60 | |
| @@ -69,19 +69,19 @@ pub fn main() !void { | ||
| 69 | 69 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 70 | 70 | |
| 71 | 71 | 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 | |
| 75 | 75 | }; |
| 76 | 76 | |
| 77 | 77 | const test_text = ("some random text " ** 10) ++ "hello world"; |
| 78 | 78 | |
| 79 | 79 | for (patterns) |pattern| { |
| 80 | var profiler = Profiler.init(allocator, true); | |
| 80 | var profiler = Profiler.init(allocator, init.io, true); | |
| 81 | 81 | |
| 82 | 82 | std.debug.print("\nPattern: \"{s}\"\n", .{pattern}); |
| 83 | 83 | |
| 84 | var regex = try Regex.compile(allocator, pattern); | |
| 84 | var regex = try Regex.compile(allocator, init.io, pattern); | |
| 85 | 85 | defer regex.deinit(); |
| 86 | 86 | |
| 87 | 87 | // Run multiple matches |
| @@ -94,8 +94,8 @@ pub fn main() !void { | ||
| 94 | 94 | } |
| 95 | 95 | |
| 96 | 96 | 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 }); | |
| 99 | 99 | } |
| 100 | 100 | } |
| 101 | 101 | |
| @@ -105,12 +105,12 @@ pub fn main() !void { | ||
| 105 | 105 | std.debug.print("Example 4: Memory tracking\n", .{}); |
| 106 | 106 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 107 | 107 | |
| 108 | var profiler = Profiler.init(allocator, true); | |
| 108 | var profiler = Profiler.init(allocator, init.io, true); | |
| 109 | 109 | |
| 110 | 110 | const complex_pattern = "(a|b)+c(d|e)*f"; |
| 111 | 111 | std.debug.print("Pattern: \"{s}\"\n", .{complex_pattern}); |
| 112 | 112 | |
| 113 | var regex = try Regex.compile(allocator, complex_pattern); | |
| 113 | var regex = try Regex.compile(allocator, init.io, complex_pattern); | |
| 114 | 114 | defer regex.deinit(); |
| 115 | 115 | |
| 116 | 116 | // Simulate memory tracking (in real implementation, this would be integrated) |
| @@ -131,9 +131,9 @@ pub fn main() !void { | ||
| 131 | 131 | const test_pattern = "hello"; |
| 132 | 132 | |
| 133 | 133 | // Baseline |
| 134 | var baseline_profiler = Profiler.init(allocator, true); | |
| 134 | var baseline_profiler = Profiler.init(allocator, init.io, true); | |
| 135 | 135 | { |
| 136 | var regex = try Regex.compile(allocator, baseline_pattern); | |
| 136 | var regex = try Regex.compile(allocator, init.io, baseline_pattern); | |
| 137 | 137 | defer regex.deinit(); |
| 138 | 138 | |
| 139 | 139 | var i: usize = 0; |
| @@ -145,9 +145,9 @@ pub fn main() !void { | ||
| 145 | 145 | } |
| 146 | 146 | |
| 147 | 147 | // Test |
| 148 | var test_profiler = Profiler.init(allocator, true); | |
| 148 | var test_profiler = Profiler.init(allocator, init.io, true); | |
| 149 | 149 | { |
| 150 | var regex = try Regex.compile(allocator, test_pattern); | |
| 150 | var regex = try Regex.compile(allocator, init.io, test_pattern); | |
| 151 | 151 | defer regex.deinit(); |
| 152 | 152 | |
| 153 | 153 | var i: usize = 0; |
| @@ -162,8 +162,8 @@ pub fn main() !void { | ||
| 162 | 162 | const test_time = test_profiler.getMetrics().match_time_ns; |
| 163 | 163 | const diff_percent = @as(f64, @floatFromInt(test_time)) / @as(f64, @floatFromInt(baseline_time)) * 100.0 - 100.0; |
| 164 | 164 | |
| 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)}); | |
| 167 | 167 | std.debug.print("Difference: {d:.1}%\n", .{diff_percent}); |
| 168 | 168 | |
| 169 | 169 | if (diff_percent > 10.0) { |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | const SharedRegex = .SharedRegex; |
| 4 | 4 | const RegexCache = .RegexCache; |
| 5 | 5 | |
| 6 | pub fn main() !void { | |
| 6 | pub fn main(init: std.process.Init) !void { | |
| 7 | 7 | var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){}; |
| 8 | 8 | defer _ = gpa.deinit(); |
| 9 | 9 | const allocator = gpa.allocator(); |
| @@ -17,7 +17,7 @@ pub fn main() !void { | ||
| 17 | 17 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 18 | 18 | |
| 19 | 19 | // Compile once |
| 20 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 20 | var regex = try Regex.compile(allocator, init.io, "\\d+"); | |
| 21 | 21 | defer regex.deinit(); |
| 22 | 22 | |
| 23 | 23 | const Worker = struct { |
| @@ -63,7 +63,7 @@ pub fn main() !void { | ||
| 63 | 63 | std.debug.print("Example 2: SharedRegex with reference counting\n", .{}); |
| 64 | 64 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 65 | 65 | |
| 66 | var shared = try SharedRegex.init(allocator, "test"); | |
| 66 | var shared = try SharedRegex.init(allocator, init.io, "test"); | |
| 67 | 67 | defer shared.deinit(); |
| 68 | 68 | |
| 69 | 69 | const Worker = struct { |
| @@ -103,9 +103,9 @@ pub fn main() !void { | ||
| 103 | 103 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 104 | 104 | |
| 105 | 105 | 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 { | |
| 107 | 107 | // Each thread gets its own cache |
| 108 | var cache = RegexCache.init(alloc); | |
| 108 | var cache = RegexCache.init(alloc, io); | |
| 109 | 109 | defer cache.deinit(); |
| 110 | 110 | |
| 111 | 111 | std.debug.print("Thread {d}: Created local cache\n", .{thread_id}); |
| @@ -128,7 +128,7 @@ pub fn main() !void { | ||
| 128 | 128 | var threads: [thread_count]std.Thread = undefined; |
| 129 | 129 | |
| 130 | 130 | 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 }); | |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | 134 | for (threads) |thread| { |
| @@ -144,12 +144,12 @@ pub fn main() !void { | ||
| 144 | 144 | std.debug.print("Example 4: Performance with concurrent matching\n", .{}); |
| 145 | 145 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 146 | 146 | |
| 147 | var regex = try Regex.compile(allocator, "test"); | |
| 147 | var regex = try Regex.compile(allocator, init.io, "test"); | |
| 148 | 148 | defer regex.deinit(); |
| 149 | 149 | |
| 150 | 150 | 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); | |
| 153 | 153 | |
| 154 | 154 | var count: usize = 0; |
| 155 | 155 | var i: usize = 0; |
| @@ -159,8 +159,8 @@ pub fn main() !void { | ||
| 159 | 159 | } else |_| {} |
| 160 | 160 | } |
| 161 | 161 | |
| 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(); | |
| 164 | 164 | } |
| 165 | 165 | }; |
| 166 | 166 | |
| @@ -171,15 +171,15 @@ pub fn main() !void { | ||
| 171 | 171 | // Parallel execution |
| 172 | 172 | const thread_count = 4; |
| 173 | 173 | var threads: [thread_count]std.Thread = undefined; |
| 174 | var times: [thread_count]usize = undefined; | |
| 174 | var times: [thread_count]i64 = undefined; | |
| 175 | 175 | |
| 176 | 176 | const ParallelWorker = struct { |
| 177 | fn run(r: *const Regex, time_ptr: *usize) void { | |
| 177 | fn run(r: *const Regex, time_ptr: *i64) void { | |
| 178 | 178 | time_ptr.* = Worker.run(r); |
| 179 | 179 | } |
| 180 | 180 | }; |
| 181 | 181 | |
| 182 | var parallel_timer = try std.time.Timer.start(); | |
| 182 | var parallel_timer = std.Io.Clock.awake.now(init.io); | |
| 183 | 183 | |
| 184 | 184 | for (&threads, 0..) |*thread, i| { |
| 185 | 185 | thread.* = try std.Thread.spawn(.{}, ParallelWorker.run, .{ ®ex, ×[i] }); |
| @@ -189,7 +189,7 @@ pub fn main() !void { | ||
| 189 | 189 | thread.join(); |
| 190 | 190 | } |
| 191 | 191 | |
| 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); | |
| 193 | 193 | |
| 194 | 194 | std.debug.print("Parallel ({d} threads): 40,000 matches in {d}ms\n", .{ thread_count, total_parallel }); |
| 195 | 195 | std.debug.print("Individual thread times: ", .{}); |
| @@ -206,13 +206,13 @@ pub fn main() !void { | ||
| 206 | 206 | std.debug.print("Example 5: Multiple patterns with concurrent access\n", .{}); |
| 207 | 207 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 208 | 208 | |
| 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]+"); | |
| 210 | 210 | defer email_regex.deinit(); |
| 211 | 211 | |
| 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}"); | |
| 213 | 213 | defer phone_regex.deinit(); |
| 214 | 214 | |
| 215 | var url_regex = try Regex.compile(allocator, "https?://"); | |
| 215 | var url_regex = try Regex.compile(allocator, init.io, "https?://"); | |
| 216 | 216 | defer url_regex.deinit(); |
| 217 | 217 | |
| 218 | 218 | const Worker = struct { |
| @@ -16,10 +16,10 @@ pub const AtomicGroupNode = struct { | ||
| 16 | 16 | /// Possessive Quantifiers: *+, ++, ?+, {n,m}+ |
| 17 | 17 | /// Like greedy quantifiers but don't backtrack |
| 18 | 18 | pub const PossessiveQuantifier = enum { |
| 19 | star_possessive, // *+ | |
| 20 | plus_possessive, // ++ | |
| 19 | star_possessive, // *+ | |
| 20 | plus_possessive, // ++ | |
| 21 | 21 | optional_possessive, // ?+ |
| 22 | repeat_possessive, // {n,m}+ | |
| 22 | repeat_possessive, // {n,m}+ | |
| 23 | 23 | |
| 24 | 24 | pub fn fromGreedy(_: bool) ?PossessiveQuantifier { |
| 25 | 25 | // Helper to detect possessive syntax |
| @@ -74,7 +74,7 @@ pub const AdvancedNodeType = enum { | ||
| 74 | 74 | // Tests |
| 75 | 75 | test "atomic group creation" { |
| 76 | 76 | const allocator = std.testing.allocator; |
| 77 | ||
| 77 | ||
| 78 | 78 | // Create a simple child node (literal 'a') |
| 79 | 79 | const child = try ast.Node.createLiteral(allocator, 'a', .{ .start = 0, .end = 1 }); |
| 80 | 80 | defer allocator.destroy(child); |
| @@ -5,7 +5,6 @@ const common = ; | ||
| 5 | 5 | /// Backtracking-based regex engine |
| 6 | 6 | /// Supports: lazy quantifiers, lookahead/lookbehind, backreferences |
| 7 | 7 | /// Trade-off: O(2^n) worst case, but supports features impossible in Thompson NFA |
| 8 | ||
| 9 | 8 | /// Match result from backtracking engine |
| 10 | 9 | pub const BacktrackMatch = struct { |
| 11 | 10 | start: usize, |
| @@ -26,7 +26,7 @@ pub fn benchmark( | ||
| 26 | 26 | const avg_ns = elapsed_ns / iterations; |
| 27 | 27 | const avg_us = avg_ns / 1000; |
| 28 | 28 | |
| 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 }); | |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | pub fn benchmarkCompile( |
| @@ -47,5 +47,5 @@ pub fn benchmarkCompile( | ||
| 47 | 47 | const avg_ns = elapsed_ns / iterations; |
| 48 | 48 | const avg_us = avg_ns / 1000; |
| 49 | 49 | |
| 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 }); | |
| 51 | 51 | } |
| @@ -1,215 +1,214 @@ | ||
| 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 | ||
| 22 | /// Opaque handle to a compiled regex | |
| 23 | pub const ZigRegex = opaque {}; | |
| 24 | ||
| 25 | /// Opaque handle to a match result | |
| 26 | pub const ZigMatch = opaque {}; | |
| 27 | ||
| 28 | /// Error codes | |
| 29 | pub 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) | |
| 39 | var 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(). | |
| 45 | export 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 | |
| 58 | export 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 | |
| 69 | export 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(). | |
| 83 | export 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. | |
| 103 | export 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 | |
| 113 | export 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 | |
| 121 | export 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 | |
| 129 | export 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 | |
| 139 | export fn zig_regex_version() [*:0]const u8 { | |
| 140 | return "0.1.0"; | |
| 141 | } | |
| 142 | ||
| 143 | // Header file generator (for documentation) | |
| 144 | pub 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 | ||
| 206 | test "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 | //} | |
| @@ -106,7 +106,7 @@ pub fn main(init: std.process.Init) !void { | ||
| 106 | 106 | break :blk stdin_alloc.?; |
| 107 | 107 | }; |
| 108 | 108 | |
| 109 | var re = regex.Regex.compileWithFlags(allocator, pattern, .{ | |
| 109 | var re = regex.Regex.compileWithFlags(allocator,io, pattern, .{ | |
| 110 | 110 | .case_insensitive = case_insensitive, |
| 111 | 111 | .multiline = multiline, |
| 112 | 112 | }) catch |err| { |
| @@ -657,10 +657,7 @@ pub const Parser = struct { | ||
| 657 | 657 | .dollar => '$', |
| 658 | 658 | .lbracket => '[', // Allow [ as literal (for non-POSIX cases) |
| 659 | 659 | // 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, | |
| 664 | 661 | }; |
| 665 | 662 | } |
| 666 | 663 | |
| @@ -685,7 +682,8 @@ pub const Parser = struct { | ||
| 685 | 682 | |
| 686 | 683 | if (current_pos + 1 < self.lexer.input.len and |
| 687 | 684 | self.lexer.input[current_pos] == '[' and |
| 688 | self.lexer.input[current_pos + 1] == ':') { | |
| 685 | self.lexer.input[current_pos + 1] == ':') | |
| 686 | { | |
| 689 | 687 | |
| 690 | 688 | // Find the closing :] |
| 691 | 689 | var found_posix = false; |
| @@ -693,7 +691,7 @@ pub const Parser = struct { formatting only | ||
| 693 | 691 | while (i + 1 < self.lexer.input.len) : (i += 1) { |
| 694 | 692 | if (self.lexer.input[i] == ':' and self.lexer.input[i + 1] == ']') { |
| 695 | 693 | // Found [:name:] |
| 696 | const class_name = self.lexer.input[current_pos + 2..i]; | |
| 694 | const class_name = self.lexer.input[current_pos + 2 .. i]; | |
| 697 | 695 | |
| 698 | 696 | // Skip to the character AFTER ':]' which should be the outer ']' or more chars |
| 699 | 697 | // We want the lexer to be positioned so that the NEXT token read will be correct |
| @@ -324,7 +324,6 @@ pub const PatternAnalyzer = struct { | ||
| 324 | 324 | } |
| 325 | 325 | |
| 326 | 326 | fn nodesAreIdentical(self: *PatternAnalyzer, left: *ast.Node, right: *ast.Node) bool { |
| 327 | ||
| 328 | 327 | if (left.node_type != right.node_type) return false; |
| 329 | 328 | |
| 330 | 329 | return switch (left.node_type) { |
| @@ -337,7 +336,7 @@ pub const PatternAnalyzer = struct { | ||
| 337 | 336 | const left_concat = left.data.concat; |
| 338 | 337 | const right_concat = right.data.concat; |
| 339 | 338 | 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); | |
| 341 | 340 | }, |
| 342 | 341 | else => false, |
| 343 | 342 | }; |
| @@ -1,15 +1,17 @@ | ||
| 1 | 1 | const std = ; |
| 2 | const Clock = std.Io.Clock; | |
| 2 | 3 | |
| 3 | 4 | /// Performance profiling and metrics tracking for regex operations |
| 4 | 5 | pub const Profiler = struct { |
| 5 | 6 | allocator: std.mem.Allocator, |
| 7 | io: std.Io, | |
| 6 | 8 | enabled: bool, |
| 7 | 9 | metrics: Metrics, |
| 8 | start_time: ?std.time.Instant, | |
| 10 | start_time: ?std.Io.Timestamp, | |
| 9 | 11 | |
| 10 | 12 | 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, | |
| 13 | 15 | match_count: usize = 0, |
| 14 | 16 | nfa_states_created: usize = 0, |
| 15 | 17 | backtrack_count: usize = 0, |
| @@ -25,13 +27,8 @@ pub const Profiler = struct { | ||
| 25 | 27 | |
| 26 | 28 | pub fn format( |
| 27 | 29 | self: Metrics, |
| 28 | comptime fmt: []const u8, | |
| 29 | options: std.fmt.FormatOptions, | |
| 30 | writer: anytype, | |
| 30 | writer: *std.Io.Writer, | |
| 31 | 31 | ) !void { |
| 32 | _ = fmt; | |
| 33 | _ = options; | |
| 34 | ||
| 35 | 32 | try writer.writeAll("\n=== Regex Performance Metrics ===\n"); |
| 36 | 33 | try writer.print("Compilation time: {d}μs\n", .{self.compilation_time_ns / 1000}); |
| 37 | 34 | try writer.print("Match time: {d}μs\n", .{self.match_time_ns / 1000}); |
| @@ -49,41 +46,40 @@ pub const Profiler = struct { | ||
| 49 | 46 | } |
| 50 | 47 | }; |
| 51 | 48 | |
| 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 { | |
| 53 | 50 | return .{ |
| 51 | .io = io, | |
| 54 | 52 | .allocator = allocator, |
| 55 | 53 | .enabled = enabled, |
| 56 | 54 | .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, | |
| 58 | 56 | }; |
| 59 | 57 | } |
| 60 | 58 | |
| 61 | 59 | /// Start timing a compilation phase |
| 62 | 60 | pub fn startCompilation(self: *Profiler) void { |
| 63 | 61 | if (!self.enabled) return; |
| 64 | self.start_time = std.time.Instant.now() catch null; | |
| 62 | self.start_time = Clock.awake.now(self.io); | |
| 65 | 63 | } |
| 66 | 64 | |
| 67 | 65 | /// End timing a compilation phase |
| 68 | 66 | pub fn endCompilation(self: *Profiler) void { |
| 69 | 67 | if (!self.enabled) return; |
| 70 | 68 | 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(); | |
| 73 | 70 | } |
| 74 | 71 | |
| 75 | 72 | /// Start timing a match operation |
| 76 | 73 | pub fn startMatch(self: *Profiler) void { |
| 77 | 74 | if (!self.enabled) return; |
| 78 | self.start_time = std.time.Instant.now() catch null; | |
| 75 | self.start_time = Clock.awake.now(self.io); | |
| 79 | 76 | } |
| 80 | 77 | |
| 81 | 78 | /// End timing a match operation |
| 82 | 79 | pub fn endMatch(self: *Profiler) void { |
| 83 | 80 | if (!self.enabled) return; |
| 84 | 81 | 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(); | |
| 87 | 83 | self.metrics.match_count += 1; |
| 88 | 84 | } |
| 89 | 85 | |
| @@ -142,7 +138,7 @@ pub const Profiler = struct { | ||
| 142 | 138 | /// Reset all metrics |
| 143 | 139 | pub fn reset(self: *Profiler) void { |
| 144 | 140 | 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; | |
| 146 | 142 | } |
| 147 | 143 | |
| 148 | 144 | /// Print metrics to stderr |
| @@ -156,7 +152,7 @@ pub const Profiler = struct { | ||
| 156 | 152 | pub const ScopedTimer = struct { |
| 157 | 153 | profiler: *Profiler, |
| 158 | 154 | timer_type: TimerType, |
| 159 | start: ?std.time.Instant, | |
| 155 | start: ?std.Io.Timestamp, | |
| 160 | 156 | |
| 161 | 157 | pub const TimerType = enum { |
| 162 | 158 | compilation, |
| @@ -167,15 +163,14 @@ pub const ScopedTimer = struct { | ||
| 167 | 163 | return .{ |
| 168 | 164 | .profiler = profiler, |
| 169 | 165 | .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, | |
| 171 | 167 | }; |
| 172 | 168 | } |
| 173 | 169 | |
| 174 | 170 | pub fn deinit(self: *ScopedTimer) void { |
| 175 | 171 | if (!self.profiler.enabled) return; |
| 176 | 172 | 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(); | |
| 179 | 174 | switch (self.timer_type) { |
| 180 | 175 | .compilation => self.profiler.metrics.compilation_time_ns += elapsed, |
| 181 | 176 | .match => { |
| @@ -188,7 +183,7 @@ pub const ScopedTimer = struct { | ||
| 188 | 183 | |
| 189 | 184 | test "profiler basic operations" { |
| 190 | 185 | const allocator = std.testing.allocator; |
| 191 | var profiler = Profiler.init(allocator, true); | |
| 186 | var profiler = Profiler.init(allocator, std.testing.io, true); | |
| 192 | 187 | |
| 193 | 188 | profiler.startCompilation(); |
| 194 | 189 | // Do some work (just loop to consume time) |
| @@ -210,7 +205,7 @@ test "profiler basic operations" { | ||
| 210 | 205 | |
| 211 | 206 | test "scoped timer" { |
| 212 | 207 | const allocator = std.testing.allocator; |
| 213 | var profiler = Profiler.init(allocator, true); | |
| 208 | var profiler = Profiler.init(allocator, std.testing.io, true); | |
| 214 | 209 | |
| 215 | 210 | { |
| 216 | 211 | var timer = ScopedTimer.init(&profiler, .match); |
| @@ -41,6 +41,7 @@ pub const EngineType = enum { | ||
| 41 | 41 | /// Main regex type - represents a compiled regular expression pattern |
| 42 | 42 | pub const Regex = struct { |
| 43 | 43 | allocator: std.mem.Allocator, |
| 44 | io: std.Io, | |
| 44 | 45 | pattern: []const u8, |
| 45 | 46 | nfa: compiler.NFA, |
| 46 | 47 | backtrack_engine: ?backtrack.BacktrackEngine, |
| @@ -52,12 +53,12 @@ pub const Regex = struct { | ||
| 52 | 53 | named_captures: std.StringHashMap(usize), // name -> capture_index mapping |
| 53 | 54 | |
| 54 | 55 | /// 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, .{}); | |
| 57 | 58 | } |
| 58 | 59 | |
| 59 | 60 | /// 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 { | |
| 61 | 62 | if (pattern.len == 0) { |
| 62 | 63 | return RegexError.EmptyPattern; |
| 63 | 64 | } |
| @@ -92,12 +93,7 @@ pub const Regex = struct { | ||
| 92 | 93 | |
| 93 | 94 | if (needs_backtracking) { |
| 94 | 95 | // 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); | |
| 101 | 97 | errdefer backtrack_engine.deinit(); |
| 102 | 98 | |
| 103 | 99 | // Create a dummy NFA (not used) |
| @@ -106,6 +102,7 @@ pub const Regex = struct { | ||
| 106 | 102 | _ = try dummy_nfa.addState(); |
| 107 | 103 | |
| 108 | 104 | return Regex{ |
| 105 | .io = io, | |
| 109 | 106 | .allocator = allocator, |
| 110 | 107 | .pattern = owned_pattern, |
| 111 | 108 | .nfa = dummy_nfa, |
| @@ -126,6 +123,7 @@ pub const Regex = struct { | ||
| 126 | 123 | _ = try comp.compile(&tree); |
| 127 | 124 | |
| 128 | 125 | return Regex{ |
| 126 | .io = io, | |
| 129 | 127 | .allocator = allocator, |
| 130 | 128 | .pattern = owned_pattern, |
| 131 | 129 | .nfa = comp.nfa, |
| @@ -676,11 +674,11 @@ fn requiresBacktracking(node: *ast.Node) bool { formatting only | ||
| 676 | 674 | // Recursively check compound nodes |
| 677 | 675 | .concat => { |
| 678 | 676 | return requiresBacktracking(node.data.concat.left) or |
| 679 | requiresBacktracking(node.data.concat.right); | |
| 677 | requiresBacktracking(node.data.concat.right); | |
| 680 | 678 | }, |
| 681 | 679 | .alternation => { |
| 682 | 680 | return requiresBacktracking(node.data.alternation.left) or |
| 683 | requiresBacktracking(node.data.alternation.right); | |
| 681 | requiresBacktracking(node.data.alternation.right); | |
| 684 | 682 | }, |
| 685 | 683 | .group => return requiresBacktracking(node.data.group.child), |
| 686 | 684 | |
| @@ -727,20 +725,20 @@ fn collectNamedCaptures(node: *ast.Node, map: *std.StringHashMap(usize)) !void { | ||
| 727 | 725 | |
| 728 | 726 | test "compile empty pattern" { |
| 729 | 727 | const allocator = std.testing.allocator; |
| 730 | const result = Regex.compile(allocator, ""); | |
| 728 | const result = Regex.compile(allocator, std.testing.io, ""); | |
| 731 | 729 | try std.testing.expectError(RegexError.EmptyPattern, result); |
| 732 | 730 | } |
| 733 | 731 | |
| 734 | 732 | test "compile basic pattern" { |
| 735 | 733 | const allocator = std.testing.allocator; |
| 736 | var regex = try Regex.compile(allocator, "test"); | |
| 734 | var regex = try Regex.compile(allocator, std.testing.io, "test"); | |
| 737 | 735 | defer regex.deinit(); |
| 738 | 736 | try std.testing.expectEqualStrings("test", regex.pattern); |
| 739 | 737 | } |
| 740 | 738 | |
| 741 | 739 | test "match literal" { |
| 742 | 740 | const allocator = std.testing.allocator; |
| 743 | var regex = try Regex.compile(allocator, "hello"); | |
| 741 | var regex = try Regex.compile(allocator, std.testing.io, "hello"); | |
| 744 | 742 | defer regex.deinit(); |
| 745 | 743 | |
| 746 | 744 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -749,7 +747,7 @@ test "match literal" { | ||
| 749 | 747 | |
| 750 | 748 | test "find literal" { |
| 751 | 749 | const allocator = std.testing.allocator; |
| 752 | var regex = try Regex.compile(allocator, "world"); | |
| 750 | var regex = try Regex.compile(allocator, std.testing.io, "world"); | |
| 753 | 751 | defer regex.deinit(); |
| 754 | 752 | |
| 755 | 753 | if (try regex.find("hello world")) |match_result| { |
| @@ -765,7 +763,7 @@ test "find literal" { | ||
| 765 | 763 | |
| 766 | 764 | test "alternation" { |
| 767 | 765 | 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"); | |
| 769 | 767 | defer regex.deinit(); |
| 770 | 768 | |
| 771 | 769 | try std.testing.expect(try regex.isMatch("cat")); |
| @@ -775,7 +773,7 @@ test "alternation" { | ||
| 775 | 773 | |
| 776 | 774 | test "star quantifier" { |
| 777 | 775 | const allocator = std.testing.allocator; |
| 778 | var regex = try Regex.compile(allocator, "a*"); | |
| 776 | var regex = try Regex.compile(allocator, std.testing.io, "a*"); | |
| 779 | 777 | defer regex.deinit(); |
| 780 | 778 | |
| 781 | 779 | try std.testing.expect(try regex.isMatch("")); |
| @@ -785,7 +783,7 @@ test "star quantifier" { | ||
| 785 | 783 | |
| 786 | 784 | test "plus quantifier" { |
| 787 | 785 | const allocator = std.testing.allocator; |
| 788 | var regex = try Regex.compile(allocator, "a+"); | |
| 786 | var regex = try Regex.compile(allocator, std.testing.io, "a+"); | |
| 789 | 787 | defer regex.deinit(); |
| 790 | 788 | |
| 791 | 789 | try std.testing.expect(!try regex.isMatch("")); |
| @@ -795,7 +793,7 @@ test "plus quantifier" { | ||
| 795 | 793 | |
| 796 | 794 | test "optional quantifier" { |
| 797 | 795 | const allocator = std.testing.allocator; |
| 798 | var regex = try Regex.compile(allocator, "a?"); | |
| 796 | var regex = try Regex.compile(allocator, std.testing.io, "a?"); | |
| 799 | 797 | defer regex.deinit(); |
| 800 | 798 | |
| 801 | 799 | try std.testing.expect(try regex.isMatch("")); |
| @@ -804,7 +802,7 @@ test "optional quantifier" { | ||
| 804 | 802 | |
| 805 | 803 | test "dot wildcard" { |
| 806 | 804 | 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"); | |
| 808 | 806 | defer regex.deinit(); |
| 809 | 807 | |
| 810 | 808 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -814,7 +812,7 @@ test "dot wildcard" { | ||
| 814 | 812 | |
| 815 | 813 | test "character class \\d" { |
| 816 | 814 | const allocator = std.testing.allocator; |
| 817 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 815 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 818 | 816 | defer regex.deinit(); |
| 819 | 817 | |
| 820 | 818 | if (try regex.find("abc123def")) |match_result| { |
| @@ -828,7 +826,7 @@ test "character class \\d" { | ||
| 828 | 826 | |
| 829 | 827 | test "replace" { |
| 830 | 828 | const allocator = std.testing.allocator; |
| 831 | var regex = try Regex.compile(allocator, "world"); | |
| 829 | var regex = try Regex.compile(allocator, std.testing.io, "world"); | |
| 832 | 830 | defer regex.deinit(); |
| 833 | 831 | |
| 834 | 832 | const result = try regex.replace(allocator, "hello world", "Zig"); |
| @@ -839,7 +837,7 @@ test "replace" { | ||
| 839 | 837 | |
| 840 | 838 | test "replace all" { |
| 841 | 839 | const allocator = std.testing.allocator; |
| 842 | var regex = try Regex.compile(allocator, "a"); | |
| 840 | var regex = try Regex.compile(allocator, std.testing.io, "a"); | |
| 843 | 841 | defer regex.deinit(); |
| 844 | 842 | |
| 845 | 843 | const result = try regex.replaceAll(allocator, "banana", "o"); |
| @@ -850,7 +848,7 @@ test "replace all" { | ||
| 850 | 848 | |
| 851 | 849 | test "split" { |
| 852 | 850 | const allocator = std.testing.allocator; |
| 853 | var regex = try Regex.compile(allocator, ","); | |
| 851 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 854 | 852 | defer regex.deinit(); |
| 855 | 853 | |
| 856 | 854 | const parts = try regex.split(allocator, "a,b,c"); |
| @@ -866,7 +864,7 @@ test "compile rejects dangerous nested quantifiers" { | ||
| 866 | 864 | const allocator = std.testing.allocator; |
| 867 | 865 | |
| 868 | 866 | // 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+)+"); | |
| 870 | 868 | try std.testing.expectError(RegexError.PatternTooComplex, result); |
| 871 | 869 | } |
| 872 | 870 | |
| @@ -874,7 +872,7 @@ test "compile rejects nested stars" { | ||
| 874 | 872 | const allocator = std.testing.allocator; |
| 875 | 873 | |
| 876 | 874 | // This pattern should be rejected |
| 877 | const result = Regex.compile(allocator, "(a*)*"); | |
| 875 | const result = Regex.compile(allocator, std.testing.io, "(a*)*"); | |
| 878 | 876 | try std.testing.expectError(RegexError.PatternTooComplex, result); |
| 879 | 877 | } |
| 880 | 878 | |
| @@ -882,6 +880,6 @@ test "compile accepts safe complex patterns" { | ||
| 882 | 880 | const allocator = std.testing.allocator; |
| 883 | 881 | |
| 884 | 882 | // 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}"); | |
| 886 | 884 | defer regex.deinit(); |
| 887 | 885 | } |
| @@ -8,7 +8,7 @@ const std = ; | ||
| 8 | 8 | /// Example usage: |
| 9 | 9 | /// ```zig |
| 10 | 10 | /// const Regex = .Regex; |
| 11 | /// const regex = try Regex.compile(allocator, "\\d+"); | |
| 11 | /// const regex = try Regex.compile(allocator, init.io, "\\d+"); | |
| 12 | 12 | /// defer regex.deinit(); |
| 13 | 13 | /// if (try regex.find("abc123")) |match| { |
| 14 | 14 | /// std.debug.print("Found: {s}\n", .{match.slice}); |
| @@ -43,7 +43,7 @@ const std = ; | ||
| 43 | 43 | /// var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 44 | 44 | /// const allocator = gpa.allocator(); |
| 45 | 45 | /// |
| 46 | /// const regex = try Regex.compile(allocator, "\\d+"); | |
| 46 | /// const regex = try Regex.compile(allocator, init.io, "\\d+"); | |
| 47 | 47 | /// defer regex.deinit(); |
| 48 | 48 | /// |
| 49 | 49 | /// // Thread 1 |
| @@ -115,12 +115,12 @@ pub fn SharedRegex(comptime Regex: type) type { | ||
| 115 | 115 | allocator: std.mem.Allocator, |
| 116 | 116 | |
| 117 | 117 | /// 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 { | |
| 119 | 119 | const self = try allocator.create(Self); |
| 120 | 120 | errdefer allocator.destroy(self); |
| 121 | 121 | |
| 122 | 122 | self.* = .{ |
| 123 | .regex = try Regex.compile(allocator, pattern), | |
| 123 | .regex = try Regex.compile(allocator, io, pattern), | |
| 124 | 124 | .ref_count = std.atomic.Value(usize).init(1), |
| 125 | 125 | .allocator = allocator, |
| 126 | 126 | }; |
| @@ -198,11 +198,13 @@ pub fn RegexCache(comptime Regex: type) type { | ||
| 198 | 198 | }; |
| 199 | 199 | |
| 200 | 200 | cache: std.StringHashMap(Regex), |
| 201 | io: std.Io, | |
| 201 | 202 | allocator: std.mem.Allocator, |
| 202 | 203 | initialized: bool = false, |
| 203 | 204 | |
| 204 | pub fn init(allocator: std.mem.Allocator) Self { | |
| 205 | pub fn init(allocator: std.mem.Allocator, io: std.Io) Self { | |
| 205 | 206 | return .{ |
| 207 | .io = io, | |
| 206 | 208 | .cache = std.StringHashMap(Regex).init(allocator), |
| 207 | 209 | .allocator = allocator, |
| 208 | 210 | .initialized = true, |
| @@ -229,7 +231,7 @@ pub fn RegexCache(comptime Regex: type) type { | ||
| 229 | 231 | } |
| 230 | 232 | |
| 231 | 233 | // 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); | |
| 233 | 235 | errdefer { |
| 234 | 236 | var mut_regex = regex; |
| 235 | 237 | mut_regex.deinit(); |
| @@ -261,62 +261,99 @@ test "UTF-8 encoding" { | ||
| 261 | 261 | /// Unicode property names for \p{Property} matching |
| 262 | 262 | pub const UnicodeProperty = enum { |
| 263 | 263 | // 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, | |
| 307 | 344 | |
| 308 | 345 | pub fn fromString(s: []const u8) ?UnicodeProperty { |
| 309 | 346 | const map = std.StaticStringMap(UnicodeProperty).initComptime(.{ |
| 310 | .{ "Letter", .Letter }, .{ "L", .L }, | |
| 347 | .{ "Letter", .Letter }, .{ "L", .L }, | |
| 311 | 348 | .{ "Lowercase_Letter", .Lowercase_Letter }, .{ "Ll", .Ll }, |
| 312 | 349 | .{ "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 }, | |
| 320 | 357 | }); |
| 321 | 358 | return map.get(s); |
| 322 | 359 | } |
| @@ -368,7 +405,7 @@ test "Unicode property matching" { formatting only | ||
| 368 | 405 | // Edge case tests |
| 369 | 406 | test "unicode: invalid UTF-8 sequences" { |
| 370 | 407 | // 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 | |
| 372 | 409 | try std.testing.expectError(error.InvalidUtf8, decodeUtf8(&truncated)); |
| 373 | 410 | |
| 374 | 411 | // Invalid continuation byte |
| @@ -432,12 +469,12 @@ test "unicode: round-trip encoding/decoding" { formatting only | ||
| 432 | 469 | |
| 433 | 470 | // Test various codepoints can be encoded and decoded back |
| 434 | 471 | 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 | |
| 441 | 478 | }; |
| 442 | 479 | |
| 443 | 480 | for (test_codepoints) |cp| { |
| @@ -10,7 +10,7 @@ const RegexError = .RegexError; | ||
| 10 | 10 | |
| 11 | 11 | test "advanced: positive lookahead basic" { |
| 12 | 12 | 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)"); | |
| 14 | 14 | defer regex.deinit(); |
| 15 | 15 | |
| 16 | 16 | // "foobar" should match "foo" (lookahead is zero-width) |
| @@ -28,7 +28,7 @@ test "advanced: positive lookahead basic" { | ||
| 28 | 28 | |
| 29 | 29 | test "advanced: negative lookahead basic" { |
| 30 | 30 | 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)"); | |
| 32 | 32 | defer regex.deinit(); |
| 33 | 33 | |
| 34 | 34 | // "foobaz" should match "foo" (bar not following) |
| @@ -41,7 +41,7 @@ test "advanced: negative lookahead basic" { | ||
| 41 | 41 | |
| 42 | 42 | test "advanced: lookahead is zero-width" { |
| 43 | 43 | 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"); | |
| 45 | 45 | defer regex.deinit(); |
| 46 | 46 | |
| 47 | 47 | // Lookahead doesn't consume, so "bar" after it should match the same "bar" |
| @@ -52,7 +52,7 @@ test "advanced: lookahead is zero-width" { | ||
| 52 | 52 | |
| 53 | 53 | test "advanced: positive lookbehind" { |
| 54 | 54 | const allocator = std.testing.allocator; |
| 55 | var regex = try Regex.compile(allocator, "(?<=@)\\w+"); | |
| 55 | var regex = try Regex.compile(allocator, std.testing.io, "(?<=@)\\w+"); | |
| 56 | 56 | defer regex.deinit(); |
| 57 | 57 | |
| 58 | 58 | if (try regex.find("user@domain")) |match| { |
| @@ -66,7 +66,7 @@ test "advanced: positive lookbehind" { | ||
| 66 | 66 | |
| 67 | 67 | test "advanced: negative lookbehind" { |
| 68 | 68 | 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+"); | |
| 70 | 70 | defer regex.deinit(); |
| 71 | 71 | |
| 72 | 72 | if (try regex.find("abc")) |match| { |
| @@ -82,7 +82,7 @@ test "advanced: negative lookbehind" { | ||
| 82 | 82 | |
| 83 | 83 | test "advanced: backreference repeated word" { |
| 84 | 84 | 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"); | |
| 86 | 86 | defer regex.deinit(); |
| 87 | 87 | |
| 88 | 88 | try std.testing.expect(try regex.isMatch("the the")); |
| @@ -92,7 +92,7 @@ test "advanced: backreference repeated word" { | ||
| 92 | 92 | |
| 93 | 93 | test "advanced: backreference HTML tag matching" { |
| 94 | 94 | 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>"); | |
| 96 | 96 | defer regex.deinit(); |
| 97 | 97 | |
| 98 | 98 | try std.testing.expect(try regex.isMatch("<b>bold</b>")); |
| @@ -104,7 +104,7 @@ test "advanced: backreference HTML tag matching" { | ||
| 104 | 104 | |
| 105 | 105 | test "advanced: non-capturing group doesn't affect capture numbering" { |
| 106 | 106 | 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)"); | |
| 108 | 108 | defer regex.deinit(); |
| 109 | 109 | |
| 110 | 110 | if (try regex.find("abcdef")) |match| { |
| @@ -121,7 +121,7 @@ test "advanced: non-capturing group doesn't affect capture numbering" { | ||
| 121 | 121 | |
| 122 | 122 | test "advanced: non-capturing with alternation" { |
| 123 | 123 | 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"); | |
| 125 | 125 | defer regex.deinit(); |
| 126 | 126 | |
| 127 | 127 | try std.testing.expect(try regex.isMatch("catfish")); |
| @@ -131,7 +131,7 @@ test "advanced: non-capturing with alternation" { | ||
| 131 | 131 | |
| 132 | 132 | test "advanced: mixed capturing and non-capturing" { |
| 133 | 133 | 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+)"); | |
| 135 | 135 | defer regex.deinit(); |
| 136 | 136 | |
| 137 | 137 | if (try regex.find("hello world")) |match| { |
| @@ -149,7 +149,7 @@ test "advanced: mixed capturing and non-capturing" { | ||
| 149 | 149 | |
| 150 | 150 | test "advanced: named capture python style" { |
| 151 | 151 | 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})"); | |
| 153 | 153 | defer regex.deinit(); |
| 154 | 154 | |
| 155 | 155 | if (try regex.find("date: 2024-01 ok")) |match| { |
| @@ -166,7 +166,7 @@ test "advanced: named capture python style" { | ||
| 166 | 166 | |
| 167 | 167 | test "advanced: named capture angle bracket style" { |
| 168 | 168 | 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+)"); | |
| 170 | 170 | defer regex.deinit(); |
| 171 | 171 | |
| 172 | 172 | if (try regex.find("John Doe")) |match| { |
| @@ -184,7 +184,7 @@ test "advanced: named capture angle bracket style" { | ||
| 184 | 184 | |
| 185 | 185 | test "advanced: anchored pattern with captures" { |
| 186 | 186 | 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+)$"); | |
| 188 | 188 | defer regex.deinit(); |
| 189 | 189 | |
| 190 | 190 | if (try regex.find("hello world")) |match| { |
| @@ -202,7 +202,7 @@ test "advanced: anchored pattern with captures" { | ||
| 202 | 202 | |
| 203 | 203 | test "advanced: case insensitive find with captures" { |
| 204 | 204 | 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 }); | |
| 206 | 206 | defer regex.deinit(); |
| 207 | 207 | |
| 208 | 208 | if (try regex.find("HELLO WORLD")) |match| { |
| @@ -221,7 +221,7 @@ test "advanced: case insensitive find with captures" { | ||
| 221 | 221 | |
| 222 | 222 | test "advanced: IPv4 address pattern" { |
| 223 | 223 | 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}"); | |
| 225 | 225 | defer regex.deinit(); |
| 226 | 226 | |
| 227 | 227 | try std.testing.expect(try regex.isMatch("192.168.1.1")); |
| @@ -231,7 +231,7 @@ test "advanced: IPv4 address pattern" { | ||
| 231 | 231 | |
| 232 | 232 | test "advanced: hex color pattern" { |
| 233 | 233 | 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}"); | |
| 235 | 235 | defer regex.deinit(); |
| 236 | 236 | |
| 237 | 237 | try std.testing.expect(try regex.isMatch("#ff0000")); |
| @@ -241,7 +241,7 @@ test "advanced: hex color pattern" { | ||
| 241 | 241 | |
| 242 | 242 | test "advanced: semver pattern" { |
| 243 | 243 | 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+)"); | |
| 245 | 245 | defer regex.deinit(); |
| 246 | 246 | |
| 247 | 247 | if (try regex.find("version 1.2.3-beta")) |match| { |
| @@ -259,7 +259,7 @@ test "advanced: semver pattern" { | ||
| 259 | 259 | |
| 260 | 260 | test "advanced: log line pattern" { |
| 261 | 261 | 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+(.+)"); | |
| 263 | 263 | defer regex.deinit(); |
| 264 | 264 | |
| 265 | 265 | if (try regex.find("[ERROR] Something went wrong")) |match| { |
| @@ -275,7 +275,7 @@ test "advanced: log line pattern" { | ||
| 275 | 275 | |
| 276 | 276 | test "advanced: URL extraction" { |
| 277 | 277 | 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._/-]+"); | |
| 279 | 279 | defer regex.deinit(); |
| 280 | 280 | |
| 281 | 281 | if (try regex.find("visit http://example.com/path today")) |match| { |
| @@ -291,7 +291,7 @@ test "advanced: URL extraction" { | ||
| 291 | 291 | |
| 292 | 292 | test "advanced: iterator over empty result" { |
| 293 | 293 | const allocator = std.testing.allocator; |
| 294 | var regex = try Regex.compile(allocator, "xyz"); | |
| 294 | var regex = try Regex.compile(allocator, std.testing.io, "xyz"); | |
| 295 | 295 | defer regex.deinit(); |
| 296 | 296 | |
| 297 | 297 | var iter = regex.iterator("hello world"); |
| @@ -301,7 +301,7 @@ test "advanced: iterator over empty result" { | ||
| 301 | 301 | |
| 302 | 302 | test "advanced: iterator collects all matches" { |
| 303 | 303 | const allocator = std.testing.allocator; |
| 304 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 304 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 305 | 305 | defer regex.deinit(); |
| 306 | 306 | |
| 307 | 307 | var iter = regex.iterator("a1b23c456"); |
| @@ -320,7 +320,7 @@ test "advanced: iterator collects all matches" { | ||
| 320 | 320 | |
| 321 | 321 | test "advanced: multiline with captures" { |
| 322 | 322 | 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 }); | |
| 324 | 324 | defer regex.deinit(); |
| 325 | 325 | |
| 326 | 326 | const matches = try regex.findAll(allocator, "key1: val\nkey2: val"); |
| @@ -343,7 +343,7 @@ test "advanced: multiline with captures" { | ||
| 343 | 343 | |
| 344 | 344 | test "advanced: match at very start of input" { |
| 345 | 345 | const allocator = std.testing.allocator; |
| 346 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 346 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 347 | 347 | defer regex.deinit(); |
| 348 | 348 | |
| 349 | 349 | if (try regex.find("123abc")) |match| { |
| @@ -358,7 +358,7 @@ test "advanced: match at very start of input" { | ||
| 358 | 358 | |
| 359 | 359 | test "advanced: match consuming entire input" { |
| 360 | 360 | const allocator = std.testing.allocator; |
| 361 | var regex = try Regex.compile(allocator, "\\w+"); | |
| 361 | var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); | |
| 362 | 362 | defer regex.deinit(); |
| 363 | 363 | |
| 364 | 364 | if (try regex.find("hello")) |match| { |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "backreference: simple capture group replacement" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "(\\w+)"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | const result = try regex.replace(allocator, "hello", "$1!"); |
| @@ -16,7 +16,7 @@ test "backreference: simple capture group replacement" { | ||
| 16 | 16 | |
| 17 | 17 | test "backreference: swap two words" { |
| 18 | 18 | 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+)"); | |
| 20 | 20 | defer regex.deinit(); |
| 21 | 21 | |
| 22 | 22 | const result = try regex.replace(allocator, "hello world", "$2 $1"); |
| @@ -27,7 +27,7 @@ test "backreference: swap two words" { | ||
| 27 | 27 | |
| 28 | 28 | test "backreference: repeat capture" { |
| 29 | 29 | const allocator = std.testing.allocator; |
| 30 | var regex = try Regex.compile(allocator, "(\\w+)"); | |
| 30 | var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); | |
| 31 | 31 | defer regex.deinit(); |
| 32 | 32 | |
| 33 | 33 | const result = try regex.replace(allocator, "test", "$1-$1"); |
| @@ -38,7 +38,7 @@ test "backreference: repeat capture" { | ||
| 38 | 38 | |
| 39 | 39 | test "backreference: multiple captures" { |
| 40 | 40 | 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+)"); | |
| 42 | 42 | defer regex.deinit(); |
| 43 | 43 | |
| 44 | 44 | const result = try regex.replace(allocator, "2025-10-26", "$3/$2/$1"); |
| @@ -49,7 +49,7 @@ test "backreference: multiple captures" { | ||
| 49 | 49 | |
| 50 | 50 | test "backreference: escaped dollar sign" { |
| 51 | 51 | const allocator = std.testing.allocator; |
| 52 | var regex = try Regex.compile(allocator, "(\\d+)"); | |
| 52 | var regex = try Regex.compile(allocator, std.testing.io, "(\\d+)"); | |
| 53 | 53 | defer regex.deinit(); |
| 54 | 54 | |
| 55 | 55 | const result = try regex.replace(allocator, "100", "$$$1"); |
| @@ -60,7 +60,7 @@ test "backreference: escaped dollar sign" { | ||
| 60 | 60 | |
| 61 | 61 | test "backreference: replaceAll with captures" { |
| 62 | 62 | 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+)"); | |
| 64 | 64 | defer regex.deinit(); |
| 65 | 65 | |
| 66 | 66 | const result = try regex.replaceAll(allocator, "user@example and admin@test", "$1 at $2"); |
| @@ -71,7 +71,7 @@ test "backreference: replaceAll with captures" { | ||
| 71 | 71 | |
| 72 | 72 | test "backreference: extract and format phone numbers" { |
| 73 | 73 | 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})"); | |
| 75 | 75 | defer regex.deinit(); |
| 76 | 76 | |
| 77 | 77 | const result = try regex.replace(allocator, "555-123-4567", "($1) $2-$3"); |
| @@ -82,7 +82,7 @@ test "backreference: extract and format phone numbers" { | ||
| 82 | 82 | |
| 83 | 83 | test "backreference: reformat dates" { |
| 84 | 84 | 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})"); | |
| 86 | 86 | defer regex.deinit(); |
| 87 | 87 | |
| 88 | 88 | const result = try regex.replaceAll(allocator, "2025-10-26 and 2024-12-31", "$2/$3/$1"); |
| @@ -93,7 +93,7 @@ test "backreference: reformat dates" { | ||
| 93 | 93 | |
| 94 | 94 | test "backreference: wrap matches in tags" { |
| 95 | 95 | const allocator = std.testing.allocator; |
| 96 | var regex = try Regex.compile(allocator, "(\\w+)"); | |
| 96 | var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); | |
| 97 | 97 | defer regex.deinit(); |
| 98 | 98 | |
| 99 | 99 | const result = try regex.replaceAll(allocator, "hello world", "<b>$1</b>"); |
| @@ -104,7 +104,7 @@ test "backreference: wrap matches in tags" { | ||
| 104 | 104 | |
| 105 | 105 | test "backreference: invalid capture index" { |
| 106 | 106 | const allocator = std.testing.allocator; |
| 107 | var regex = try Regex.compile(allocator, "(\\w+)"); | |
| 107 | var regex = try Regex.compile(allocator, std.testing.io, "(\\w+)"); | |
| 108 | 108 | defer regex.deinit(); |
| 109 | 109 | |
| 110 | 110 | // Only one capture group, $2 should be treated as literal |
| @@ -116,7 +116,7 @@ test "backreference: invalid capture index" { | ||
| 116 | 116 | |
| 117 | 117 | test "backreference: nested captures" { |
| 118 | 118 | 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+))"); | |
| 120 | 120 | defer regex.deinit(); |
| 121 | 121 | |
| 122 | 122 | const result = try regex.replace(allocator, "user@example.com", "Email: $1 (user=$2, domain=$3)"); |
| @@ -127,7 +127,7 @@ test "backreference: nested captures" { | ||
| 127 | 127 | |
| 128 | 128 | test "backreference: quote words" { |
| 129 | 129 | 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"); | |
| 131 | 131 | defer regex.deinit(); |
| 132 | 132 | |
| 133 | 133 | const result = try regex.replaceAll(allocator, "hello world", "'$1'"); |
| @@ -138,7 +138,7 @@ test "backreference: quote words" { | ||
| 138 | 138 | |
| 139 | 139 | test "backreference: transform case context" { |
| 140 | 140 | 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+)"); | |
| 142 | 142 | defer regex.deinit(); |
| 143 | 143 | |
| 144 | 144 | const result = try regex.replace(allocator, "Hello Mr Smith", "$1. $2"); |
| @@ -151,7 +151,7 @@ test "backreference: transform case context" { | ||
| 151 | 151 | |
| 152 | 152 | test "pattern backreference: basic \\1" { |
| 153 | 153 | 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"); | |
| 155 | 155 | defer regex.deinit(); |
| 156 | 156 | |
| 157 | 157 | // Should match repeated words |
| @@ -171,7 +171,7 @@ test "pattern backreference: basic \\1" { | ||
| 171 | 171 | |
| 172 | 172 | test "pattern backreference: multiple captures" { |
| 173 | 173 | 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"); | |
| 175 | 175 | defer regex.deinit(); |
| 176 | 176 | |
| 177 | 177 | try std.testing.expect(try regex.isMatch("foo bar foo bar")); |
| @@ -191,7 +191,7 @@ test "pattern backreference: multiple captures" { | ||
| 191 | 191 | |
| 192 | 192 | test "pattern backreference: with quantifiers" { |
| 193 | 193 | 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"); | |
| 195 | 195 | defer regex.deinit(); |
| 196 | 196 | |
| 197 | 197 | // Match patterns like "5+5", "123+123" |
| @@ -202,7 +202,7 @@ test "pattern backreference: with quantifiers" { | ||
| 202 | 202 | |
| 203 | 203 | test "pattern backreference: HTML tag matching" { |
| 204 | 204 | 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>"); | |
| 206 | 206 | defer regex.deinit(); |
| 207 | 207 | |
| 208 | 208 | // Match matching HTML tags |
| @@ -213,7 +213,7 @@ test "pattern backreference: HTML tag matching" { | ||
| 213 | 213 | |
| 214 | 214 | test "pattern backreference: case sensitive" { |
| 215 | 215 | 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"); | |
| 217 | 217 | defer regex.deinit(); |
| 218 | 218 | |
| 219 | 219 | // Backreferences should be case sensitive |
| @@ -223,7 +223,7 @@ test "pattern backreference: case sensitive" { | ||
| 223 | 223 | |
| 224 | 224 | test "pattern backreference: nested groups" { |
| 225 | 225 | 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"); | |
| 227 | 227 | defer regex.deinit(); |
| 228 | 228 | |
| 229 | 229 | // \\2 refers to second group (single char), so "aba" should match: |
| @@ -246,7 +246,7 @@ test "pattern backreference: nested groups" { | ||
| 246 | 246 | |
| 247 | 247 | test "pattern backreference: with alternation" { |
| 248 | 248 | 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"); | |
| 250 | 250 | defer regex.deinit(); |
| 251 | 251 | |
| 252 | 252 | try std.testing.expect(try regex.isMatch("aa")); |
| @@ -257,7 +257,7 @@ test "pattern backreference: with alternation" { | ||
| 257 | 257 | |
| 258 | 258 | test "pattern backreference: multiple in same pattern" { |
| 259 | 259 | 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"); | |
| 261 | 261 | defer regex.deinit(); |
| 262 | 262 | |
| 263 | 263 | // Match three of the same character |
| @@ -269,7 +269,7 @@ test "pattern backreference: multiple in same pattern" { | ||
| 269 | 269 | |
| 270 | 270 | test "pattern backreference: findAll" { |
| 271 | 271 | 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"); | |
| 273 | 273 | defer regex.deinit(); |
| 274 | 274 | |
| 275 | 275 | const matches = try regex.findAll(allocator, "foo foo bar bar baz qux"); |
| @@ -288,7 +288,7 @@ test "pattern backreference: findAll" { | ||
| 288 | 288 | |
| 289 | 289 | test "pattern backreference: quoted strings" { |
| 290 | 290 | const allocator = std.testing.allocator; |
| 291 | var regex = try Regex.compile(allocator, "(['\"]).*\\1"); | |
| 291 | var regex = try Regex.compile(allocator, std.testing.io, "(['\"]).*\\1"); | |
| 292 | 292 | defer regex.deinit(); |
| 293 | 293 | |
| 294 | 294 | // Match quoted strings with same quote type |
| @@ -4,7 +4,7 @@ const common = .common; | ||
| 4 | 4 | |
| 5 | 5 | test "case insensitive: simple literal" { |
| 6 | 6 | 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 }); | |
| 8 | 8 | defer regex.deinit(); |
| 9 | 9 | |
| 10 | 10 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -16,7 +16,7 @@ test "case insensitive: simple literal" { | ||
| 16 | 16 | |
| 17 | 17 | test "case insensitive: with quantifiers" { |
| 18 | 18 | 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 }); | |
| 20 | 20 | defer regex.deinit(); |
| 21 | 21 | |
| 22 | 22 | try std.testing.expect(try regex.isMatch("aaa")); |
| @@ -29,7 +29,7 @@ test "case insensitive: with quantifiers" { | ||
| 29 | 29 | |
| 30 | 30 | test "case insensitive: mixed with alternation" { |
| 31 | 31 | 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 }); | |
| 33 | 33 | defer regex.deinit(); |
| 34 | 34 | |
| 35 | 35 | try std.testing.expect(try regex.isMatch("cat")); |
| @@ -42,7 +42,7 @@ test "case insensitive: mixed with alternation" { | ||
| 42 | 42 | |
| 43 | 43 | test "case insensitive: find in text" { |
| 44 | 44 | 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 }); | |
| 46 | 46 | defer regex.deinit(); |
| 47 | 47 | |
| 48 | 48 | if (try regex.find("Say HELLO world")) |match| { |
| @@ -56,7 +56,7 @@ test "case insensitive: find in text" { | ||
| 56 | 56 | |
| 57 | 57 | test "case insensitive: with anchors" { |
| 58 | 58 | 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 }); | |
| 60 | 60 | defer regex.deinit(); |
| 61 | 61 | |
| 62 | 62 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -68,7 +68,7 @@ test "case insensitive: with anchors" { | ||
| 68 | 68 | |
| 69 | 69 | test "case insensitive: replace" { |
| 70 | 70 | 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 }); | |
| 72 | 72 | defer regex.deinit(); |
| 73 | 73 | |
| 74 | 74 | const result = try regex.replace(allocator, "Say HELLO world", "hi"); |
| @@ -79,7 +79,7 @@ test "case insensitive: replace" { | ||
| 79 | 79 | |
| 80 | 80 | test "case insensitive: replaceAll" { |
| 81 | 81 | 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 }); | |
| 83 | 83 | defer regex.deinit(); |
| 84 | 84 | |
| 85 | 85 | const result = try regex.replaceAll(allocator, "AaAa", "X"); |
| @@ -90,7 +90,7 @@ test "case insensitive: replaceAll" { | ||
| 90 | 90 | |
| 91 | 91 | test "case insensitive: disabled by default" { |
| 92 | 92 | const allocator = std.testing.allocator; |
| 93 | var regex = try Regex.compile(allocator, "hello"); | |
| 93 | var regex = try Regex.compile(allocator, std.testing.io, "hello"); | |
| 94 | 94 | defer regex.deinit(); |
| 95 | 95 | |
| 96 | 96 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -100,7 +100,7 @@ test "case insensitive: disabled by default" { | ||
| 100 | 100 | |
| 101 | 101 | test "case insensitive: with numbers and special chars" { |
| 102 | 102 | 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 }); | |
| 104 | 104 | defer regex.deinit(); |
| 105 | 105 | |
| 106 | 106 | try std.testing.expect(try regex.isMatch("test123")); |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | |
| 4 | 4 | test "comprehensive: email-like pattern" { |
| 5 | 5 | 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+"); | |
| 7 | 7 | defer regex.deinit(); |
| 8 | 8 | |
| 9 | 9 | try std.testing.expect(try regex.isMatch("user@domain")); |
| @@ -12,7 +12,7 @@ test "comprehensive: email-like pattern" { | ||
| 12 | 12 | |
| 13 | 13 | test "comprehensive: phone number pattern" { |
| 14 | 14 | 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+"); | |
| 16 | 16 | defer regex.deinit(); |
| 17 | 17 | |
| 18 | 18 | if (try regex.find("Call 555-1234 now")) |match| { |
| @@ -26,7 +26,7 @@ test "comprehensive: phone number pattern" { | ||
| 26 | 26 | |
| 27 | 27 | test "comprehensive: complex alternation" { |
| 28 | 28 | 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"); | |
| 30 | 30 | defer regex.deinit(); |
| 31 | 31 | |
| 32 | 32 | try std.testing.expect(try regex.isMatch("cat")); |
| @@ -37,7 +37,7 @@ test "comprehensive: complex alternation" { | ||
| 37 | 37 | |
| 38 | 38 | test "comprehensive: nested grouping" { |
| 39 | 39 | 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)"); | |
| 41 | 41 | defer regex.deinit(); |
| 42 | 42 | |
| 43 | 43 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -46,7 +46,7 @@ test "comprehensive: nested grouping" { | ||
| 46 | 46 | |
| 47 | 47 | test "comprehensive: multiple quantifiers" { |
| 48 | 48 | 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?"); | |
| 50 | 50 | defer regex.deinit(); |
| 51 | 51 | |
| 52 | 52 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -57,7 +57,7 @@ test "comprehensive: multiple quantifiers" { | ||
| 57 | 57 | |
| 58 | 58 | test "comprehensive: start and end anchors" { |
| 59 | 59 | const allocator = std.testing.allocator; |
| 60 | var regex = try Regex.compile(allocator, "^hello$"); | |
| 60 | var regex = try Regex.compile(allocator, std.testing.io, "^hello$"); | |
| 61 | 61 | defer regex.deinit(); |
| 62 | 62 | |
| 63 | 63 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -67,7 +67,7 @@ test "comprehensive: start and end anchors" { | ||
| 67 | 67 | |
| 68 | 68 | test "comprehensive: character class ranges" { |
| 69 | 69 | 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]+"); | |
| 71 | 71 | defer regex.deinit(); |
| 72 | 72 | |
| 73 | 73 | if (try regex.find("ABC123xyz")) |match| { |
| @@ -81,7 +81,7 @@ test "comprehensive: character class ranges" { | ||
| 81 | 81 | |
| 82 | 82 | test "comprehensive: negated character class" { |
| 83 | 83 | 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]+"); | |
| 85 | 85 | defer regex.deinit(); |
| 86 | 86 | |
| 87 | 87 | if (try regex.find("123abc456")) |match| { |
| @@ -95,7 +95,7 @@ test "comprehensive: negated character class" { | ||
| 95 | 95 | |
| 96 | 96 | test "comprehensive: findAll with multiple matches" { |
| 97 | 97 | const allocator = std.testing.allocator; |
| 98 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 98 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | const matches = try regex.findAll(allocator, "10 cats and 20 dogs"); |
| @@ -114,7 +114,7 @@ test "comprehensive: findAll with multiple matches" { | ||
| 114 | 114 | |
| 115 | 115 | test "comprehensive: replaceAll multiple occurrences" { |
| 116 | 116 | const allocator = std.testing.allocator; |
| 117 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 117 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 118 | 118 | defer regex.deinit(); |
| 119 | 119 | |
| 120 | 120 | const result = try regex.replaceAll(allocator, "I have 2 cats and 3 dogs", "many"); |
| @@ -125,7 +125,7 @@ test "comprehensive: replaceAll multiple occurrences" { | ||
| 125 | 125 | |
| 126 | 126 | test "comprehensive: split by whitespace pattern" { |
| 127 | 127 | const allocator = std.testing.allocator; |
| 128 | var regex = try Regex.compile(allocator, "\\s+"); | |
| 128 | var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); | |
| 129 | 129 | defer regex.deinit(); |
| 130 | 130 | |
| 131 | 131 | const parts = try regex.split(allocator, "hello world test"); |
| @@ -139,7 +139,7 @@ test "comprehensive: split by whitespace pattern" { | ||
| 139 | 139 | |
| 140 | 140 | test "comprehensive: dot matches any character" { |
| 141 | 141 | 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"); | |
| 143 | 143 | defer regex.deinit(); |
| 144 | 144 | |
| 145 | 145 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -150,7 +150,7 @@ test "comprehensive: dot matches any character" { | ||
| 150 | 150 | |
| 151 | 151 | test "comprehensive: escape special characters" { |
| 152 | 152 | 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+"); | |
| 154 | 154 | defer regex.deinit(); |
| 155 | 155 | |
| 156 | 156 | if (try regex.find("Price: $19.99")) |match| { |
| @@ -164,7 +164,7 @@ test "comprehensive: escape special characters" { | ||
| 164 | 164 | |
| 165 | 165 | test "comprehensive: word boundary" { |
| 166 | 166 | 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"); | |
| 168 | 168 | defer regex.deinit(); |
| 169 | 169 | |
| 170 | 170 | if (try regex.find("a word here")) |match| { |
| @@ -5,7 +5,7 @@ const RegexError = .RegexError; | ||
| 5 | 5 | // Edge case: Empty input |
| 6 | 6 | test "edge: empty input with star" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "a*"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "a*"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | try std.testing.expect(try regex.isMatch("")); |
| @@ -13,7 +13,7 @@ test "edge: empty input with star" { | ||
| 13 | 13 | |
| 14 | 14 | test "edge: empty input with plus should fail" { |
| 15 | 15 | const allocator = std.testing.allocator; |
| 16 | var regex = try Regex.compile(allocator, "a+"); | |
| 16 | var regex = try Regex.compile(allocator, std.testing.io, "a+"); | |
| 17 | 17 | defer regex.deinit(); |
| 18 | 18 | |
| 19 | 19 | try std.testing.expect(!try regex.isMatch("")); |
| @@ -22,7 +22,7 @@ test "edge: empty input with plus should fail" { | ||
| 22 | 22 | // Edge case: Single character patterns |
| 23 | 23 | test "edge: single character literal" { |
| 24 | 24 | const allocator = std.testing.allocator; |
| 25 | var regex = try Regex.compile(allocator, "x"); | |
| 25 | var regex = try Regex.compile(allocator, std.testing.io, "x"); | |
| 26 | 26 | defer regex.deinit(); |
| 27 | 27 | |
| 28 | 28 | try std.testing.expect(try regex.isMatch("x")); |
| @@ -33,7 +33,7 @@ test "edge: single character literal" { | ||
| 33 | 33 | // Edge case: Nested quantifiers behavior |
| 34 | 34 | test "edge: star after plus" { |
| 35 | 35 | 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*"); | |
| 37 | 37 | defer regex.deinit(); |
| 38 | 38 | |
| 39 | 39 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -44,7 +44,7 @@ test "edge: star after plus" { | ||
| 44 | 44 | // Edge case: Multiple alternations |
| 45 | 45 | test "edge: multiple alternations" { |
| 46 | 46 | 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"); | |
| 48 | 48 | defer regex.deinit(); |
| 49 | 49 | |
| 50 | 50 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -57,7 +57,7 @@ test "edge: multiple alternations" { | ||
| 57 | 57 | // Edge case: Alternation with empty branch |
| 58 | 58 | test "edge: alternation with quantifiers" { |
| 59 | 59 | 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+"); | |
| 61 | 61 | defer regex.deinit(); |
| 62 | 62 | |
| 63 | 63 | try std.testing.expect(try regex.isMatch("")); |
| @@ -68,7 +68,7 @@ test "edge: alternation with quantifiers" { | ||
| 68 | 68 | // Edge case: Complex nested groups |
| 69 | 69 | test "edge: nested groups" { |
| 70 | 70 | const allocator = std.testing.allocator; |
| 71 | var regex = try Regex.compile(allocator, "((a))"); | |
| 71 | var regex = try Regex.compile(allocator, std.testing.io, "((a))"); | |
| 72 | 72 | defer regex.deinit(); |
| 73 | 73 | |
| 74 | 74 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -77,7 +77,7 @@ test "edge: nested groups" { | ||
| 77 | 77 | // Edge case: Character class edge cases |
| 78 | 78 | test "edge: empty character class range" { |
| 79 | 79 | const allocator = std.testing.allocator; |
| 80 | var regex = try Regex.compile(allocator, "[a]"); | |
| 80 | var regex = try Regex.compile(allocator, std.testing.io, "[a]"); | |
| 81 | 81 | defer regex.deinit(); |
| 82 | 82 | |
| 83 | 83 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -86,7 +86,7 @@ test "edge: empty character class range" { | ||
| 86 | 86 | |
| 87 | 87 | test "edge: character class with single range" { |
| 88 | 88 | 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]"); | |
| 90 | 90 | defer regex.deinit(); |
| 91 | 91 | |
| 92 | 92 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -95,7 +95,7 @@ test "edge: character class with single range" { | ||
| 95 | 95 | |
| 96 | 96 | test "edge: negated character class matching nothing in range" { |
| 97 | 97 | 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]"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | try std.testing.expect(try regex.isMatch("A")); |
| @@ -106,7 +106,7 @@ test "edge: negated character class matching nothing in range" { | ||
| 106 | 106 | // Edge case: Anchors at different positions |
| 107 | 107 | test "edge: start anchor not at beginning" { |
| 108 | 108 | 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"); | |
| 110 | 110 | defer regex.deinit(); |
| 111 | 111 | |
| 112 | 112 | // This should not match anything since ^ is not at the start |
| @@ -115,7 +115,7 @@ test "edge: start anchor not at beginning" { | ||
| 115 | 115 | |
| 116 | 116 | test "edge: end anchor not at end" { |
| 117 | 117 | 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"); | |
| 119 | 119 | defer regex.deinit(); |
| 120 | 120 | |
| 121 | 121 | // This should not match anything since $ is not at the end |
| @@ -125,7 +125,7 @@ test "edge: end anchor not at end" { | ||
| 125 | 125 | // Edge case: Dot matching |
| 126 | 126 | test "edge: dot with quantifiers" { |
| 127 | 127 | const allocator = std.testing.allocator; |
| 128 | var regex = try Regex.compile(allocator, ".*"); | |
| 128 | var regex = try Regex.compile(allocator, std.testing.io, ".*"); | |
| 129 | 129 | defer regex.deinit(); |
| 130 | 130 | |
| 131 | 131 | try std.testing.expect(try regex.isMatch("")); |
| @@ -136,7 +136,7 @@ test "edge: dot with quantifiers" { | ||
| 136 | 136 | // Edge case: Escape sequences |
| 137 | 137 | test "edge: escaped backslash" { |
| 138 | 138 | const allocator = std.testing.allocator; |
| 139 | var regex = try Regex.compile(allocator, "\\\\"); | |
| 139 | var regex = try Regex.compile(allocator, std.testing.io, "\\\\"); | |
| 140 | 140 | defer regex.deinit(); |
| 141 | 141 | |
| 142 | 142 | try std.testing.expect(try regex.isMatch("\\")); |
| @@ -145,7 +145,7 @@ test "edge: escaped backslash" { | ||
| 145 | 145 | |
| 146 | 146 | test "edge: escaped dot" { |
| 147 | 147 | const allocator = std.testing.allocator; |
| 148 | var regex = try Regex.compile(allocator, "\\."); | |
| 148 | var regex = try Regex.compile(allocator, std.testing.io, "\\."); | |
| 149 | 149 | defer regex.deinit(); |
| 150 | 150 | |
| 151 | 151 | try std.testing.expect(try regex.isMatch(".")); |
| @@ -155,7 +155,7 @@ test "edge: escaped dot" { | ||
| 155 | 155 | // Edge case: findAll edge cases |
| 156 | 156 | test "edge: findAll with no matches" { |
| 157 | 157 | const allocator = std.testing.allocator; |
| 158 | var regex = try Regex.compile(allocator, "x"); | |
| 158 | var regex = try Regex.compile(allocator, std.testing.io, "x"); | |
| 159 | 159 | defer regex.deinit(); |
| 160 | 160 | |
| 161 | 161 | const matches = try regex.findAll(allocator, "aaabbb"); |
| @@ -166,7 +166,7 @@ test "edge: findAll with no matches" { | ||
| 166 | 166 | |
| 167 | 167 | test "edge: findAll with overlapping potential matches" { |
| 168 | 168 | const allocator = std.testing.allocator; |
| 169 | var regex = try Regex.compile(allocator, "a+"); | |
| 169 | var regex = try Regex.compile(allocator, std.testing.io, "a+"); | |
| 170 | 170 | defer regex.deinit(); |
| 171 | 171 | |
| 172 | 172 | const matches = try regex.findAll(allocator, "aaa"); |
| @@ -186,7 +186,7 @@ test "edge: findAll with overlapping potential matches" { | ||
| 186 | 186 | // Edge case: replace edge cases |
| 187 | 187 | test "edge: replace with empty string" { |
| 188 | 188 | const allocator = std.testing.allocator; |
| 189 | var regex = try Regex.compile(allocator, "a"); | |
| 189 | var regex = try Regex.compile(allocator, std.testing.io, "a"); | |
| 190 | 190 | defer regex.deinit(); |
| 191 | 191 | |
| 192 | 192 | const result = try regex.replace(allocator, "banana", ""); |
| @@ -197,7 +197,7 @@ test "edge: replace with empty string" { | ||
| 197 | 197 | |
| 198 | 198 | test "edge: replace in empty string" { |
| 199 | 199 | const allocator = std.testing.allocator; |
| 200 | var regex = try Regex.compile(allocator, "a"); | |
| 200 | var regex = try Regex.compile(allocator, std.testing.io, "a"); | |
| 201 | 201 | defer regex.deinit(); |
| 202 | 202 | |
| 203 | 203 | const result = try regex.replace(allocator, "", "x"); |
| @@ -209,7 +209,7 @@ test "edge: replace in empty string" { | ||
| 209 | 209 | // Edge case: split edge cases |
| 210 | 210 | test "edge: split empty string" { |
| 211 | 211 | const allocator = std.testing.allocator; |
| 212 | var regex = try Regex.compile(allocator, ","); | |
| 212 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 213 | 213 | defer regex.deinit(); |
| 214 | 214 | |
| 215 | 215 | const parts = try regex.split(allocator, ""); |
| @@ -221,7 +221,7 @@ test "edge: split empty string" { | ||
| 221 | 221 | |
| 222 | 222 | test "edge: split with no delimiter found" { |
| 223 | 223 | const allocator = std.testing.allocator; |
| 224 | var regex = try Regex.compile(allocator, ","); | |
| 224 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 225 | 225 | defer regex.deinit(); |
| 226 | 226 | |
| 227 | 227 | const parts = try regex.split(allocator, "hello"); |
| @@ -234,7 +234,7 @@ test "edge: split with no delimiter found" { | ||
| 234 | 234 | // Edge case: Word boundaries |
| 235 | 235 | test "edge: word boundary at start" { |
| 236 | 236 | const allocator = std.testing.allocator; |
| 237 | var regex = try Regex.compile(allocator, "\\bword"); | |
| 237 | var regex = try Regex.compile(allocator, std.testing.io, "\\bword"); | |
| 238 | 238 | defer regex.deinit(); |
| 239 | 239 | |
| 240 | 240 | if (try regex.find("word here")) |match| { |
| @@ -248,7 +248,7 @@ test "edge: word boundary at start" { | ||
| 248 | 248 | |
| 249 | 249 | test "edge: word boundary at end" { |
| 250 | 250 | 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"); | |
| 252 | 252 | defer regex.deinit(); |
| 253 | 253 | |
| 254 | 254 | if (try regex.find("a word")) |match| { |
| @@ -263,7 +263,7 @@ test "edge: word boundary at end" { | ||
| 263 | 263 | // Edge case: Character classes with special chars |
| 264 | 264 | test "edge: whitespace character class" { |
| 265 | 265 | const allocator = std.testing.allocator; |
| 266 | var regex = try Regex.compile(allocator, "\\s+"); | |
| 266 | var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); | |
| 267 | 267 | defer regex.deinit(); |
| 268 | 268 | |
| 269 | 269 | if (try regex.find("hello world")) |match| { |
| @@ -277,7 +277,7 @@ test "edge: whitespace character class" { | ||
| 277 | 277 | |
| 278 | 278 | test "edge: digit character class at boundaries" { |
| 279 | 279 | const allocator = std.testing.allocator; |
| 280 | var regex = try Regex.compile(allocator, "^\\d+$"); | |
| 280 | var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$"); | |
| 281 | 281 | defer regex.deinit(); |
| 282 | 282 | |
| 283 | 283 | try std.testing.expect(try regex.isMatch("123")); |
| @@ -21,7 +21,7 @@ test "fuzz: random valid patterns" { formatting only | ||
| 21 | 21 | const char_choice = random.intRangeAtMost(u8, 0, 4); |
| 22 | 22 | pattern_buf[j] = switch (char_choice) { |
| 23 | 23 | 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 | |
| 25 | 25 | 2 => '.', // wildcard |
| 26 | 26 | 3 => '*', // quantifier |
| 27 | 27 | else => '+', // quantifier |
| @@ -129,18 +129,18 @@ test "fuzz: malformed patterns should return errors" { formatting only | ||
| 129 | 129 | const allocator = std.testing.allocator; |
| 130 | 130 | |
| 131 | 131 | 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) | |
| 144 | 144 | }; |
| 145 | 145 | |
| 146 | 146 | for (bad_patterns) |pattern| { |
| @@ -180,7 +180,7 @@ test "fuzz: edge cases with empty strings" { | ||
| 180 | 180 | test "fuzz: long input strings" { |
| 181 | 181 | const allocator = std.testing.allocator; |
| 182 | 182 | |
| 183 | var regex = try Regex.compile(allocator, "a*b+c?"); | |
| 183 | var regex = try Regex.compile(allocator, std.testing.io, "a*b+c?"); | |
| 184 | 184 | defer regex.deinit(); |
| 185 | 185 | |
| 186 | 186 | // Create progressively longer inputs |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | test "integration: email validation - basic pattern" { |
| 6 | 6 | const allocator = std.testing.allocator; |
| 7 | 7 | // 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+"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | try std.testing.expect(try regex.isMatch("user@example.com")); |
| @@ -16,7 +16,7 @@ test "integration: email validation - basic pattern" { | ||
| 16 | 16 | |
| 17 | 17 | test "integration: email validation - extract from text" { |
| 18 | 18 | 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+"); | |
| 20 | 20 | defer regex.deinit(); |
| 21 | 21 | |
| 22 | 22 | const text = "Contact support@example.com for help"; |
| @@ -33,7 +33,7 @@ test "integration: email validation - extract from text" { | ||
| 33 | 33 | test "integration: URL extraction from text" { |
| 34 | 34 | const allocator = std.testing.allocator; |
| 35 | 35 | // 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+"); | |
| 37 | 37 | defer regex.deinit(); |
| 38 | 38 | |
| 39 | 39 | const text = "Visit https://example.com for more info."; |
| @@ -49,7 +49,7 @@ test "integration: URL extraction from text" { | ||
| 49 | 49 | // Phone Number Tests |
| 50 | 50 | test "integration: US phone number validation" { |
| 51 | 51 | 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}$"); | |
| 53 | 53 | defer regex.deinit(); |
| 54 | 54 | |
| 55 | 55 | try std.testing.expect(try regex.isMatch("555-123-4567")); |
| @@ -60,7 +60,7 @@ test "integration: US phone number validation" { | ||
| 60 | 60 | |
| 61 | 61 | test "integration: extract phone numbers from text" { |
| 62 | 62 | 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}"); | |
| 64 | 64 | defer regex.deinit(); |
| 65 | 65 | |
| 66 | 66 | const text = "Call 555-123-4567 or 800-555-9876 for support."; |
| @@ -81,7 +81,7 @@ test "integration: extract phone numbers from text" { | ||
| 81 | 81 | // Date/Time Patterns |
| 82 | 82 | test "integration: ISO date format" { |
| 83 | 83 | 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}$"); | |
| 85 | 85 | defer regex.deinit(); |
| 86 | 86 | |
| 87 | 87 | try std.testing.expect(try regex.isMatch("2025-01-26")); |
| @@ -92,7 +92,7 @@ test "integration: ISO date format" { | ||
| 92 | 92 | |
| 93 | 93 | test "integration: time format HH:MM:SS" { |
| 94 | 94 | 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}$"); | |
| 96 | 96 | defer regex.deinit(); |
| 97 | 97 | |
| 98 | 98 | try std.testing.expect(try regex.isMatch("14:30:45")); |
| @@ -106,7 +106,7 @@ test "integration: time format HH:MM:SS" { | ||
| 106 | 106 | test "integration: extract log levels" { |
| 107 | 107 | const allocator = std.testing.allocator; |
| 108 | 108 | // 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"); | |
| 110 | 110 | defer regex.deinit(); |
| 111 | 111 | |
| 112 | 112 | try std.testing.expect(try regex.isMatch("[INFO] Application started")); |
| @@ -115,7 +115,7 @@ test "integration: extract log levels" { | ||
| 115 | 115 | |
| 116 | 116 | test "integration: parse log timestamp and message" { |
| 117 | 117 | 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}"); | |
| 119 | 119 | defer regex.deinit(); |
| 120 | 120 | |
| 121 | 121 | const log = "[2025-01-26 14:30:45] User login successful"; |
| @@ -132,7 +132,7 @@ test "integration: parse log timestamp and message" { | ||
| 132 | 132 | test "integration: IPv4 address matching" { |
| 133 | 133 | const allocator = std.testing.allocator; |
| 134 | 134 | // 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}$"); | |
| 136 | 136 | defer regex.deinit(); |
| 137 | 137 | |
| 138 | 138 | try std.testing.expect(try regex.isMatch("192.168.1.1")); |
| @@ -145,7 +145,7 @@ test "integration: IPv4 address matching" { | ||
| 145 | 145 | test "integration: password strength - minimum length" { |
| 146 | 146 | const allocator = std.testing.allocator; |
| 147 | 147 | // 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}"); | |
| 149 | 149 | defer regex.deinit(); |
| 150 | 150 | |
| 151 | 151 | try std.testing.expect(try regex.isMatch("password123")); // has 8+ chars |
| @@ -155,7 +155,7 @@ test "integration: password strength - minimum length" { | ||
| 155 | 155 | |
| 156 | 156 | test "integration: password contains digit" { |
| 157 | 157 | const allocator = std.testing.allocator; |
| 158 | var regex = try Regex.compile(allocator, "\\d"); | |
| 158 | var regex = try Regex.compile(allocator, std.testing.io, "\\d"); | |
| 159 | 159 | defer regex.deinit(); |
| 160 | 160 | |
| 161 | 161 | try std.testing.expect(try regex.isMatch("password1")); |
| @@ -167,7 +167,7 @@ test "integration: password contains digit" { | ||
| 167 | 167 | test "integration: username alphanumeric with underscores" { |
| 168 | 168 | const allocator = std.testing.allocator; |
| 169 | 169 | // 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}"); | |
| 171 | 171 | defer regex.deinit(); |
| 172 | 172 | |
| 173 | 173 | try std.testing.expect(try regex.isMatch("user123")); |
| @@ -179,7 +179,7 @@ test "integration: username alphanumeric with underscores" { | ||
| 179 | 179 | // Hashtag Extraction |
| 180 | 180 | test "integration: extract hashtags from text" { |
| 181 | 181 | const allocator = std.testing.allocator; |
| 182 | var regex = try Regex.compile(allocator, "#\\w+"); | |
| 182 | var regex = try Regex.compile(allocator, std.testing.io, "#\\w+"); | |
| 183 | 183 | defer regex.deinit(); |
| 184 | 184 | |
| 185 | 185 | const tweet = "Loving #Zig programming! #opensource #systems"; |
| @@ -202,7 +202,7 @@ test "integration: extract hashtags from text" { | ||
| 202 | 202 | test "integration: match HTML tags" { |
| 203 | 203 | const allocator = std.testing.allocator; |
| 204 | 204 | // 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]+>"); | |
| 206 | 206 | defer regex.deinit(); |
| 207 | 207 | |
| 208 | 208 | const html = "<div>content<span>text"; |
| @@ -223,7 +223,7 @@ test "integration: match HTML tags" { | ||
| 223 | 223 | // CSV Parsing |
| 224 | 224 | test "integration: split CSV line" { |
| 225 | 225 | const allocator = std.testing.allocator; |
| 226 | var regex = try Regex.compile(allocator, ","); | |
| 226 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 227 | 227 | defer regex.deinit(); |
| 228 | 228 | |
| 229 | 229 | const csv = "John,Doe,30,Engineer"; |
| @@ -240,7 +240,7 @@ test "integration: split CSV line" { | ||
| 240 | 240 | // Whitespace Normalization |
| 241 | 241 | test "integration: replace multiple spaces with single space" { |
| 242 | 242 | const allocator = std.testing.allocator; |
| 243 | var regex = try Regex.compile(allocator, "\\s+"); | |
| 243 | var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); | |
| 244 | 244 | defer regex.deinit(); |
| 245 | 245 | |
| 246 | 246 | const text = "This has too many spaces"; |
| @@ -254,7 +254,7 @@ test "integration: replace multiple spaces with single space" { | ||
| 254 | 254 | test "integration: extract markdown links" { |
| 255 | 255 | const allocator = std.testing.allocator; |
| 256 | 256 | // 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+\\)"); | |
| 258 | 258 | defer regex.deinit(); |
| 259 | 259 | |
| 260 | 260 | const markdown = "Check [Zig](https://ziglang.org) for more info"; |
| @@ -270,7 +270,7 @@ test "integration: extract markdown links" { | ||
| 270 | 270 | // Version Number Extraction |
| 271 | 271 | test "integration: semver version numbers" { |
| 272 | 272 | 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+"); | |
| 274 | 274 | defer regex.deinit(); |
| 275 | 275 | |
| 276 | 276 | const text = "Version 1.2.3 released. Upgrade from 1.0.0 required."; |
| @@ -291,7 +291,7 @@ test "integration: semver version numbers" { | ||
| 291 | 291 | // Hex Color Codes |
| 292 | 292 | test "integration: match hex color codes" { |
| 293 | 293 | 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}"); | |
| 295 | 295 | defer regex.deinit(); |
| 296 | 296 | |
| 297 | 297 | const css = "color: #FF5733; background: #FFFFFF;"; |
| @@ -312,7 +312,7 @@ test "integration: match hex color codes" { | ||
| 312 | 312 | // Code Comment Extraction |
| 313 | 313 | test "integration: extract single-line comments" { |
| 314 | 314 | const allocator = std.testing.allocator; |
| 315 | var regex = try Regex.compile(allocator, "//.*"); | |
| 315 | var regex = try Regex.compile(allocator, std.testing.io, "//.*"); | |
| 316 | 316 | defer regex.deinit(); |
| 317 | 317 | |
| 318 | 318 | const code = "const x = 5; // This is a comment"; |
| @@ -328,7 +328,7 @@ test "integration: extract single-line comments" { | ||
| 328 | 328 | // Environment Variable Format |
| 329 | 329 | test "integration: match environment variables" { |
| 330 | 330 | 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_]+"); | |
| 332 | 332 | defer regex.deinit(); |
| 333 | 333 | |
| 334 | 334 | const script = "export PATH=$HOME/bin:$PATH"; |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "iterator: basic iteration" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | const text = "123 abc 456 def 789"; |
| @@ -29,7 +29,7 @@ test "iterator: basic iteration" { | ||
| 29 | 29 | |
| 30 | 30 | test "iterator: empty input" { |
| 31 | 31 | const allocator = std.testing.allocator; |
| 32 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 32 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 33 | 33 | defer regex.deinit(); |
| 34 | 34 | |
| 35 | 35 | const text = ""; |
| @@ -41,7 +41,7 @@ test "iterator: empty input" { | ||
| 41 | 41 | |
| 42 | 42 | test "iterator: no matches" { |
| 43 | 43 | const allocator = std.testing.allocator; |
| 44 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 44 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 45 | 45 | defer regex.deinit(); |
| 46 | 46 | |
| 47 | 47 | const text = "abc def"; |
| @@ -53,7 +53,7 @@ test "iterator: no matches" { | ||
| 53 | 53 | |
| 54 | 54 | test "iterator: reset functionality" { |
| 55 | 55 | const allocator = std.testing.allocator; |
| 56 | var regex = try Regex.compile(allocator, "\\w+"); | |
| 56 | var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); | |
| 57 | 57 | defer regex.deinit(); |
| 58 | 58 | |
| 59 | 59 | const text = "hello world"; |
| @@ -72,7 +72,7 @@ test "iterator: reset functionality" { | ||
| 72 | 72 | |
| 73 | 73 | test "iterator: captures in iteration" { |
| 74 | 74 | 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+)"); | |
| 76 | 76 | defer regex.deinit(); |
| 77 | 77 | |
| 78 | 78 | const text = "user@example and admin@test"; |
| @@ -94,7 +94,7 @@ test "iterator: captures in iteration" { | ||
| 94 | 94 | |
| 95 | 95 | test "iterator: large input streaming" { |
| 96 | 96 | const allocator = std.testing.allocator; |
| 97 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 97 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 98 | 98 | defer regex.deinit(); |
| 99 | 99 | |
| 100 | 100 | // Simulate processing large input without loading all matches |
| @@ -115,7 +115,7 @@ test "iterator: large input streaming" { | ||
| 115 | 115 | |
| 116 | 116 | test "iterator: manual loop pattern" { |
| 117 | 117 | 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]+"); | |
| 119 | 119 | defer regex.deinit(); |
| 120 | 120 | |
| 121 | 121 | const text = "hello world test"; |
| @@ -146,7 +146,7 @@ test "iterator: manual loop pattern" { | ||
| 146 | 146 | |
| 147 | 147 | test "iterator: memory efficiency" { |
| 148 | 148 | const allocator = std.testing.allocator; |
| 149 | var regex = try Regex.compile(allocator, "\\w+"); | |
| 149 | var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); | |
| 150 | 150 | defer regex.deinit(); |
| 151 | 151 | |
| 152 | 152 | // Iterator should not allocate all matches at once |
| @@ -171,7 +171,7 @@ test "iterator: memory efficiency" { | ||
| 171 | 171 | |
| 172 | 172 | test "iterator: overlapping matches prevention" { |
| 173 | 173 | 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"); | |
| 175 | 175 | defer regex.deinit(); |
| 176 | 176 | |
| 177 | 177 | const text = "abcd"; |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "lazy star: a*? matches minimal" { |
| 7 | 7 | 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"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | // Greedy would match "aaab", lazy should match "b" |
| @@ -22,7 +22,7 @@ test "lazy star vs greedy star" { | ||
| 22 | 22 | const allocator = std.testing.allocator; |
| 23 | 23 | |
| 24 | 24 | // Greedy star |
| 25 | var greedy = try Regex.compile(allocator, "a*b"); | |
| 25 | var greedy = try Regex.compile(allocator, std.testing.io, "a*b"); | |
| 26 | 26 | defer greedy.deinit(); |
| 27 | 27 | |
| 28 | 28 | if (try greedy.find("aaab")) |match| { |
| @@ -35,7 +35,7 @@ test "lazy star vs greedy star" { | ||
| 35 | 35 | } |
| 36 | 36 | |
| 37 | 37 | // Lazy star |
| 38 | var lazy = try Regex.compile(allocator, "a*?b"); | |
| 38 | var lazy = try Regex.compile(allocator, std.testing.io, "a*?b"); | |
| 39 | 39 | defer lazy.deinit(); |
| 40 | 40 | |
| 41 | 41 | if (try lazy.find("aaab")) |match| { |
| @@ -50,7 +50,7 @@ test "lazy star vs greedy star" { | ||
| 50 | 50 | |
| 51 | 51 | test "lazy plus: a+? matches minimal (at least one)" { |
| 52 | 52 | 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"); | |
| 54 | 54 | defer regex.deinit(); |
| 55 | 55 | |
| 56 | 56 | // Lazy plus must match at least one 'a' |
| @@ -67,7 +67,7 @@ test "lazy plus vs greedy plus" { | ||
| 67 | 67 | const allocator = std.testing.allocator; |
| 68 | 68 | |
| 69 | 69 | // Greedy plus |
| 70 | var greedy = try Regex.compile(allocator, "a+b"); | |
| 70 | var greedy = try Regex.compile(allocator, std.testing.io, "a+b"); | |
| 71 | 71 | defer greedy.deinit(); |
| 72 | 72 | |
| 73 | 73 | if (try greedy.find("aaab")) |match| { |
| @@ -80,7 +80,7 @@ test "lazy plus vs greedy plus" { | ||
| 80 | 80 | } |
| 81 | 81 | |
| 82 | 82 | // Lazy plus |
| 83 | var lazy = try Regex.compile(allocator, "a+?b"); | |
| 83 | var lazy = try Regex.compile(allocator, std.testing.io, "a+?b"); | |
| 84 | 84 | defer lazy.deinit(); |
| 85 | 85 | |
| 86 | 86 | if (try lazy.find("aaab")) |match| { |
| @@ -95,7 +95,7 @@ test "lazy plus vs greedy plus" { | ||
| 95 | 95 | |
| 96 | 96 | test "lazy optional: a?? matches minimal (zero)" { |
| 97 | 97 | 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"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | // Lazy optional prefers zero matches |
| @@ -112,7 +112,7 @@ test "lazy optional vs greedy optional" { | ||
| 112 | 112 | const allocator = std.testing.allocator; |
| 113 | 113 | |
| 114 | 114 | // Greedy optional |
| 115 | var greedy = try Regex.compile(allocator, "a?b"); | |
| 115 | var greedy = try Regex.compile(allocator, std.testing.io, "a?b"); | |
| 116 | 116 | defer greedy.deinit(); |
| 117 | 117 | |
| 118 | 118 | if (try greedy.find("ab")) |match| { |
| @@ -125,7 +125,7 @@ test "lazy optional vs greedy optional" { | ||
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | // Lazy optional |
| 128 | var lazy = try Regex.compile(allocator, "a??b"); | |
| 128 | var lazy = try Regex.compile(allocator, std.testing.io, "a??b"); | |
| 129 | 129 | defer lazy.deinit(); |
| 130 | 130 | |
| 131 | 131 | if (try lazy.find("ab")) |match| { |
| @@ -140,7 +140,7 @@ test "lazy optional vs greedy optional" { | ||
| 140 | 140 | |
| 141 | 141 | test "lazy repeat: a{2,4}? matches minimal" { |
| 142 | 142 | 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"); | |
| 144 | 144 | defer regex.deinit(); |
| 145 | 145 | |
| 146 | 146 | // Lazy repeat matches minimum (2 'a's) |
| @@ -157,7 +157,7 @@ test "lazy repeat vs greedy repeat" { | ||
| 157 | 157 | const allocator = std.testing.allocator; |
| 158 | 158 | |
| 159 | 159 | // 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"); | |
| 161 | 161 | defer greedy.deinit(); |
| 162 | 162 | |
| 163 | 163 | if (try greedy.find("aaaaab")) |match| { |
| @@ -170,7 +170,7 @@ test "lazy repeat vs greedy repeat" { | ||
| 170 | 170 | } |
| 171 | 171 | |
| 172 | 172 | // 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"); | |
| 174 | 174 | defer lazy.deinit(); |
| 175 | 175 | |
| 176 | 176 | if (try lazy.find("aaaaab")) |match| { |
| @@ -185,7 +185,7 @@ test "lazy repeat vs greedy repeat" { | ||
| 185 | 185 | |
| 186 | 186 | test "lazy quantifier in alternation" { |
| 187 | 187 | 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"); | |
| 189 | 189 | defer regex.deinit(); |
| 190 | 190 | |
| 191 | 191 | try std.testing.expect(try regex.isMatch("b")); |
| @@ -195,7 +195,7 @@ test "lazy quantifier in alternation" { | ||
| 195 | 195 | |
| 196 | 196 | test "multiple lazy quantifiers" { |
| 197 | 197 | 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"); | |
| 199 | 199 | defer regex.deinit(); |
| 200 | 200 | |
| 201 | 201 | if (try regex.find("aaabbbbc")) |match| { |
| @@ -210,7 +210,7 @@ test "multiple lazy quantifiers" { | ||
| 210 | 210 | |
| 211 | 211 | test "lazy quantifier with character class" { |
| 212 | 212 | 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"); | |
| 214 | 214 | defer regex.deinit(); |
| 215 | 215 | |
| 216 | 216 | if (try regex.find("abc123")) |match| { |
| @@ -225,7 +225,7 @@ test "lazy quantifier with character class" { | ||
| 225 | 225 | |
| 226 | 226 | test "lazy star with dot" { |
| 227 | 227 | const allocator = std.testing.allocator; |
| 228 | var regex = try Regex.compile(allocator, ".*?x"); | |
| 228 | var regex = try Regex.compile(allocator, std.testing.io, ".*?x"); | |
| 229 | 229 | defer regex.deinit(); |
| 230 | 230 | |
| 231 | 231 | if (try regex.find("abcxyz")) |match| { |
| @@ -240,7 +240,7 @@ test "lazy star with dot" { | ||
| 240 | 240 | |
| 241 | 241 | test "lazy quantifier in capture group" { |
| 242 | 242 | 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"); | |
| 244 | 244 | defer regex.deinit(); |
| 245 | 245 | |
| 246 | 246 | if (try regex.find("aaab")) |match| { |
| @@ -258,7 +258,7 @@ test "lazy quantifier in capture group" { | ||
| 258 | 258 | |
| 259 | 259 | test "lazy repeat {n,}?" { |
| 260 | 260 | 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"); | |
| 262 | 262 | defer regex.deinit(); |
| 263 | 263 | |
| 264 | 264 | if (try regex.find("aaaaab")) |match| { |
| @@ -274,7 +274,7 @@ test "lazy repeat {n,}?" { | ||
| 274 | 274 | test "lazy quantifier backtracking" { |
| 275 | 275 | const allocator = std.testing.allocator; |
| 276 | 276 | // 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"); | |
| 278 | 278 | defer regex.deinit(); |
| 279 | 279 | |
| 280 | 280 | if (try regex.find("aaab")) |match| { |
| @@ -289,7 +289,7 @@ test "lazy quantifier backtracking" { | ||
| 289 | 289 | |
| 290 | 290 | test "nested lazy quantifiers" { |
| 291 | 291 | 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"); | |
| 293 | 293 | defer regex.deinit(); |
| 294 | 294 | |
| 295 | 295 | try std.testing.expect(try regex.isMatch("c")); |
| @@ -299,7 +299,7 @@ test "nested lazy quantifiers" { | ||
| 299 | 299 | |
| 300 | 300 | test "lazy quantifier at end of pattern" { |
| 301 | 301 | const allocator = std.testing.allocator; |
| 302 | var regex = try Regex.compile(allocator, "a+?"); | |
| 302 | var regex = try Regex.compile(allocator, std.testing.io, "a+?"); | |
| 303 | 303 | defer regex.deinit(); |
| 304 | 304 | |
| 305 | 305 | if (try regex.find("aaa")) |match| { |
| @@ -318,10 +318,10 @@ test "lazy vs greedy performance comparison" { | ||
| 318 | 318 | const input = "a" ** 100 ++ "b"; |
| 319 | 319 | |
| 320 | 320 | // 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"); | |
| 322 | 322 | defer greedy.deinit(); |
| 323 | 323 | |
| 324 | var lazy = try Regex.compile(allocator, "a*?b"); | |
| 324 | var lazy = try Regex.compile(allocator, std.testing.io, "a*?b"); | |
| 325 | 325 | defer lazy.deinit(); |
| 326 | 326 | |
| 327 | 327 | // Greedy matches all 100 'a's + 'b' |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | |
| 4 | 4 | test "positive lookahead: basic (?=...)" { |
| 5 | 5 | 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)"); | |
| 7 | 7 | defer regex.deinit(); |
| 8 | 8 | |
| 9 | 9 | // Should match "foo" only when followed by "bar" |
| @@ -24,7 +24,7 @@ test "positive lookahead: basic (?=...)" { | ||
| 24 | 24 | |
| 25 | 25 | test "positive lookahead: at end of string" { |
| 26 | 26 | 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"); | |
| 28 | 28 | defer regex.deinit(); |
| 29 | 29 | |
| 30 | 30 | try std.testing.expect(try regex.isMatch("foobar")); |
| @@ -40,7 +40,7 @@ test "positive lookahead: at end of string" { | ||
| 40 | 40 | |
| 41 | 41 | test "positive lookahead: with alternation" { |
| 42 | 42 | 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)"); | |
| 44 | 44 | defer regex.deinit(); |
| 45 | 45 | |
| 46 | 46 | try std.testing.expect(try regex.isMatch("foobar")); |
| @@ -50,7 +50,7 @@ test "positive lookahead: with alternation" { | ||
| 50 | 50 | |
| 51 | 51 | test "negative lookahead: basic (?!...)" { |
| 52 | 52 | 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)"); | |
| 54 | 54 | defer regex.deinit(); |
| 55 | 55 | |
| 56 | 56 | // Should match "foo" when NOT followed by "bar" |
| @@ -68,7 +68,7 @@ test "negative lookahead: basic (?!...)" { | ||
| 68 | 68 | |
| 69 | 69 | test "negative lookahead: with character class" { |
| 70 | 70 | 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)"); | |
| 72 | 72 | defer regex.deinit(); |
| 73 | 73 | |
| 74 | 74 | // Should match digits not followed by another digit |
| @@ -84,7 +84,7 @@ test "negative lookahead: with character class" { | ||
| 84 | 84 | test "multiple lookaheads" { |
| 85 | 85 | const allocator = std.testing.allocator; |
| 86 | 86 | // 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]).+"); | |
| 88 | 88 | defer regex.deinit(); |
| 89 | 89 | |
| 90 | 90 | try std.testing.expect(try regex.isMatch("abc123")); |
| @@ -95,7 +95,7 @@ test "multiple lookaheads" { | ||
| 95 | 95 | |
| 96 | 96 | test "lookahead in alternation" { |
| 97 | 97 | 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)"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | try std.testing.expect(try regex.isMatch("foo")); |
| @@ -104,7 +104,7 @@ test "lookahead in alternation" { | ||
| 104 | 104 | |
| 105 | 105 | test "lookahead with quantifiers" { |
| 106 | 106 | 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+)"); | |
| 108 | 108 | defer regex.deinit(); |
| 109 | 109 | |
| 110 | 110 | if (try regex.find("aaabbb")) |match| { |
| @@ -118,7 +118,7 @@ test "lookahead with quantifiers" { | ||
| 118 | 118 | |
| 119 | 119 | test "nested lookahead" { |
| 120 | 120 | 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))"); | |
| 122 | 122 | defer regex.deinit(); |
| 123 | 123 | |
| 124 | 124 | try std.testing.expect(try regex.isMatch("foobarbaz")); |
| @@ -127,7 +127,7 @@ test "nested lookahead" { | ||
| 127 | 127 | |
| 128 | 128 | test "lookahead: zero-width assertion" { |
| 129 | 129 | 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"); | |
| 131 | 131 | defer regex.deinit(); |
| 132 | 132 | |
| 133 | 133 | if (try regex.find("a")) |match| { |
| @@ -144,7 +144,7 @@ test "lookahead: zero-width assertion" { | ||
| 144 | 144 | |
| 145 | 145 | test "lookahead with capture groups" { |
| 146 | 146 | 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)"); | |
| 148 | 148 | defer regex.deinit(); |
| 149 | 149 | |
| 150 | 150 | if (try regex.find("hello world")) |match| { |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | |
| 4 | 4 | test "positive lookbehind: basic (?<=...)" { |
| 5 | 5 | 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"); | |
| 7 | 7 | defer regex.deinit(); |
| 8 | 8 | |
| 9 | 9 | // Should match "bar" only when preceded by "foo" |
| @@ -25,7 +25,7 @@ test "positive lookbehind: basic (?<=...)" { | ||
| 25 | 25 | |
| 26 | 26 | test "positive lookbehind: at start of pattern" { |
| 27 | 27 | 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"); | |
| 29 | 29 | defer regex.deinit(); |
| 30 | 30 | |
| 31 | 31 | try std.testing.expect(try regex.isMatch("foobar")); |
| @@ -34,7 +34,7 @@ test "positive lookbehind: at start of pattern" { | ||
| 34 | 34 | |
| 35 | 35 | test "positive lookbehind: with alternation" { |
| 36 | 36 | 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"); | |
| 38 | 38 | defer regex.deinit(); |
| 39 | 39 | |
| 40 | 40 | try std.testing.expect(try regex.isMatch("foobar")); |
| @@ -44,7 +44,7 @@ test "positive lookbehind: with alternation" { | ||
| 44 | 44 | |
| 45 | 45 | test "negative lookbehind: basic (?<!...)" { |
| 46 | 46 | 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"); | |
| 48 | 48 | defer regex.deinit(); |
| 49 | 49 | |
| 50 | 50 | // Should match "bar" when NOT preceded by "foo" |
| @@ -62,7 +62,7 @@ test "negative lookbehind: basic (?<!...)" { | ||
| 62 | 62 | |
| 63 | 63 | test "negative lookbehind: at string start" { |
| 64 | 64 | 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"); | |
| 66 | 66 | defer regex.deinit(); |
| 67 | 67 | |
| 68 | 68 | // "bar" at start should match (nothing before it, so not preceded by 'x') |
| @@ -78,7 +78,7 @@ test "negative lookbehind: at string start" { | ||
| 78 | 78 | |
| 79 | 79 | test "lookbehind with literal characters" { |
| 80 | 80 | const allocator = std.testing.allocator; |
| 81 | var regex = try Regex.compile(allocator, "(?<=\\$)\\d+"); | |
| 81 | var regex = try Regex.compile(allocator, std.testing.io, "(?<=\\$)\\d+"); | |
| 82 | 82 | defer regex.deinit(); |
| 83 | 83 | |
| 84 | 84 | // Match digits preceded by dollar sign |
| @@ -95,7 +95,7 @@ test "lookbehind with literal characters" { | ||
| 95 | 95 | |
| 96 | 96 | test "lookbehind: zero-width assertion" { |
| 97 | 97 | 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"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | if (try regex.find("ab")) |match| { |
| @@ -112,7 +112,7 @@ test "lookbehind: zero-width assertion" { | ||
| 112 | 112 | |
| 113 | 113 | test "combined lookahead and lookbehind" { |
| 114 | 114 | 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)"); | |
| 116 | 116 | defer regex.deinit(); |
| 117 | 117 | |
| 118 | 118 | try std.testing.expect(try regex.isMatch("foobarbaz")); |
| @@ -132,7 +132,7 @@ test "combined lookahead and lookbehind" { | ||
| 132 | 132 | |
| 133 | 133 | test "lookbehind with capture groups" { |
| 134 | 134 | const allocator = std.testing.allocator; |
| 135 | var regex = try Regex.compile(allocator, "(?<=@)(\\w+)"); | |
| 135 | var regex = try Regex.compile(allocator, std.testing.io, "(?<=@)(\\w+)"); | |
| 136 | 136 | defer regex.deinit(); |
| 137 | 137 | |
| 138 | 138 | if (try regex.find("@username")) |match| { |
| @@ -148,7 +148,7 @@ test "lookbehind with capture groups" { | ||
| 148 | 148 | |
| 149 | 149 | test "multiple lookbehinds" { |
| 150 | 150 | 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"); | |
| 152 | 152 | defer regex.deinit(); |
| 153 | 153 | |
| 154 | 154 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -157,7 +157,7 @@ test "multiple lookbehinds" { | ||
| 157 | 157 | |
| 158 | 158 | test "lookbehind in findAll" { |
| 159 | 159 | 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+"); | |
| 161 | 161 | defer regex.deinit(); |
| 162 | 162 | |
| 163 | 163 | const matches = try regex.findAll(allocator, "hello world foo bar"); |
| @@ -6,11 +6,11 @@ test "multiline flag: ^ matches after newlines" { | ||
| 6 | 6 | const allocator = std.testing.allocator; |
| 7 | 7 | |
| 8 | 8 | // 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 }); | |
| 10 | 10 | defer regex_single.deinit(); |
| 11 | 11 | |
| 12 | 12 | // 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 }); | |
| 14 | 14 | defer regex_multi.deinit(); |
| 15 | 15 | |
| 16 | 16 | const input = "line1\ntest"; |
| @@ -26,11 +26,11 @@ test "multiline flag: $ matches before newlines" { | ||
| 26 | 26 | const allocator = std.testing.allocator; |
| 27 | 27 | |
| 28 | 28 | // 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 }); | |
| 30 | 30 | defer regex_single.deinit(); |
| 31 | 31 | |
| 32 | 32 | // 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 }); | |
| 34 | 34 | defer regex_multi.deinit(); |
| 35 | 35 | |
| 36 | 36 | const input = "test\nline2"; |
| @@ -45,7 +45,7 @@ test "multiline flag: $ matches before newlines" { | ||
| 45 | 45 | test "multiline flag: complex pattern" { |
| 46 | 46 | const allocator = std.testing.allocator; |
| 47 | 47 | |
| 48 | var regex = try Regex.compileWithFlags(allocator, "^\\w+$", .{ .multiline = true }); | |
| 48 | var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\w+$", .{ .multiline = true }); | |
| 49 | 49 | defer regex.deinit(); |
| 50 | 50 | |
| 51 | 51 | // Should match each line |
| @@ -57,11 +57,11 @@ test "dot-all flag: . matches newlines" { | ||
| 57 | 57 | const allocator = std.testing.allocator; |
| 58 | 58 | |
| 59 | 59 | // 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 }); | |
| 61 | 61 | defer regex_normal.deinit(); |
| 62 | 62 | |
| 63 | 63 | // 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 }); | |
| 65 | 65 | defer regex_dotall.deinit(); |
| 66 | 66 | |
| 67 | 67 | const input_with_newline = "a\nb"; |
| @@ -80,11 +80,11 @@ test "dot-all flag: .* matches across lines" { | ||
| 80 | 80 | const allocator = std.testing.allocator; |
| 81 | 81 | |
| 82 | 82 | // 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 }); | |
| 84 | 84 | defer regex_normal.deinit(); |
| 85 | 85 | |
| 86 | 86 | // 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 }); | |
| 88 | 88 | defer regex_dotall.deinit(); |
| 89 | 89 | |
| 90 | 90 | const input = "start\nmiddle\nend"; |
| @@ -99,7 +99,7 @@ test "dot-all flag: .* matches across lines" { | ||
| 99 | 99 | test "combined multiline and dot-all flags" { |
| 100 | 100 | const allocator = std.testing.allocator; |
| 101 | 101 | |
| 102 | var regex = try Regex.compileWithFlags(allocator, "^.*$", .{ | |
| 102 | var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^.*$", .{ | |
| 103 | 103 | .multiline = true, |
| 104 | 104 | .dot_all = true, |
| 105 | 105 | }); |
| @@ -112,7 +112,7 @@ test "combined multiline and dot-all flags" { | ||
| 112 | 112 | test "multiline flag: multiple matches with findAll" { |
| 113 | 113 | const allocator = std.testing.allocator; |
| 114 | 114 | |
| 115 | var regex = try Regex.compileWithFlags(allocator, "^\\d+", .{ .multiline = true }); | |
| 115 | var regex = try Regex.compileWithFlags(allocator, std.testing.io, "^\\d+", .{ .multiline = true }); | |
| 116 | 116 | defer regex.deinit(); |
| 117 | 117 | |
| 118 | 118 | const matches = try regex.findAll(allocator, "123\n456\n789"); |
| @@ -135,7 +135,7 @@ test "default flags: multiline and dot-all are false" { | ||
| 135 | 135 | const allocator = std.testing.allocator; |
| 136 | 136 | |
| 137 | 137 | // 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$"); | |
| 139 | 139 | defer regex.deinit(); |
| 140 | 140 | |
| 141 | 141 | // Should not match with newlines |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "named group: Python style (?P<name>...)" { |
| 7 | 7 | 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+)"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | if (try regex.find("hello")) |match| { |
| @@ -33,7 +33,7 @@ test "named group: Python style (?P<name>...)" { | ||
| 33 | 33 | |
| 34 | 34 | test "named group: .NET/Perl style (?<name>...)" { |
| 35 | 35 | 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+)"); | |
| 37 | 37 | defer regex.deinit(); |
| 38 | 38 | |
| 39 | 39 | if (try regex.find("123")) |match| { |
| @@ -52,7 +52,7 @@ test "named group: .NET/Perl style (?<name>...)" { | ||
| 52 | 52 | |
| 53 | 53 | test "named group: multiple named groups" { |
| 54 | 54 | 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})"); | |
| 56 | 56 | defer regex.deinit(); |
| 57 | 57 | |
| 58 | 58 | if (try regex.find("2025-10-27")) |match| { |
| @@ -79,7 +79,7 @@ test "named group: multiple named groups" { | ||
| 79 | 79 | |
| 80 | 80 | test "named group: mixed with numbered groups" { |
| 81 | 81 | 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+)"); | |
| 83 | 83 | defer regex.deinit(); |
| 84 | 84 | |
| 85 | 85 | if (try regex.find("user@example.com")) |match| { |
| @@ -100,7 +100,7 @@ test "named group: mixed with numbered groups" { | ||
| 100 | 100 | |
| 101 | 101 | test "named group: non-existent name returns null" { |
| 102 | 102 | 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+)"); | |
| 104 | 104 | defer regex.deinit(); |
| 105 | 105 | |
| 106 | 106 | if (try regex.find("hello")) |match| { |
| @@ -119,7 +119,7 @@ test "named group: non-existent name returns null" { | ||
| 119 | 119 | |
| 120 | 120 | test "named group: with alternation" { |
| 121 | 121 | 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>[^/]+)"); | |
| 123 | 123 | defer regex.deinit(); |
| 124 | 124 | |
| 125 | 125 | if (try regex.find("https://example.com")) |match| { |
| @@ -141,7 +141,7 @@ test "named group: with alternation" { | ||
| 141 | 141 | |
| 142 | 142 | test "named group: with quantifiers" { |
| 143 | 143 | 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>.*)"); | |
| 145 | 145 | defer regex.deinit(); |
| 146 | 146 | |
| 147 | 147 | if (try regex.find("<div>hello world</div>")) |match| { |
| @@ -163,7 +163,7 @@ test "named group: with quantifiers" { | ||
| 163 | 163 | |
| 164 | 164 | test "named group: nested groups" { |
| 165 | 165 | 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))"); | |
| 167 | 167 | defer regex.deinit(); |
| 168 | 168 | |
| 169 | 169 | if (try regex.find("inner")) |match| { |
| @@ -186,7 +186,7 @@ test "named group: nested groups" { | ||
| 186 | 186 | |
| 187 | 187 | test "named group: with non-capturing group" { |
| 188 | 188 | 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+)"); | |
| 190 | 190 | defer regex.deinit(); |
| 191 | 191 | |
| 192 | 192 | if (try regex.find("prefix-test")) |match| { |
| @@ -206,7 +206,7 @@ test "named group: with non-capturing group" { | ||
| 206 | 206 | |
| 207 | 207 | test "named group: email parser example" { |
| 208 | 208 | 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>[^@]+)"); | |
| 210 | 210 | defer regex.deinit(); |
| 211 | 211 | |
| 212 | 212 | if (try regex.find("john.doe@example.com")) |match| { |
| @@ -228,7 +228,7 @@ test "named group: email parser example" { | ||
| 228 | 228 | |
| 229 | 229 | test "named group: IP address parser" { |
| 230 | 230 | 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+)"); | |
| 232 | 232 | defer regex.deinit(); |
| 233 | 233 | |
| 234 | 234 | if (try regex.find("192.168.1.1")) |match| { |
| @@ -256,7 +256,7 @@ test "named group: IP address parser" { | ||
| 256 | 256 | |
| 257 | 257 | test "named group: findAll preserves names" { |
| 258 | 258 | 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+)"); | |
| 260 | 260 | defer regex.deinit(); |
| 261 | 261 | |
| 262 | 262 | var matches = try regex.findAll(allocator, "123 456 789"); |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "non-capturing: basic non-capturing group" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "(?:\\d+)"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | const result = try regex.find("123"); |
| @@ -22,7 +22,7 @@ test "non-capturing: basic non-capturing group" { | ||
| 22 | 22 | |
| 23 | 23 | test "non-capturing: mixed capturing and non-capturing" { |
| 24 | 24 | 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+)"); | |
| 26 | 26 | defer regex.deinit(); |
| 27 | 27 | |
| 28 | 28 | const result = try regex.find("user@example.com"); |
| @@ -41,7 +41,7 @@ test "non-capturing: mixed capturing and non-capturing" { | ||
| 41 | 41 | |
| 42 | 42 | test "non-capturing: alternation in non-capturing group" { |
| 43 | 43 | 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+)"); | |
| 45 | 45 | defer regex.deinit(); |
| 46 | 46 | |
| 47 | 47 | const result1 = try regex.find("cat food"); |
| @@ -69,7 +69,7 @@ test "non-capturing: alternation in non-capturing group" { | ||
| 69 | 69 | |
| 70 | 70 | test "non-capturing: nested non-capturing groups" { |
| 71 | 71 | 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))"); | |
| 73 | 73 | defer regex.deinit(); |
| 74 | 74 | |
| 75 | 75 | try std.testing.expect(try regex.isMatch("ac")); |
| @@ -82,7 +82,7 @@ test "non-capturing: nested non-capturing groups" { | ||
| 82 | 82 | test "non-capturing: capture numbering with non-capturing groups" { |
| 83 | 83 | const allocator = std.testing.allocator; |
| 84 | 84 | // (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)"); | |
| 86 | 86 | defer regex.deinit(); |
| 87 | 87 | |
| 88 | 88 | const result = try regex.find("abc"); |
| @@ -100,7 +100,7 @@ test "non-capturing: capture numbering with non-capturing groups" { | ||
| 100 | 100 | |
| 101 | 101 | test "non-capturing: with quantifiers" { |
| 102 | 102 | const allocator = std.testing.allocator; |
| 103 | var regex = try Regex.compile(allocator, "(?:\\d+)+"); | |
| 103 | var regex = try Regex.compile(allocator, std.testing.io, "(?:\\d+)+"); | |
| 104 | 104 | defer regex.deinit(); |
| 105 | 105 | |
| 106 | 106 | try std.testing.expect(try regex.isMatch("123")); |
| @@ -110,7 +110,7 @@ test "non-capturing: with quantifiers" { | ||
| 110 | 110 | test "non-capturing: complex pattern" { |
| 111 | 111 | const allocator = std.testing.allocator; |
| 112 | 112 | // 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+)*"); | |
| 114 | 114 | defer regex.deinit(); |
| 115 | 115 | |
| 116 | 116 | const result1 = try regex.find("http://example/path/to/page"); |
| @@ -138,7 +138,7 @@ test "non-capturing: complex pattern" { | ||
| 138 | 138 | |
| 139 | 139 | test "non-capturing: in replacement" { |
| 140 | 140 | 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+)"); | |
| 142 | 142 | defer regex.deinit(); |
| 143 | 143 | |
| 144 | 144 | const result = try regex.replace(allocator, "Hello Mr Smith", "$1"); |
| @@ -149,7 +149,7 @@ test "non-capturing: in replacement" { | ||
| 149 | 149 | |
| 150 | 150 | test "non-capturing: multiple non-capturing with one capturing" { |
| 151 | 151 | 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)"); | |
| 153 | 153 | defer regex.deinit(); |
| 154 | 154 | |
| 155 | 155 | const result = try regex.find("abcd"); |
| @@ -166,7 +166,7 @@ test "non-capturing: multiple non-capturing with one capturing" { | ||
| 166 | 166 | |
| 167 | 167 | test "non-capturing: with character classes" { |
| 168 | 168 | 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+)"); | |
| 170 | 170 | defer regex.deinit(); |
| 171 | 171 | |
| 172 | 172 | const result = try regex.find("user@example"); |
| @@ -183,7 +183,7 @@ test "non-capturing: with character classes" { | ||
| 183 | 183 | |
| 184 | 184 | test "non-capturing: nested capturing inside non-capturing" { |
| 185 | 185 | 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+))"); | |
| 187 | 187 | defer regex.deinit(); |
| 188 | 188 | |
| 189 | 189 | const result = try regex.find("12:30"); |
| @@ -201,7 +201,7 @@ test "non-capturing: nested capturing inside non-capturing" { | ||
| 201 | 201 | |
| 202 | 202 | test "non-capturing: all groups non-capturing" { |
| 203 | 203 | 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)"); | |
| 205 | 205 | defer regex.deinit(); |
| 206 | 206 | |
| 207 | 207 | const result = try regex.find("abc"); |
| @@ -218,7 +218,7 @@ test "non-capturing: all groups non-capturing" { | ||
| 218 | 218 | |
| 219 | 219 | test "non-capturing: with anchors" { |
| 220 | 220 | 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+)$"); | |
| 222 | 222 | defer regex.deinit(); |
| 223 | 223 | |
| 224 | 224 | const result = try regex.find("hello world"); |
| @@ -237,7 +237,7 @@ test "non-capturing: with anchors" { | ||
| 237 | 237 | |
| 238 | 238 | test "non-capturing: replaceAll with non-capturing groups" { |
| 239 | 239 | 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+)"); | |
| 241 | 241 | defer regex.deinit(); |
| 242 | 242 | |
| 243 | 243 | const result = try regex.replaceAll(allocator, "the cat and a dog", "$1"); |
| @@ -248,7 +248,7 @@ test "non-capturing: replaceAll with non-capturing groups" { | ||
| 248 | 248 | |
| 249 | 249 | test "non-capturing: empty non-capturing group" { |
| 250 | 250 | 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"); | |
| 252 | 252 | defer regex.deinit(); |
| 253 | 253 | |
| 254 | 254 | try std.testing.expect(try regex.isMatch("ab")); |
| @@ -258,7 +258,7 @@ test "non-capturing: verify capture count" { | ||
| 258 | 258 | const allocator = std.testing.allocator; |
| 259 | 259 | |
| 260 | 260 | // 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)"); | |
| 262 | 262 | defer regex.deinit(); |
| 263 | 263 | |
| 264 | 264 | // Should only have 2 captures, not 5 |
| @@ -10,13 +10,13 @@ const RegexError = .RegexError; | ||
| 10 | 10 | |
| 11 | 11 | test "parser: quantifier ? at start is rejected" { |
| 12 | 12 | const allocator = std.testing.allocator; |
| 13 | const result = Regex.compile(allocator, "?abc"); | |
| 13 | const result = Regex.compile(allocator, std.testing.io, "?abc"); | |
| 14 | 14 | try std.testing.expectError(RegexError.UnexpectedCharacter, result); |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | test "parser: nested empty groups" { |
| 18 | 18 | const allocator = std.testing.allocator; |
| 19 | var regex = try Regex.compile(allocator, "(())"); | |
| 19 | var regex = try Regex.compile(allocator, std.testing.io, "(())"); | |
| 20 | 20 | defer regex.deinit(); |
| 21 | 21 | // Should compile without error |
| 22 | 22 | try std.testing.expect(try regex.isMatch("")); |
| @@ -25,7 +25,7 @@ test "parser: nested empty groups" { | ||
| 25 | 25 | test "parser: deeply nested groups" { |
| 26 | 26 | const allocator = std.testing.allocator; |
| 27 | 27 | // 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))))))))))))))))))))"); | |
| 29 | 29 | defer regex.deinit(); |
| 30 | 30 | try std.testing.expect(try regex.isMatch("a")); |
| 31 | 31 | } |
| @@ -33,20 +33,20 @@ test "parser: deeply nested groups" { | ||
| 33 | 33 | test "parser: consecutive quantifiers rejected by analyzer" { |
| 34 | 34 | const allocator = std.testing.allocator; |
| 35 | 35 | // 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**"); | |
| 37 | 37 | try std.testing.expectError(RegexError.PatternTooComplex, result); |
| 38 | 38 | } |
| 39 | 39 | |
| 40 | 40 | test "parser: quantifier on quantifier rejected by analyzer" { |
| 41 | 41 | const allocator = std.testing.allocator; |
| 42 | 42 | // 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+*"); | |
| 44 | 44 | try std.testing.expectError(RegexError.PatternTooComplex, result); |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | test "parser: empty alternation branch is valid" { |
| 48 | 48 | const allocator = std.testing.allocator; |
| 49 | var regex = try Regex.compile(allocator, "a|"); | |
| 49 | var regex = try Regex.compile(allocator, std.testing.io, "a|"); | |
| 50 | 50 | defer regex.deinit(); |
| 51 | 51 | // "a|" means "a or empty string" - should match empty |
| 52 | 52 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -55,7 +55,7 @@ test "parser: empty alternation branch is valid" { | ||
| 55 | 55 | |
| 56 | 56 | test "parser: leading pipe alternation" { |
| 57 | 57 | const allocator = std.testing.allocator; |
| 58 | var regex = try Regex.compile(allocator, "|a"); | |
| 58 | var regex = try Regex.compile(allocator, std.testing.io, "|a"); | |
| 59 | 59 | defer regex.deinit(); |
| 60 | 60 | // "|a" means "empty string or a" |
| 61 | 61 | try std.testing.expect(try regex.isMatch("")); |
| @@ -66,7 +66,7 @@ test "parser: leading pipe alternation" { | ||
| 66 | 66 | |
| 67 | 67 | test "parser: {0} means zero occurrences" { |
| 68 | 68 | 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$"); | |
| 70 | 70 | defer regex.deinit(); |
| 71 | 71 | |
| 72 | 72 | try std.testing.expect(try regex.isMatch("b")); |
| @@ -75,7 +75,7 @@ test "parser: {0} means zero occurrences" { | ||
| 75 | 75 | |
| 76 | 76 | test "parser: {1} means exactly once" { |
| 77 | 77 | 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}$"); | |
| 79 | 79 | defer regex.deinit(); |
| 80 | 80 | |
| 81 | 81 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -86,7 +86,7 @@ test "parser: {1} means exactly once" { | ||
| 86 | 86 | test "parser: large but valid quantifier" { |
| 87 | 87 | const allocator = std.testing.allocator; |
| 88 | 88 | // {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}"); | |
| 90 | 90 | defer regex.deinit(); |
| 91 | 91 | try std.testing.expect(try regex.isMatch("")); |
| 92 | 92 | } |
| @@ -95,7 +95,7 @@ test "parser: large but valid quantifier" { | ||
| 95 | 95 | |
| 96 | 96 | test "parser: character class with escaped ]" { |
| 97 | 97 | const allocator = std.testing.allocator; |
| 98 | var regex = try Regex.compile(allocator, "[\\]]"); | |
| 98 | var regex = try Regex.compile(allocator, std.testing.io, "[\\]]"); | |
| 99 | 99 | defer regex.deinit(); |
| 100 | 100 | |
| 101 | 101 | try std.testing.expect(try regex.isMatch("]")); |
| @@ -105,7 +105,7 @@ test "parser: character class with escaped ]" { | ||
| 105 | 105 | test "parser: character class with hyphen between ranges" { |
| 106 | 106 | const allocator = std.testing.allocator; |
| 107 | 107 | // 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]"); | |
| 109 | 109 | defer regex.deinit(); |
| 110 | 110 | |
| 111 | 111 | try std.testing.expect(try regex.isMatch("-")); |
| @@ -116,7 +116,7 @@ test "parser: character class with hyphen between ranges" { | ||
| 116 | 116 | |
| 117 | 117 | test "parser: negated class with range" { |
| 118 | 118 | 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]+$"); | |
| 120 | 120 | defer regex.deinit(); |
| 121 | 121 | |
| 122 | 122 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -128,7 +128,7 @@ test "parser: negated class with range" { | ||
| 128 | 128 | |
| 129 | 129 | test "parser: tab and newline escapes" { |
| 130 | 130 | const allocator = std.testing.allocator; |
| 131 | var regex = try Regex.compile(allocator, "\\t"); | |
| 131 | var regex = try Regex.compile(allocator, std.testing.io, "\\t"); | |
| 132 | 132 | defer regex.deinit(); |
| 133 | 133 | |
| 134 | 134 | try std.testing.expect(try regex.isMatch("\t")); |
| @@ -137,7 +137,7 @@ test "parser: tab and newline escapes" { | ||
| 137 | 137 | |
| 138 | 138 | test "parser: carriage return escape" { |
| 139 | 139 | 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"); | |
| 141 | 141 | defer regex.deinit(); |
| 142 | 142 | |
| 143 | 143 | try std.testing.expect(try regex.isMatch("\r\n")); |
| @@ -145,7 +145,7 @@ test "parser: carriage return escape" { | ||
| 145 | 145 | |
| 146 | 146 | test "parser: escaped caret and dollar" { |
| 147 | 147 | const allocator = std.testing.allocator; |
| 148 | var regex = try Regex.compile(allocator, "\\^\\$"); | |
| 148 | var regex = try Regex.compile(allocator, std.testing.io, "\\^\\$"); | |
| 149 | 149 | defer regex.deinit(); |
| 150 | 150 | |
| 151 | 151 | try std.testing.expect(try regex.isMatch("^$")); |
| @@ -156,7 +156,7 @@ test "parser: escaped caret and dollar" { | ||
| 156 | 156 | |
| 157 | 157 | test "compiler: alternation with quantifiers" { |
| 158 | 158 | 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$"); | |
| 160 | 160 | defer regex.deinit(); |
| 161 | 161 | |
| 162 | 162 | try std.testing.expect(try regex.isMatch("abe")); |
| @@ -169,7 +169,7 @@ test "compiler: alternation with quantifiers" { | ||
| 169 | 169 | |
| 170 | 170 | test "compiler: character class in alternation" { |
| 171 | 171 | 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]+)$"); | |
| 173 | 173 | defer regex.deinit(); |
| 174 | 174 | |
| 175 | 175 | try std.testing.expect(try regex.isMatch("123")); |
| @@ -180,7 +180,7 @@ test "compiler: character class in alternation" { | ||
| 180 | 180 | |
| 181 | 181 | test "compiler: optional group" { |
| 182 | 182 | 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$"); | |
| 184 | 184 | defer regex.deinit(); |
| 185 | 185 | |
| 186 | 186 | try std.testing.expect(try regex.isMatch("color")); |
| @@ -190,7 +190,7 @@ test "compiler: optional group" { | ||
| 190 | 190 | |
| 191 | 191 | test "compiler: optional group with parentheses" { |
| 192 | 192 | 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$"); | |
| 194 | 194 | defer regex.deinit(); |
| 195 | 195 | |
| 196 | 196 | try std.testing.expect(try regex.isMatch("happy")); |
| @@ -200,7 +200,7 @@ test "compiler: optional group with parentheses" { | ||
| 200 | 200 | |
| 201 | 201 | test "compiler: multiple capture groups" { |
| 202 | 202 | 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})"); | |
| 204 | 204 | defer regex.deinit(); |
| 205 | 205 | |
| 206 | 206 | if (try regex.find("Today is 2024-01-15!")) |match| { |
| @@ -220,7 +220,7 @@ test "compiler: multiple capture groups" { | ||
| 220 | 220 | |
| 221 | 221 | test "compiler: anchored pattern only tries position 0" { |
| 222 | 222 | const allocator = std.testing.allocator; |
| 223 | var regex = try Regex.compile(allocator, "^abc"); | |
| 223 | var regex = try Regex.compile(allocator, std.testing.io, "^abc"); | |
| 224 | 224 | defer regex.deinit(); |
| 225 | 225 | |
| 226 | 226 | try std.testing.expect(try regex.isMatch("abcdef")); |
| @@ -230,7 +230,7 @@ test "compiler: anchored pattern only tries position 0" { | ||
| 230 | 230 | |
| 231 | 231 | test "compiler: end-anchored pattern" { |
| 232 | 232 | const allocator = std.testing.allocator; |
| 233 | var regex = try Regex.compile(allocator, "abc$"); | |
| 233 | var regex = try Regex.compile(allocator, std.testing.io, "abc$"); | |
| 234 | 234 | defer regex.deinit(); |
| 235 | 235 | |
| 236 | 236 | if (try regex.find("xyzabc")) |match| { |
| @@ -244,7 +244,7 @@ test "compiler: end-anchored pattern" { | ||
| 244 | 244 | |
| 245 | 245 | test "compiler: both anchors" { |
| 246 | 246 | const allocator = std.testing.allocator; |
| 247 | var regex = try Regex.compile(allocator, "^exact$"); | |
| 247 | var regex = try Regex.compile(allocator, std.testing.io, "^exact$"); | |
| 248 | 248 | defer regex.deinit(); |
| 249 | 249 | |
| 250 | 250 | try std.testing.expect(try regex.isMatch("exact")); |
| @@ -257,13 +257,13 @@ test "compiler: both anchors" { | ||
| 257 | 257 | |
| 258 | 258 | test "compiler: password strength check" { |
| 259 | 259 | 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"); | |
| 261 | 261 | 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]"); | |
| 263 | 263 | 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]"); | |
| 265 | 265 | 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,}"); | |
| 267 | 267 | defer min_length.deinit(); |
| 268 | 268 | |
| 269 | 269 | const password = "MyP4ssword"; |
| @@ -280,7 +280,7 @@ test "compiler: password strength check" { | ||
| 280 | 280 | |
| 281 | 281 | test "compiler: CSV field extraction" { |
| 282 | 282 | const allocator = std.testing.allocator; |
| 283 | var regex = try Regex.compile(allocator, "[^,]+"); | |
| 283 | var regex = try Regex.compile(allocator, std.testing.io, "[^,]+"); | |
| 284 | 284 | defer regex.deinit(); |
| 285 | 285 | |
| 286 | 286 | const matches = try regex.findAll(allocator, "foo,bar,baz"); |
| @@ -300,7 +300,7 @@ test "compiler: CSV field extraction" { | ||
| 300 | 300 | |
| 301 | 301 | test "compiler: whitespace normalization via replaceAll" { |
| 302 | 302 | const allocator = std.testing.allocator; |
| 303 | var regex = try Regex.compile(allocator, "\\s+"); | |
| 303 | var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); | |
| 304 | 304 | defer regex.deinit(); |
| 305 | 305 | |
| 306 | 306 | const result = try regex.replaceAll(allocator, " hello world ", " "); |
| @@ -312,7 +312,7 @@ test "compiler: whitespace normalization via replaceAll" { | ||
| 312 | 312 | |
| 313 | 313 | test "compiler: pattern with only ^ anchor" { |
| 314 | 314 | const allocator = std.testing.allocator; |
| 315 | var regex = try Regex.compile(allocator, "^"); | |
| 315 | var regex = try Regex.compile(allocator, std.testing.io, "^"); | |
| 316 | 316 | defer regex.deinit(); |
| 317 | 317 | |
| 318 | 318 | // ^ matches at position 0 of any string (matches zero-width) |
| @@ -322,7 +322,7 @@ test "compiler: pattern with only ^ anchor" { | ||
| 322 | 322 | |
| 323 | 323 | test "compiler: pattern with only $ anchor" { |
| 324 | 324 | const allocator = std.testing.allocator; |
| 325 | var regex = try Regex.compile(allocator, "$"); | |
| 325 | var regex = try Regex.compile(allocator, std.testing.io, "$"); | |
| 326 | 326 | defer regex.deinit(); |
| 327 | 327 | |
| 328 | 328 | // $ matches at end of any string (zero-width) |
| @@ -334,7 +334,7 @@ test "compiler: pattern with only $ anchor" { | ||
| 334 | 334 | |
| 335 | 335 | test "compiler: dot plus" { |
| 336 | 336 | const allocator = std.testing.allocator; |
| 337 | var regex = try Regex.compile(allocator, "^.+$"); | |
| 337 | var regex = try Regex.compile(allocator, std.testing.io, "^.+$"); | |
| 338 | 338 | defer regex.deinit(); |
| 339 | 339 | |
| 340 | 340 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -344,7 +344,7 @@ test "compiler: dot plus" { | ||
| 344 | 344 | |
| 345 | 345 | test "compiler: dot question" { |
| 346 | 346 | const allocator = std.testing.allocator; |
| 347 | var regex = try Regex.compile(allocator, "^.?$"); | |
| 347 | var regex = try Regex.compile(allocator, std.testing.io, "^.?$"); | |
| 348 | 348 | defer regex.deinit(); |
| 349 | 349 | |
| 350 | 350 | try std.testing.expect(try regex.isMatch("")); |
| @@ -354,7 +354,7 @@ test "compiler: dot question" { | ||
| 354 | 354 | |
| 355 | 355 | test "compiler: dot repeat" { |
| 356 | 356 | const allocator = std.testing.allocator; |
| 357 | var regex = try Regex.compile(allocator, "^.{3}$"); | |
| 357 | var regex = try Regex.compile(allocator, std.testing.io, "^.{3}$"); | |
| 358 | 358 | defer regex.deinit(); |
| 359 | 359 | |
| 360 | 360 | try std.testing.expect(try regex.isMatch("abc")); |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "posix: [:alpha:] matches letters" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "[[:alpha:]]+"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -17,7 +17,7 @@ test "posix: [:alpha:] matches letters" { | ||
| 17 | 17 | |
| 18 | 18 | test "posix: [:digit:] matches digits" { |
| 19 | 19 | const allocator = std.testing.allocator; |
| 20 | var regex = try Regex.compile(allocator, "[[:digit:]]+"); | |
| 20 | var regex = try Regex.compile(allocator, std.testing.io, "[[:digit:]]+"); | |
| 21 | 21 | defer regex.deinit(); |
| 22 | 22 | |
| 23 | 23 | try std.testing.expect(try regex.isMatch("123")); |
| @@ -29,7 +29,7 @@ test "posix: [:digit:] matches digits" { | ||
| 29 | 29 | |
| 30 | 30 | test "posix: [:alnum:] matches alphanumeric" { |
| 31 | 31 | const allocator = std.testing.allocator; |
| 32 | var regex = try Regex.compile(allocator, "[[:alnum:]]+"); | |
| 32 | var regex = try Regex.compile(allocator, std.testing.io, "[[:alnum:]]+"); | |
| 33 | 33 | defer regex.deinit(); |
| 34 | 34 | |
| 35 | 35 | try std.testing.expect(try regex.isMatch("hello123")); |
| @@ -41,7 +41,7 @@ test "posix: [:alnum:] matches alphanumeric" { | ||
| 41 | 41 | |
| 42 | 42 | test "posix: [:space:] matches whitespace" { |
| 43 | 43 | const allocator = std.testing.allocator; |
| 44 | var regex = try Regex.compile(allocator, "[[:space:]]+"); | |
| 44 | var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+"); | |
| 45 | 45 | defer regex.deinit(); |
| 46 | 46 | |
| 47 | 47 | try std.testing.expect(try regex.isMatch(" ")); |
| @@ -53,7 +53,7 @@ test "posix: [:space:] matches whitespace" { | ||
| 53 | 53 | |
| 54 | 54 | test "posix: [:upper:] matches uppercase" { |
| 55 | 55 | const allocator = std.testing.allocator; |
| 56 | var regex = try Regex.compile(allocator, "[[:upper:]]+"); | |
| 56 | var regex = try Regex.compile(allocator, std.testing.io, "[[:upper:]]+"); | |
| 57 | 57 | defer regex.deinit(); |
| 58 | 58 | |
| 59 | 59 | try std.testing.expect(try regex.isMatch("HELLO")); |
| @@ -65,7 +65,7 @@ test "posix: [:upper:] matches uppercase" { | ||
| 65 | 65 | |
| 66 | 66 | test "posix: [:lower:] matches lowercase" { |
| 67 | 67 | const allocator = std.testing.allocator; |
| 68 | var regex = try Regex.compile(allocator, "[[:lower:]]+"); | |
| 68 | var regex = try Regex.compile(allocator, std.testing.io, "[[:lower:]]+"); | |
| 69 | 69 | defer regex.deinit(); |
| 70 | 70 | |
| 71 | 71 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -77,7 +77,7 @@ test "posix: [:lower:] matches lowercase" { | ||
| 77 | 77 | |
| 78 | 78 | test "posix: [:xdigit:] matches hex digits" { |
| 79 | 79 | const allocator = std.testing.allocator; |
| 80 | var regex = try Regex.compile(allocator, "[[:xdigit:]]+"); | |
| 80 | var regex = try Regex.compile(allocator, std.testing.io, "[[:xdigit:]]+"); | |
| 81 | 81 | defer regex.deinit(); |
| 82 | 82 | |
| 83 | 83 | try std.testing.expect(try regex.isMatch("0123456789")); |
| @@ -90,7 +90,7 @@ test "posix: [:xdigit:] matches hex digits" { | ||
| 90 | 90 | |
| 91 | 91 | test "posix: [:punct:] matches punctuation" { |
| 92 | 92 | const allocator = std.testing.allocator; |
| 93 | var regex = try Regex.compile(allocator, "[[:punct:]]+"); | |
| 93 | var regex = try Regex.compile(allocator, std.testing.io, "[[:punct:]]+"); | |
| 94 | 94 | defer regex.deinit(); |
| 95 | 95 | |
| 96 | 96 | try std.testing.expect(try regex.isMatch("!@#$%")); |
| @@ -102,7 +102,7 @@ test "posix: [:punct:] matches punctuation" { | ||
| 102 | 102 | |
| 103 | 103 | test "posix: [:blank:] matches space and tab" { |
| 104 | 104 | const allocator = std.testing.allocator; |
| 105 | var regex = try Regex.compile(allocator, "[[:blank:]]+"); | |
| 105 | var regex = try Regex.compile(allocator, std.testing.io, "[[:blank:]]+"); | |
| 106 | 106 | defer regex.deinit(); |
| 107 | 107 | |
| 108 | 108 | try std.testing.expect(try regex.isMatch(" ")); |
| @@ -114,7 +114,7 @@ test "posix: [:blank:] matches space and tab" { | ||
| 114 | 114 | |
| 115 | 115 | test "posix: [:print:] matches printable characters" { |
| 116 | 116 | const allocator = std.testing.allocator; |
| 117 | var regex = try Regex.compile(allocator, "[[:print:]]+"); | |
| 117 | var regex = try Regex.compile(allocator, std.testing.io, "[[:print:]]+"); | |
| 118 | 118 | defer regex.deinit(); |
| 119 | 119 | |
| 120 | 120 | try std.testing.expect(try regex.isMatch("Hello World!")); |
| @@ -124,7 +124,7 @@ test "posix: [:print:] matches printable characters" { | ||
| 124 | 124 | |
| 125 | 125 | test "posix: [:graph:] matches visible characters" { |
| 126 | 126 | const allocator = std.testing.allocator; |
| 127 | var regex = try Regex.compile(allocator, "[[:graph:]]+"); | |
| 127 | var regex = try Regex.compile(allocator, std.testing.io, "[[:graph:]]+"); | |
| 128 | 128 | defer regex.deinit(); |
| 129 | 129 | |
| 130 | 130 | try std.testing.expect(try regex.isMatch("Hello")); |
| @@ -136,7 +136,7 @@ test "posix: [:graph:] matches visible characters" { | ||
| 136 | 136 | |
| 137 | 137 | test "posix: [:cntrl:] matches control characters" { |
| 138 | 138 | const allocator = std.testing.allocator; |
| 139 | var regex = try Regex.compile(allocator, "[[:cntrl:]]"); | |
| 139 | var regex = try Regex.compile(allocator, std.testing.io, "[[:cntrl:]]"); | |
| 140 | 140 | defer regex.deinit(); |
| 141 | 141 | |
| 142 | 142 | try std.testing.expect(try regex.isMatch("\x00")); |
| @@ -149,7 +149,7 @@ test "posix: [:cntrl:] matches control characters" { | ||
| 149 | 149 | |
| 150 | 150 | test "posix: combined with regular characters" { |
| 151 | 151 | 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_]+"); | |
| 153 | 153 | defer regex.deinit(); |
| 154 | 154 | |
| 155 | 155 | try std.testing.expect(try regex.isMatch("hello123")); |
| @@ -159,7 +159,7 @@ test "posix: combined with regular characters" { | ||
| 159 | 159 | |
| 160 | 160 | test "posix: multiple POSIX classes in one bracket" { |
| 161 | 161 | 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:]]+"); | |
| 163 | 163 | defer regex.deinit(); |
| 164 | 164 | |
| 165 | 165 | try std.testing.expect(try regex.isMatch("abc123")); |
| @@ -169,7 +169,7 @@ test "posix: multiple POSIX classes in one bracket" { | ||
| 169 | 169 | |
| 170 | 170 | test "posix: extract words with [:alpha:]" { |
| 171 | 171 | const allocator = std.testing.allocator; |
| 172 | var regex = try Regex.compile(allocator, "[[:alpha:]]+"); | |
| 172 | var regex = try Regex.compile(allocator, std.testing.io, "[[:alpha:]]+"); | |
| 173 | 173 | defer regex.deinit(); |
| 174 | 174 | |
| 175 | 175 | const text = "Hello World 123"; |
| @@ -189,7 +189,7 @@ test "posix: extract words with [:alpha:]" { | ||
| 189 | 189 | |
| 190 | 190 | test "posix: extract hex numbers with [:xdigit:]" { |
| 191 | 191 | 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:]]+"); | |
| 193 | 193 | defer regex.deinit(); |
| 194 | 194 | |
| 195 | 195 | const text = "Colors: 0xFF5733 and 0xABCDEF"; |
| @@ -209,7 +209,7 @@ test "posix: extract hex numbers with [:xdigit:]" { | ||
| 209 | 209 | |
| 210 | 210 | test "posix: validate identifier with [:alnum:]" { |
| 211 | 211 | 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:]_]*$"); | |
| 213 | 213 | defer regex.deinit(); |
| 214 | 214 | |
| 215 | 215 | try std.testing.expect(try regex.isMatch("valid_identifier")); |
| @@ -221,7 +221,7 @@ test "posix: validate identifier with [:alnum:]" { | ||
| 221 | 221 | |
| 222 | 222 | test "posix: split on whitespace with [:space:]" { |
| 223 | 223 | const allocator = std.testing.allocator; |
| 224 | var regex = try Regex.compile(allocator, "[[:space:]]+"); | |
| 224 | var regex = try Regex.compile(allocator, std.testing.io, "[[:space:]]+"); | |
| 225 | 225 | defer regex.deinit(); |
| 226 | 226 | |
| 227 | 227 | const text = "one\ttwo\nthree four"; |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | |
| 4 | 4 | test "quantifier: exactly n times {3}" { |
| 5 | 5 | 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}"); | |
| 7 | 7 | defer regex.deinit(); |
| 8 | 8 | |
| 9 | 9 | try std.testing.expect(!try regex.isMatch("aa")); |
| @@ -13,7 +13,7 @@ test "quantifier: exactly n times {3}" { | ||
| 13 | 13 | |
| 14 | 14 | test "quantifier: range {2,4}" { |
| 15 | 15 | 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}"); | |
| 17 | 17 | defer regex.deinit(); |
| 18 | 18 | |
| 19 | 19 | try std.testing.expect(!try regex.isMatch("a")); |
| @@ -25,7 +25,7 @@ test "quantifier: range {2,4}" { | ||
| 25 | 25 | |
| 26 | 26 | test "quantifier: at least n {2,}" { |
| 27 | 27 | 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,}"); | |
| 29 | 29 | defer regex.deinit(); |
| 30 | 30 | |
| 31 | 31 | try std.testing.expect(!try regex.isMatch("a")); |
| @@ -36,7 +36,7 @@ test "quantifier: at least n {2,}" { | ||
| 36 | 36 | |
| 37 | 37 | test "quantifier: {0,1} equivalent to ?" { |
| 38 | 38 | 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}"); | |
| 40 | 40 | defer regex.deinit(); |
| 41 | 41 | |
| 42 | 42 | try std.testing.expect(try regex.isMatch("")); |
| @@ -46,7 +46,7 @@ test "quantifier: {0,1} equivalent to ?" { | ||
| 46 | 46 | |
| 47 | 47 | test "quantifier: {1,} equivalent to +" { |
| 48 | 48 | 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,}"); | |
| 50 | 50 | defer regex.deinit(); |
| 51 | 51 | |
| 52 | 52 | try std.testing.expect(!try regex.isMatch("")); |
| @@ -56,7 +56,7 @@ test "quantifier: {1,} equivalent to +" { | ||
| 56 | 56 | |
| 57 | 57 | test "quantifier: {0,} equivalent to *" { |
| 58 | 58 | 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,}"); | |
| 60 | 60 | defer regex.deinit(); |
| 61 | 61 | |
| 62 | 62 | try std.testing.expect(try regex.isMatch("")); |
| @@ -66,7 +66,7 @@ test "quantifier: {0,} equivalent to *" { | ||
| 66 | 66 | |
| 67 | 67 | test "quantifier: complex pattern with {m,n}" { |
| 68 | 68 | 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}"); | |
| 70 | 70 | defer regex.deinit(); |
| 71 | 71 | |
| 72 | 72 | if (try regex.find("Call 555-1234 now")) |match| { |
| @@ -80,7 +80,7 @@ test "quantifier: complex pattern with {m,n}" { | ||
| 80 | 80 | |
| 81 | 81 | test "quantifier: multiple bounded quantifiers" { |
| 82 | 82 | 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}"); | |
| 84 | 84 | defer regex.deinit(); |
| 85 | 85 | |
| 86 | 86 | try std.testing.expect(!try regex.isMatch("ab")); |
| @@ -91,7 +91,7 @@ test "quantifier: multiple bounded quantifiers" { | ||
| 91 | 91 | |
| 92 | 92 | test "quantifier: {m,n} with character class" { |
| 93 | 93 | 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}"); | |
| 95 | 95 | defer regex.deinit(); |
| 96 | 96 | |
| 97 | 97 | try std.testing.expect(!try regex.isMatch("ab")); |
| @@ -10,7 +10,7 @@ const RegexError = .RegexError; | ||
| 10 | 10 | |
| 11 | 11 | test "regression: $0 replacement expands to full match" { |
| 12 | 12 | const allocator = std.testing.allocator; |
| 13 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 13 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 14 | 14 | defer regex.deinit(); |
| 15 | 15 | |
| 16 | 16 | const result = try regex.replace(allocator, "abc 123 def", "[$0]"); |
| @@ -20,7 +20,7 @@ test "regression: $0 replacement expands to full match" { | ||
| 20 | 20 | |
| 21 | 21 | test "regression: $0 replacement with captures" { |
| 22 | 22 | 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+)"); | |
| 24 | 24 | defer regex.deinit(); |
| 25 | 25 | |
| 26 | 26 | 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" { | ||
| 30 | 30 | |
| 31 | 31 | test "regression: $0 in replaceAll" { |
| 32 | 32 | const allocator = std.testing.allocator; |
| 33 | var regex = try Regex.compile(allocator, "\\w+"); | |
| 33 | var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); | |
| 34 | 34 | defer regex.deinit(); |
| 35 | 35 | |
| 36 | 36 | const result = try regex.replaceAll(allocator, "a b c", "[$0]"); |
| @@ -42,7 +42,7 @@ test "regression: $0 in replaceAll" { | ||
| 42 | 42 | |
| 43 | 43 | test "regression: case-insensitive backreference" { |
| 44 | 44 | 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 }); | |
| 46 | 46 | defer regex.deinit(); |
| 47 | 47 | |
| 48 | 48 | try std.testing.expect(try regex.isMatch("hello HELLO")); |
| @@ -52,7 +52,7 @@ test "regression: case-insensitive backreference" { | ||
| 52 | 52 | |
| 53 | 53 | test "regression: case-sensitive backreference rejects case mismatch" { |
| 54 | 54 | 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"); | |
| 56 | 56 | defer regex.deinit(); |
| 57 | 57 | |
| 58 | 58 | try std.testing.expect(!try regex.isMatch("hello HELLO")); |
| @@ -63,19 +63,19 @@ test "regression: case-sensitive backreference rejects case mismatch" { | ||
| 63 | 63 | |
| 64 | 64 | test "regression: {10,5} is rejected as invalid" { |
| 65 | 65 | 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}"); | |
| 67 | 67 | try std.testing.expectError(RegexError.InvalidQuantifier, result); |
| 68 | 68 | } |
| 69 | 69 | |
| 70 | 70 | test "regression: {5,3} is rejected" { |
| 71 | 71 | 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}"); | |
| 73 | 73 | try std.testing.expectError(RegexError.InvalidQuantifier, result); |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | test "regression: {3,3} is accepted (min == max)" { |
| 77 | 77 | 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}"); | |
| 79 | 79 | defer regex.deinit(); |
| 80 | 80 | |
| 81 | 81 | try std.testing.expect(try regex.isMatch("aaa")); |
| @@ -86,7 +86,7 @@ test "regression: {3,3} is accepted (min == max)" { | ||
| 86 | 86 | |
| 87 | 87 | test "regression: {0,3} matches 0 to 3 occurrences" { |
| 88 | 88 | 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}$"); | |
| 90 | 90 | defer regex.deinit(); |
| 91 | 91 | |
| 92 | 92 | try std.testing.expect(try regex.isMatch("")); |
| @@ -98,9 +98,9 @@ test "regression: {0,3} matches 0 to 3 occurrences" { | ||
| 98 | 98 | |
| 99 | 99 | test "regression: {0,1} is equivalent to ?" { |
| 100 | 100 | 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$"); | |
| 102 | 102 | defer regex1.deinit(); |
| 103 | var regex2 = try Regex.compile(allocator, "^ab?c$"); | |
| 103 | var regex2 = try Regex.compile(allocator, std.testing.io, "^ab?c$"); | |
| 104 | 104 | defer regex2.deinit(); |
| 105 | 105 | |
| 106 | 106 | const inputs = [_][]const u8{ "ac", "abc", "abbc", "" }; |
| @@ -111,7 +111,7 @@ test "regression: {0,1} is equivalent to ?" { | ||
| 111 | 111 | |
| 112 | 112 | test "regression: {0,0} matches empty only" { |
| 113 | 113 | 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$"); | |
| 115 | 115 | defer regex.deinit(); |
| 116 | 116 | |
| 117 | 117 | try std.testing.expect(try regex.isMatch("b")); |
| @@ -123,7 +123,7 @@ test "regression: {0,0} matches empty only" { | ||
| 123 | 123 | test "regression: prefix optimization finds match after false prefix start" { |
| 124 | 124 | const allocator = std.testing.allocator; |
| 125 | 125 | // 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+"); | |
| 127 | 127 | defer regex.deinit(); |
| 128 | 128 | |
| 129 | 129 | // "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" { | ||
| 141 | 141 | test "regression: backtrack step counter resets between positions" { |
| 142 | 142 | const allocator = std.testing.allocator; |
| 143 | 143 | // 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"); | |
| 145 | 145 | defer regex.deinit(); |
| 146 | 146 | |
| 147 | 147 | // Verify basic backreference works |
| @@ -161,7 +161,7 @@ test "regression: backtrack step counter resets between positions" { | ||
| 161 | 161 | |
| 162 | 162 | test "regression: deinit on regex that was never used" { |
| 163 | 163 | const allocator = std.testing.allocator; |
| 164 | var regex = try Regex.compile(allocator, "test"); | |
| 164 | var regex = try Regex.compile(allocator, std.testing.io, "test"); | |
| 165 | 165 | regex.deinit(); |
| 166 | 166 | // Should not crash - just testing that deinit works without any find/match calls |
| 167 | 167 | } |
| @@ -169,12 +169,12 @@ test "regression: deinit on regex that was never used" { | ||
| 169 | 169 | test "regression: double pattern compile and deinit" { |
| 170 | 170 | const allocator = std.testing.allocator; |
| 171 | 171 | { |
| 172 | var regex = try Regex.compile(allocator, "abc"); | |
| 172 | var regex = try Regex.compile(allocator, std.testing.io, "abc"); | |
| 173 | 173 | defer regex.deinit(); |
| 174 | 174 | try std.testing.expect(try regex.isMatch("abc")); |
| 175 | 175 | } |
| 176 | 176 | { |
| 177 | var regex = try Regex.compile(allocator, "def"); | |
| 177 | var regex = try Regex.compile(allocator, std.testing.io, "def"); | |
| 178 | 178 | defer regex.deinit(); |
| 179 | 179 | try std.testing.expect(try regex.isMatch("def")); |
| 180 | 180 | } |
| @@ -11,7 +11,7 @@ const RegexError = .RegexError; | ||
| 11 | 11 | test "stress: empty alternation branch" { |
| 12 | 12 | const allocator = std.testing.allocator; |
| 13 | 13 | // 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)$"); | |
| 15 | 15 | defer regex.deinit(); |
| 16 | 16 | |
| 17 | 17 | try std.testing.expect(try regex.isMatch("")); |
| @@ -21,7 +21,7 @@ test "stress: empty alternation branch" { | ||
| 21 | 21 | |
| 22 | 22 | test "stress: pattern that only matches empty string" { |
| 23 | 23 | const allocator = std.testing.allocator; |
| 24 | var regex = try Regex.compile(allocator, "^$"); | |
| 24 | var regex = try Regex.compile(allocator, std.testing.io, "^$"); | |
| 25 | 25 | defer regex.deinit(); |
| 26 | 26 | |
| 27 | 27 | try std.testing.expect(try regex.isMatch("")); |
| @@ -31,7 +31,7 @@ test "stress: pattern that only matches empty string" { | ||
| 31 | 31 | |
| 32 | 32 | test "stress: dot star matches everything" { |
| 33 | 33 | const allocator = std.testing.allocator; |
| 34 | var regex = try Regex.compile(allocator, ".*"); | |
| 34 | var regex = try Regex.compile(allocator, std.testing.io, ".*"); | |
| 35 | 35 | defer regex.deinit(); |
| 36 | 36 | |
| 37 | 37 | try std.testing.expect(try regex.isMatch("")); |
| @@ -41,7 +41,7 @@ test "stress: dot star matches everything" { | ||
| 41 | 41 | |
| 42 | 42 | test "stress: single dot" { |
| 43 | 43 | const allocator = std.testing.allocator; |
| 44 | var regex = try Regex.compile(allocator, "^.$"); | |
| 44 | var regex = try Regex.compile(allocator, std.testing.io, "^.$"); | |
| 45 | 45 | defer regex.deinit(); |
| 46 | 46 | |
| 47 | 47 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -55,7 +55,7 @@ test "stress: single dot" { | ||
| 55 | 55 | |
| 56 | 56 | test "stress: {1,1} is same as no quantifier" { |
| 57 | 57 | 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}$"); | |
| 59 | 59 | defer regex.deinit(); |
| 60 | 60 | |
| 61 | 61 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -65,7 +65,7 @@ test "stress: {1,1} is same as no quantifier" { | ||
| 65 | 65 | |
| 66 | 66 | test "stress: {2,} matches 2 or more" { |
| 67 | 67 | 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,}$"); | |
| 69 | 69 | defer regex.deinit(); |
| 70 | 70 | |
| 71 | 71 | try std.testing.expect(!try regex.isMatch("")); |
| @@ -77,7 +77,7 @@ test "stress: {2,} matches 2 or more" { | ||
| 77 | 77 | |
| 78 | 78 | test "stress: exact repeat {5}" { |
| 79 | 79 | 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}$"); | |
| 81 | 81 | defer regex.deinit(); |
| 82 | 82 | |
| 83 | 83 | try std.testing.expect(!try regex.isMatch("xxxx")); |
| @@ -87,7 +87,7 @@ test "stress: exact repeat {5}" { | ||
| 87 | 87 | |
| 88 | 88 | test "stress: mixed quantifiers in sequence" { |
| 89 | 89 | 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}$"); | |
| 91 | 91 | defer regex.deinit(); |
| 92 | 92 | |
| 93 | 93 | try std.testing.expect(try regex.isMatch("add")); |
| @@ -101,7 +101,7 @@ test "stress: mixed quantifiers in sequence" { | ||
| 101 | 101 | |
| 102 | 102 | test "stress: character class with hyphen at start" { |
| 103 | 103 | const allocator = std.testing.allocator; |
| 104 | var regex = try Regex.compile(allocator, "^[-abc]+$"); | |
| 104 | var regex = try Regex.compile(allocator, std.testing.io, "^[-abc]+$"); | |
| 105 | 105 | defer regex.deinit(); |
| 106 | 106 | |
| 107 | 107 | try std.testing.expect(try regex.isMatch("-")); |
| @@ -112,7 +112,7 @@ test "stress: character class with hyphen at start" { | ||
| 112 | 112 | |
| 113 | 113 | test "stress: character class with hyphen at end" { |
| 114 | 114 | const allocator = std.testing.allocator; |
| 115 | var regex = try Regex.compile(allocator, "^[abc-]+$"); | |
| 115 | var regex = try Regex.compile(allocator, std.testing.io, "^[abc-]+$"); | |
| 116 | 116 | defer regex.deinit(); |
| 117 | 117 | |
| 118 | 118 | try std.testing.expect(try regex.isMatch("-")); |
| @@ -121,7 +121,7 @@ test "stress: character class with hyphen at end" { | ||
| 121 | 121 | |
| 122 | 122 | test "stress: negated character class" { |
| 123 | 123 | const allocator = std.testing.allocator; |
| 124 | var regex = try Regex.compile(allocator, "^[^abc]+$"); | |
| 124 | var regex = try Regex.compile(allocator, std.testing.io, "^[^abc]+$"); | |
| 125 | 125 | defer regex.deinit(); |
| 126 | 126 | |
| 127 | 127 | try std.testing.expect(try regex.isMatch("xyz")); |
| @@ -132,7 +132,7 @@ test "stress: negated character class" { | ||
| 132 | 132 | |
| 133 | 133 | test "stress: character class with single char range" { |
| 134 | 134 | 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]+$"); | |
| 136 | 136 | defer regex.deinit(); |
| 137 | 137 | |
| 138 | 138 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -142,7 +142,7 @@ test "stress: character class with single char range" { | ||
| 142 | 142 | |
| 143 | 143 | test "stress: overlapping ranges in character class" { |
| 144 | 144 | 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]+$"); | |
| 146 | 146 | defer regex.deinit(); |
| 147 | 147 | |
| 148 | 148 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -154,20 +154,20 @@ test "stress: overlapping ranges in character class" { | ||
| 154 | 154 | test "stress: shorthand classes \\d \\w \\s" { |
| 155 | 155 | const allocator = std.testing.allocator; |
| 156 | 156 | { |
| 157 | var regex = try Regex.compile(allocator, "^\\d+$"); | |
| 157 | var regex = try Regex.compile(allocator, std.testing.io, "^\\d+$"); | |
| 158 | 158 | defer regex.deinit(); |
| 159 | 159 | try std.testing.expect(try regex.isMatch("12345")); |
| 160 | 160 | try std.testing.expect(!try regex.isMatch("abc")); |
| 161 | 161 | try std.testing.expect(!try regex.isMatch("")); |
| 162 | 162 | } |
| 163 | 163 | { |
| 164 | var regex = try Regex.compile(allocator, "^\\w+$"); | |
| 164 | var regex = try Regex.compile(allocator, std.testing.io, "^\\w+$"); | |
| 165 | 165 | defer regex.deinit(); |
| 166 | 166 | try std.testing.expect(try regex.isMatch("hello_123")); |
| 167 | 167 | try std.testing.expect(!try regex.isMatch("hello world")); |
| 168 | 168 | } |
| 169 | 169 | { |
| 170 | var regex = try Regex.compile(allocator, "^\\s+$"); | |
| 170 | var regex = try Regex.compile(allocator, std.testing.io, "^\\s+$"); | |
| 171 | 171 | defer regex.deinit(); |
| 172 | 172 | try std.testing.expect(try regex.isMatch(" ")); |
| 173 | 173 | try std.testing.expect(try regex.isMatch("\t\n")); |
| @@ -178,19 +178,19 @@ test "stress: shorthand classes \\d \\w \\s" { | ||
| 178 | 178 | test "stress: negated shorthand classes \\D \\W \\S" { |
| 179 | 179 | const allocator = std.testing.allocator; |
| 180 | 180 | { |
| 181 | var regex = try Regex.compile(allocator, "^\\D+$"); | |
| 181 | var regex = try Regex.compile(allocator, std.testing.io, "^\\D+$"); | |
| 182 | 182 | defer regex.deinit(); |
| 183 | 183 | try std.testing.expect(try regex.isMatch("abc")); |
| 184 | 184 | try std.testing.expect(!try regex.isMatch("123")); |
| 185 | 185 | } |
| 186 | 186 | { |
| 187 | var regex = try Regex.compile(allocator, "^\\W+$"); | |
| 187 | var regex = try Regex.compile(allocator, std.testing.io, "^\\W+$"); | |
| 188 | 188 | defer regex.deinit(); |
| 189 | 189 | try std.testing.expect(try regex.isMatch(" !@#")); |
| 190 | 190 | try std.testing.expect(!try regex.isMatch("abc")); |
| 191 | 191 | } |
| 192 | 192 | { |
| 193 | var regex = try Regex.compile(allocator, "^\\S+$"); | |
| 193 | var regex = try Regex.compile(allocator, std.testing.io, "^\\S+$"); | |
| 194 | 194 | defer regex.deinit(); |
| 195 | 195 | try std.testing.expect(try regex.isMatch("abc")); |
| 196 | 196 | try std.testing.expect(!try regex.isMatch("a b")); |
| @@ -201,7 +201,7 @@ test "stress: negated shorthand classes \\D \\W \\S" { | ||
| 201 | 201 | |
| 202 | 202 | test "stress: ^ only matches at start" { |
| 203 | 203 | const allocator = std.testing.allocator; |
| 204 | var regex = try Regex.compile(allocator, "^abc"); | |
| 204 | var regex = try Regex.compile(allocator, std.testing.io, "^abc"); | |
| 205 | 205 | defer regex.deinit(); |
| 206 | 206 | |
| 207 | 207 | try std.testing.expect(try regex.isMatch("abcdef")); |
| @@ -210,7 +210,7 @@ test "stress: ^ only matches at start" { | ||
| 210 | 210 | |
| 211 | 211 | test "stress: $ only matches at end" { |
| 212 | 212 | const allocator = std.testing.allocator; |
| 213 | var regex = try Regex.compile(allocator, "abc$"); | |
| 213 | var regex = try Regex.compile(allocator, std.testing.io, "abc$"); | |
| 214 | 214 | defer regex.deinit(); |
| 215 | 215 | |
| 216 | 216 | try std.testing.expect(try regex.isMatch("xyzabc")); |
| @@ -219,7 +219,7 @@ test "stress: $ only matches at end" { | ||
| 219 | 219 | |
| 220 | 220 | test "stress: ^$ on empty string" { |
| 221 | 221 | const allocator = std.testing.allocator; |
| 222 | var regex = try Regex.compile(allocator, "^$"); | |
| 222 | var regex = try Regex.compile(allocator, std.testing.io, "^$"); | |
| 223 | 223 | defer regex.deinit(); |
| 224 | 224 | |
| 225 | 225 | try std.testing.expect(try regex.isMatch("")); |
| @@ -228,7 +228,7 @@ test "stress: ^$ on empty string" { | ||
| 228 | 228 | |
| 229 | 229 | test "stress: word boundary \\b" { |
| 230 | 230 | 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"); | |
| 232 | 232 | defer regex.deinit(); |
| 233 | 233 | |
| 234 | 234 | try std.testing.expect(try regex.isMatch("a word here")); |
| @@ -239,7 +239,7 @@ test "stress: word boundary \\b" { | ||
| 239 | 239 | |
| 240 | 240 | test "stress: non-word boundary \\B" { |
| 241 | 241 | 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"); | |
| 243 | 243 | defer regex.deinit(); |
| 244 | 244 | |
| 245 | 245 | try std.testing.expect(try regex.isMatch("swordwords")); |
| @@ -251,7 +251,7 @@ test "stress: non-word boundary \\B" { | ||
| 251 | 251 | |
| 252 | 252 | test "stress: alternation with different lengths" { |
| 253 | 253 | 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)$"); | |
| 255 | 255 | defer regex.deinit(); |
| 256 | 256 | |
| 257 | 257 | try std.testing.expect(try regex.isMatch("a")); |
| @@ -263,7 +263,7 @@ test "stress: alternation with different lengths" { | ||
| 263 | 263 | |
| 264 | 264 | test "stress: nested alternation" { |
| 265 | 265 | 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)$"); | |
| 267 | 267 | defer regex.deinit(); |
| 268 | 268 | |
| 269 | 269 | try std.testing.expect(try regex.isMatch("abd")); |
| @@ -275,7 +275,7 @@ test "stress: nested alternation" { | ||
| 275 | 275 | |
| 276 | 276 | test "stress: NFA finds longest match for alternation" { |
| 277 | 277 | 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"); | |
| 279 | 279 | defer regex.deinit(); |
| 280 | 280 | |
| 281 | 281 | if (try regex.find("catfish")) |match| { |
| @@ -292,7 +292,7 @@ test "stress: NFA finds longest match for alternation" { | ||
| 292 | 292 | |
| 293 | 293 | test "stress: capture group with alternation - only one branch matches" { |
| 294 | 294 | 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)"); | |
| 296 | 296 | defer regex.deinit(); |
| 297 | 297 | |
| 298 | 298 | if (try regex.find("b")) |match| { |
| @@ -306,7 +306,7 @@ test "stress: capture group with alternation - only one branch matches" { | ||
| 306 | 306 | |
| 307 | 307 | test "stress: nested capture groups" { |
| 308 | 308 | 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))"); | |
| 310 | 310 | defer regex.deinit(); |
| 311 | 311 | |
| 312 | 312 | if (try regex.find("ab")) |match| { |
| @@ -325,7 +325,7 @@ test "stress: nested capture groups" { | ||
| 325 | 325 | |
| 326 | 326 | test "stress: capture with quantifier" { |
| 327 | 327 | const allocator = std.testing.allocator; |
| 328 | var regex = try Regex.compile(allocator, "(a)+"); | |
| 328 | var regex = try Regex.compile(allocator, std.testing.io, "(a)+"); | |
| 329 | 329 | defer regex.deinit(); |
| 330 | 330 | |
| 331 | 331 | if (try regex.find("aaa")) |match| { |
| @@ -341,7 +341,7 @@ test "stress: capture with quantifier" { | ||
| 341 | 341 | |
| 342 | 342 | test "stress: findAll with no matches" { |
| 343 | 343 | const allocator = std.testing.allocator; |
| 344 | var regex = try Regex.compile(allocator, "xyz"); | |
| 344 | var regex = try Regex.compile(allocator, std.testing.io, "xyz"); | |
| 345 | 345 | defer regex.deinit(); |
| 346 | 346 | |
| 347 | 347 | const matches = try regex.findAll(allocator, "abc def ghi"); |
| @@ -358,7 +358,7 @@ test "stress: findAll with no matches" { | ||
| 358 | 358 | |
| 359 | 359 | test "stress: findAll adjacent matches" { |
| 360 | 360 | const allocator = std.testing.allocator; |
| 361 | var regex = try Regex.compile(allocator, "\\d"); | |
| 361 | var regex = try Regex.compile(allocator, std.testing.io, "\\d"); | |
| 362 | 362 | defer regex.deinit(); |
| 363 | 363 | |
| 364 | 364 | const matches = try regex.findAll(allocator, "123"); |
| @@ -378,7 +378,7 @@ test "stress: findAll adjacent matches" { | ||
| 378 | 378 | |
| 379 | 379 | test "stress: findAll non-overlapping" { |
| 380 | 380 | const allocator = std.testing.allocator; |
| 381 | var regex = try Regex.compile(allocator, "aa"); | |
| 381 | var regex = try Regex.compile(allocator, std.testing.io, "aa"); | |
| 382 | 382 | defer regex.deinit(); |
| 383 | 383 | |
| 384 | 384 | const matches = try regex.findAll(allocator, "aaaa"); |
| @@ -398,7 +398,7 @@ test "stress: findAll non-overlapping" { | ||
| 398 | 398 | |
| 399 | 399 | test "stress: replace with no match returns input" { |
| 400 | 400 | const allocator = std.testing.allocator; |
| 401 | var regex = try Regex.compile(allocator, "xyz"); | |
| 401 | var regex = try Regex.compile(allocator, std.testing.io, "xyz"); | |
| 402 | 402 | defer regex.deinit(); |
| 403 | 403 | |
| 404 | 404 | const result = try regex.replace(allocator, "hello world", "!"); |
| @@ -408,7 +408,7 @@ test "stress: replace with no match returns input" { | ||
| 408 | 408 | |
| 409 | 409 | test "stress: replace with empty replacement" { |
| 410 | 410 | const allocator = std.testing.allocator; |
| 411 | var regex = try Regex.compile(allocator, "\\s+"); | |
| 411 | var regex = try Regex.compile(allocator, std.testing.io, "\\s+"); | |
| 412 | 412 | defer regex.deinit(); |
| 413 | 413 | |
| 414 | 414 | const result = try regex.replace(allocator, "hello world", ""); |
| @@ -418,7 +418,7 @@ test "stress: replace with empty replacement" { | ||
| 418 | 418 | |
| 419 | 419 | test "stress: replaceAll removes all matches" { |
| 420 | 420 | const allocator = std.testing.allocator; |
| 421 | var regex = try Regex.compile(allocator, "\\d"); | |
| 421 | var regex = try Regex.compile(allocator, std.testing.io, "\\d"); | |
| 422 | 422 | defer regex.deinit(); |
| 423 | 423 | |
| 424 | 424 | const result = try regex.replaceAll(allocator, "a1b2c3", ""); |
| @@ -428,7 +428,7 @@ test "stress: replaceAll removes all matches" { | ||
| 428 | 428 | |
| 429 | 429 | test "stress: replace with $$ escaped dollar" { |
| 430 | 430 | const allocator = std.testing.allocator; |
| 431 | var regex = try Regex.compile(allocator, "price"); | |
| 431 | var regex = try Regex.compile(allocator, std.testing.io, "price"); | |
| 432 | 432 | defer regex.deinit(); |
| 433 | 433 | |
| 434 | 434 | const result = try regex.replace(allocator, "the price is", "$$5"); |
| @@ -440,7 +440,7 @@ test "stress: replace with $$ escaped dollar" { | ||
| 440 | 440 | |
| 441 | 441 | test "stress: split with no match returns whole string" { |
| 442 | 442 | const allocator = std.testing.allocator; |
| 443 | var regex = try Regex.compile(allocator, ","); | |
| 443 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 444 | 444 | defer regex.deinit(); |
| 445 | 445 | |
| 446 | 446 | const parts = try regex.split(allocator, "hello"); |
| @@ -452,7 +452,7 @@ test "stress: split with no match returns whole string" { | ||
| 452 | 452 | |
| 453 | 453 | test "stress: split on every character" { |
| 454 | 454 | const allocator = std.testing.allocator; |
| 455 | var regex = try Regex.compile(allocator, ","); | |
| 455 | var regex = try Regex.compile(allocator, std.testing.io, ","); | |
| 456 | 456 | defer regex.deinit(); |
| 457 | 457 | |
| 458 | 458 | const parts = try regex.split(allocator, "a,b,c"); |
| @@ -466,7 +466,7 @@ test "stress: split on every character" { | ||
| 466 | 466 | |
| 467 | 467 | test "stress: split with regex separator" { |
| 468 | 468 | 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*"); | |
| 470 | 470 | defer regex.deinit(); |
| 471 | 471 | |
| 472 | 472 | const parts = try regex.split(allocator, "a , b , c"); |
| @@ -482,7 +482,7 @@ test "stress: split with regex separator" { | ||
| 482 | 482 | |
| 483 | 483 | test "stress: escaped special characters" { |
| 484 | 484 | const allocator = std.testing.allocator; |
| 485 | var regex = try Regex.compile(allocator, "\\(\\)\\[\\]\\{\\}"); | |
| 485 | var regex = try Regex.compile(allocator, std.testing.io, "\\(\\)\\[\\]\\{\\}"); | |
| 486 | 486 | defer regex.deinit(); |
| 487 | 487 | |
| 488 | 488 | try std.testing.expect(try regex.isMatch("()[]{}")); |
| @@ -491,7 +491,7 @@ test "stress: escaped special characters" { | ||
| 491 | 491 | |
| 492 | 492 | test "stress: escaped dot matches literal dot" { |
| 493 | 493 | 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"); | |
| 495 | 495 | defer regex.deinit(); |
| 496 | 496 | |
| 497 | 497 | try std.testing.expect(try regex.isMatch("a.b")); |
| @@ -500,7 +500,7 @@ test "stress: escaped dot matches literal dot" { | ||
| 500 | 500 | |
| 501 | 501 | test "stress: escaped backslash" { |
| 502 | 502 | const allocator = std.testing.allocator; |
| 503 | var regex = try Regex.compile(allocator, "\\\\"); | |
| 503 | var regex = try Regex.compile(allocator, std.testing.io, "\\\\"); | |
| 504 | 504 | defer regex.deinit(); |
| 505 | 505 | |
| 506 | 506 | try std.testing.expect(try regex.isMatch("\\")); |
| @@ -511,7 +511,7 @@ test "stress: escaped backslash" { | ||
| 511 | 511 | |
| 512 | 512 | test "stress: match at end of long input" { |
| 513 | 513 | const allocator = std.testing.allocator; |
| 514 | var regex = try Regex.compile(allocator, "needle"); | |
| 514 | var regex = try Regex.compile(allocator, std.testing.io, "needle"); | |
| 515 | 515 | defer regex.deinit(); |
| 516 | 516 | |
| 517 | 517 | // Build a long haystack with needle at the very end |
| @@ -531,7 +531,7 @@ test "stress: match at end of long input" { | ||
| 531 | 531 | |
| 532 | 532 | test "stress: no match in long input" { |
| 533 | 533 | const allocator = std.testing.allocator; |
| 534 | var regex = try Regex.compile(allocator, "needle"); | |
| 534 | var regex = try Regex.compile(allocator, std.testing.io, "needle"); | |
| 535 | 535 | defer regex.deinit(); |
| 536 | 536 | |
| 537 | 537 | var buf: [1024]u8 = undefined; |
| @@ -544,7 +544,7 @@ test "stress: no match in long input" { | ||
| 544 | 544 | |
| 545 | 545 | test "stress: multiline ^ matches after each newline" { |
| 546 | 546 | 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 }); | |
| 548 | 548 | defer regex.deinit(); |
| 549 | 549 | |
| 550 | 550 | 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" { | ||
| 567 | 567 | // Without multiline, $ only matches at end of input |
| 568 | 568 | // So .+ matches "hello" but $ doesn't match at position 5 |
| 569 | 569 | // 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, ".+"); | |
| 571 | 571 | defer regex.deinit(); |
| 572 | 572 | |
| 573 | 573 | if (try regex.find("hello\nworld")) |match| { |
| @@ -582,7 +582,7 @@ test "stress: dot does not match newline by default - find first line" { | ||
| 582 | 582 | |
| 583 | 583 | test "stress: dot_all makes dot match newline" { |
| 584 | 584 | 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 }); | |
| 586 | 586 | defer regex.deinit(); |
| 587 | 587 | |
| 588 | 588 | if (try regex.find("hello\nworld")) |match| { |
| @@ -598,7 +598,7 @@ test "stress: dot_all makes dot match newline" { | ||
| 598 | 598 | |
| 599 | 599 | test "stress: case insensitive literal" { |
| 600 | 600 | 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 }); | |
| 602 | 602 | defer regex.deinit(); |
| 603 | 603 | |
| 604 | 604 | try std.testing.expect(try regex.isMatch("HELLO")); |
| @@ -610,38 +610,38 @@ test "stress: case insensitive literal" { | ||
| 610 | 610 | |
| 611 | 611 | test "stress: unmatched closing paren" { |
| 612 | 612 | const allocator = std.testing.allocator; |
| 613 | const result = Regex.compile(allocator, "abc)"); | |
| 613 | const result = Regex.compile(allocator, std.testing.io, "abc)"); | |
| 614 | 614 | try std.testing.expectError(RegexError.UnmatchedParenthesis, result); |
| 615 | 615 | } |
| 616 | 616 | |
| 617 | 617 | test "stress: unmatched opening paren" { |
| 618 | 618 | const allocator = std.testing.allocator; |
| 619 | const result = Regex.compile(allocator, "(abc"); | |
| 619 | const result = Regex.compile(allocator, std.testing.io, "(abc"); | |
| 620 | 620 | try std.testing.expectError(RegexError.UnexpectedCharacter, result); |
| 621 | 621 | } |
| 622 | 622 | |
| 623 | 623 | test "stress: unmatched bracket" { |
| 624 | 624 | const allocator = std.testing.allocator; |
| 625 | const result = Regex.compile(allocator, "[abc"); | |
| 625 | const result = Regex.compile(allocator, std.testing.io, "[abc"); | |
| 626 | 626 | try std.testing.expectError(RegexError.UnexpectedCharacter, result); |
| 627 | 627 | } |
| 628 | 628 | |
| 629 | 629 | test "stress: quantifier star at start" { |
| 630 | 630 | const allocator = std.testing.allocator; |
| 631 | const result = Regex.compile(allocator, "*abc"); | |
| 631 | const result = Regex.compile(allocator, std.testing.io, "*abc"); | |
| 632 | 632 | try std.testing.expectError(RegexError.UnexpectedCharacter, result); |
| 633 | 633 | } |
| 634 | 634 | |
| 635 | 635 | test "stress: quantifier plus at start" { |
| 636 | 636 | const allocator = std.testing.allocator; |
| 637 | const result = Regex.compile(allocator, "+abc"); | |
| 637 | const result = Regex.compile(allocator, std.testing.io, "+abc"); | |
| 638 | 638 | try std.testing.expectError(RegexError.UnexpectedCharacter, result); |
| 639 | 639 | } |
| 640 | 640 | |
| 641 | 641 | test "stress: empty character class" { |
| 642 | 642 | const allocator = std.testing.allocator; |
| 643 | 643 | // [] followed by ] - parser sees empty class then stray ] |
| 644 | if (Regex.compile(allocator, "[]")) |*r| { | |
| 644 | if (Regex.compile(allocator, std.testing.io, "[]")) |*r| { | |
| 645 | 645 | var mut_r = r.*; |
| 646 | 646 | mut_r.deinit(); |
| 647 | 647 | } else |_| { |
| @@ -653,7 +653,7 @@ test "stress: empty character class" { | ||
| 653 | 653 | |
| 654 | 654 | test "stress: greedy star matches longest" { |
| 655 | 655 | 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"); | |
| 657 | 657 | defer regex.deinit(); |
| 658 | 658 | |
| 659 | 659 | if (try regex.find("aXXbYYb")) |match| { |
| @@ -669,7 +669,7 @@ test "stress: greedy star matches longest" { | ||
| 669 | 669 | test "stress: complex pattern with multiple features" { |
| 670 | 670 | const allocator = std.testing.allocator; |
| 671 | 671 | // 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,})"); | |
| 673 | 673 | defer regex.deinit(); |
| 674 | 674 | |
| 675 | 675 | if (try regex.find("contact: user.name@example.com today")) |match| { |
| @@ -692,7 +692,7 @@ test "stress: compile many patterns sequentially" { | ||
| 692 | 692 | |
| 693 | 693 | var i: usize = 0; |
| 694 | 694 | 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+"); | |
| 696 | 696 | defer regex.deinit(); |
| 697 | 697 | try std.testing.expect(try regex.isMatch("test123")); |
| 698 | 698 | } |
| @@ -3,7 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | |
| 4 | 4 | test "string anchor: \\A start of text" { |
| 5 | 5 | const allocator = std.testing.allocator; |
| 6 | var regex = try Regex.compile(allocator, "\\Aabc"); | |
| 6 | var regex = try Regex.compile(allocator, std.testing.io, "\\Aabc"); | |
| 7 | 7 | defer regex.deinit(); |
| 8 | 8 | |
| 9 | 9 | // Should match at the start of the text |
| @@ -17,7 +17,7 @@ test "string anchor: \\A start of text" { | ||
| 17 | 17 | |
| 18 | 18 | test "string anchor: \\z end of text" { |
| 19 | 19 | 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"); | |
| 21 | 21 | defer regex.deinit(); |
| 22 | 22 | |
| 23 | 23 | // Should match at the end of the text |
| @@ -30,7 +30,7 @@ test "string anchor: \\z end of text" { | ||
| 30 | 30 | |
| 31 | 31 | test "string anchor: \\Z end of text (before optional newline)" { |
| 32 | 32 | 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"); | |
| 34 | 34 | defer regex.deinit(); |
| 35 | 35 | |
| 36 | 36 | // Should match at the end of the text |
| @@ -45,10 +45,10 @@ test "string anchor: \\Z end of text (before optional newline)" { | ||
| 45 | 45 | test "string anchors: \\A vs ^" { |
| 46 | 46 | const allocator = std.testing.allocator; |
| 47 | 47 | |
| 48 | var regex_A = try Regex.compile(allocator, "\\Atest"); | |
| 48 | var regex_A = try Regex.compile(allocator, std.testing.io, "\\Atest"); | |
| 49 | 49 | defer regex_A.deinit(); |
| 50 | 50 | |
| 51 | var regex_caret = try Regex.compile(allocator, "^test"); | |
| 51 | var regex_caret = try Regex.compile(allocator, std.testing.io, "^test"); | |
| 52 | 52 | defer regex_caret.deinit(); |
| 53 | 53 | |
| 54 | 54 | const input1 = "test"; |
| @@ -68,7 +68,7 @@ test "string anchors: \\A vs ^" { | ||
| 68 | 68 | |
| 69 | 69 | test "string anchors: combined \\A and \\z" { |
| 70 | 70 | 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"); | |
| 72 | 72 | defer regex.deinit(); |
| 73 | 73 | |
| 74 | 74 | // Should match only exact strings |
| @@ -28,7 +28,7 @@ test "concurrent matching with same regex" { | ||
| 28 | 28 | const allocator = testing.allocator; |
| 29 | 29 | |
| 30 | 30 | // Compile a regex once |
| 31 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 31 | var regex = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 32 | 32 | defer regex.deinit(); |
| 33 | 33 | |
| 34 | 34 | // Test inputs |
| @@ -73,7 +73,7 @@ test "concurrent matching with same regex" { | ||
| 73 | 73 | test "concurrent find operations" { |
| 74 | 74 | const allocator = testing.allocator; |
| 75 | 75 | |
| 76 | var regex = try Regex.compile(allocator, "[0-9]+"); | |
| 76 | var regex = try Regex.compile(allocator, std.testing.io, "[0-9]+"); | |
| 77 | 77 | defer regex.deinit(); |
| 78 | 78 | |
| 79 | 79 | const Worker = struct { |
| @@ -114,7 +114,7 @@ test "concurrent find operations" { | ||
| 114 | 114 | test "SharedRegex reference counting" { |
| 115 | 115 | const allocator = testing.allocator; |
| 116 | 116 | |
| 117 | var shared = try SharedRegex.init(allocator, "test"); | |
| 117 | var shared = try SharedRegex.init(allocator, testing.io, "test"); | |
| 118 | 118 | defer shared.deinit(); |
| 119 | 119 | |
| 120 | 120 | // Acquire multiple references |
| @@ -137,7 +137,7 @@ test "SharedRegex reference counting" { | ||
| 137 | 137 | test "SharedRegex concurrent access" { |
| 138 | 138 | const allocator = testing.allocator; |
| 139 | 139 | |
| 140 | var shared = try SharedRegex.init(allocator, "\\d+"); | |
| 140 | var shared = try SharedRegex.init(allocator, testing.io, "\\d+"); | |
| 141 | 141 | defer shared.deinit(); |
| 142 | 142 | |
| 143 | 143 | const Worker = struct { |
| @@ -172,7 +172,7 @@ test "SharedRegex concurrent access" { | ||
| 172 | 172 | test "RegexCache basic operations" { |
| 173 | 173 | const allocator = testing.allocator; |
| 174 | 174 | |
| 175 | var cache = RegexCache.init(allocator); | |
| 175 | var cache = RegexCache.init(allocator, testing.io); | |
| 176 | 176 | defer cache.deinit(); |
| 177 | 177 | |
| 178 | 178 | // Get and cache a pattern |
| @@ -195,7 +195,7 @@ test "thread-safe allocator with concurrent matching" { | ||
| 195 | 195 | defer _ = gpa.deinit(); |
| 196 | 196 | const allocator = gpa.allocator(); |
| 197 | 197 | |
| 198 | var regex = try Regex.compile(allocator, "test"); | |
| 198 | var regex = try Regex.compile(allocator, std.testing.io, "test"); | |
| 199 | 199 | defer regex.deinit(); |
| 200 | 200 | |
| 201 | 201 | const Worker = struct { |
| @@ -227,13 +227,13 @@ test "concurrent matching different patterns" { | ||
| 227 | 227 | const allocator = gpa.allocator(); |
| 228 | 228 | |
| 229 | 229 | // Compile multiple patterns |
| 230 | var regex1 = try Regex.compile(allocator, "\\d+"); | |
| 230 | var regex1 = try Regex.compile(allocator, std.testing.io, "\\d+"); | |
| 231 | 231 | defer regex1.deinit(); |
| 232 | 232 | |
| 233 | var regex2 = try Regex.compile(allocator, "[a-z]+"); | |
| 233 | var regex2 = try Regex.compile(allocator, std.testing.io, "[a-z]+"); | |
| 234 | 234 | defer regex2.deinit(); |
| 235 | 235 | |
| 236 | var regex3 = try Regex.compile(allocator, "test"); | |
| 236 | var regex3 = try Regex.compile(allocator, std.testing.io, "test"); | |
| 237 | 237 | defer regex3.deinit(); |
| 238 | 238 | |
| 239 | 239 | const Worker = struct { |
| @@ -276,7 +276,7 @@ test "stress test: many threads many matches" { | ||
| 276 | 276 | defer _ = gpa.deinit(); |
| 277 | 277 | const allocator = gpa.allocator(); |
| 278 | 278 | |
| 279 | var regex = try Regex.compile(allocator, "test"); | |
| 279 | var regex = try Regex.compile(allocator, std.testing.io, "test"); | |
| 280 | 280 | defer regex.deinit(); |
| 281 | 281 | |
| 282 | 282 | const Worker = struct { |
| @@ -5,7 +5,7 @@ const Regex = .Regex; | ||
| 5 | 5 | |
| 6 | 6 | test "UTF-8: literal matching" { |
| 7 | 7 | const allocator = std.testing.allocator; |
| 8 | var regex = try Regex.compile(allocator, "café"); | |
| 8 | var regex = try Regex.compile(allocator, std.testing.io, "café"); | |
| 9 | 9 | defer regex.deinit(); |
| 10 | 10 | |
| 11 | 11 | try std.testing.expect(try regex.isMatch("café")); |
| @@ -14,7 +14,7 @@ test "UTF-8: literal matching" { | ||
| 14 | 14 | |
| 15 | 15 | test "UTF-8: emoji matching" { |
| 16 | 16 | 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"); | |
| 18 | 18 | defer regex.deinit(); |
| 19 | 19 | |
| 20 | 20 | try std.testing.expect(try regex.isMatch("Hello 👋 World")); |
| @@ -22,7 +22,7 @@ test "UTF-8: emoji matching" { | ||
| 22 | 22 | |
| 23 | 23 | test "UTF-8: Chinese characters" { |
| 24 | 24 | const allocator = std.testing.allocator; |
| 25 | var regex = try Regex.compile(allocator, "你好"); | |
| 25 | var regex = try Regex.compile(allocator, std.testing.io, "你好"); | |
| 26 | 26 | defer regex.deinit(); |
| 27 | 27 | |
| 28 | 28 | try std.testing.expect(try regex.isMatch("你好")); |
| @@ -31,7 +31,7 @@ test "UTF-8: Chinese characters" { | ||
| 31 | 31 | |
| 32 | 32 | test "UTF-8: mixed ASCII and Unicode" { |
| 33 | 33 | const allocator = std.testing.allocator; |
| 34 | var regex = try Regex.compile(allocator, "test-тест-テスト"); | |
| 34 | var regex = try Regex.compile(allocator, std.testing.io, "test-тест-テスト"); | |
| 35 | 35 | defer regex.deinit(); |
| 36 | 36 | |
| 37 | 37 | try std.testing.expect(try regex.isMatch("test-тест-テスト")); |
| @@ -39,7 +39,7 @@ test "UTF-8: mixed ASCII and Unicode" { | ||
| 39 | 39 | |
| 40 | 40 | test "UTF-8: dot matches multi-byte character" { |
| 41 | 41 | 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é"); | |
| 43 | 43 | defer regex.deinit(); |
| 44 | 44 | |
| 45 | 45 | // Currently . matches one byte, not one character |
| @@ -49,7 +49,7 @@ test "UTF-8: dot matches multi-byte character" { | ||
| 49 | 49 | |
| 50 | 50 | test "UTF-8: alternation with Unicode" { |
| 51 | 51 | const allocator = std.testing.allocator; |
| 52 | var regex = try Regex.compile(allocator, "hello|你好|こんにちは"); | |
| 52 | var regex = try Regex.compile(allocator, std.testing.io, "hello|你好|こんにちは"); | |
| 53 | 53 | defer regex.deinit(); |
| 54 | 54 | |
| 55 | 55 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -60,7 +60,7 @@ test "UTF-8: alternation with Unicode" { | ||
| 60 | 60 | test "UTF-8: character class range with multi-byte" { |
| 61 | 61 | const allocator = std.testing.allocator; |
| 62 | 62 | // 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]+"); | |
| 64 | 64 | defer regex.deinit(); |
| 65 | 65 | |
| 66 | 66 | try std.testing.expect(try regex.isMatch("hello")); |
| @@ -79,7 +79,7 @@ test "UTF-8: character class range with multi-byte" { | ||
| 79 | 79 | |
| 80 | 80 | test "UTF-8: quantifiers with Unicode literals" { |
| 81 | 81 | const allocator = std.testing.allocator; |
| 82 | var regex = try Regex.compile(allocator, "あ+"); | |
| 82 | var regex = try Regex.compile(allocator, std.testing.io, "あ+"); | |
| 83 | 83 | defer regex.deinit(); |
| 84 | 84 | |
| 85 | 85 | try std.testing.expect(try regex.isMatch("あ")); |
| @@ -88,7 +88,7 @@ test "UTF-8: quantifiers with Unicode literals" { | ||
| 88 | 88 | |
| 89 | 89 | test "UTF-8: capture groups with Unicode" { |
| 90 | 90 | const allocator = std.testing.allocator; |
| 91 | var regex = try Regex.compile(allocator, "(你好)(世界)"); | |
| 91 | var regex = try Regex.compile(allocator, std.testing.io, "(你好)(世界)"); | |
| 92 | 92 | defer regex.deinit(); |
| 93 | 93 | |
| 94 | 94 | const result = try regex.find("你好世界"); |
| @@ -106,7 +106,7 @@ test "UTF-8: capture groups with Unicode" { | ||
| 106 | 106 | |
| 107 | 107 | test "UTF-8: replacement with Unicode" { |
| 108 | 108 | 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+)"); | |
| 110 | 110 | defer regex.deinit(); |
| 111 | 111 | |
| 112 | 112 | // ASCII works |
| @@ -117,7 +117,7 @@ test "UTF-8: replacement with Unicode" { | ||
| 117 | 117 | |
| 118 | 118 | test "UTF-8: anchors with Unicode" { |
| 119 | 119 | const allocator = std.testing.allocator; |
| 120 | var regex = try Regex.compile(allocator, "^你好$"); | |
| 120 | var regex = try Regex.compile(allocator, std.testing.io, "^你好$"); | |
| 121 | 121 | defer regex.deinit(); |
| 122 | 122 | |
| 123 | 123 | try std.testing.expect(try regex.isMatch("你好")); |
| @@ -127,7 +127,7 @@ test "UTF-8: anchors with Unicode" { | ||
| 127 | 127 | |
| 128 | 128 | test "UTF-8: non-capturing groups with Unicode" { |
| 129 | 129 | 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|세계)"); | |
| 131 | 131 | defer regex.deinit(); |
| 132 | 132 | |
| 133 | 133 | const result1 = try regex.find("hello world"); |
| @@ -156,7 +156,7 @@ test "UTF-8: non-capturing groups with Unicode" { | ||
| 156 | 156 | // Document current limitations |
| 157 | 157 | test "UTF-8: known limitation - dot is byte-based" { |
| 158 | 158 | const allocator = std.testing.allocator; |
| 159 | var regex = try Regex.compile(allocator, "^.$"); | |
| 159 | var regex = try Regex.compile(allocator, std.testing.io, "^.$"); | |
| 160 | 160 | defer regex.deinit(); |
| 161 | 161 | |
| 162 | 162 | // Single ASCII character |
| @@ -170,7 +170,7 @@ test "UTF-8: known limitation - dot is byte-based" { | ||
| 170 | 170 | |
| 171 | 171 | test "UTF-8: known limitation - \\w is ASCII-only" { |
| 172 | 172 | const allocator = std.testing.allocator; |
| 173 | var regex = try Regex.compile(allocator, "\\w+"); | |
| 173 | var regex = try Regex.compile(allocator, std.testing.io, "\\w+"); | |
| 174 | 174 | defer regex.deinit(); |
| 175 | 175 | |
| 176 | 176 | // ASCII word characters work |