Skip to content

Commit 7661a47

Browse files
authored
feat: add dedicated control flow combinators (#89)
Closes #86 Add new control flow combinators to improve parser validation, error handling, and flow control: - `Verify`: validate parsed results against a predicate - `Recognize`: return consumed input as output - `Consumed`: return both raw consumed text and parsed output - `Eof`: match only at end of input - `AllConsuming`: ensure entire input is consumed - `Rest`: return all remaining unconsumed input - `Value`: return fixed value on parser success - `Cond`: conditionally apply parser based on boolean - `Cut`: convert recoverable errors to fatal failures (prevents backtracking) - `PeekNot`: negative lookahead (succeed when inner parser fails) Signed-off-by: purpleclay <purpleclaygh@gmail.com>
1 parent 2994a76 commit 7661a47

File tree

4 files changed

+719
-1
lines changed

4 files changed

+719
-1
lines changed

.golangci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ linters:
3030
- common-false-positives
3131
- legacy
3232
- std-error-handling
33+
rules:
34+
- linters:
35+
- revive
36+
text: "var-naming: func Eof should be EOF"
3337
paths:
3438
- third_party$
3539
- builtin$

docs/combinators.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A comprehensive reference of all combinators provided by `chomp`.
55
- [Character Combinators](#character-combinators)
66
- [Tag Combinators](#tag-combinators)
77
- [Sequence Combinators](#sequence-combinators)
8+
- [Control Flow Combinators](#control-flow-combinators)
89
- [Predicate Combinators](#predicate-combinators)
910
- [Convenience Combinators](#convenience-combinators)
1011
- [Modifier Combinators](#modifier-combinators)
@@ -414,6 +415,139 @@ chomp.Fill(chomp.OneOf("abc"), 3)("abcdef")
414415

415416
---
416417

418+
## Control Flow Combinators
419+
420+
### Verify
421+
422+
Validates the parsed result against a predicate function without modifying the output. If the predicate returns false, the combinator fails.
423+
424+
```go
425+
chomp.Verify(chomp.Alpha(), func(s string) bool {
426+
return len(s) >= 3
427+
})("Hello, World!")
428+
// rem: ", World!"
429+
// ext: "Hello"
430+
```
431+
432+
### Recognize
433+
434+
Returns the consumed input as the output, regardless of the inner parser's result. Useful for capturing complex patterns as text.
435+
436+
```go
437+
chomp.Recognize(chomp.SepPair(chomp.Alpha(), chomp.Tag(", "), chomp.Alpha()))("Hello, World!")
438+
// rem: "!"
439+
// ext: "Hello, World"
440+
```
441+
442+
### Consumed
443+
444+
Provides both the raw consumed text and the parsed output. The first element is the raw consumed text, followed by the parsed result.
445+
446+
```go
447+
chomp.Consumed(chomp.SepPair(chomp.Alpha(), chomp.Tag(", "), chomp.Alpha()))("Hello, World!")
448+
// rem: "!"
449+
// ext: ["Hello, World", "Hello", "World"]
450+
```
451+
452+
### Eof
453+
454+
Matches only when at the end of input, returning an empty string on success. Prevents partial parsing.
455+
456+
```go
457+
chomp.Eof()("")
458+
// rem: ""
459+
// ext: ""
460+
461+
chomp.Pair(chomp.Tag("Hello"), chomp.Eof())("Hello")
462+
// rem: ""
463+
// ext: ["Hello", ""]
464+
```
465+
466+
### AllConsuming
467+
468+
Ensures the entire input is consumed by the inner parser, failing if any text remains unparsed.
469+
470+
```go
471+
chomp.AllConsuming(chomp.Tag("Hello"))("Hello")
472+
// rem: ""
473+
// ext: "Hello"
474+
475+
chomp.AllConsuming(chomp.Tag("Hello"))("Hello, World!")
476+
// error: all_consuming failed
477+
```
478+
479+
### Rest
480+
481+
Returns all remaining unconsumed input as a string value. Always succeeds, even with empty input.
482+
483+
```go
484+
chomp.Rest()("Hello, World!")
485+
// rem: ""
486+
// ext: "Hello, World!"
487+
488+
chomp.Pair(chomp.Tag("Hello"), chomp.Rest())("Hello, World!")
489+
// rem: ""
490+
// ext: ["Hello", ", World!"]
491+
```
492+
493+
### Value
494+
495+
Returns a fixed value upon parser success, discarding the actual parse result. Useful for assigning semantic meaning to parsed tokens.
496+
497+
```go
498+
chomp.Value(chomp.Tag("true"), true)("true")
499+
// rem: ""
500+
// ext: true
501+
502+
chomp.Value(chomp.Tag("null"), nil)("null")
503+
// rem: ""
504+
// ext: nil
505+
```
506+
507+
### Cond
508+
509+
Conditionally applies a parser based on a boolean flag. If the condition is true, the parser is applied. Otherwise, returns an empty result without consuming input.
510+
511+
```go
512+
chomp.Cond(true, chomp.Tag("Hello"))("Hello, World!")
513+
// rem: ", World!"
514+
// ext: "Hello"
515+
516+
chomp.Cond(false, chomp.Tag("Hello"))("Hello, World!")
517+
// rem: "Hello, World!"
518+
// ext: ""
519+
```
520+
521+
### Cut
522+
523+
Converts recoverable parsing errors into fatal failures, preventing backtracking past decision points. Improves error messaging by committing to a parsing path. When used with `First`, a `CutError` stops backtracking immediately.
524+
525+
```go
526+
// Without Cut, First would try the second alternative and succeed
527+
// With Cut, once "if" matches, failure on "(" is fatal
528+
chomp.First(
529+
chomp.Flatten(chomp.All(
530+
chomp.Tag("if"),
531+
chomp.Cut(chomp.Tag("(")))),
532+
chomp.Tag("if x"))("if x")
533+
// error: CutError (no backtracking to "if x")
534+
```
535+
536+
### PeekNot
537+
538+
Succeeds when the inner parser fails without consuming input. Implements negative lookahead for validation. Pairs with `Peek` for positive lookahead.
539+
540+
```go
541+
chomp.PeekNot(chomp.Tag("Hello"))("World!")
542+
// rem: "World!"
543+
// ext: ""
544+
545+
chomp.PeekNot(chomp.Tag("Hello"))("Hello, World!")
546+
// error: peek_not failed
547+
```
548+
549+
---
550+
417551
## Predicate Combinators
418552

419553
### While

0 commit comments

Comments
 (0)