flabk/pkg/asld/walker.go

264 lines
4.9 KiB
Go

package asld
import (
"bytes"
"fmt"
)
// Walker.... texas ranger.
// Except he's a cringe conservative.
//
// This is also cringe but not for cringe reasons.
type symbol byte
const (
symbolOpenParen symbol = iota
symbolClosedParen
symbolOpenArray
symbolClosedArray
symbolString
symbolColon
symbolNullStart
symbolEOB // End-Of-Buffer
)
const (
statusOK status = iota
statusError
statusWalkerNotAffected
)
var (
stoppable = []SymbolInfo{
symbolOpenParen: {
self: '{',
closer: '}',
},
symbolClosedParen: {
self: '}',
},
symbolOpenArray: {
self: '[',
closer: ']',
},
symbolClosedArray: {
self: ']',
},
symbolString: {
self: '"',
closer: '"',
},
symbolColon: {
self: ':',
},
}
)
type status int
type walkerStatus struct {
walker
status
}
func (w walker) Reset() walker {
w.position = 0
return w
}
func (w walker) Debug() {
for index, b := range w.content {
out := string(b)
if w.position == index {
out = "<[_" + out + "_]>"
}
print(out)
}
print("\n")
print("globPos", w.globPos)
print("\n")
}
func (w walker) SliceInner() (walker, bool) {
// the !ok scenario here is only if the code is bad
s, ok := stoppableByByte[w.Current()]
// Debug
if !ok {
panic(w)
}
var height uint
for pos := w.position + 1; pos < w.len; pos++ {
curr := w.content[pos]
if curr == s.self && s.self != s.closer {
height++
continue
}
if curr == s.closer {
if height == 0 {
return w.Between(w.position+1, pos), ok
}
height--
}
}
return w, false
}
type walker struct {
content []byte
len int
position int
globPos int
}
func newWalker(data []byte) walker {
return walker{
content: data,
len: len(data),
}
}
func (w walker) Between(lower, upper int) walker {
// As lower, and upper, are offsets from the beginning
// of this walker's buffer, we can diff lower and w.position
// for an offset to set global position at
w.content = w.content[lower:upper]
offset := lower - w.position
w.position = 0
w.globPos += offset
return w
}
// Sub returns a subwalker from the current position
func (w walker) Sub() walker {
w.content = w.content[w.position:]
w.len = len(w.content)
w.position = 0
return w
}
// Until returns a subwalker from position 0 to the current position
func (w walker) Until() walker {
w.content = w.content[:w.position]
w.len = len(w.content)
return w
}
func (w walker) Pos(v int) walker {
offset := v - w.position
w.position = v
w.globPos += offset
return w
}
// ToOrStay will stay at where the walker is if b is the
// same as the current walker position.
//
// Otherwise, calls To
func (w walker) ToOrStay(b byte) (walker, bool) {
if w.Current() == b {
return w, true
}
return w.To(b)
}
func (w walker) To(b byte) (walker, bool) {
for pos := w.position + 1; pos < w.len; pos++ {
if w.content[pos] == b {
return w.Pos(pos), true
}
}
return w, false
}
func (w walker) WalkThroughSpaces() walker {
for pos := w.position; pos < w.len; pos++ {
b := w.content[pos]
if _, ok := stoppableByByte[b]; ok || (b >= '0' && b <= '9') || b == '-' || w.IsNullAt(pos) {
return w.Pos(pos)
}
}
return w
}
func (w walker) StayOrNext() (walker, symbol) {
if sym, ok := stoppableByByte[w.content[w.position]]; ok {
return w, sym.enum
}
return w.Next()
}
func (w walker) Next() (walker, symbol) {
w, s := w.ToNext()
return w.Sub(), s
}
func (w walker) IsNullAt(pos int) bool {
return w.content[pos] == 'n' && w.len-pos >= 4 && bytes.Equal(w.content[pos:pos+4], null)
}
func (w walker) ToNext() (walker, symbol) {
for pos := w.position + 1; pos < w.len; pos++ {
if w.IsNullAt(pos) {
return w.Pos(pos), symbolNullStart
}
if s, ok := stoppableByByte[w.content[pos]]; ok {
return w.Pos(pos), s.enum
}
}
return w, symbolEOB
}
// CommaOrEnd walks to the next comma, or, if none
// is present in the current walker scope, returns
// itself with status statusWalkerNotAffected.
func (w walker) CommaOrEnd() (walkerStatus, error) {
if w.Current() == ',' {
w = w.Pos(w.position + 1)
}
for pos := w.position; pos < w.len; pos++ {
if in(openRaw, w.content[pos]) {
sb, ok := stoppableByByte[w.content[pos]]
if !ok {
panic("ok someone fucked up somewhere")
}
w, ok = w.To(sb.closer)
if !ok {
return walkerStatus{
status: statusError,
walker: w,
}, fmt.Errorf("%w %s", ErrNoMatching, string(sb.closer))
}
pos = w.position
continue
}
if w.content[pos] == ',' {
return walkerStatus{
status: statusOK,
walker: w.Pos(pos),
}, nil
}
}
return walkerStatus{
status: statusWalkerNotAffected,
walker: w,
}, nil
}
func (w walker) Current() byte {
return w.content[w.position]
}
func (w walker) CurrentSymbol() (SymbolInfo, bool) {
b, ok := stoppableByByte[w.Current()]
return b, ok
}
func (w walker) String() string {
if w.Current() == '"' {
w, _ = w.SliceInner()
}
return string(w.content)
}