Refactored match to use function chaining
diff --git a/jpath/match.go b/jpath/match.go
index 9a9ef0b..ffde749 100644
--- a/jpath/match.go
+++ b/jpath/match.go
@@ -4,14 +4,84 @@
 	. "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 @@
 
 // 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 @@
     }
     // 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 @@
     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
-}
diff --git a/jpath/match_test.go b/jpath/match_test.go
index 5e7f73c..514962b 100644
--- a/jpath/match_test.go
+++ b/jpath/match_test.go
@@ -6,181 +6,184 @@
 	"testing"
 )
 
-func pathString(path QueryPath) string {
-  result := "["
-  for _, v := range path {
-    result += fmt.Sprintf("%T:%v, ", v, v)
+// dump path tree to a string
+func pathString(root PathFn) string {
+  result := fmt.Sprintf("%T:")
+  switch fn := root.(type) {
+  case *terminatingFn:
+    result += "{}"
+  case *matchKeyFn:
+    result += fmt.Sprintf("{%s}", fn.Name)
+    result += pathString(fn.next)
+  case *matchIndexFn:
+    result += fmt.Sprintf("{%d}", fn.Idx)
+    result += pathString(fn.next)
+  case *matchSliceFn:
+    result += fmt.Sprintf("{%d:%d:%d}",
+      fn.Start, fn.End, fn.Step)
+    result += pathString(fn.next)
+  case *matchAnyFn:
+    result += "{}"
+    result += pathString(fn.next)
+  case *matchUnionFn:
+    result += "{["
+    for _, v := range fn.Union {
+      result += pathString(v)
+    }
+    result += "]}"
+  case *matchRecursiveFn:
+    result += "{}"
+    result += pathString(fn.next)
   }
-  return result + "]"
+  return result
 }
 
-func assertPathMatch(t *testing.T, path, ref QueryPath) bool {
-  if len(path) != len(ref) {
-    t.Errorf("lengths do not match: %v vs %v",
-      pathString(path), pathString(ref))
+func assertPathMatch(t *testing.T, path, ref *QueryPath) bool {
+  pathStr := pathString(path.root)
+  refStr := pathString(ref.root)
+  if pathStr != refStr {
+    t.Errorf("paths do not match: %v vs %v")
+    t.Log("test:", pathStr)
+    t.Log("ref: ", refStr)
     return false
-  } else {
-    for i, v := range ref {
-      pass := false
-      node := path[i]
-      // compare by value
-      switch refNode := v.(type) {
-      case *matchKeyFn:
-        castNode, ok := node.(*matchKeyFn)
-        pass = ok && (*refNode == *castNode)
-      case *matchIndexFn:
-        castNode, ok := node.(*matchIndexFn)
-        pass = ok && (*refNode == *castNode)
-      case *matchSliceFn:
-        castNode, ok := node.(*matchSliceFn)
-        pass = ok && (*refNode == *castNode)
-      case *matchAnyFn:
-        castNode, ok := node.(*matchAnyFn)
-        pass = ok && (*refNode == *castNode)
-      case *matchUnionFn:
-        castNode, ok := node.(*matchUnionFn)
-        // special case - comapre all contents
-        pass = ok && assertPathMatch(t, castNode.Union, refNode.Union)
-      case *matchRecursiveFn:
-        castNode, ok := node.(*matchRecursiveFn)
-        pass = ok && (*refNode == *castNode)
-      }
-      if !pass {
-        t.Errorf("paths do not match at index %d: %v vs %v",
-          i, pathString(path), pathString(ref))
-        return false
-      }
-    }
   }
   return true
 }
 
-func assertPath(t *testing.T, query string, ref QueryPath) {
+func assertPath(t *testing.T, query string, ref *QueryPath) {
 	_, flow := lex(query)
 	path := parse(flow)
   assertPathMatch(t, path, ref)
 }
 
+func buildPath(parts... PathFn) *QueryPath {
+  path := newQueryPath()
+  for _, v := range parts {
+    path.Append(v)
+  }
+  return path
+}
+
 func TestPathRoot(t *testing.T) {
 	assertPath(t,
 		"$",
-		QueryPath{
+		buildPath(
       // empty
-    })
+    ))
 }
 
 func TestPathKey(t *testing.T) {
 	assertPath(t,
 		"$.foo",
-		QueryPath{
-      &matchKeyFn{ "foo" },
-    })
+		buildPath(
+      newMatchKeyFn("foo"),
+    ))
 }
 
 func TestPathBracketKey(t *testing.T) {
 	assertPath(t,
 		"$[foo]",
-		QueryPath{
-      &matchKeyFn{ "foo" },
-    })
+		buildPath(
+      newMatchKeyFn("foo"),
+    ))
 }
 
 func TestPathBracketStringKey(t *testing.T) {
 	assertPath(t,
 		"$['foo']",
-		QueryPath{
-      &matchKeyFn{ "foo" },
-    })
+		buildPath(
+      newMatchKeyFn("foo"),
+    ))
 }
 
 func TestPathIndex(t *testing.T) {
 	assertPath(t,
 		"$[123]",
-		QueryPath{
-      &matchIndexFn{ 123 },
-    })
+		buildPath(
+      newMatchIndexFn(123),
+    ))
 }
 
 func TestPathSliceStart(t *testing.T) {
 	assertPath(t,
 		"$[123:]",
-		QueryPath{
-      &matchSliceFn{ 123, math.MaxInt64, 1 },
-    })
+		buildPath(
+      newMatchSliceFn(123, math.MaxInt64, 1),
+    ))
 }
 
 func TestPathSliceStartEnd(t *testing.T) {
 	assertPath(t,
 		"$[123:456]",
-		QueryPath{
-      &matchSliceFn{ 123, 456, 1 },
-    })
+		buildPath(
+      newMatchSliceFn(123, 456, 1),
+    ))
 }
 
 func TestPathSliceStartEndColon(t *testing.T) {
 	assertPath(t,
 		"$[123:456:]",
-		QueryPath{
-      &matchSliceFn{ 123, 456, 1 },
-    })
+		buildPath(
+      newMatchSliceFn(123, 456, 1),
+    ))
 }
 
 func TestPathSliceStartStep(t *testing.T) {
 	assertPath(t,
 		"$[123::7]",
-		QueryPath{
-      &matchSliceFn{ 123, math.MaxInt64, 7 },
-    })
+		buildPath(
+      newMatchSliceFn(123, math.MaxInt64, 7),
+    ))
 }
 
 func TestPathSliceEndStep(t *testing.T) {
 	assertPath(t,
 		"$[:456:7]",
-		QueryPath{
-      &matchSliceFn{ 0, 456, 7 },
-    })
+		buildPath(
+      newMatchSliceFn(0, 456, 7),
+    ))
 }
 
 func TestPathSliceStep(t *testing.T) {
 	assertPath(t,
 		"$[::7]",
-		QueryPath{
-      &matchSliceFn{ 0, math.MaxInt64, 7 },
-    })
+		buildPath(
+      newMatchSliceFn(0, math.MaxInt64, 7),
+    ))
 }
 
 func TestPathSliceAll(t *testing.T) {
 	assertPath(t,
 		"$[123:456:7]",
-		QueryPath{
-      &matchSliceFn{ 123, 456, 7 },
-    })
+		buildPath(
+      newMatchSliceFn(123, 456, 7),
+    ))
 }
 
 func TestPathAny(t *testing.T) {
 	assertPath(t,
 		"$.*",
-		QueryPath{
-      &matchAnyFn{},
-    })
+		buildPath(
+      newMatchAnyFn(),
+    ))
 }
 
 func TestPathUnion(t *testing.T) {
 	assertPath(t,
 		"$[foo, bar, baz]",
-		QueryPath{
+		buildPath(
       &matchUnionFn{ []PathFn {
-        &matchKeyFn{ "foo" },
-        &matchKeyFn{ "bar" },
-        &matchKeyFn{ "baz" },
+        newMatchKeyFn("foo"),
+        newMatchKeyFn("bar"),
+        newMatchKeyFn("baz"),
       }},
-    })
+    ))
 }
 
 func TestPathRecurse(t *testing.T) {
 	assertPath(t,
 		"$..*",
-		QueryPath{
-      &matchRecursiveFn{},
-    })
+		buildPath(
+      newMatchRecursiveFn(),
+    ))
 }
