102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
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
|
|
Msg string
|
|
Expected []string
|
|
}
|
|
|
|
type ParseError struct {
|
|
Message
|
|
}
|
|
|
|
func (pe ParseError) Error() string {
|
|
return fmt.Sprintf("parse error: %d: %s", pe.Message.Pos, pe.Message.Msg)
|
|
}
|
|
|
|
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}, nil
|
|
}
|
|
}
|
|
|
|
func Satisfy[T any](pred func(T) bool) Parser[T, T] {
|
|
return func(state State[T]) (bool, Result[T, T], error) {
|
|
b := make([]T, 1)
|
|
n, next, err := state.Read(b)
|
|
if errors.Is(err, io.EOF) {
|
|
return false, Result[T, T]{}, ErrNoParse
|
|
}
|
|
if n != 1 {
|
|
panic(fmt.Sprintf("expected 1 element from Read, but got %d", n))
|
|
}
|
|
if pred(b[0]) {
|
|
return true, Result[T, T]{Value: b[0], State: next}, nil
|
|
}
|
|
return false, Result[T, T]{}, ErrNoParse
|
|
}
|
|
}
|
|
|
|
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, 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
|
|
}
|
|
}
|