Refactored match to use function chaining

This commit is contained in:
eanderton
2014-09-07 16:32:29 -04:00
parent a98788e0d7
commit c81f1892c2
4 changed files with 232 additions and 145 deletions
+119 -38
View File
@@ -4,14 +4,84 @@ import (
. "github.com/pelletier/go-toml"
)
type PathFn interface{
Call(context interface{}, next QueryPath)
// result set for storage of results
type pathResult struct {
Values []interface{}
}
type QueryPath []PathFn
func newPathResult() *pathResult {
return &pathResult {
Values: []interface{}{},
}
}
func (path QueryPath) Fn(context interface{}) {
path[0].Call(context, path[1:])
func (r *pathResult) Append(value interface{}) {
r.Values = append(r.Values, value)
}
// generic path functor interface
type PathFn interface{
SetNext(next PathFn)
Call(context interface{}, results *pathResult)
}
// contains a functor chain
type QueryPath struct {
root PathFn
tail PathFn
}
func newQueryPath() *QueryPath {
return &QueryPath {
root: nil,
tail: nil,
}
}
func (path *QueryPath) Append(next PathFn) {
if path.root == nil {
path.root = next
} else {
path.tail.SetNext(next)
}
path.tail = next
next.SetNext(newTerminatingFn()) // init the next functor
}
func (path *QueryPath) Call(context interface{}) []interface{} {
results := newPathResult()
if path.root == nil {
results.Append(context) // identity query for no predicates
} else {
path.root.Call(context, results)
}
return results.Values
}
// base match
type matchBase struct {
next PathFn
}
func (f *matchBase) SetNext(next PathFn) {
f.next = next
}
// terminating functor - gathers results
type terminatingFn struct {
// empty
}
func newTerminatingFn() *terminatingFn {
return &terminatingFn{}
}
func (f *terminatingFn) SetNext(next PathFn) {
// do nothing
}
func (f *terminatingFn) Call(context interface{}, results *pathResult) {
results.Append(context)
}
// shim to ease functor writing
@@ -21,37 +91,52 @@ func treeValue(tree *TomlTree, key string) interface{} {
// match single key
type matchKeyFn struct {
matchBase
Name string
}
func (f *matchKeyFn) Call(context interface{}, next QueryPath) {
func newMatchKeyFn(name string) *matchKeyFn {
return &matchKeyFn{ Name: name }
}
func (f *matchKeyFn) Call(context interface{}, results *pathResult) {
if tree, ok := context.(*TomlTree); ok {
item := treeValue(tree, f.Name)
if item != nil {
next.Fn(item)
f.next.Call(item, results)
}
}
}
// match single index
type matchIndexFn struct {
matchBase
Idx int
}
func (f *matchIndexFn) Call(context interface{}, next QueryPath) {
func newMatchIndexFn(idx int) *matchIndexFn {
return &matchIndexFn{ Idx: idx }
}
func (f *matchIndexFn) Call(context interface{}, results *pathResult) {
if arr, ok := context.([]interface{}); ok {
if f.Idx < len(arr) && f.Idx >= 0 {
next.Fn(arr[f.Idx])
f.next.Call(arr[f.Idx], results)
}
}
}
// filter by slicing
type matchSliceFn struct {
matchBase
Start, End, Step int
}
func (f *matchSliceFn) Call(context interface{}, next QueryPath) {
func newMatchSliceFn(start, end, step int) *matchSliceFn {
return &matchSliceFn{ Start: start, End: end, Step: step }
}
func (f *matchSliceFn) Call(context interface{}, results *pathResult) {
if arr, ok := context.([]interface{}); ok {
// adjust indexes for negative values, reverse ordering
realStart, realEnd := f.Start, f.End
@@ -66,48 +151,63 @@ func (f *matchSliceFn) Call(context interface{}, next QueryPath) {
}
// loop and gather
for idx := realStart; idx < realEnd; idx += f.Step {
next.Fn(arr[idx])
f.next.Call(arr[idx], results)
}
}
}
// match anything
type matchAnyFn struct {
matchBase
// empty
}
func (f *matchAnyFn) Call(context interface{}, next QueryPath) {
func newMatchAnyFn() *matchAnyFn {
return &matchAnyFn{}
}
func (f *matchAnyFn) Call(context interface{}, results *pathResult) {
if tree, ok := context.(*TomlTree); ok {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
f.next.Call(item, results)
}
}
}
// filter through union
type matchUnionFn struct {
Union QueryPath
Union []PathFn
}
func (f *matchUnionFn) Call(context interface{}, next QueryPath) {
func (f *matchUnionFn) SetNext(next PathFn) {
for _, fn := range f.Union {
fn.Call(context, next)
fn.SetNext(next)
}
}
func (f *matchUnionFn) Call(context interface{}, results *pathResult) {
for _, fn := range f.Union {
fn.Call(context, results)
}
}
// match every single last node in the tree
type matchRecursiveFn struct {
// empty
matchBase
}
func (f *matchRecursiveFn) Call(context interface{}, next QueryPath) {
func newMatchRecursiveFn() *matchRecursiveFn{
return &matchRecursiveFn{}
}
func (f *matchRecursiveFn) Call(context interface{}, results *pathResult) {
if tree, ok := context.(*TomlTree); ok {
var visit func(tree *TomlTree)
visit = func(tree *TomlTree) {
for _, key := range tree.Keys() {
item := treeValue(tree, key)
next.Fn(item)
f.next.Call(item, results)
switch node := item.(type) {
case *TomlTree:
visit(node)
@@ -121,22 +221,3 @@ func (f *matchRecursiveFn) Call(context interface{}, next QueryPath) {
visit(tree)
}
}
// terminating expression
type matchEndFn struct {
Result []interface{}
}
func (f *matchEndFn) Call(context interface{}, next QueryPath) {
f.Result = append(f.Result, context)
}
func processPath(path QueryPath, context interface{}) []interface{} {
// terminate the path with a collection funciton
endFn := &matchEndFn{ []interface{}{} }
newPath := append(path, endFn)
// execute the path
newPath.Fn(context)
return endFn.Result
}