Skip to content

Commit 990adf5

Browse files
authored
Expr walk (#188)
* Determine function to walk expr matches and allow modification * Add reflection to child, nth, and wildcard * Add descent and filter to expr.Walk
1 parent 0844e7e commit 990adf5

File tree

20 files changed

+811
-58
lines changed

20 files changed

+811
-58
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
44

55
The structure and content of this file follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## [1.25.0] - 2024-10-26
8+
### Added
9+
- The `Expr.Walk()` function was added. Similar to jp.Walk but walk expression matches.
10+
711
## [1.24.1] - 2024-09-15
812
### Fixed
913
- Fixed reflection map key matches when keys are string derivitives.

jp/at.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ func (f At) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
1818
}
1919
return
2020
}
21+
22+
// Walk continues with the next in rest.
23+
func (f At) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
24+
if 0 < len(rest) {
25+
rest[0].Walk(rest[1:], path, nodes, cb)
26+
} else {
27+
cb(path, nodes)
28+
}
29+
}

jp/bracket.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ func (f Bracket) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
1818
}
1919
return
2020
}
21+
22+
// Walk continues with the next in rest.
23+
func (f Bracket) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
24+
if 0 < len(rest) {
25+
rest[0].Walk(rest[1:], path, nodes, cb)
26+
} else {
27+
cb(path, nodes)
28+
}
29+
}

jp/child.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,36 @@ func (f Child) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
7979
case Keyed:
8080
v, has = td.ValueForKey(string(f))
8181
default:
82-
v, has = pp.reflectGetChild(td, string(f))
82+
v, has = reflectGetChild(td, string(f))
8383
}
8484
if has {
8585
locs = locateNthChildHas(pp, f, v, rest, max)
8686
}
8787
return
8888
}
89+
90+
// Walk follows the matching element in a map or map like element.
91+
func (f Child) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
92+
var (
93+
value any
94+
has bool
95+
)
96+
switch tv := nodes[len(nodes)-1].(type) {
97+
case map[string]any:
98+
value, has = tv[string(f)]
99+
case gen.Object:
100+
value, has = tv[string(f)]
101+
case Keyed:
102+
value, has = tv.ValueForKey(string(f))
103+
default:
104+
value, has = reflectGetChild(tv, string(f))
105+
}
106+
if has {
107+
path = append(path, f)
108+
if 0 < len(rest) {
109+
rest[0].Walk(rest[1:], path, append(nodes, value), cb)
110+
} else {
111+
cb(path, append(nodes, value))
112+
}
113+
}
114+
}

jp/descent.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,8 @@ func (f Descent) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
147147
}
148148
return
149149
}
150+
151+
// Walk each element in the tree of elements.
152+
func (f Descent) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
153+
wildWalk(rest, path, nodes, cb, f)
154+
}

