gigaparsec/gigaparsec.go

194 lines
4.6 KiB
Go
Raw Normal View History

package gigaparsec
import (
"errors"
"fmt"
"io"
2024-09-03 03:14:46 +00:00
"slices"
"git.codemonkeysoftware.net/b/gigaparsec/cursor"
)
type Result[In, Out any] struct {
Value Out
State[In]
Message
}
type Message struct {
Pos uint64
Got string
Expected []string
}
func MessageOK(pos uint64) Message { return Message{Pos: pos} }
func MessageEnd(pos uint64) Message { return Message{Pos: pos, Got: "end of input"} }
type ParseError Message
func (pe ParseError) Error() string {
return fmt.Sprintf("parse error: %d: %s", pe.Pos, pe.Got)
}
2024-09-08 18:09:20 +00:00
type State[T any] struct {
cursor cursor.Cursor[T]
}
func (s State[T]) Cursor() cursor.Cursor[T] {
return s.cursor
}
func (s State[T]) Read(dst []T) (n uint64, next State[T], err error) {
n, c, err := s.cursor.Read(dst)
return n, State[T]{cursor: c}, err
}
func (s State[T]) Pos() uint64 {
return s.cursor.Pos()
}
2024-09-09 16:27:54 +00:00
func Parse[In, Out any](p Parser[In, Out], c cursor.Cursor[In]) (result Out, err error) {
st := State[In]{cursor: c}
var reply Result[In, Out]
_, reply, err = p(st)
return reply.Value, err
}
type Parser[In, Out any] func(State[In]) (consumed bool, reply Result[In, Out], err error)
func Return[In, Out any](value Out) Parser[In, Out] {
return func(state State[In]) (bool, Result[In, Out], error) {
return false, Result[In, Out]{
Value: value,
State: state,
Message: MessageOK(state.Pos()),
}, nil
}
}
func Satisfy[T any](pred func(T) bool) Parser[T, T] {
return func(state State[T]) (bool, Result[T, T], error) {
token := make([]T, 1)
n, next, err := state.Read(token)
if errors.Is(err, io.EOF) {
return false, Result[T, T]{}, ParseError(MessageEnd(state.Pos()))
}
2024-09-08 18:09:20 +00:00
if err != nil {
return false, Result[T, T]{}, err
}
if n != 1 {
panic(fmt.Sprintf("expected 1 element from Read, but got %d", n))
}
if pred(token[0]) {
return true, Result[T, T]{
Value: token[0],
State: next,
Message: MessageOK(state.Pos()),
}, nil
}
return false, Result[T, T]{}, ParseError{
Pos: state.Pos(),
Got: fmt.Sprint(token),
}
}
}
2024-09-08 18:09:20 +00:00
func Slice[T comparable](s []T) Parser[T, []T] {
expected := fmt.Sprint(s)
return func(state State[T]) (consumed bool, reply Result[T, []T], err error) {
token := make([]T, len(s))
_, next, err := state.Read(token)
if errors.Is(err, io.EOF) {
return false, Result[T, []T]{}, ParseError(MessageEnd(state.Pos()))
}
if err != nil {
return false, Result[T, []T]{}, err
}
if !slices.Equal(s, token) {
return false, Result[T, []T]{}, ParseError{
Pos: state.Pos(),
Got: fmt.Sprint(token),
Expected: []string{expected},
}
}
return true, Result[T, []T]{
Value: token,
State: next,
Message: MessageOK(state.Pos()),
}, nil
}
}
func Bind[In, A, B any](p Parser[In, A], f func(A) Parser[In, B]) Parser[In, B] {
return func(input State[In]) (bool, Result[In, B], error) {
consumed, resultA, err := p(input)
if err != nil {
return false, Result[In, B]{}, err
}
consumed2, replyB, err := f(resultA.Value)(resultA.State)
return consumed || consumed2, replyB, err
}
}
2024-09-03 03:20:13 +00:00
func Choose[In, Out any](p Parser[In, Out], ps ...Parser[In, Out]) Parser[In, Out] {
2024-09-03 03:14:46 +00:00
all := append([]Parser[In, Out]{p}, ps...)
return func(input State[In]) (bool, Result[In, Out], error) {
expecteds := make([][]string, 0, len(all))
var value Out
var got string
var gotGot bool
var failed bool
for _, q := range all {
consumed, result, err := q(input)
if consumed {
return consumed, result, err
}
var qMsg Message
if err != nil {
var parseErr ParseError
if !errors.As(err, &parseErr) {
// It broke. Give up.
return consumed, result, err
}
failed = failed && true
qMsg = Message(parseErr)
} else {
if failed {
value = result.Value
failed = false
}
qMsg = result.Message
}
if !gotGot {
got = qMsg.Got
gotGot = true
}
}
msg := Message{Pos: input.Pos(), Got: got, Expected: slices.Concat(expecteds...)}
if failed {
return false, Result[In, Out]{}, ParseError(msg)
}
return false, Result[In, Out]{
Value: value,
State: input,
Message: msg,
}, nil
}
}
2024-09-02 18:48:48 +00:00
// Try behaves identically to p, except that if p returns an error,
// Try will pretend that no input was consumed. This allows infinite
// lookahead: Since Choose only calls another parser when the previous
// parser consumed nothing, Try will allow backing out of a complex
// parser that did partially succeeded.
func Try[In, Out any](p Parser[In, Out]) Parser[In, Out] {
return func(input State[In]) (bool, Result[In, Out], error) {
consumed, reply, err := p(input)
if err != nil {
return false, Result[In, Out]{}, err
}
return consumed, reply, err
}
}