parent
360857d506
commit
c2adfa3b46
@ -1,73 +1,310 @@ |
||||
package gg |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"strconv" |
||||
"unicode" |
||||
|
||||
. "github.com/mediocregopher/ginger/gg/grammar" |
||||
"github.com/mediocregopher/ginger/graph" |
||||
"golang.org/x/exp/slices" |
||||
) |
||||
|
||||
// Decoder reads Value's off of a byte stream.
|
||||
type Decoder struct { |
||||
br *bufio.Reader |
||||
brNextLoc Location |
||||
func s(str string) fmt.Stringer { |
||||
return Stringer{S: str} |
||||
} |
||||
|
||||
var ( |
||||
notNewline = RuneFunc( |
||||
s("not-newline"), func(r rune) bool { return r != '\n' }, |
||||
) |
||||
|
||||
comment = Prefixed( |
||||
Prefixed(Rune('*'), ZeroOrMore(notNewline)), Rune('\n'), |
||||
) |
||||
|
||||
whitespace = ZeroOrMore(FirstOf( |
||||
Discard(RuneFunc(s("whitespace"), unicode.IsSpace)), |
||||
Discard(comment), |
||||
)) |
||||
) |
||||
|
||||
unread []locatableRune |
||||
lastRead locatableRune |
||||
func trimmed[T any](sym Symbol[T]) Symbol[T] { |
||||
sym = PrefixDiscarded(whitespace, sym) |
||||
sym = Suffixed(sym, whitespace) |
||||
return sym |
||||
} |
||||
|
||||
// NewDecoder returns a Decoder which will decode the given stream as a gg
|
||||
// formatted stream of a Values.
|
||||
func NewDecoder(r io.Reader) *Decoder { |
||||
return &Decoder{ |
||||
br: bufio.NewReader(r), |
||||
brNextLoc: Location{Row: 1, Col: 1}, |
||||
} |
||||
func trimmedRune(r rune) Symbol[Located[rune]] { |
||||
return trimmed(Rune(r)) |
||||
} |
||||
|
||||
func (d *Decoder) readRune() (locatableRune, error) { |
||||
if len(d.unread) > 0 { |
||||
d.lastRead = d.unread[len(d.unread)-1] |
||||
d.unread = d.unread[:len(d.unread)-1] |
||||
return d.lastRead, nil |
||||
var ( |
||||
digit = RuneFunc( |
||||
s("digit"), func(r rune) bool { return '0' <= r && r <= '9' }, |
||||
) |
||||
|
||||
positiveNumber = StringFromRunes(OneOrMore(digit)) |
||||
|
||||
negativeNumber = Reduction( |
||||
s("negative-number"), |
||||
Rune('-'), |
||||
positiveNumber, |
||||
func(neg Located[rune], posNum Located[string]) Located[string] { |
||||
return Located[string]{ |
||||
neg.Location, string(neg.Value) + posNum.Value, |
||||
} |
||||
}, |
||||
) |
||||
|
||||
number = Mapping( |
||||
s("number"), |
||||
FirstOf(negativeNumber, positiveNumber), |
||||
func(str Located[string]) Located[Value] { |
||||
i, err := strconv.ParseInt(str.Value, 10, 64) |
||||
if err != nil { |
||||
panic(fmt.Errorf("parsing %q as int: %w", str, err)) |
||||
} |
||||
|
||||
return Located[Value]{str.Location, Number(i)} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
var ( |
||||
letter = RuneFunc( |
||||
s("letter"), |
||||
func(r rune) bool { |
||||
return unicode.In(r, unicode.Letter, unicode.Mark) |
||||
}, |
||||
) |
||||
|
||||
nameTail = ZeroOrMore(FirstOf(letter, digit)) |
||||
|
||||
name = Reduction( |
||||
s("name"), |
||||
letter, |
||||
nameTail, |
||||
func(head Located[rune], tail []Located[rune]) Located[Value] { |
||||
name := make([]rune, 0, len(tail)+1) |
||||
name = append(name, head.Value) |
||||
for _, r := range tail { |
||||
name = append(name, r.Value) |
||||
} |
||||
return Located[Value]{head.Location, Name(string(name))} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
func openEdgeIntoValue(val Value, oe *OpenEdge) *OpenEdge { |
||||
switch { |
||||
case oe == nil: |
||||
return graph.ValueOut(None, val) |
||||
case !oe.EdgeValue().Valid: |
||||
return oe.WithEdgeValue(Some(val)) |
||||
default: |
||||
return graph.TupleOut(Some(val), oe) |
||||
} |
||||
} |
||||
|
||||
loc := d.brNextLoc |
||||
var graphSym, value = func() ( |
||||
Symbol[Located[Value]], Symbol[Located[Value]], |
||||
) { |
||||
|
||||
r, _, err := d.br.ReadRune() |
||||
if err != nil { |
||||
return d.lastRead, err |
||||
type tupleState struct { |
||||
ins []*OpenEdge |
||||
oe *OpenEdge |
||||
} |
||||
|
||||
if r == '\n' { |
||||
d.brNextLoc.Row++ |
||||
d.brNextLoc.Col = 1 |
||||
} else { |
||||
d.brNextLoc.Col++ |
||||
type graphState struct { |
||||
g *Graph |
||||
oe *OpenEdge |
||||
} |
||||
|
||||
d.lastRead = locatableRune{loc, r} |
||||
return d.lastRead, nil |
||||
} |
||||
var ( |
||||
rightParen = trimmedRune(')') |
||||
tupleEnd = Mapping( |
||||
rightParen, |
||||
rightParen, |
||||
func(Located[rune]) tupleState { |
||||
// if ')', then map that to an empty state. This acts as a
|
||||
// sentinel value to indicate "end of tuple".
|
||||
return tupleState{} |
||||
}, |
||||
) |
||||
|
||||
func (d *Decoder) unreadRune(lr locatableRune) { |
||||
if d.lastRead != lr { |
||||
panic(fmt.Sprintf( |
||||
"unreading rune %#v, but last read rune was %#v", lr, d.lastRead, |
||||
)) |
||||
} |
||||
rightCurlyBrace = trimmedRune('}') |
||||
graphEnd = Mapping( |
||||
rightCurlyBrace, |
||||
rightCurlyBrace, |
||||
func(Located[rune]) graphState { |
||||
// if '}', then map that to an empty state. This acts as a
|
||||
// sentinel value to indicate "end of graph".
|
||||
return graphState{} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
d.unread = append(d.unread, lr) |
||||
var ( |
||||
// pre-define these, and then fill in the pointers after, in order to
|
||||
// deal with recursive dependencies between them.
|
||||
value = new(SymbolPtr[Located[Value]]) |
||||
|
||||
tuple = new(SymbolPtr[*OpenEdge]) |
||||
tupleTail = new(SymbolPtr[tupleState]) |
||||
tupleOpenEdge = new(SymbolPtr[tupleState]) |
||||
tupleOpenEdgeTail = new(SymbolPtr[tupleState]) |
||||
tupleOpenEdgeValueTail = new(SymbolPtr[tupleState]) |
||||
|
||||
graphSym = new(SymbolPtr[Located[Value]]) |
||||
graphTail = new(SymbolPtr[graphState]) |
||||
graphOpenEdge = new(SymbolPtr[graphState]) |
||||
graphOpenEdgeTail = new(SymbolPtr[graphState]) |
||||
graphOpenEdgeValueTail = new(SymbolPtr[graphState]) |
||||
) |
||||
|
||||
tuple.Symbol = Reduction[Located[rune], tupleState, *OpenEdge]( |
||||
s("tuple"), |
||||
trimmedRune('('), |
||||
tupleTail, |
||||
func(_ Located[rune], ts tupleState) *OpenEdge { |
||||
slices.Reverse(ts.ins) |
||||
return graph.TupleOut(None, ts.ins...) |
||||
}, |
||||
) |
||||
|
||||
tupleTail.Symbol = FirstOf( |
||||
tupleEnd, |
||||
Mapping[tupleState, tupleState]( |
||||
tupleOpenEdge, |
||||
tupleOpenEdge, |
||||
func(ts tupleState) tupleState { |
||||
ts.ins = append(ts.ins, ts.oe) |
||||
ts.oe = nil |
||||
return ts |
||||
}, |
||||
), |
||||
) |
||||
|
||||
tupleOpenEdge.Symbol = FirstOf( |
||||
Reduction[Located[Value], tupleState, tupleState]( |
||||
value, |
||||
value, |
||||
tupleOpenEdgeValueTail, |
||||
func(val Located[Value], ts tupleState) tupleState { |
||||
ts.oe = openEdgeIntoValue(val.Value, ts.oe) |
||||
return ts |
||||
}, |
||||
), |
||||
Reduction[*OpenEdge, tupleState, tupleState]( |
||||
tuple, |
||||
tuple, |
||||
tupleOpenEdgeTail, |
||||
func(oe *OpenEdge, ts tupleState) tupleState { |
||||
ts.oe = oe |
||||
return ts |
||||
}, |
||||
), |
||||
) |
||||
|
||||
tupleOpenEdgeTail.Symbol = FirstOf( |
||||
tupleEnd, |
||||
Prefixed[Located[rune], tupleState](trimmedRune(','), tupleTail), |
||||
) |
||||
|
||||
tupleOpenEdgeValueTail.Symbol = FirstOf[tupleState]( |
||||
tupleOpenEdgeTail, |
||||
Prefixed[Located[rune], tupleState](trimmedRune('<'), tupleOpenEdge), |
||||
) |
||||
|
||||
graphSym.Symbol = Reduction[Located[rune], graphState, Located[Value]]( |
||||
s("graph"), |
||||
trimmedRune('{'), |
||||
graphTail, |
||||
func(r Located[rune], gs graphState) Located[Value] { |
||||
if gs.g == nil { |
||||
gs.g = new(Graph) |
||||
} |
||||
|
||||
return Located[Value]{r.Location, Value{Graph: gs.g}} |
||||
}, |
||||
) |
||||
|
||||
graphTail.Symbol = FirstOf( |
||||
graphEnd, |
||||
Reduction( |
||||
name, |
||||
name, |
||||
Prefixed[Located[rune], graphState]( |
||||
trimmedRune('='), graphOpenEdge, |
||||
), |
||||
func(name Located[Value], gs graphState) graphState { |
||||
if gs.g == nil { |
||||
gs.g = new(Graph) |
||||
} |
||||
|
||||
gs.g = gs.g.AddValueIn(name.Value, gs.oe) |
||||
gs.oe = nil |
||||
return gs |
||||
}, |
||||
), |
||||
) |
||||
log.Printf("populated graphTail with %v", graphTail) |
||||
|
||||
graphOpenEdge.Symbol = FirstOf( |
||||
Reduction[Located[Value], graphState, graphState]( |
||||
value, |
||||
value, |
||||
graphOpenEdgeValueTail, |
||||
func(val Located[Value], gs graphState) graphState { |
||||
gs.oe = openEdgeIntoValue(val.Value, gs.oe) |
||||
return gs |
||||
}, |
||||
), |
||||
Reduction[*OpenEdge, graphState, graphState]( |
||||
tuple, |
||||
tuple, |
||||
graphOpenEdgeTail, |
||||
func(oe *OpenEdge, gs graphState) graphState { |
||||
gs.oe = oe |
||||
return gs |
||||
}, |
||||
), |
||||
) |
||||
|
||||
graphOpenEdgeTail.Symbol = FirstOf( |
||||
graphEnd, |
||||
Prefixed[Located[rune], graphState](trimmedRune(';'), graphTail), |
||||
) |
||||
log.Printf("populated graphOpenEdgeTail with %v", graphOpenEdgeTail) |
||||
|
||||
graphOpenEdgeValueTail.Symbol = FirstOf[graphState]( |
||||
graphOpenEdgeTail, |
||||
Prefixed[Located[rune], graphState](trimmedRune('<'), graphOpenEdge), |
||||
) |
||||
|
||||
value.Symbol = trimmed(FirstOf[Located[Value]](name, number, graphSym)) |
||||
|
||||
return graphSym, value |
||||
}() |
||||
|
||||
// Decoder reads Values off of an io.Reader, or return io.EOF.
|
||||
type Decoder interface { |
||||
Next() (Located[Value], error) |
||||
} |
||||
|
||||
func (d *Decoder) nextLoc() Location { |
||||
if len(d.unread) > 0 { |
||||
return d.unread[len(d.unread)-1].Location |
||||
} |
||||
type decoder struct { |
||||
r Reader |
||||
} |
||||
|
||||
return d.brNextLoc |
||||
// NewDecoder returns a Decoder which reads off the given io.Reader. The
|
||||
// io.Reader should not be read from after this call.
|
||||
func NewDecoder(r io.Reader) Decoder { |
||||
return &decoder{r: NewReader(r)} |
||||
} |
||||
|
||||
// Next returns the next top-level value in the stream, or io.EOF.
|
||||
func (d *Decoder) Next() (Value, error) { |
||||
return topLevelTerm.decodeFn(d) |
||||
func (d *decoder) Next() (Located[Value], error) { |
||||
return value.Decode(d.r) |
||||
} |
||||
|
@ -0,0 +1,27 @@ |
||||
// Package grammar is used for parsing a stream of runes according to a set of
|
||||
// grammatical rules. This package only supports context-free grammars.
|
||||
package grammar |
||||
|
||||
import "fmt" |
||||
|
||||
// Stringer is a convenience tool for working with fmt.Stringer. Exactly one of
|
||||
// the fields must be set, and will be used to implement the fmt.Stringer
|
||||
// interface.
|
||||
type Stringer struct { |
||||
I fmt.Stringer |
||||
F func() string |
||||
S string |
||||
} |
||||
|
||||
func (s Stringer) String() string { |
||||
switch { |
||||
case s.I != nil: |
||||
return s.I.String() |
||||
case s.F != nil: |
||||
return s.F() |
||||
case s.S != "": |
||||
return s.S |
||||
default: |
||||
panic("no fields set on Stringer") |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
package grammar |
||||
|
||||
import "fmt" |
||||
|
||||
// Location indicates a position in a stream of runes identified by column
|
||||
// within newline-separated rows.
|
||||
type Location struct { |
||||
Row, Col int |
||||
} |
||||
|
||||
func (l Location) errf(str string, args ...any) LocatedError { |
||||
return LocatedError{l, fmt.Errorf(str, args...)} |
||||
} |
||||
|
||||
// Located wraps a value so that it has a Location attached to it.
|
||||
type Located[T any] struct { |
||||
Location |
||||
Value T |
||||
} |
||||
|
||||
// LocatedError is an error related to a specific point within a stream of
|
||||
// runes.
|
||||
type LocatedError Located[error] |
||||
|
||||
func (e LocatedError) Error() string { |
||||
return fmt.Sprintf("%d:%d: %v", e.Row, e.Col, e.Value) |
||||
} |
@ -0,0 +1,74 @@ |
||||
package grammar |
||||
|
||||
import ( |
||||
"bufio" |
||||
"io" |
||||
) |
||||
|
||||
// Reader is used for reading Runes from a stream.
|
||||
type Reader interface { |
||||
|
||||
// ReadRune reads the next Rune off the stream, or returns io.EOF.
|
||||
ReadRune() (Located[rune], error) |
||||
|
||||
// UnreadRune can be used to place a Rune onto an internal buffer, such that
|
||||
// the Rune will be the next to be read using ReadRune. If called multiple
|
||||
// times then ReadRune will produce the given Runes in LIFO order.
|
||||
UnreadRune(Located[rune]) |
||||
|
||||
// NextLocation returns the Location of the next Rune which will be returned
|
||||
// with ReadRune.
|
||||
NextLocation() Location |
||||
} |
||||
|
||||
type reader struct { |
||||
br *bufio.Reader |
||||
brNextLoc Location |
||||
|
||||
unread []Located[rune] |
||||
} |
||||
|
||||
// NewReader wraps the io.Reader as a Reader. The given Reader should not be
|
||||
// read from after this call.
|
||||
func NewReader(r io.Reader) Reader { |
||||
return &reader{ |
||||
br: bufio.NewReader(r), |
||||
brNextLoc: Location{Row: 1, Col: 1}, |
||||
} |
||||
} |
||||
|
||||
func (rr *reader) ReadRune() (Located[rune], error) { |
||||
if len(rr.unread) > 0 { |
||||
r := rr.unread[len(rr.unread)-1] |
||||
rr.unread = rr.unread[:len(rr.unread)-1] |
||||
return r, nil |
||||
} |
||||
|
||||
loc := rr.brNextLoc |
||||
|
||||
r, _, err := rr.br.ReadRune() |
||||
if err != nil { |
||||
return Located[rune]{}, err |
||||
} |
||||
|
||||
if r == '\n' { |
||||
rr.brNextLoc.Row++ |
||||
rr.brNextLoc.Col = 1 |
||||
} else { |
||||
rr.brNextLoc.Col++ |
||||
} |
||||
|
||||
return Located[rune]{loc, r}, nil |
||||
} |
||||
|
||||
func (rr *reader) UnreadRune(r Located[rune]) { |
||||
rr.unread = append(rr.unread, r) |
||||
} |
||||
|
||||
func (rr *reader) NextLocation() Location { |
||||
if len(rr.unread) > 0 { |
||||
return rr.unread[len(rr.unread)-1].Location |
||||
} |
||||
|
||||
return rr.brNextLoc |
||||
} |
@ -0,0 +1,294 @@ |
||||
package grammar |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
) |
||||
|
||||
// ErrNoMatch is used by Symbol's Decode method, see that method's docs for more
|
||||
// details.
|
||||
var ErrNoMatch = errors.New("no match") |
||||
|
||||
// Symbol represents a symbol in the grammar. A Symbol is expected to be
|
||||
// stateless, and is usually constructed from other Symbols using functions in
|
||||
// this package.
|
||||
type Symbol[T any] interface { |
||||
fmt.Stringer // Used when generating errors related to this Symbol, e.g. "number"
|
||||
|
||||
// Decode reads and parses a value represented by this Symbol off the
|
||||
// Reader.
|
||||
//
|
||||
// This may return ErrNoMatch to indicate that the upcoming data on the
|
||||
// Reader is rejected by this Symbol. In this case the Symbol should leave
|
||||
// the Reader in the same state it was passed.
|
||||
Decode(Reader) (T, error) |
||||
} |
||||
|
||||
type symbol[T any] struct { |
||||
fmt.Stringer |
||||
decodeFn func(Reader) (T, error) |
||||
} |
||||
|
||||
func (s *symbol[T]) Decode(r Reader) (T, error) { return s.decodeFn(r) } |
||||
|
||||
// SymbolPtr wraps a Symbol in a such a way as to make lazily initializing a
|
||||
// Symbol variable possible. This allows for recursion amongst different
|
||||
// Symbols.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// a := new(SymbolPtr)
|
||||
// b := new(SymbolPtr)
|
||||
// a.Symbol = FirstOf(Rune('a'), b)
|
||||
// b.Symbol = FirstOf(Rune('b'), a)
|
||||
type SymbolPtr[T any] struct { |
||||
Symbol[T] |
||||
} |
||||
|
||||
// RuneFunc matches and produces any rune for which the given function returns
|
||||
// true.
|
||||
func RuneFunc(stringer fmt.Stringer, fn func(rune) bool) Symbol[Located[rune]] { |
||||
return &symbol[Located[rune]]{ |
||||
stringer, |
||||
func(rr Reader) (Located[rune], error) { |
||||
var zero Located[rune] |
||||
|
||||
r, err := rr.ReadRune() |
||||
if errors.Is(err, io.EOF) { |
||||
return zero, ErrNoMatch |
||||
} else if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
if !fn(r.Value) { |
||||
rr.UnreadRune(r) |
||||
return zero, ErrNoMatch |
||||
} |
||||
|
||||
return r, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Rune matches and produces the given rune.
|
||||
func Rune(r rune) Symbol[Located[rune]] { |
||||
return RuneFunc( |
||||
Stringer{S: fmt.Sprintf("'%c'", r)}, |
||||
func(r2 rune) bool { return r == r2 }, |
||||
) |
||||
} |
||||
|
||||
// StringFromRunes produces a string from the slice of runes produced by the
|
||||
// given Symbol. The slice must not be empty. StringFromRunes does not match if
|
||||
// the given Symbol does not match.
|
||||
func StringFromRunes(sym Symbol[[]Located[rune]]) Symbol[Located[string]] { |
||||
return Mapping(sym, sym, func(runes []Located[rune]) Located[string] { |
||||
if len(runes) == 0 { |
||||
panic("StringFromRunes used on empty set of runes") |
||||
} |
||||
|
||||
str := make([]rune, len(runes)) |
||||
for i := range runes { |
||||
str[i] = runes[i].Value |
||||
} |
||||
return Located[string]{runes[0].Location, string(str)} |
||||
}) |
||||
} |
||||
|
||||
// Mapping produces a value of type Tb by decoding a value from the given
|
||||
// Symbol and passing it through the given mapping function. If the given Symbol
|
||||
// doesn't match then neither does Map.
|
||||
func Mapping[Ta, Tb any]( |
||||
stringer fmt.Stringer, sym Symbol[Ta], fn func(Ta) Tb, |
||||
) Symbol[Tb] { |
||||
return &symbol[Tb]{ |
||||
stringer, |
||||
func(rr Reader) (Tb, error) { |
||||
var zero Tb |
||||
va, err := sym.Decode(rr) |
||||
if err != nil { |
||||
return zero, err |
||||
} |
||||
return fn(va), nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// OneOrMore will produce as many of the given Symbol's value as can be found
|
||||
// sequentially, up until a non-matching value is encountered. If no matches are
|
||||
// found then OneOrMore does not match.
|
||||
func OneOrMore[T any](sym Symbol[T]) Symbol[[]T] { |
||||
return &symbol[[]T]{ |
||||
Stringer{F: func() string { |
||||
return fmt.Sprintf("one or more %v", sym) |
||||
}}, |
||||
func(rr Reader) ([]T, error) { |
||||
var vv []T |
||||
for { |
||||
v, err := sym.Decode(rr) |
||||
if errors.Is(err, ErrNoMatch) { |
||||
break |
||||
} else if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
vv = append(vv, v) |
||||
} |
||||
|
||||
if len(vv) == 0 { |
||||
return nil, ErrNoMatch |
||||
} |
||||
|
||||
return vv, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// ZeroOrMore will produce as many of the given Symbol's value as can be found
|
||||
// sequentially, up until a non-matching value is encountered. If no matches are
|
||||
// found then an empty slice is produced.
|
||||
func ZeroOrMore[T any](sym Symbol[T]) Symbol[[]T] { |
||||
return &symbol[[]T]{ |
||||
Stringer{F: func() string { |
||||
return fmt.Sprintf("zero or more %v", sym) |
||||
}}, |
||||
func(rr Reader) ([]T, error) { |
||||
var vv []T |
||||
for { |
||||
v, err := sym.Decode(rr) |
||||
if errors.Is(err, ErrNoMatch) { |
||||
break |
||||
} else if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
vv = append(vv, v) |
||||
} |
||||
|
||||
return vv, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func firstOf[T any](stringer fmt.Stringer, syms ...Symbol[T]) Symbol[T] { |
||||
return &symbol[T]{ |
||||
stringer, |
||||
func(rr Reader) (T, error) { |
||||
var zero T |
||||
for _, sym := range syms { |
||||
v, err := sym.Decode(rr) |
||||
if errors.Is(err, ErrNoMatch) { |
||||
continue |
||||
} else if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
return v, nil |
||||
} |
||||
|
||||
return zero, ErrNoMatch |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// FirstOf matches and produces the value for the first Symbol in the list which
|
||||
// matches. FirstOf does not match if none of the given Symbols match.
|
||||
func FirstOf[T any](syms ...Symbol[T]) Symbol[T] { |
||||
return firstOf( |
||||
Stringer{F: func() string { |
||||
descrs := make([]string, len(syms)) |
||||
for i := range syms { |
||||
descrs[i] = syms[i].String() |
||||
} |
||||
return strings.Join(descrs, " or ") |
||||
}}, |
||||
syms..., |
||||
) |
||||
} |
||||
|
||||
// Reduction produces a value of type Tc by first reading a value from symA,
|
||||
// then symB, and then running those through the given function.
|
||||
//
|
||||
// If symA does not match then Reduction does not match. If symA matches but
|
||||
// symB does not then also match then Reduction produces a LocatedError.
|
||||
func Reduction[Ta, Tb, Tc any]( |
||||
stringer fmt.Stringer, |
||||
symA Symbol[Ta], |
||||
symB Symbol[Tb], |
||||
fn func(Ta, Tb) Tc, |
||||
) Symbol[Tc] { |
||||
return &symbol[Tc]{ |
||||
stringer, |
||||
func(rr Reader) (Tc, error) { |
||||
var zero Tc |
||||
|
||||
va, err := symA.Decode(rr) |
||||
if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
vb, err := symB.Decode(rr) |
||||
if errors.Is(err, ErrNoMatch) { |
||||
return zero, rr.NextLocation().errf("expected %v", symB) |
||||
} else if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
return fn(va, vb), nil |
||||
|
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Prefixed matches on prefixSym, discards its value, then produces the value
|
||||
// produced by sym.
|
||||
//
|
||||
// If prefixSym does not match then Prefixed does not match. If prefixSym
|
||||
// matches but sym does not also match then Prefixed produces a LocatedError.
|
||||
func Prefixed[Ta, Tb any](prefixSym Symbol[Ta], sym Symbol[Tb]) Symbol[Tb] { |
||||
return Reduction(prefixSym, prefixSym, sym, func(_ Ta, b Tb) Tb { |
||||
return b |
||||
}) |
||||
} |
||||
|
||||
// PrefixDiscarded is similar to Prefixed, except that if sym does not match
|
||||
// then PrefixDiscarded does not match, whereas Prefixed produces a LocatedError
|
||||
// in that case.
|
||||
//
|
||||
// NOTE PrefixDiscarded does not fully honor the contract of Symbol. If
|
||||
// prefixSym matches, but sym does not, then only sym will restore Reader to its
|
||||
// prior state; prefixSym cannot return whatever data it read back onto the
|
||||
// Reader. Therefore ErrNoMatch can be returned without Reader being fully back
|
||||
// in its original state. In practice this isn't a big deal, given the common
|
||||
// use-cases of PrefixDiscarded, but it may prove tricky.
|
||||
func PrefixDiscarded[Ta, Tb any](prefixSym Symbol[Ta], sym Symbol[Tb]) Symbol[Tb] { |
||||
return &symbol[Tb]{ |
||||
sym, |
||||
func(rr Reader) (Tb, error) { |
||||
var zero Tb |
||||
if _, err := prefixSym.Decode(rr); err != nil { |
||||
return zero, err |
||||
} |
||||
return sym.Decode(rr) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// Suffixed matchs on sym and then suffixSym, returning the value produced by
|
||||
// sym and discarding the one produced by suffixSym.
|
||||
//
|
||||
// If sym does not match then Suffixed does not match. If sym matches but
|
||||
// suffixSym does not also match then Suffixed produces a LocatedError.
|
||||
func Suffixed[Ta, Tb any](sym Symbol[Ta], suffixSym Symbol[Tb]) Symbol[Ta] { |
||||
return Reduction(sym, sym, suffixSym, func(a Ta, _ Tb) Ta { |
||||
return a |
||||
}) |
||||
} |
||||
|
||||
// Discard matches if the given Symbol does, but discards the value it produces,
|
||||
// producing an empty value instead.
|
||||
func Discard[T any](sym Symbol[T]) Symbol[struct{}] { |
||||
return Mapping(sym, sym, func(T) struct{} { return struct{}{} }) |
||||
} |
@ -1,36 +0,0 @@ |
||||
package gg |
||||
|
||||
import "fmt" |
||||
|
||||
// Location indicates a position in a stream of bytes identified by column
|
||||
// within newline-separated rows.
|
||||
type Location struct { |
||||
Row, Col int |
||||
} |
||||
|
||||
func (l Location) errf(str string, args ...any) LocatedError { |
||||
return LocatedError{l, fmt.Errorf(str, args...)} |
||||
} |
||||
|
||||
func (l Location) locate() Location { return l } |
||||
|
||||
// LocatedError is an error related to a specific point within a decode gg
|
||||
// stream.
|
||||
type LocatedError struct { |
||||
Location |
||||
Err error |
||||
} |
||||
|
||||
func (e LocatedError) Error() string { |
||||
return fmt.Sprintf("%d:%d: %v", e.Row, e.Col, e.Err) |
||||
} |
||||
|
||||
type locatableRune struct { |
||||
Location |
||||
r rune |
||||
} |
||||
|
||||
type locatableString struct { |
||||
Location |
||||
str string |
||||
} |
@ -1,508 +0,0 @@ |
||||
package gg |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"strconv" |
||||
"strings" |
||||
"unicode" |
||||
|
||||
"github.com/mediocregopher/ginger/graph" |
||||
"golang.org/x/exp/slices" |
||||
) |
||||
|
||||
var ( |
||||
errNoMatch = errors.New("not found") |
||||
) |
||||
|
||||
type stringerFn func() string |
||||
|
||||
func (fn stringerFn) String() string { |
||||
return fn() |
||||
} |
||||
|
||||
type stringerStr string |
||||
|
||||
func (str stringerStr) String() string { |
||||
return string(str) |
||||
} |
||||
|
||||
type term[T any] struct { |
||||
name fmt.Stringer |
||||
decodeFn func(d *Decoder) (T, error) |
||||
} |
||||
|
||||
func (t term[T]) String() string { |
||||
return t.name.String() |
||||
} |
||||
|
||||
func firstOf[T any](terms ...*term[T]) *term[T] { |
||||
if len(terms) < 2 { |
||||
panic("firstOfTerms requires at least 2 terms") |
||||
} |
||||
|
||||
return &term[T]{ |
||||
name: stringerFn(func() string { |
||||
descrs := make([]string, len(terms)) |
||||
for i := range terms { |
||||
descrs[i] = terms[i].String() |
||||
} |
||||
return strings.Join(descrs, " or ") |
||||
}), |
||||
decodeFn: func(d *Decoder) (T, error) { |
||||
var zero T |
||||
for _, t := range terms { |
||||
v, err := t.decodeFn(d) |
||||
if errors.Is(err, errNoMatch) { |
||||
continue |
||||
} else if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
return v, nil |
||||
} |
||||
|
||||
return zero, errNoMatch |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func seq[Ta, Tb, Tc any]( |
||||
name fmt.Stringer, |
||||
termA *term[Ta], |
||||
termB *term[Tb], |
||||
fn func(Ta, Tb) Tc, |
||||
) *term[Tc] { |
||||
return &term[Tc]{ |
||||
name: name, |
||||
decodeFn: func(d *Decoder) (Tc, error) { |
||||
var zero Tc |
||||
|
||||
va, err := termA.decodeFn(d) |
||||
if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
vb, err := termB.decodeFn(d) |
||||
if errors.Is(err, errNoMatch) { |
||||
return zero, d.nextLoc().errf("expected %v", termB) |
||||
} else if err != nil { |
||||
return zero, err |
||||
} |
||||
|
||||
return fn(va, vb), nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func prefixed[Ta, Tb any](termA *term[Ta], termB *term[Tb]) *term[Tb] { |
||||
return seq(termA, termA, termB, func(_ Ta, b Tb) Tb { |
||||
return b |
||||
}) |
||||
} |
||||
|
||||
func prefixIgnored[Ta, Tb any](termA *term[Ta], termB *term[Tb]) *term[Tb] { |
||||
return &term[Tb]{ |
||||
name: termB, |
||||
decodeFn: func(d *Decoder) (Tb, error) { |
||||
var zero Tb |
||||
if _, err := termA.decodeFn(d); err != nil { |
||||
return zero, err |
||||
} |
||||
return termB.decodeFn(d) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func suffixIgnored[Ta, Tb any]( |
||||
termA *term[Ta], termB *term[Tb], |
||||
) *term[Ta] { |
||||
return seq(termA, termA, termB, func(a Ta, _ Tb) Ta { |
||||
return a |
||||
}) |
||||
} |
||||
|
||||
func oneOrMore[T any](t *term[T]) *term[[]T] { |
||||
return &term[[]T]{ |
||||
name: stringerFn(func() string { |
||||
return fmt.Sprintf("one or more %v", t) |
||||
}), |
||||
decodeFn: func(d *Decoder) ([]T, error) { |
||||
var vv []T |
||||
for { |
||||
v, err := t.decodeFn(d) |
||||
if errors.Is(err, errNoMatch) { |
||||
break |
||||
} else if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
vv = append(vv, v) |
||||
} |
||||
|
||||
if len(vv) == 0 { |
||||
return nil, errNoMatch |
||||
} |
||||
|
||||
return vv, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func zeroOrMore[T any](t *term[T]) *term[[]T] { |
||||
return &term[[]T]{ |
||||
name: stringerFn(func() string { |
||||
return fmt.Sprintf("zero or more %v", t) |
||||
}), |
||||
decodeFn: func(d *Decoder) ([]T, error) { |
||||
var vv []T |
||||
for { |
||||
v, err := t.decodeFn(d) |
||||
if errors.Is(err, errNoMatch) { |
||||
break |
||||
} else if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
vv = append(vv, v) |
||||
} |
||||
|
||||
return vv, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func mapTerm[Ta, Tb any]( |
||||
name fmt.Stringer, t *term[Ta], fn func(Ta) Tb, |
||||
) *term[Tb] { |
||||
return &term[Tb]{ |
||||
name: name, |
||||
decodeFn: func(d *Decoder) (Tb, error) { |
||||
var zero Tb |
||||
va, err := t.decodeFn(d) |
||||
if err != nil { |
||||
return zero, err |
||||
} |
||||
return fn(va), nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func runePredTerm( |
||||
name fmt.Stringer, pred func(rune) bool, |
||||
) *term[locatableRune] { |
||||
return &term[locatableRune]{ |
||||
name: name, |
||||
decodeFn: func(d *Decoder) (locatableRune, error) { |
||||
lr, err := d.readRune() |
||||
if errors.Is(err, io.EOF) { |
||||
return locatableRune{}, errNoMatch |
||||
} else if err != nil { |
||||
return locatableRune{}, err |
||||
} |
||||
|
||||
if !pred(lr.r) { |
||||
d.unreadRune(lr) |
||||
return locatableRune{}, errNoMatch |
||||
} |
||||
|
||||
return lr, nil |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func runeTerm(r rune) *term[locatableRune] { |
||||
return runePredTerm( |
||||
stringerStr(fmt.Sprintf("'%c'", r)), |
||||
func(r2 rune) bool { return r2 == r }, |
||||
) |
||||
} |
||||
|
||||
func locatableRunesToString(rr []locatableRune) string { |
||||
str := make([]rune, len(rr)) |
||||
for i := range rr { |
||||
str[i] = rr[i].r |
||||
} |
||||
return string(str) |
||||
} |
||||
|
||||
func runesToStringTerm( |
||||
t *term[[]locatableRune], |
||||
) *term[locatableString] { |
||||
return mapTerm( |
||||
t, t, func(rr []locatableRune) locatableString { |
||||
return locatableString{rr[0].locate(), locatableRunesToString(rr)} |
||||
}, |
||||
) |
||||
} |
||||
|
||||
func discard[T any](t *term[T]) *term[struct{}] { |
||||
return mapTerm(t, t, func(_ T) struct{} { return struct{}{} }) |
||||
} |
||||
|
||||
var ( |
||||
notNewlineTerm = runePredTerm( |
||||
stringerStr("not-newline"), |
||||
func(r rune) bool { return r != '\n' }, |
||||
) |
||||
|
||||
commentTerm = prefixed( |
||||
prefixed(runeTerm('*'), zeroOrMore(notNewlineTerm)), |
||||
runeTerm('\n'), |
||||
) |
||||
|
||||
whitespaceTerm = zeroOrMore(firstOf( |
||||
discard(runePredTerm(stringerStr("whitespace"), unicode.IsSpace)), |
||||
discard(commentTerm), |
||||
)) |
||||
) |
||||
|
||||
func trimmedTerm[T any](t *term[T]) *term[T] { |
||||
t = prefixIgnored(whitespaceTerm, t) |
||||
t = suffixIgnored(t, whitespaceTerm) |
||||
return t |
||||
} |
||||
|
||||
func trimmedRuneTerm(r rune) *term[locatableRune] { |
||||
return trimmedTerm(runeTerm(r)) |
||||
} |
||||
|
||||
var ( |
||||
digitTerm = runePredTerm( |
||||
stringerStr("digit"), |
||||
func(r rune) bool { return '0' <= r && r <= '9' }, |
||||
) |
||||
|
||||
positiveNumberTerm = runesToStringTerm(oneOrMore(digitTerm)) |
||||
|
||||
negativeNumberTerm = seq( |
||||
stringerStr("negative-number"), |
||||
runeTerm('-'), |
||||
positiveNumberTerm, |
||||
func(neg locatableRune, posNum locatableString) locatableString { |
||||
return locatableString{neg.locate(), string(neg.r) + posNum.str} |
||||
}, |
||||
) |
||||
|
||||
numberTerm = mapTerm( |
||||
stringerStr("number"), |
||||
firstOf(negativeNumberTerm, positiveNumberTerm), |
||||
func(str locatableString) Value { |
||||
i, err := strconv.ParseInt(str.str, 10, 64) |
||||
if err != nil { |
||||
panic(fmt.Errorf("parsing %q as int: %w", str, err)) |
||||
} |
||||
|
||||
return Value{Number: &i, Location: str.locate()} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
var ( |
||||
letterTerm = runePredTerm( |
||||
stringerStr("letter"), |
||||
func(r rune) bool { |
||||
return unicode.In(r, unicode.Letter, unicode.Mark) |
||||
}, |
||||
) |
||||
|
||||
letterTailTerm = zeroOrMore(firstOf(letterTerm, digitTerm)) |
||||
|
||||
nameTerm = seq( |
||||
stringerStr("name"), |
||||
letterTerm, |
||||
letterTailTerm, |
||||
func(head locatableRune, tail []locatableRune) Value { |
||||
name := string(head.r) + locatableRunesToString(tail) |
||||
return Value{Name: &name, Location: head.locate()} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
func openEdgeIntoValue(val Value, oe *OpenEdge) *OpenEdge { |
||||
switch { |
||||
case oe == nil: |
||||
return graph.ValueOut(None, val) |
||||
case !oe.EdgeValue().Valid: |
||||
return oe.WithEdgeValue(Some(val)) |
||||
default: |
||||
return graph.TupleOut(Some(val), oe) |
||||
} |
||||
} |
||||
|
||||
var graphTerm, valueTerm = func() (*term[Value], *term[Value]) { |
||||
type tupleState struct { |
||||
ins []*OpenEdge |
||||
oe *OpenEdge |
||||
} |
||||
|
||||
type graphState struct { |
||||
g *Graph |
||||
oe *OpenEdge |
||||
} |
||||
|
||||
var ( |
||||
rightParenthesis = trimmedRuneTerm(')') |
||||
tupleEndTerm = mapTerm( |
||||
rightParenthesis, |
||||
rightParenthesis, |
||||
func(lr locatableRune) tupleState { |
||||
// if ')', then map that to an empty state. This acts as a
|
||||
// sentinel value to indicate "end of tuple".
|
||||
return tupleState{} |
||||
}, |
||||
) |
||||
|
||||
rightCurlyBrace = trimmedRuneTerm('}') |
||||
graphEndTerm = mapTerm( |
||||
rightCurlyBrace, |
||||
rightCurlyBrace, |
||||
func(lr locatableRune) graphState { |
||||
// if '}', then map that to an empty state. This acts as a
|
||||
// sentinel value to indicate "end of graph".
|
||||
return graphState{} |
||||
}, |
||||
) |
||||
) |
||||
|
||||
var ( |
||||
// pre-define these, and then fill in the pointers after, in order to
|
||||
// deal with recursive dependencies between them.
|
||||
valueTerm = new(term[Value]) |
||||
|
||||
tupleTerm = new(term[*OpenEdge]) |
||||
tupleTailTerm = new(term[tupleState]) |
||||
tupleOpenEdgeTerm = new(term[tupleState]) |
||||
tupleOpenEdgeTailTerm = new(term[tupleState]) |
||||
tupleOpenEdgeValueTailTerm = new(term[tupleState]) |
||||
|
||||
graphTerm = new(term[Value]) |
||||
graphTailTerm = new(term[graphState]) |
||||
graphOpenEdgeTerm = new(term[graphState]) |
||||
graphOpenEdgeTailTerm = new(term[graphState]) |
||||
graphOpenEdgeValueTailTerm = new(term[graphState]) |
||||
) |
||||
|
||||
*tupleTerm = *seq( |
||||
stringerStr("tuple"), |
||||
trimmedRuneTerm('('), |
||||
tupleTailTerm, |
||||
func(lr locatableRune, ts tupleState) *OpenEdge { |
||||
slices.Reverse(ts.ins) |
||||
return graph.TupleOut(None, ts.ins...) |
||||
}, |
||||
) |
||||
|
||||
*tupleTailTerm = *firstOf( |
||||
tupleEndTerm, |
||||
mapTerm( |
||||
tupleOpenEdgeTerm, |
||||
tupleOpenEdgeTerm, |
||||
func(ts tupleState) tupleState { |
||||
ts.ins = append(ts.ins, ts.oe) |
||||
ts.oe = nil |
||||
return ts |
||||
}, |
||||
), |
||||
) |
||||
|
||||
*tupleOpenEdgeTerm = *firstOf( |
||||
seq( |
||||
valueTerm, |
||||
valueTerm, |
||||
tupleOpenEdgeValueTailTerm, |
||||
func(val Value, ts tupleState) tupleState { |
||||
ts.oe = openEdgeIntoValue(val, ts.oe) |
||||
return ts |
||||
}, |
||||
), |
||||
seq( |
||||
tupleTerm, |
||||
tupleTerm, |
||||
tupleOpenEdgeTailTerm, |
||||
func(oe *OpenEdge, ts tupleState) tupleState { |
||||
ts.oe = oe |
||||
return ts |
||||
}, |
||||
), |
||||
) |
||||
|
||||
*tupleOpenEdgeTailTerm = *firstOf( |
||||
tupleEndTerm, |
||||
prefixed(trimmedRuneTerm(','), tupleTailTerm), |
||||
) |
||||
|
||||
*tupleOpenEdgeValueTailTerm = *firstOf( |
||||
tupleOpenEdgeTailTerm, |
||||
prefixed(trimmedRuneTerm('<'), tupleOpenEdgeTerm), |
||||
) |
||||
|
||||
*graphTerm = *seq( |
||||
stringerStr("graph"), |
||||
trimmedRuneTerm('{'), |
||||
graphTailTerm, |
||||
func(lr locatableRune, gs graphState) Value { |
||||
if gs.g == nil { |
||||
gs.g = new(Graph) |
||||
} |
||||
|
||||
return Value{Graph: gs.g, Location: lr.locate()} |
||||
}, |
||||
) |
||||
|
||||
*graphTailTerm = *firstOf( |
||||
graphEndTerm, |
||||
seq( |
||||
nameTerm, |
||||
nameTerm, |
||||
prefixed(trimmedRuneTerm('='), graphOpenEdgeTerm), |
||||
func(name Value, gs graphState) graphState { |
||||
if gs.g == nil { |
||||
gs.g = new(Graph) |
||||
} |
||||
|
||||
gs.g = gs.g.AddValueIn(name, gs.oe) |
||||
gs.oe = nil |
||||
return gs |
||||
}, |
||||
), |
||||
) |
||||
|
||||
*graphOpenEdgeTerm = *firstOf( |
||||
seq( |
||||
valueTerm, |
||||
valueTerm, |
||||
graphOpenEdgeValueTailTerm, |
||||
func(val Value, gs graphState) graphState { |
||||
gs.oe = openEdgeIntoValue(val, gs.oe) |
||||
return gs |
||||
}, |
||||
), |
||||
seq( |
||||
tupleTerm, |
||||
tupleTerm, |
||||
graphOpenEdgeTailTerm, |
||||
func(oe *OpenEdge, gs graphState) graphState { |
||||
gs.oe = oe |
||||
return gs |
||||
}, |
||||
), |
||||
) |
||||
|
||||
*graphOpenEdgeTailTerm = *firstOf( |
||||
graphEndTerm, |
||||
prefixed(trimmedRuneTerm(';'), graphTailTerm), |
||||
) |
||||
|
||||
*graphOpenEdgeValueTailTerm = *firstOf( |
||||
graphOpenEdgeTailTerm, |
||||
prefixed(trimmedRuneTerm('<'), graphOpenEdgeTerm), |
||||
) |
||||
|
||||
*valueTerm = *firstOf(nameTerm, numberTerm, graphTerm) |
||||
|
||||
return graphTerm, valueTerm |
||||
}() |
||||
|
||||
var topLevelTerm = trimmedTerm(valueTerm) |
Loading…
Reference in new issue