Skip to content

Commit

Permalink
Expr walk (#188)
Browse files Browse the repository at this point in the history
* Determine function to walk expr matches and allow modification
* Add reflection to child, nth, and wildcard
* Add descent and filter to expr.Walk
  • Loading branch information
ohler55 authored Oct 26, 2024
1 parent 0844e7e commit 990adf5
Show file tree
Hide file tree
Showing 20 changed files with 811 additions and 58 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

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

## [1.25.0] - 2024-10-26
### Added
- The `Expr.Walk()` function was added. Similar to jp.Walk but walk expression matches.

## [1.24.1] - 2024-09-15
### Fixed
- Fixed reflection map key matches when keys are string derivitives.
Expand Down
9 changes: 9 additions & 0 deletions jp/at.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ func (f At) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
}
return
}

// Walk continues with the next in rest.
func (f At) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
9 changes: 9 additions & 0 deletions jp/bracket.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ func (f Bracket) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
}
return
}

// Walk continues with the next in rest.
func (f Bracket) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
28 changes: 27 additions & 1 deletion jp/child.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,36 @@ func (f Child) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
case Keyed:
v, has = td.ValueForKey(string(f))
default:
v, has = pp.reflectGetChild(td, string(f))
v, has = reflectGetChild(td, string(f))
}
if has {
locs = locateNthChildHas(pp, f, v, rest, max)
}
return
}

// Walk follows the matching element in a map or map like element.
func (f Child) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
var (
value any
has bool
)
switch tv := nodes[len(nodes)-1].(type) {
case map[string]any:
value, has = tv[string(f)]
case gen.Object:
value, has = tv[string(f)]
case Keyed:
value, has = tv.ValueForKey(string(f))
default:
value, has = reflectGetChild(tv, string(f))
}
if has {
path = append(path, f)
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, append(nodes, value), cb)
} else {
cb(path, append(nodes, value))
}
}
}
5 changes: 5 additions & 0 deletions jp/descent.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,8 @@ func (f Descent) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
}
return
}

// Walk each element in the tree of elements.
func (f Descent) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
wildWalk(rest, path, nodes, cb, f)
}
136 changes: 136 additions & 0 deletions jp/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,139 @@ func (f Filter) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) {
}
return
}

// Walk each element that matches the filter.
func (f *Filter) Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any)) {
path = append(path, nil)
data := nodes[len(nodes)-1]
nodes = append(nodes, nil)
switch tv := data.(type) {
case []any:
for i, v := range tv {
if f.Match(v) {
path[len(path)-1] = Nth(i)
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
case Indexed:
size := tv.Size()
for i := 0; i < size; i++ {
v := tv.ValueAtIndex(i)
if f.Match(v) {
path[len(path)-1] = Nth(i)
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
case gen.Array:
for i, v := range tv {
if f.Match(v) {
path[len(path)-1] = Nth(i)
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
case map[string]any:
if 0 < len(tv) {
keys := make([]string, 0, len(tv))
for k := range tv {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if f.Match(tv[k]) {
path[len(path)-1] = Child(k)
nodes[len(nodes)-1] = tv[k]
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
}
case gen.Object:
if 0 < len(tv) {
keys := make([]string, 0, len(tv))
for k := range tv {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if f.Match(tv[k]) {
path[len(path)-1] = Child(k)
nodes[len(nodes)-1] = tv[k]
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
}
case Keyed:
keys := tv.Keys()
sort.Strings(keys)
for _, key := range keys {
v, _ := tv.ValueForKey(key)
if f.Match(v) {
path[len(path)-1] = Child(key)
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
default:
rv := reflect.ValueOf(tv)
switch rv.Kind() {
case reflect.Slice:
cnt := rv.Len()
for i := 0; i < cnt; i++ {
v := rv.Index(i).Interface()
if f.Match(v) {
path[len(path)-1] = Nth(i)
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
case reflect.Map:
keys := rv.MapKeys()
sort.Slice(keys, func(i, j int) bool {
return strings.Compare(keys[i].String(), keys[j].String()) < 0
})
for _, k := range keys {
mv := rv.MapIndex(k)
v := mv.Interface()
if f.Match(v) {
path[len(path)-1] = Child(k.String())
nodes[len(nodes)-1] = v
if 0 < len(rest) {
rest[0].Walk(rest[1:], path, nodes, cb)
} else {
cb(path, nodes)
}
}
}
}
}
}
7 changes: 7 additions & 0 deletions jp/frag.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ type Frag interface {
Append(buf []byte, bracket, first bool) []byte

locate(pp Expr, data any, rest Expr, max int) (locs []Expr)

// Walk the matching elements in tail of nodes and call cb on the matches
// or follow on to the matching if not the last fragment in an
// expression. The rest argument is the rest of the expression after this
// fragment. The path is the normalized path up to this point. The nodes
// argument is the chain of data elements to the current location.
Walk(rest, path Expr, nodes []any, cb func(path Expr, nodes []any))
}
Loading

0 comments on commit 990adf5

Please sign in to comment.