-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Start on procedures * Add jp-proc tests
- Loading branch information
Showing
8 changed files
with
338 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) 2024, Peter Ohler, All rights reserved. | ||
|
||
package jp | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// CompileScript if non-nil should return object that implments the Procedure | ||
// interface. This function is called when a script notation bracketed by [( | ||
// and )] is encountered. Note the string code argument will included the open | ||
// and close parenthesis but not the square brackets. | ||
var CompileScript func(code []byte) Procedure | ||
|
||
// Proc is a script used as a procedure which is a script not limited to being | ||
// a selector. While both Locate() and Walk() are supported the results may | ||
// not be as expected since the procedure can modify the original | ||
// data. Remove() is not supported with this fragment type. | ||
type Proc struct { | ||
Procedure Procedure | ||
Script []byte | ||
} | ||
|
||
// MustNewProc creates a new Proc and panics on error. | ||
func MustNewProc(code []byte) (p *Proc) { | ||
if CompileScript == nil { | ||
panic(fmt.Errorf("jp.CompileScript has not been set")) | ||
} | ||
return &Proc{ | ||
Procedure: CompileScript(code), | ||
Script: code, | ||
} | ||
} | ||
|
||
// String representation of the proc. | ||
func (p *Proc) String() string { | ||
return string(p.Append([]byte{}, true, false)) | ||
} | ||
|
||
// Append a fragment string representation of the fragment to the buffer | ||
// then returning the expanded buffer. | ||
func (p *Proc) Append(buf []byte, _, _ bool) []byte { | ||
buf = append(buf, "["...) | ||
buf = append(buf, p.Script...) | ||
|
||
return append(buf, ']') | ||
} | ||
|
||
func (p *Proc) locate(pp Expr, data any, rest Expr, max int) (locs []Expr) { | ||
got := p.Procedure.Get(data) | ||
if len(rest) == 0 { // last one | ||
for i := range got { | ||
locs = locateAppendFrag(locs, pp, Nth(i)) | ||
if 0 < max && max <= len(locs) { | ||
break | ||
} | ||
} | ||
} else { | ||
cp := append(pp, nil) // place holder | ||
for i, v := range got { | ||
cp[len(pp)] = Nth(i) | ||
locs = locateContinueFrag(locs, cp, v, rest, max) | ||
if 0 < max && max <= len(locs) { | ||
break | ||
} | ||
} | ||
} | ||
return | ||
} | ||
|
||
// Walk each element returned from the procedure call. Note that this may or | ||
// may not correspond to the original data as the procedure can modify not only | ||
// the elements in the original data but also the contents of each. | ||
func (p *Proc) 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) | ||
|
||
for i, v := range p.Procedure.Get(data) { | ||
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright (c) 2024, Peter Ohler, All rights reserved. | ||
|
||
package jp_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/ohler55/ojg/jp" | ||
"github.com/ohler55/ojg/pretty" | ||
"github.com/ohler55/ojg/tt" | ||
) | ||
|
||
type mathProc struct { | ||
op rune | ||
left int | ||
right int | ||
} | ||
|
||
func (mp *mathProc) Get(data any) []any { | ||
return []any{mp.First(data)} | ||
} | ||
|
||
func (mp *mathProc) First(data any) any { | ||
a, ok := data.([]any) | ||
if ok { | ||
var ( | ||
left int | ||
right int | ||
) | ||
if mp.left < len(a) { | ||
left, _ = a[mp.left].(int) | ||
} | ||
if mp.right < len(a) { | ||
right, _ = a[mp.right].(int) | ||
} | ||
switch mp.op { | ||
case '+': | ||
return left + right | ||
case '-': | ||
return left - right | ||
} | ||
return 0 | ||
} | ||
return nil | ||
} | ||
|
||
func compileMathProc(code []byte) jp.Procedure { | ||
var mp mathProc | ||
_, _ = fmt.Sscanf(string(code), "(%c %d %d)", &mp.op, &mp.left, &mp.right) | ||
|
||
return &mp | ||
} | ||
|
||
type mapProc struct{} | ||
|
||
func (mp mapProc) Get(data any) (result []any) { | ||
a, _ := data.([]any) | ||
for i, v := range a { | ||
result = append(result, map[string]any{"i": i, "v": v}) | ||
} | ||
return | ||
} | ||
|
||
func (mp mapProc) First(data any) any { | ||
if a, _ := data.([]any); 0 < len(a) { | ||
return map[string]any{"i": 0, "v": a[0]} | ||
} | ||
return nil | ||
} | ||
|
||
func compileMapProc(code []byte) jp.Procedure { | ||
return mapProc{} | ||
} | ||
|
||
type mapIntProc struct{} | ||
|
||
func (mip mapIntProc) Get(data any) (result []any) { | ||
a, _ := data.([]int) | ||
for i, v := range a { | ||
result = append(result, map[string]int{"i": i, "v": v}) | ||
} | ||
return | ||
} | ||
|
||
func (mip mapIntProc) First(data any) any { | ||
if a, _ := data.([]int); 0 < len(a) { | ||
return map[string]int{"i": 0, "v": a[0]} | ||
} | ||
return nil | ||
} | ||
|
||
func compileMapIntProc(code []byte) jp.Procedure { | ||
return mapIntProc{} | ||
} | ||
|
||
func TestProcLast(t *testing.T) { | ||
jp.CompileScript = compileMathProc | ||
|
||
p := jp.MustNewProc([]byte("(+ 0 1)")) | ||
tt.Equal(t, "[(+ 0 1)]", p.String()) | ||
|
||
x := jp.MustParseString("[(+ 0 1)]") | ||
tt.Equal(t, "[(+ 0 1)]", x.String()) | ||
|
||
data := []any{2, 3, 4} | ||
result := x.First(data) | ||
tt.Equal(t, 5, result) | ||
|
||
got := x.Get(data) | ||
tt.Equal(t, []any{5}, got) | ||
|
||
locs := x.Locate(data, 1) | ||
tt.Equal(t, "[[0]]", pretty.SEN(locs)) | ||
|
||
var buf []byte | ||
x.Walk(data, func(path jp.Expr, nodes []any) { | ||
buf = fmt.Appendf(buf, "%s : %v\n", path, nodes) | ||
}) | ||
tt.Equal(t, "[0] : [[2 3 4] 5]\n", string(buf)) | ||
} | ||
|
||
func TestProcNotLast(t *testing.T) { | ||
jp.CompileScript = compileMapProc | ||
|
||
x := jp.MustParseString("[(quux)].v") | ||
tt.Equal(t, "[(quux)].v", x.String()) | ||
|
||
data := []any{2, 3, 4} | ||
result := x.First(data) | ||
tt.Equal(t, 2, result) | ||
|
||
got := x.Get(data) | ||
tt.Equal(t, []any{2, 3, 4}, got) | ||
|
||
locs := x.Locate(data, 2) | ||
tt.Equal(t, "[[0 v] [1 v]]", pretty.SEN(locs)) | ||
|
||
var buf []byte | ||
x.Walk(data, func(path jp.Expr, nodes []any) { | ||
buf = fmt.Appendf(buf, "%s : %v\n", path, nodes) | ||
}) | ||
tt.Equal(t, `[0].v : [[2 3 4] map[i:0 v:2] 2] | ||
[1].v : [[2 3 4] map[i:1 v:3] 3] | ||
[2].v : [[2 3 4] map[i:2 v:4] 4] | ||
`, string(buf)) | ||
} | ||
|
||
func TestProcNotLastReflect(t *testing.T) { | ||
jp.CompileScript = compileMapIntProc | ||
|
||
x := jp.MustParseString("[(quux)].v") | ||
tt.Equal(t, "[(quux)].v", x.String()) | ||
|
||
data := []int{2, 3, 4} | ||
result := x.First(data) | ||
tt.Equal(t, 2, result) | ||
|
||
got := x.Get(data) | ||
tt.Equal(t, []any{2, 3, 4}, got) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) 2025, Peter Ohler, All rights reserved. | ||
|
||
package jp | ||
|
||
// Procedure defines the interface for functions for script fragments between | ||
// [( and )] delimiters. | ||
type Procedure interface { | ||
// Get should return a list of matching in the data element. | ||
Get(data any) []any | ||
|
||
// First should return a single matching in the data element or nil if | ||
// there are no matches. | ||
First(data any) any | ||
} |
Oops, something went wrong.