diff --git a/jpath/parser.go b/jpath/parser.go
index ce37081..7b842a4 100644
--- a/jpath/parser.go
+++ b/jpath/parser.go
@@ -1,3 +1,10 @@
+/*
+  Based on the "jsonpath" spec/concept.
+
+  http://goessner.net/articles/JsonPath/
+  https://code.google.com/p/json-path/
+*/
+
 package jpath
 
 import (
@@ -8,7 +15,7 @@
 type parser struct {
 	flow         chan token
 	tokensBuffer []token
-	path         []PathFn
+	path         *QueryPath
   union        []PathFn
 }
 
@@ -76,10 +83,6 @@
 	return &tok
 }
 
-func (p *parser) appendPath(fn PathFn) {
-	p.path = append(p.path, fn)
-}
-
 func parseStart(p *parser) parserStateFn {
 	tok := p.getToken()
 
@@ -99,12 +102,12 @@
 	tok := p.getToken()
 	switch tok.typ {
 	case tokenDotDot:
-    p.appendPath(&matchRecursiveFn{})
+    p.path.Append(&matchRecursiveFn{})
     // nested parse for '..'
     tok := p.getToken()
     switch tok.typ {
     case tokenKey:
-      p.appendPath(&matchKeyFn{tok.val})
+      p.path.Append(newMatchKeyFn(tok.val))
       return parseMatchExpr
     case tokenLBracket:
       return parseBracketExpr
@@ -118,10 +121,10 @@
     tok := p.getToken()
     switch tok.typ {
     case tokenKey:
-      p.appendPath(&matchKeyFn{tok.val})
+      p.path.Append(newMatchKeyFn(tok.val))
       return parseMatchExpr
     case tokenStar:
-      p.appendPath(&matchAnyFn{})
+      p.path.Append(&matchAnyFn{})
       return parseMatchExpr
     }
 
@@ -158,11 +161,11 @@
 		tok := p.getToken()
 		switch tok.typ {
 		case tokenInteger:
-			p.union = append(p.union, &matchIndexFn{tok.Int()})
+			p.union = append(p.union, newMatchIndexFn(tok.Int()))
 		case tokenKey:
-			p.union = append(p.union, &matchKeyFn{tok.val})
+			p.union = append(p.union, newMatchKeyFn(tok.val))
 		case tokenString:
-			p.union = append(p.union, &matchKeyFn{tok.val})
+			p.union = append(p.union, newMatchKeyFn(tok.val))
 		case tokenQuestion:
 			return parseFilterExpr
 		case tokenLParen:
@@ -184,9 +187,9 @@
 
   // if there is only one sub-expression, use that instead
   if len(p.union) == 1 {
-    p.appendPath(p.union[0])
+    p.path.Append(p.union[0])
   }else {
-    p.appendPath(&matchUnionFn{p.union})
+    p.path.Append(&matchUnionFn{p.union})
   }
 
   p.union = nil // clear out state
@@ -214,7 +217,7 @@
 		tok = p.getToken()
 	}
   if tok.typ == tokenRBracket {
-	  p.appendPath(&matchSliceFn{start, end, step})
+	  p.path.Append(newMatchSliceFn(start, end, step))
     return parseMatchExpr
   }
   if tok.typ != tokenColon {
@@ -234,7 +237,7 @@
 		p.raiseError(tok, "expected ']'")
 	}
 
-	p.appendPath(&matchSliceFn{start, end, step})
+	p.path.Append(newMatchSliceFn(start, end, step))
 	return parseMatchExpr
 }
 
@@ -248,11 +251,11 @@
 	return nil
 }
 
-func parse(flow chan token) []PathFn {
+func parse(flow chan token) *QueryPath {
 	parser := &parser{
 		flow:         flow,
 		tokensBuffer: []token{},
-		path:         []PathFn{},
+		path:         newQueryPath(),
 	}
 	parser.run()
 	return parser.path
diff --git a/jpath/parser_test.go b/jpath/parser_test.go
index 6daba44..3103970 100644
--- a/jpath/parser_test.go
+++ b/jpath/parser_test.go
@@ -18,7 +18,7 @@
 		return
 	}
 	path := parse(flow)
-	result := processPath(path, tree)
+	result := path.Call(tree)
 	assertValue(t, result, ref, "((" + query + ")) -> ")
 }