package gigaparsec import ( "errors" "fmt" "io" "git.codemonkeysoftware.net/b/gigaparsec/cursor" ) var ErrNoParse = errors.New("no parse") 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) } type State[T any] cursor.Cursor[T] 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 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 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 } } // TODO Add error propagation func Choose[In, Out any](p, q Parser[In, Out]) Parser[In, Out] { return func(input State[In]) (bool, Result[In, Out], error) { consumedP, replyP, errP := p(input) if consumedP { return consumedP, replyP, errP } if errP != nil { return q(input) } consumedQ, replyQ, errQ := q(input) if consumedQ { return consumedQ, replyQ, errQ } return consumedP, replyP, errP } } // 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 } }