221 lines
5.2 KiB
Go
221 lines
5.2 KiB
Go
package gigaparsec
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"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 (m Message) expect(s string) Message {
|
|
m.Expected = []string{s}
|
|
return m
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func MakeState[In any](c cursor.Cursor[In]) State[In] {
|
|
return State[In]{cursor: c}
|
|
}
|
|
|
|
type State[In any] struct {
|
|
cursor cursor.Cursor[In]
|
|
}
|
|
|
|
func (s State[In]) Cursor() cursor.Cursor[In] {
|
|
return s.cursor
|
|
}
|
|
|
|
func (s State[In]) Read(dst []In) (n uint64, next State[In], err error) {
|
|
n, c, err := s.cursor.Read(dst)
|
|
return n, State[In]{cursor: c}, err
|
|
}
|
|
|
|
func (s State[In]) Pos() uint64 {
|
|
return s.cursor.Pos()
|
|
}
|
|
|
|
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()))
|
|
}
|
|
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),
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func Choose[In, Out any](p Parser[In, Out], ps ...Parser[In, Out]) Parser[In, Out] {
|
|
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
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
func Label[In, Out any](p Parser[In, Out], l string) Parser[In, Out] {
|
|
return func(input State[In]) (consumed bool, reply Result[In, Out], err error) {
|
|
consumed, reply, err = p(input)
|
|
if consumed {
|
|
return
|
|
}
|
|
if err == nil {
|
|
reply.Message = reply.Message.expect(l)
|
|
return
|
|
}
|
|
var parseErr ParseError
|
|
if errors.As(err, &parseErr) {
|
|
err = ParseError(Message(parseErr).expect(l))
|
|
return
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func Map[In, Out1, Out2 any](p Parser[In, Out1], f func(Out1) Out2) Parser[In, Out2] {
|
|
return Bind(p, func(out Out1) Parser[In, Out2] {
|
|
return Return[In](f(out))
|
|
})
|
|
}
|