Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: include documentation on available combinators and parsers #63

Merged
merged 60 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e819a86
basic docs
purpleclay Aug 12, 2024
5032bb8
basic docs
purpleclay Aug 12, 2024
7b3931e
basic docs
purpleclay Aug 12, 2024
ca7004d
basic docs
purpleclay Aug 12, 2024
344388d
basic docs
purpleclay Aug 12, 2024
e335256
basic docs
purpleclay Aug 12, 2024
429d96a
basic docs
purpleclay Aug 12, 2024
6e926d8
basic docs
purpleclay Aug 12, 2024
4f1554d
basic docs
purpleclay Aug 12, 2024
4409362
basic docs
purpleclay Aug 12, 2024
3ed0c59
basic docs
purpleclay Aug 12, 2024
d6b2441
basic docs
purpleclay Aug 12, 2024
4ab2717
basic docs
purpleclay Aug 12, 2024
65c9135
basic docs
purpleclay Aug 12, 2024
8dfbf4a
basic docs
purpleclay Aug 12, 2024
d66b040
predicate docs
purpleclay Aug 12, 2024
d032c9c
predicate docs
purpleclay Aug 12, 2024
98a7232
predicate docs
purpleclay Aug 12, 2024
dbdbc6d
predicate docs
purpleclay Aug 12, 2024
7a9f1df
predicate docs
purpleclay Aug 12, 2024
780a80c
predicate docs
purpleclay Aug 12, 2024
21e3920
predicate docs
purpleclay Aug 12, 2024
2a55a32
predicate docs
purpleclay Aug 12, 2024
ab5417b
predicate docs
purpleclay Aug 12, 2024
f5cab1d
sequence combinators
purpleclay Aug 12, 2024
79cd19a
sequence combinators
purpleclay Aug 12, 2024
53efba7
sequence combinators
purpleclay Aug 12, 2024
b545684
sequence combinators
purpleclay Aug 13, 2024
9dfa1b0
sequence combinators
purpleclay Aug 13, 2024
513d560
sequence combinators
purpleclay Aug 13, 2024
2174e41
sequence predicates
purpleclay Aug 13, 2024
7a198ab
sequence predicates
purpleclay Aug 13, 2024
7cdf851
sequence predicates
purpleclay Aug 13, 2024
4679d8a
sequence predicates
purpleclay Aug 13, 2024
5ee30f0
sequence predicates
purpleclay Aug 13, 2024
04ae858
sequence predicates
purpleclay Aug 13, 2024
502b3cd
modifier combinators
purpleclay Aug 13, 2024
714dee6
modifier combinators
purpleclay Aug 13, 2024
085c38b
modifier combinators
purpleclay Aug 13, 2024
118a3c1
ready-made parsers
purpleclay Aug 13, 2024
b407d4b
contents page
purpleclay Aug 13, 2024
4e368a9
contents page
purpleclay Aug 13, 2024
ab1104b
contents page
purpleclay Aug 13, 2024
832c7ad
contents page
purpleclay Aug 13, 2024
9ee16ee
contents page
purpleclay Aug 13, 2024
6a79f64
tidying up
purpleclay Aug 15, 2024
5475abe
convert readme
purpleclay Aug 15, 2024
54702dc
fix badges
purpleclay Aug 16, 2024
9f9a401
fix badges
purpleclay Aug 16, 2024
9b4da8a
fix badges
purpleclay Aug 16, 2024
f53d164
fix badges
purpleclay Aug 16, 2024
bb183ea
fix badges
purpleclay Aug 16, 2024
e4c6679
fix badges
purpleclay Aug 16, 2024
dfbc955
fix badges
purpleclay Aug 16, 2024
ea1adac
fix badges
purpleclay Aug 16, 2024
79a29eb
fix badges
purpleclay Aug 16, 2024
a4960ef
fix badges
purpleclay Aug 16, 2024
d89307e
fix badges
purpleclay Aug 16, 2024
9044893
link to combinators glossary
purpleclay Aug 16, 2024
fea0c17
align all documentation
purpleclay Aug 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Allow project files
!.gitignore
!README.md
!README.adoc
!LICENSE
!CODE_OF_CONDUCT.md
!taskfile.yaml
Expand All @@ -12,6 +12,7 @@
!.goreleaser.yaml
!.github/**/*
!.zed/*
!docs/*.adoc

# Allow Nix flake
!.envrc
Expand Down
38 changes: 21 additions & 17 deletions README.md → README.adoc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Chomp
= Chomp

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_).

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

## Design
== Design

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)`:

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

Here's a sneak peek at its definition:

```go
[source,go]
----
type Result interface {
string | []string
}

type Combinator[T Result] func(string) (string, T, error)
```
----

A combinator in its simplest form would look like this:

```go
[source,go]
----
func Tag(str string) chomp.Combinator[string] {
return func(s string) (string, string, error) {
if strings.HasPrefix(s, str) {
Expand All @@ -42,27 +44,29 @@ func Tag(str string) chomp.Combinator[string] {
}
}
}
```
----

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

## Writing a Parser Combinator
== Writing a Parser Combinator

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

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

## Why use Chomp?
A full glossary of combinators can be be viewed xref:docs/combinators.adoc[here].

== Why use Chomp?

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

## Badges
== Badges

[![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)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/purpleclay/chomp?style=flat-square)](https://goreportcard.com/report/github.com/purpleclay/chomp)
[![Go Version](https://img.shields.io/github/go-mod/go-version/purpleclay/chomp.svg?style=flat-square)](go.mod)
[![DeepSource](https://app.deepsource.com/gh/purpleclay/chomp.svg/?label=active+issues&show_trend=false&token=DFB8RRar8iHJrVaNF7e9JaVm)](https://app.deepsource.com/gh/purpleclay/chomp/)
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]
image:https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square["License MIT", link=/LICENSE]
image:https://goreportcard.com/badge/github.com/purpleclay/chomp?style=flat-square["Go Report Card", link=https://goreportcard.com/report/github.com/purpleclay/chomp]
image:https://img.shields.io/github/go-mod/go-version/purpleclay/chomp.svg?style=flat-square["Go Version", link=go.mod]
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/]
165 changes: 9 additions & 156 deletions basic.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package chomp

import (
"fmt"
"strings"
)

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

// Any must match at least one character at the beginning of the input text,
// from the provided sequence. Parsing immediately stops upon the first
// unmatched character.
// Any must match at least one character from the provided sequence at the
// beginning of the input text. Parsing stops upon the first unmatched character.
//
// chomp.Any("eH")("Hello, World!")
// // ("llo, World!", "He", nil)
Expand Down Expand Up @@ -50,9 +48,8 @@ func Any(str string) Combinator[string] {
}
}

// Not must not match at least one character at the beginning of the input
// text from the provided sequence. Parsing immediately stops upon the
// first matched character.
// Not must not match at least one character at the beginning of the input text
// from the provided sequence. Parsing stops upon the first matched character.
//
// chomp.Not("ol")("Hello, World!")
// // ("llo, World!", "He", nil)
Expand All @@ -79,23 +76,8 @@ func Not(str string) Combinator[string] {
}
}

// Crlf must match either a CR or CRLF line ending.
//
// chomp.Crlf()("\r\nHello")
// // ("Hello", "\r\n", nil)
func Crlf() Combinator[string] {
return func(s string) (string, string, error) {
idx := strings.Index(s, "\n")
if idx == 0 || (idx == 1 && s[0] == '\r') {
return s[idx+1:], s[:idx+1], nil
}

return s, "", CombinatorParseError{Text: s, Type: "crlf"}
}
}

// OneOf must match a single character at the beginning of the text from the
// provided sequence.
// OneOf must match a single character at the beginning of the text from
// the provided sequence.
//
// chomp.OneOf("!,eH")("Hello, World!")
// // ("ello, World!", "H", nil)
Expand All @@ -114,8 +96,8 @@ func OneOf(str string) Combinator[string] {
}
}

// NoneOf must not match a single character at the beginning of the text from
// the provided sequence.
// NoneOf must not match a single character at the beginning of the text
// from the provided sequence.
//
// chomp.NoneOf("loWrd!e")("Hello, World!")
// // ("ello, World!", "H", nil)
Expand Down Expand Up @@ -150,132 +132,3 @@ func Until(str string) Combinator[string] {
return s, "", CombinatorParseError{Input: str, Text: s, Type: "until"}
}
}

// Opt allows a combinator to be optional. Any error returned by the underlying
// combinator will be swallowed. The parsed text will not be modified if the
// underlying combinator did not run.
//
// chomp.Opt(chomp.Tag("Hey"))("Hello, World!")
// // ("Hello, World!", "", nil)
func Opt[T Result](c Combinator[T]) Combinator[T] {
return func(s string) (string, T, error) {
rem, out, _ := c(s)
return rem, out, nil
}
}

// S wraps the result of the inner combinator within a string slice.
// Combinators of differing return types can be successfully chained
// together while using this conversion combinator.
//
// chomp.S(chomp.Until(","))("Hello, World!")
// // (", World!", []string{"Hello"}, nil)
func S(c Combinator[string]) Combinator[[]string] {
return func(s string) (string, []string, error) {
rem, ext, err := c(s)
if err != nil {
return rem, nil, err
}

return rem, []string{ext}, err
}
}

// I extracts and returns a single string from the result of the inner combinator.
// Combinators of differing return types can be successfully chained together while
// using this conversion combinator.
//
// chomp.I(chomp.SepPair(
// chomp.Tag("Hello"),
// chomp.Tag(", "),
// chomp.Tag("World")), 1)("Hello, World!")
// // ("!", "World", nil)
func I(c Combinator[[]string], i int) Combinator[string] {
return func(s string) (string, string, error) {
rem, ext, err := c(s)
if err != nil {
return rem, "", err
}

if i < 0 || i >= len(ext) {
return rem, "", ParserError{
Err: fmt.Errorf("index %d is out of bounds within string slice of %d elements", i, len(ext)),
Type: "i",
}
}

return rem, ext[i], nil
}
}

// Prefixed will firstly scan the input text for a defined prefix and discard it.
// The remaining input text will be matched against the [Combinator] and returned
// if successful. Both combinators must match.
//
// chomp.Prefixed(
// chomp.Tag("Hello"),
// chomp.Tag(`"`))(`"Hello, World!"`)
// // (`, World!"`, "Hello", nil)
func Prefixed(c, pre Combinator[string]) Combinator[string] {
return func(s string) (string, string, error) {
rem, _, err := pre(s)
if err != nil {
return rem, "", err
}

return c(rem)
}
}

// Suffixed will firstly scan the input text and match it against the [Combinator].
// The remaining text will be scanned for a defined suffix and discarded. Both
// combinators must match.
//
// chomp.Suffixed(
// chomp.Tag("Hello"),
// chomp.Tag(", "))("Hello, World!")
// // ("World!", "Hello", nil)
func Suffixed(c, suf Combinator[string]) Combinator[string] {
return func(s string) (string, string, error) {
rem, ext, err := c(s)
if err != nil {
return rem, "", err
}

rem, _, err = suf(rem)
if err != nil {
return rem, "", err
}

return rem, ext, nil
}
}

// Eol will scan the text until it encounters any ASCII line ending characters
// identified by the [IsLineEnding] predicate. All text before the line ending
// will be returned. The line ending, if detected, will be discarded.
//
// chomp.Eol()(`Hello, World!\nIt's a great day!`)
// // ("It's a great day!", "Hello, World!", nil)
func Eol() Combinator[string] {
return func(s string) (string, string, error) {
return Suffixed(WhileNotN(IsLineEnding, 0), Opt(Crlf()))(s)
}
}

// Peek will scan the text and apply the parser without consuming any of the input.
// Useful if you need to lookahead.
//
// chomp.Peek(chomp.Tag("Hello"))("Hello, World!")
// // ("Hello, World!", "Hello", nil)
//
// chomp.Peek(
// chomp.Many(chomp.Suffixed(chomp.Tag(" "), chomp.Until(" "))),
// )("Hello and Good Morning!")
// // ("Hello and Good Morning!", []string{"Hello", "and", "Good"}, nil)
func Peek[T Result](c Combinator[T]) Combinator[T] {
return func(s string) (string, T, error) {
_, ext, err := c(s)
return s, ext, err
}
}
Loading
Loading