224 lines
4.2 KiB
Go
224 lines
4.2 KiB
Go
package chunk
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"sectorinf.com/emilis/flabk/pkg/coll"
|
|
)
|
|
|
|
var (
|
|
ErrNoMatch = errors.New("no matching")
|
|
ErrMapMemberNotString = errors.New("map member not a string")
|
|
ErrUnexpectedSymbol = errors.New("unexpected symbol")
|
|
)
|
|
|
|
type Chunk struct {
|
|
vector coll.Vector[byte]
|
|
posLeft int
|
|
posRight int
|
|
// todo global pos impl
|
|
globLeft int
|
|
globRight int
|
|
}
|
|
|
|
// Match finds the matching closer to the symbol at the left position and sets the
|
|
// right position to this index
|
|
func (c Chunk) Match() (Chunk, error) {
|
|
s, ok := matchers[c.vector[c.posLeft]]
|
|
if !ok {
|
|
panic(fmt.Sprintf("Match called on %c with no matcher defined", c.vector[c.posLeft]))
|
|
}
|
|
if s.StartFromRight {
|
|
for c.posRight > 0 {
|
|
if c.vector[c.posRight] == s.MatchByte {
|
|
return c, nil
|
|
}
|
|
c.posRight--
|
|
c.globRight--
|
|
}
|
|
} else {
|
|
for index := c.posLeft + 1; index < len(c.vector); index++ {
|
|
if c.vector[index] == s.MatchByte {
|
|
c.posRight = index
|
|
c.globRight += (c.posLeft + 1) - index
|
|
return c, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return c, fmt.Errorf("%w %c", ErrNoMatch, c.vector[c.posLeft])
|
|
}
|
|
|
|
func (c Chunk) Copy() Chunk {
|
|
return c
|
|
}
|
|
|
|
func (c Chunk) Left() byte {
|
|
return c.vector[c.posLeft]
|
|
}
|
|
|
|
// Sub returns an inclusive subchunk of [left;right]
|
|
func (c Chunk) Sub() Chunk {
|
|
return New(c.vector[c.posLeft : c.posRight+1])
|
|
}
|
|
|
|
// CookieCutter returns a subchunk of (left;right)
|
|
func (c Chunk) CookieCutter() Chunk {
|
|
return New(c.vector[c.posLeft+1 : c.posRight])
|
|
}
|
|
|
|
func (c Chunk) AtSpace() bool {
|
|
r, _ := utf8.DecodeRune(c.vector[c.posLeft:])
|
|
return unicode.IsSpace(r)
|
|
}
|
|
|
|
func (c Chunk) Seek() Chunk {
|
|
for c.posLeft < len(c.vector) && c.AtSpace() {
|
|
c.posLeft++
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c Chunk) ValueEnd() (Chunk, error) {
|
|
switch c.vector[c.posLeft] {
|
|
case '"', '{', '[':
|
|
return c.Match()
|
|
default:
|
|
for index := c.posLeft; index <= c.posRight; index++ {
|
|
if c.vector[index] == ',' {
|
|
return New(c.vector[c.posLeft:index]), nil
|
|
}
|
|
}
|
|
return c, nil
|
|
}
|
|
}
|
|
|
|
// Skip skips the current left position and then Seeks
|
|
func (c Chunk) Skip() Chunk {
|
|
if c.posLeft+1 < len(c.vector) {
|
|
c.posLeft++
|
|
}
|
|
return c.Seek()
|
|
}
|
|
|
|
type MatchRule struct {
|
|
MatchByte byte
|
|
StartFromRight bool
|
|
}
|
|
|
|
var (
|
|
matchers = map[byte]MatchRule{
|
|
'{': {
|
|
MatchByte: '}',
|
|
StartFromRight: true,
|
|
},
|
|
'[': {
|
|
MatchByte: ']',
|
|
StartFromRight: true,
|
|
},
|
|
'"': {
|
|
MatchByte: '"',
|
|
},
|
|
}
|
|
)
|
|
|
|
func New(v []byte) Chunk {
|
|
posRight := len(v) - 1
|
|
if len(v) == 0 {
|
|
posRight = 0
|
|
}
|
|
return Chunk{
|
|
vector: coll.Vector[byte](v),
|
|
posLeft: 0,
|
|
posRight: posRight,
|
|
}
|
|
}
|
|
|
|
func (c Chunk) Child(left, right int) Chunk {
|
|
sub := c.vector[left:right]
|
|
return Chunk{
|
|
vector: sub,
|
|
posLeft: 0,
|
|
posRight: len(sub),
|
|
globLeft: (c.globLeft - c.posLeft) + left,
|
|
globRight: (c.globRight - c.posRight) + right,
|
|
}
|
|
}
|
|
|
|
type ParseFunc[T any] func(T) (T, error)
|
|
|
|
func Parse[T any](c Chunk) ParseFunc[T] {
|
|
switch c.vector[c.posLeft] {
|
|
case '{':
|
|
return ParseMap[T](c.CookieCutter())
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
// return can be:
|
|
// * {}
|
|
// * []
|
|
// * ""
|
|
// * 123
|
|
// * true
|
|
// * false
|
|
// * null
|
|
}
|
|
|
|
type Row struct {
|
|
Name string
|
|
Value Chunk
|
|
}
|
|
|
|
func (c Chunk) String() string {
|
|
return string(c.vector[c.posLeft : c.posRight+1])
|
|
}
|
|
|
|
func (c Chunk) Row() (Row, error) {
|
|
c = c.Seek()
|
|
if c.vector[c.posLeft] != '"' {
|
|
return Row{}, fmt.Errorf("%w: %c", ErrMapMemberNotString, c.vector[c.posLeft])
|
|
}
|
|
name, err := c.Match()
|
|
if err != nil {
|
|
return Row{}, fmt.Errorf("match: %w", err)
|
|
}
|
|
postName := c.Copy()
|
|
postName.posLeft = name.posRight
|
|
postName = postName.Skip()
|
|
// Next we must get a :
|
|
if postName.vector[postName.posLeft] != ':' {
|
|
return Row{}, fmt.Errorf("%w '%c', expected ':'", ErrUnexpectedSymbol, postName.vector[postName.posLeft])
|
|
}
|
|
value, err := postName.Skip().ValueEnd()
|
|
if err != nil {
|
|
return Row{}, fmt.Errorf("value: %w", err)
|
|
}
|
|
|
|
return Row{
|
|
Name: name.String(),
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
func (c Chunk) After(v Chunk) Chunk {
|
|
c.posLeft = v.posLeft
|
|
return c
|
|
}
|
|
|
|
func ParseMap[T any](c Chunk) ParseFunc[T] {
|
|
return func(t T) (T, error) {
|
|
// mapper := parse.GetMap(t)
|
|
// for {
|
|
// row, err := c.Row()
|
|
// if err != nil {
|
|
// return t, err
|
|
// }
|
|
|
|
// }
|
|
panic("todo")
|
|
}
|
|
}
|