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") } }