264 lines
4.9 KiB
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)
|
|
}
|