3
0

Add an efficient expr path traverser

This commit is contained in:
Tomaž Jerman 2024-01-04 11:00:44 +01:00
parent 860e0c585c
commit 1f415be2db
2 changed files with 240 additions and 0 deletions

97
server/pkg/expr/path.go Normal file
View File

@ -0,0 +1,97 @@
package expr
type (
exprPath struct {
path string
i int
isLast bool
start, end int
}
)
// Path initializes a new exprPath helper to efficiently traverse the path
func Path(p string) (out exprPath) {
return exprPath{path: p}
}
func (p exprPath) More() bool {
return p.start < len(p.path)
}
func (p exprPath) Get() string {
return p.path[p.start:p.end]
}
func (p exprPath) Rest() string {
var rest string
if p.end+1 < len(p.path) {
rest = p.path[p.end:]
}
// @todo this is fugly but it'll do the trick for now
// Clean it up please :)
if len(rest) > 0 && (rest[0] == '.' || rest[0] == ']') {
rest = rest[1:]
}
if len(rest) > 0 && (rest[0] == '.' || rest[0] == ']') {
rest = rest[1:]
}
return rest
}
func (p exprPath) Next() (out exprPath, err error) {
if !p.More() {
return p, nil
}
if p.end > 0 {
p.start = p.end + 1
}
var ()
p.start, p.end, p.isLast, err = nxtRange(p.path, p.start)
if err != nil {
return p, err
}
p.i++
return p, nil
}
func nxtRange(path string, start int) (startOut, end int, isLast bool, err error) {
startOut = start
for i := start; i < len(path); i++ {
switch path[i] {
// This thing concludes the prev ident
case '.', '[':
if i == len(path)-1 {
return startOut, -1, false, invalidPathErr
}
if i > 0 {
if path[i-1] == ']' {
startOut++
continue
}
}
return startOut, i, false, nil
case ']':
// If we're at the end, that's that
if i == len(path)-1 {
return startOut, i, true, nil
}
if path[i+1] != '.' && path[i+1] != '[' {
return startOut, -1, false, invalidPathErr
} else {
return startOut, i, false, nil
}
}
}
return startOut, len(path), true, nil
}

View File

@ -0,0 +1,143 @@
package expr
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNxtRange(t *testing.T) {
tcc := []struct {
path string
start int
expEnd int
expErr bool
}{{
path: "a",
start: 0,
expEnd: 1,
}, {
path: "bcd",
start: 0,
expEnd: 3,
}, {
path: "bcd[e]",
start: 0,
expEnd: 3,
}, {
path: "bcd.e",
start: 0,
expEnd: 3,
}, {
path: "bcd.",
start: 0,
expErr: true,
}, {
path: "bcd[",
start: 0,
expErr: true,
}, {
path: "bcd.e",
start: 4,
expEnd: 5,
}}
for _, c := range tcc {
t.Run(c.path[c.start:], func(t *testing.T) {
_, o, _, err := nxtRange(c.path, c.start)
if c.expErr {
require.Error(t, err)
return
}
require.Equal(t, c.expEnd, o)
})
}
}
func TestPath(t *testing.T) {
tcc := []struct {
path string
expBits []string
expRests []string
expErr bool
}{{
path: "a",
expBits: []string{"a"},
expRests: []string{""},
}, {
path: "a.b",
expBits: []string{"a", "b"},
expRests: []string{"b", ""},
}, {
path: "a[b][c]",
expBits: []string{"a", "b", "c"},
expRests: []string{"[b][c]", "[c]", ""},
}, {
path: "aa.b[c][d].e.f[g][0]",
expBits: []string{"aa", "b", "c", "d", "e", "f", "g", "0"},
expRests: []string{"b[c][d].e.f[g][0]", "[c][d].e.f[g][0]", "[d].e.f[g][0]", "e.f[g][0]", "f[g][0]", "[g][0]", "[0]", ""},
},
}
for _, c := range tcc {
t.Run(c.path, func(t *testing.T) {
pp := Path(c.path)
var err error
i := -1
for {
i++
pp, err = pp.Next()
require.NoError(t, err)
if !pp.More() {
break
}
require.Equal(t, c.expBits[i], pp.Get())
require.Equal(t, c.expRests[i], pp.Rest())
}
require.Equal(t, len(c.expBits), i)
// for _, b := range c.expBits {
// require.Equal(t, b, pp.Get())
// }
// pp, err = pp.Next()
// require.NoError(t, err)
// require.False(t, pp.More())
})
}
}
func BenchmarkPath(b *testing.B) {
path := "aa.b[c][d].e.f[g][0]"
b.ResetTimer()
for n := 0; n < b.N; n++ {
pp := Path(path)
for {
pp, _ = pp.Next()
if !pp.More() {
break
}
}
}
}