Skip to content

Commit bba4768

Browse files
authored
docs: include documentation on available combinators and parsers (#63)
1 parent c5ff172 commit bba4768

File tree

14 files changed

+1079
-458
lines changed

14 files changed

+1079
-458
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Allow project files
55
!.gitignore
6-
!README.md
6+
!README.adoc
77
!LICENSE
88
!CODE_OF_CONDUCT.md
99
!taskfile.yaml
@@ -12,6 +12,7 @@
1212
!.goreleaser.yaml
1313
!.github/**/*
1414
!.zed/*
15+
!docs/*.adoc
1516

1617
# Allow Nix flake
1718
!.envrc
Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Chomp
1+
= Chomp
22

33
A parser combinator library for chomping strings (_a rune at a time_) in Go. A more intuitive way to parse text without having to write a single regex. Happy to chomp both ASCII and Unicode (_it all tastes the same_).
44

5-
Inspired by [nom](https://github.com/rust-bakery/nom) 💜.
5+
Inspired by https://github.com/rust-bakery/nom[nom] 💜.
66

7-
## Design
7+
== Design
88

99
At the heart of `chomp` is a combinator. A higher-order function capable of parsing text under a defined condition and returning a tuple `(1,2,3)`:
1010

@@ -14,17 +14,19 @@ At the heart of `chomp` is a combinator. A higher-order function capable of pars
1414

1515
Here's a sneak peek at its definition:
1616

17-
```go
17+
[source,go]
18+
----
1819
type Result interface {
1920
string | []string
2021
}
2122
2223
type Combinator[T Result] func(string) (string, T, error)
23-
```
24+
----
2425

2526
A combinator in its simplest form would look like this:
2627

27-
```go
28+
[source,go]
29+
----
2830
func Tag(str string) chomp.Combinator[string] {
2931
return func(s string) (string, string, error) {
3032
if strings.HasPrefix(s, str) {
@@ -42,27 +44,29 @@ func Tag(str string) chomp.Combinator[string] {
4244
}
4345
}
4446
}
45-
```
47+
----
4648

4749
The true power of `chomp` comes from the ability to build parsers by chaining (_or combining_) combinators together.
4850

49-
## Writing a Parser Combinator
51+
== Writing a Parser Combinator
5052

5153
Take a look at one of the examples of how to write a parser combinator.
5254

53-
1. [GPG Private Key parser](https://github.com/purpleclay/chomp/blob/main/examples/gpg/main.go)
54-
1. [Git Diff parser](https://github.com/purpleclay/chomp/blob/main/examples/git-diff/main.go)
55+
. https://github.com/purpleclay/chomp/blob/main/examples/gpg/main.go[GPG Private Key parser]
56+
. https://github.com/purpleclay/chomp/blob/main/examples/git-diff/main.go[Git Diff parser]
5557

56-
## Why use Chomp?
58+
A full glossary of combinators can be be viewed xref:docs/combinators.adoc[here].
59+
60+
== Why use Chomp?
5761

5862
- Combinators are very easy to write and combine into more complex parsers.
5963
- Code written with chomp looks like natural grammar and is easy to understand, maintain and extend.
6064
- It is incredibly easy to unit test.
6165

62-
## Badges
66+
== Badges
6367

64-
[![Build status](https://img.shields.io/github/actions/workflow/status/purpleclay/chomp/ci.yml?style=flat-square&logo=go)](https://github.com/purpleclay/chomp/actions?workflow=ci)
65-
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](/LICENSE)
66-
[![Go Report Card](https://goreportcard.com/badge/github.com/purpleclay/chomp?style=flat-square)](https://goreportcard.com/report/github.com/purpleclay/chomp)
67-
[![Go Version](https://img.shields.io/github/go-mod/go-version/purpleclay/chomp.svg?style=flat-square)](go.mod)
68-
[![DeepSource](https://app.deepsource.com/gh/purpleclay/chomp.svg/?label=active+issues&show_trend=false&token=DFB8RRar8iHJrVaNF7e9JaVm)](https://app.deepsource.com/gh/purpleclay/chomp/)
68+
image:https://img.shields.io/github/actions/workflow/status/purpleclay/chomp/ci.yml?style=flat-square&logo=go["Build Status", link=https://github.com/purpleclay/chomp/actions?workflow=ci]
69+
image:https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square["License MIT", link=/LICENSE]
70+
image:https://goreportcard.com/badge/github.com/purpleclay/chomp?style=flat-square["Go Report Card", link=https://goreportcard.com/report/github.com/purpleclay/chomp]
71+
image:https://img.shields.io/github/go-mod/go-version/purpleclay/chomp.svg?style=flat-square["Go Version", link=go.mod]
72+
image:https://app.deepsource.com/gh/purpleclay/chomp.svg/?label=active+issues&show_trend=false&token=DFB8RRar8iHJrVaNF7e9JaVm["DeepSource", link=https://app.deepsource.com/gh/purpleclay/chomp/]

basic.go

Lines changed: 9 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package chomp
22

33
import (
4-
"fmt"
54
"strings"
65
)
76

8-
// Tag must match a series of characters at the beginning of the input text,
7+
// Tag must match a series of characters at the beginning of the input text
98
// in the exact order and case provided.
109
//
1110
// chomp.Tag("Hello")("Hello, World!")
@@ -20,9 +19,8 @@ func Tag(str string) Combinator[string] {
2019
}
2120
}
2221

23-
// Any must match at least one character at the beginning of the input text,
24-
// from the provided sequence. Parsing immediately stops upon the first
25-
// unmatched character.
22+
// Any must match at least one character from the provided sequence at the
23+
// beginning of the input text. Parsing stops upon the first unmatched character.
2624
//
2725
// chomp.Any("eH")("Hello, World!")
2826
// // ("llo, World!", "He", nil)
@@ -50,9 +48,8 @@ func Any(str string) Combinator[string] {
5048
}
5149
}
5250

53-
// Not must not match at least one character at the beginning of the input
54-
// text from the provided sequence. Parsing immediately stops upon the
55-
// first matched character.
51+
// Not must not match at least one character at the beginning of the input text
52+
// from the provided sequence. Parsing stops upon the first matched character.
5653
//
5754
// chomp.Not("ol")("Hello, World!")
5855
// // ("llo, World!", "He", nil)
@@ -79,23 +76,8 @@ func Not(str string) Combinator[string] {
7976
}
8077
}
8178

82-
// Crlf must match either a CR or CRLF line ending.
83-
//
84-
// chomp.Crlf()("\r\nHello")
85-
// // ("Hello", "\r\n", nil)
86-
func Crlf() Combinator[string] {
87-
return func(s string) (string, string, error) {
88-
idx := strings.Index(s, "\n")
89-
if idx == 0 || (idx == 1 && s[0] == '\r') {
90-
return s[idx+1:], s[:idx+1], nil
91-
}
92-
93-
return s, "", CombinatorParseError{Text: s, Type: "crlf"}
94-
}
95-
}
96-
97-
// OneOf must match a single character at the beginning of the text from the
98-
// provided sequence.
79+
// OneOf must match a single character at the beginning of the text from
80+
// the provided sequence.
9981
//
10082
// chomp.OneOf("!,eH")("Hello, World!")
10183
// // ("ello, World!", "H", nil)
@@ -114,8 +96,8 @@ func OneOf(str string) Combinator[string] {
11496
}
11597
}
11698

117-
// NoneOf must not match a single character at the beginning of the text from
118-
// the provided sequence.
99+
// NoneOf must not match a single character at the beginning of the text
100+
// from the provided sequence.
119101
//
120102
// chomp.NoneOf("loWrd!e")("Hello, World!")
121103
// // ("ello, World!", "H", nil)
@@ -150,132 +132,3 @@ func Until(str string) Combinator[string] {
150132
return s, "", CombinatorParseError{Input: str, Text: s, Type: "until"}
151133
}
152134
}
153-
154-
// Opt allows a combinator to be optional. Any error returned by the underlying
155-
// combinator will be swallowed. The parsed text will not be modified if the
156-
// underlying combinator did not run.
157-
//
158-
// chomp.Opt(chomp.Tag("Hey"))("Hello, World!")
159-
// // ("Hello, World!", "", nil)
160-
func Opt[T Result](c Combinator[T]) Combinator[T] {
161-
return func(s string) (string, T, error) {
162-
rem, out, _ := c(s)
163-
return rem, out, nil
164-
}
165-
}
166-
167-
// S wraps the result of the inner combinator within a string slice.
168-
// Combinators of differing return types can be successfully chained
169-
// together while using this conversion combinator.
170-
//
171-
// chomp.S(chomp.Until(","))("Hello, World!")
172-
// // (", World!", []string{"Hello"}, nil)
173-
func S(c Combinator[string]) Combinator[[]string] {
174-
return func(s string) (string, []string, error) {
175-
rem, ext, err := c(s)
176-
if err != nil {
177-
return rem, nil, err
178-
}
179-
180-
return rem, []string{ext}, err
181-
}
182-
}
183-
184-
// I extracts and returns a single string from the result of the inner combinator.
185-
// Combinators of differing return types can be successfully chained together while
186-
// using this conversion combinator.
187-
//
188-
// chomp.I(chomp.SepPair(
189-
// chomp.Tag("Hello"),
190-
// chomp.Tag(", "),
191-
// chomp.Tag("World")), 1)("Hello, World!")
192-
// // ("!", "World", nil)
193-
func I(c Combinator[[]string], i int) Combinator[string] {
194-
return func(s string) (string, string, error) {
195-
rem, ext, err := c(s)
196-
if err != nil {
197-
return rem, "", err
198-
}
199-
200-
if i < 0 || i >= len(ext) {
201-
return rem, "", ParserError{
202-
Err: fmt.Errorf("index %d is out of bounds within string slice of %d elements", i, len(ext)),
203-
Type: "i",
204-
}
205-
}
206-
207-
return rem, ext[i], nil
208-
}
209-
}
210-
211-
// Prefixed will firstly scan the input text for a defined prefix and discard it.
212-
// The remaining input text will be matched against the [Combinator] and returned
213-
// if successful. Both combinators must match.
214-
//
215-
// chomp.Prefixed(
216-
// chomp.Tag("Hello"),
217-
// chomp.Tag(`"`))(`"Hello, World!"`)
218-
// // (`, World!"`, "Hello", nil)
219-
func Prefixed(c, pre Combinator[string]) Combinator[string] {
220-
return func(s string) (string, string, error) {
221-
rem, _, err := pre(s)
222-
if err != nil {
223-
return rem, "", err
224-
}
225-
226-
return c(rem)
227-
}
228-
}
229-
230-
// Suffixed will firstly scan the input text and match it against the [Combinator].
231-
// The remaining text will be scanned for a defined suffix and discarded. Both
232-
// combinators must match.
233-
//
234-
// chomp.Suffixed(
235-
// chomp.Tag("Hello"),
236-
// chomp.Tag(", "))("Hello, World!")
237-
// // ("World!", "Hello", nil)
238-
func Suffixed(c, suf Combinator[string]) Combinator[string] {
239-
return func(s string) (string, string, error) {
240-
rem, ext, err := c(s)
241-
if err != nil {
242-
return rem, "", err
243-
}
244-
245-
rem, _, err = suf(rem)
246-
if err != nil {
247-
return rem, "", err
248-
}
249-
250-
return rem, ext, nil
251-
}
252-
}
253-
254-
// Eol will scan the text until it encounters any ASCII line ending characters
255-
// identified by the [IsLineEnding] predicate. All text before the line ending
256-
// will be returned. The line ending, if detected, will be discarded.
257-
//
258-
// chomp.Eol()(`Hello, World!\nIt's a great day!`)
259-
// // ("It's a great day!", "Hello, World!", nil)
260-
func Eol() Combinator[string] {
261-
return func(s string) (string, string, error) {
262-
return Suffixed(WhileNotN(IsLineEnding, 0), Opt(Crlf()))(s)
263-
}
264-
}
265-
266-
// Peek will scan the text and apply the parser without consuming any of the input.
267-
// Useful if you need to lookahead.
268-
//
269-
// chomp.Peek(chomp.Tag("Hello"))("Hello, World!")
270-
// // ("Hello, World!", "Hello", nil)
271-
//
272-
// chomp.Peek(
273-
// chomp.Many(chomp.Suffixed(chomp.Tag(" "), chomp.Until(" "))),
274-
// )("Hello and Good Morning!")
275-
// // ("Hello and Good Morning!", []string{"Hello", "and", "Good"}, nil)
276-
func Peek[T Result](c Combinator[T]) Combinator[T] {
277-
return func(s string) (string, T, error) {
278-
_, ext, err := c(s)
279-
return s, ext, err
280-
}
281-
}

0 commit comments

Comments
 (0)