ginger/gg/decoder.go
Brian Picciano 4870455430 Completely refactor gg with new BNF file and decoder
The new gg format is based on a BNF file which can be found in the `gg`
directory. The code for decoding `.gg` files has been refactored to
mirror that file. The result is more resilient parsing, better errors,
and a greater ability to extend the format in the future.

The new decoder is notable in that it does not use a lexer. Both lexing
and parsing are done in a single step.

The format syntax itself has also been modified. Rather than using
semi-colons everywhere, commas are used as separators in tuples.
Additionally the final comma/semi-colon is no longer required.
2023-10-25 11:31:33 +02:00

74 lines
1.4 KiB
Go

package gg
import (
"bufio"
"fmt"
"io"
)
// Decoder reads Value's off of a byte stream.
type Decoder struct {
br *bufio.Reader
brNextLoc Location
unread []locatableRune
lastRead locatableRune
}
// 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 (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
}
loc := d.brNextLoc
r, _, err := d.br.ReadRune()
if err != nil {
return d.lastRead, err
}
if r == '\n' {
d.brNextLoc.Row++
d.brNextLoc.Col = 1
} else {
d.brNextLoc.Col++
}
d.lastRead = locatableRune{loc, r}
return d.lastRead, nil
}
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,
))
}
d.unread = append(d.unread, lr)
}
func (d *Decoder) nextLoc() Location {
if len(d.unread) > 0 {
return d.unread[len(d.unread)-1].Location
}
return d.brNextLoc
}
// Next returns the next top-level value in the stream, or io.EOF.
func (d *Decoder) Next() (Value, error) {
return topLevelTerm.decodeFn(d)
}