215 lines
5.5 KiB
Go
215 lines
5.5 KiB
Go
// Package snake lets you compare blocks of data where the ordering of certain
|
|
// parts may be swapped every which way. It is designed for comparing the
|
|
// encoding of maps where the ordering of individual elements is inconsistent.
|
|
package snake
|
|
|
|
import "fmt"
|
|
import "strings"
|
|
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
|
|
|
var _ Node = Order { }
|
|
var _ Node = Snake { }
|
|
var _ Node = Leaf { }
|
|
|
|
// Check checks the data against the specified node. If the data doesn't satisfy
|
|
// the node, or the comparison succeded but didn't consume all the data, this
|
|
// function returns false, and the index of the byte where the inequality is.
|
|
func Check(node Node, data []byte) (ok bool, n int) {
|
|
ok, n = node.Check(data)
|
|
if !ok {
|
|
return false, n
|
|
}
|
|
if n != len(data) {
|
|
return false, n
|
|
}
|
|
return true, n
|
|
}
|
|
|
|
// O returns a new order given a vararg node slice.
|
|
func O(nodes ...Node) Order {
|
|
return Order(nodes)
|
|
}
|
|
|
|
// S returns a new snake given a vararg node slice.
|
|
func S(nodes ...Node) Snake {
|
|
return Snake(nodes)
|
|
}
|
|
|
|
// L returns a new leaf given a vararg byte slice.
|
|
func L(data ...byte) Leaf {
|
|
return Leaf([]byte(data))
|
|
}
|
|
|
|
// Order is satisfied when the data satisfies each of its nodes in the order
|
|
// that they are specified in the slice.
|
|
type Order []Node
|
|
|
|
// Check determines if the data satisfies the Order.
|
|
func (this Order) Check(data []byte) (ok bool, n int) {
|
|
left := data
|
|
for _, node := range this {
|
|
ok, nn := node.Check(left)
|
|
n += nn; if !ok { return false, n }
|
|
left = left[nn:]
|
|
}
|
|
return true, n
|
|
}
|
|
|
|
// Flatten returns the Order flattened to a byte array. The result of this
|
|
// function always satisfies the Order.
|
|
func (this Order) Flatten() []byte {
|
|
flat := []byte { }
|
|
for _, node := range this {
|
|
flat = append(flat, node.Flatten()...)
|
|
}
|
|
return flat
|
|
}
|
|
|
|
func (this Order) String() string {
|
|
out := strings.Builder { }
|
|
for index, node := range this {
|
|
if index > 0 {
|
|
fmt.Fprint(&out, " :")
|
|
}
|
|
fmt.Fprintf(&out, " %v", node)
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
// Add returns a new order with the given nodes appended to it.
|
|
func (this Order) Add(nodes ...Node) Order {
|
|
newOrder := make(Order, len(this) + len(nodes))
|
|
copy(newOrder, this)
|
|
copy(newOrder[len(this):], Order(nodes))
|
|
return newOrder
|
|
}
|
|
|
|
// AddO returns a new order with the given order appended to it.
|
|
func (this Order) AddO(nodes ...Node) Order {
|
|
return this.Add(O(nodes...))
|
|
}
|
|
|
|
// AddS returns a new order with the given snake appended to it.
|
|
func (this Order) AddS(nodes ...Node) Order {
|
|
return this.Add(S(nodes...))
|
|
}
|
|
|
|
// AddL returns a new order with the given leaf appended to it.
|
|
func (this Order) AddL(data ...byte) Order {
|
|
return this.Add(L(data...))
|
|
}
|
|
|
|
// Snake is satisfied when the data satisfies each of its nodes in no particular
|
|
// order.
|
|
type Snake []Node
|
|
|
|
// Check determines if the data satisfies the snake.
|
|
func (this Snake) Check(data []byte) (ok bool, n int) {
|
|
fmt.Println("CHECKING SNAKE")
|
|
left := data
|
|
nodes := map[int] Node { }
|
|
for key, node := range this {
|
|
nodes[key] = node
|
|
}
|
|
for len(nodes) > 0 {
|
|
found := false
|
|
for key, node := range nodes {
|
|
fmt.Println(left, key, node)
|
|
ok, nn := node.Check(left)
|
|
fmt.Println(ok, nn)
|
|
if !ok { continue }
|
|
n += nn
|
|
left = data[n:]
|
|
delete(nodes, key)
|
|
found = true
|
|
break
|
|
}
|
|
if !found { return false, n }
|
|
}
|
|
return true, n
|
|
}
|
|
|
|
// Flatten returns the snake flattened to a byte array. The result of this
|
|
// function always satisfies the snake.
|
|
func (this Snake) Flatten() []byte {
|
|
flat := []byte { }
|
|
for _, node := range this {
|
|
flat = append(flat, node.Flatten()...)
|
|
}
|
|
return flat
|
|
}
|
|
|
|
func (this Snake) String() string {
|
|
out := strings.Builder { }
|
|
out.WriteString("[")
|
|
for index, node := range this {
|
|
if index > 0 {
|
|
fmt.Fprint(&out, " /")
|
|
}
|
|
fmt.Fprintf(&out, " %v", node)
|
|
}
|
|
out.WriteString(" ]")
|
|
return out.String()
|
|
}
|
|
|
|
// Add returns a new snake with the given nodes appended to it.
|
|
func (this Snake) Add(nodes ...Node) Snake {
|
|
newSnake := make(Snake, len(this) + len(nodes))
|
|
copy(newSnake, this)
|
|
copy(newSnake[len(this):], Snake(nodes))
|
|
return newSnake
|
|
}
|
|
|
|
// AddO returns a new snake with the given order appended to it.
|
|
func (this Snake) AddO(nodes ...Node) Snake {
|
|
return this.Add(O(nodes...))
|
|
}
|
|
|
|
// AddS returns a new snake with the given snake appended to it.
|
|
func (this Snake) AddS(nodes ...Node) Snake {
|
|
return this.Add(S(nodes...))
|
|
}
|
|
|
|
// AddL returns a new snake with the given leaf appended to it.
|
|
func (this Snake) AddL(data ... byte) Snake {
|
|
return this.Add(L(data...))
|
|
}
|
|
|
|
// Leaf is satisfied when the data matches it exactly.
|
|
type Leaf []byte
|
|
|
|
// Check determines if the data is equal to the leaf.
|
|
func (this Leaf) Check(data []byte) (ok bool, n int) {
|
|
if len(data) < len(this) {
|
|
return false, len(data)
|
|
}
|
|
for index, byt := range this {
|
|
if byt != data[index] {
|
|
return false, index
|
|
}
|
|
}
|
|
return true, len(this)
|
|
}
|
|
|
|
// This one's easy.
|
|
func (this Leaf) Flatten() []byte {
|
|
return []byte(this)
|
|
}
|
|
|
|
func (this Leaf) String() string {
|
|
return tu.HexBytes([]byte(this))
|
|
}
|
|
|
|
// Node represents a snake node.
|
|
type Node interface {
|
|
// Check determines if the data satisfies the node. If satisfied, the function
|
|
// will return true, and the index at which it stopped. If not, the
|
|
// function will return false, and the index of the first byte that
|
|
// didn't match. As long as the start of the data satisfies the node,
|
|
// whatever comes after it doesn't matter.
|
|
Check(data []byte) (ok bool, n int)
|
|
// Flatten returns the node flattened to a byte array. The result of
|
|
// this function always satisfies the node.
|
|
Flatten() []byte
|
|
}
|