jp/filter.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,139 @@ func (f Filter) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
312312
}
313313
return
314314
}
315+
316+
// Walk each element that matches the filter.
317+
func (f *Filter) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
318+
path = append(path, nil)
319+
data := nodes[len(nodes)-1]
320+
nodes = append(nodes, nil)
321+
switch tv := data.(type) {
322+
case []any:
323+
for i, v := range tv {
324+
if f.Match(v) {
325+
path[len(path)-1] = Nth(i)
326+
nodes[len(nodes)-1] = v
327+
if 0 < len(rest) {
328+
rest[0].Walk(rest[1:], path, nodes, cb)
329+
} else {
330+
cb(path, nodes)
331+
}
332+
}
333+
}
334+
case Indexed:
335+
size := tv.Size()
336+
for i := 0; i < size; i++ {
337+
v := tv.ValueAtIndex(i)
338+
if f.Match(v) {
339+
path[len(path)-1] = Nth(i)
340+
nodes[len(nodes)-1] = v
341+
if 0 < len(rest) {
342+
rest[0].Walk(rest[1:], path, nodes, cb)
343+
} else {
344+
cb(path, nodes)
345+
}
346+
}
347+
}
348+
case gen.Array:
349+
for i, v := range tv {
350+
if f.Match(v) {
351+
path[len(path)-1] = Nth(i)
352+
nodes[len(nodes)-1] = v
353+
if 0 < len(rest) {
354+
rest[0].Walk(rest[1:], path, nodes, cb)
355+
} else {
356+
cb(path, nodes)
357+
}
358+
}
359+
}
360+
case map[string]any:
361+
if 0 < len(tv) {
362+
keys := make([]string, 0, len(tv))
363+
for k := range tv {
364+
keys = append(keys, k)
365+
}
366+
sort.Strings(keys)
367+
for _, k := range keys {
368+
if f.Match(tv[k]) {
369+
path[len(path)-1] = Child(k)
370+
nodes[len(nodes)-1] = tv[k]
371+
if 0 < len(rest) {
372+
rest[0].Walk(rest[1:], path, nodes, cb)
373+
} else {
374+
cb(path, nodes)
375+
}
376+
}
377+
}
378+
}
379+
case gen.Object:
380+
if 0 < len(tv) {
381+
keys := make([]string, 0, len(tv))
382+
for k := range tv {
383+
keys = append(keys, k)
384+
}
385+
sort.Strings(keys)
386+
for _, k := range keys {
387+
if f.Match(tv[k]) {
388+
path[len(path)-1] = Child(k)
389+
nodes[len(nodes)-1] = tv[k]
390+
if 0 < len(rest) {
391+
rest[0].Walk(rest[1:], path, nodes, cb)
392+
} else {
393+
cb(path, nodes)
394+
}
395+
}
396+
}
397+
}
398+
case Keyed:
399+
keys := tv.Keys()
400+
sort.Strings(keys)
401+
for _, key := range keys {
402+
v, _ := tv.ValueForKey(key)
403+
if f.Match(v) {
404+
path[len(path)-1] = Child(key)
405+
nodes[len(nodes)-1] = v
406+
if 0 < len(rest) {
407+
rest[0].Walk(rest[1:], path, nodes, cb)
408+
} else {
409+
cb(path, nodes)
410+
}
411+
}
412+
}
413+
default:
414+
rv := reflect.ValueOf(tv)
415+
switch rv.Kind() {
416+
case reflect.Slice:
417+
cnt := rv.Len()
418+
for i := 0; i < cnt; i++ {
419+
v := rv.Index(i).Interface()
420+
if f.Match(v) {
421+
path[len(path)-1] = Nth(i)
422+
nodes[len(nodes)-1] = v
423+
if 0 < len(rest) {
424+
rest[0].Walk(rest[1:], path, nodes, cb)
425+
} else {
426+
cb(path, nodes)
427+
}
428+
}
429+
}
430+
case reflect.Map:
431+
keys := rv.MapKeys()
432+
sort.Slice(keys, func(i, j int) bool {
433+
return strings.Compare(keys[i].String(), keys[j].String()) < 0
434+
})
435+
for _, k := range keys {
436+
mv := rv.MapIndex(k)
437+
v := mv.Interface()
438+
if f.Match(v) {
439+
path[len(path)-1] = Child(k.String())
440+
nodes[len(nodes)-1] = v
441+
if 0 < len(rest) {
442+
rest[0].Walk(rest[1:], path, nodes, cb)
443+
} else {
444+
cb(path, nodes)
445+
}
446+
}
447+
}
448+
}
449+
}
450+
}

jp/frag.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ type Frag interface {
1111
Append(buf []byte, bracket, first bool) []byte
1212

1313
locate(pp Expr, data any, rest Expr, max int) (locs []Expr)
14+
15+
// Walk the matching elements in tail of nodes and call cb on the matches
16+
// or follow on to the matching if not the last fragment in an
17+
// expression. The rest argument is the rest of the expression after this
18+
// fragment. The path is the normalized path up to this point. The nodes
19+
// argument is the chain of data elements to the current location.
20+
Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any))
1421
}

0 commit comments

Comments
 (0)