Revised after review

* Dropped 'script' support
* Improved documentation
* Made pathFn members private
diff --git a/README.md b/README.md
index db8e427..0d62a7b 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,42 @@
 * `tree.GetPosition("comma.separated.path")` Returns the position of the given
   path in the source.
 
+### Support for queries
+:
+Go-toml also supports a JSON-Path style syntax for querying a document for
+collections of elements, and more.
+
+```go
+import (
+    "fmt"
+    "github.com/pelletier/go-toml"
+)
+config, err := toml.Load(`
+[[book]]
+title = "The Stand"
+author = "Stephen King"
+[[book]]
+title = "For Whom the Bell Tolls"
+author = "Earnest Hemmingway"
+[[book]]
+title = "Neuromancer"
+author = "William Gibson"
+`)
+
+if err != nil {
+    fmt.Println("Error ", err.Error())
+} else {
+    // find and print all the authors in the document
+    authors := config.Query("$.book.author")
+    for _, name := authors.Value() {
+        fmt.Println(name)
+    }
+}
+```
+
+More information about the format of TOML queries can be found on the
+[godoc page for go-toml](http://godoc.org/github.com/pelletier/go-toml).
+
 ## Documentation
 
 The documentation is available at
diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..a231199
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,245 @@
+// Package toml is a TOML markup language parser.
+//
+// This version supports the specification as described in
+// https://github.com/toml-lang/toml/blob/master/versions/toml-v0.2.0.md
+//
+// TOML Parsing
+//
+// TOML data may be parsed in two ways: by file, or by string.
+//
+//   // load TOML data by filename
+//   tree, err := toml.LoadFile("filename.toml")
+//
+//   // load TOML data stored in a string
+//   tree, err := toml.Load(stringContainingTomlData)
+//
+// Either way, the result is a TomlTree object that can be used to navigate the
+// structure and data within the original document.
+//
+//
+// Getting data from the TomlTree
+//
+// After parsing TOML data with Load() or LoadFile(), use the Has() and Get()
+// methods on the returned TomlTree, to find your way through the document data.
+//
+//   if tree.Has('foo') {
+//     fmt.Prinln("foo is: %v", tree.Get('foo'))
+//   }
+//
+// Working with Paths
+//
+// Go-toml has support for basic dot-separated key paths on the Has(), Get(), Set()
+// and GetDefault() methods.  These are the same kind of key paths used within the
+// TOML specification for struct tames.
+//
+//   // looks for a key named 'baz', within struct 'bar', within struct 'foo'
+//   tree.Has("foo.bar.baz")
+//
+//   // returns the key at this path, if it is there
+//   tree.Get("foo.bar.baz")
+//
+// TOML allows keys to contain '.', which can cause this syntax to be problematic
+// for some documents.  In such cases, use the GetPath(), HasPath(), and SetPath(),
+// methods to explicitly define the path.  This form is also faster, since
+// it avoids having to parse the passed key for '.' delimiters.
+//
+//   // looks for a key named 'baz', within struct 'bar', within struct 'foo'
+//   tree.HasPath(string{}{"foo","bar","baz"})
+//
+//   // returns the key at this path, if it is there
+//   tree.GetPath(string{}{"foo","bar","baz"})
+//
+// Note that this is distinct from the heavyweight query syntax supported by
+// TomlTree.Query() and the Query() struct (see below).
+//
+// Position Support
+//
+// Each element within the TomlTree is stored with position metadata, which is
+// invaluable for providing semantic feedback to a user.  This helps in
+// situations where the TOML file parses correctly, but contains data that is
+// not correct for the application.  In such cases, an error message can be
+// generated that indicates the problem line and column number in the source
+// TOML document.
+//
+//   // load TOML data
+//   tree, _ := toml.Load("filename.toml")
+//
+//   // get an entry and report an error if it's the wrong type
+//   element := tree.Get("foo")
+//   if value, ok := element.(int64); !ok {
+//       return fmt.Errorf("%v: Element 'foo' must be an integer", tree.GetPosition("foo"))
+//   }
+//
+//   // report an error if an expected element is missing
+//   if !tree.Has("bar") {
+//      return fmt.Errorf("%v: Expected 'bar' element", tree.GetPosition(""))
+//   }
+//
+// Query Support
+//
+// The TOML query path implementation is based loosely on the JSONPath specification:
+// http://goessner.net/articles/JsonPath/
+//
+// The idea behind a query path is to allow quick access to any element, or set
+// of elements within TOML document, with a single expression.
+//
+//   result := tree.Query("$.foo.bar.baz")  // result is 'nil' if the path is not present
+//
+// This is equivalent to:
+//
+//   next := tree.Get("foo")
+//   if next != nil {
+//     next = next.Get("bar")
+//     if next != nil {
+//       next = next.Get("baz")
+//     }
+//   }
+//   result := next
+//
+// As illustrated above, the query path is much more efficient, especially since
+// the structure of the TOML file can vary.  Rather than making assumptions about
+// a document's structure, a query allows the programmer to make structured
+// requests into the document, and get zero or more values as a result.
+//
+// The syntax of a query begins with a root token, followed by any number
+// sub-expressions:
+//
+//   $
+//                    Root of the TOML tree.  This must always come first.
+//   .name
+//                    Selects child of this node, where 'name' is a TOML key
+//                    name.
+//   ['name']
+//                    Selects child of this node, where 'name' is a string
+//                    containing a TOML key name.
+//   [index]
+//                    Selcts child array element at 'index'.
+//   ..expr
+//                    Recursively selects all children, filtered by an a union,
+//                    index, or slice expression.
+//   ..*
+//                    Recursive selection of all nodes at this point in the
+//                    tree.
+//   .*
+//                    Selects all children of the current node.
+//   [expr,expr]
+//                    Union operator - a logical 'or' grouping of two or more
+//                    sub-expressions: index, key name, or filter.
+//   [start:end:step]
+//                    Slice operator - selects array elements from start to
+//                    end-1, at the given step.  All three arguments are
+//                    optional.
+//   [?(filter)]
+//                    Named filter expression - the function 'filter' is
+//                    used to filter children at this node.
+//
+// Query Indexes And Slices
+//
+// Index expressions perform no bounds checking, and will contribute no
+// values to the result set if the provided index or index range is invalid.
+// Negative indexes represent values from the end of the array, counting backwards.
+//
+//   // select the last index of the array named 'foo'
+//   tree.Query("$.foo[-1]")
+//
+// Slice expressions are supported, by using ':' to separate a start/end index pair.
+//
+//   // select up to the first five elements in the array
+//   tree.Query("$.foo[0:5]")
+//
+// Slice expressions also allow negative indexes for the start and stop
+// arguments.
+//
+//   // select all array elements.
+//   tree.Query("$.foo[0:-1]")
+//
+// Slice expressions may have an optional stride/step parameter:
+//
+//   // select every other element
+//   tree.Query("$.foo[0:-1:2]")
+//
+// Slice start and end parameters are also optional:
+//
+//   // these are all equivalent and select all the values in the array
+//   tree.Query("$.foo[:]")
+//   tree.Query("$.foo[0:]")
+//   tree.Query("$.foo[:-1]")
+//   tree.Query("$.foo[0:-1:]")
+//   tree.Query("$.foo[::1]")
+//   tree.Query("$.foo[0::1]")
+//   tree.Query("$.foo[:-1:1]")
+//   tree.Query("$.foo[0:-1:1]")
+//
+// Query Filters
+//
+// Query filters are used within a Union [,] or single Filter [] expression.
+// A filter only allows nodes that qualify through to the next expression,
+// and/or into the result set.
+//
+//   // returns children of foo that are permitted by the 'bar' filter.
+//   tree.Query("$.foo[?(bar)]")
+//
+// There are several filters provided with the library:
+//
+//   tree
+//          Allows nodes of type TomlTree.
+//   int
+//          Allows nodes of type int64.
+//   float
+//          Allows nodes of type float64.
+//   string
+//          Allows nodes of type string.
+//   time
+//          Allows nodes of type time.Time.
+//   bool
+//          Allows nodes of type bool.
+//
+// Query Results
+//
+// An executed query returns a QueryResult object.  This contains the nodes
+// in the TOML tree that qualify the query expression.  Position information
+// is also available for each value in the set.
+//
+//   // display the results of a query
+//   results := tree.Query("$.foo.bar.baz")
+//   for idx, value := results.Values() {
+//       fmt.Println("%v: %v", results.Positions()[idx], value)
+//   }
+//
+// Compiled Queries
+//
+// Queries may be executed directly on a TomlTree object, or compiled ahead
+// of time and executed discretely.  The former is more convienent, but has the
+// penalty of having to recompile the query expression each time.
+//
+//   // basic query
+//   results := tree.Query("$.foo.bar.baz")
+//
+//   // compiled query
+//   query := toml.CompileQuery("$.foo.bar.baz")
+//   results := query.Execute(tree)
+//
+//   // run the compiled query again on a different tree
+//   moreResults := query.Execute(anotherTree)
+//
+// User Defined Query Filters
+//
+// Filter expressions may also be user defined by using the SetFilter()
+// function on the Query object.  The function must return true/false, which
+// signifies if the passed node is kept or discarded, respectively.
+//
+//   // create a query that references a user-defined filter
+//   query, _ := CompileQuery("$[?(bazOnly)]")
+//
+//   // define the filter, and assign it to the query
+//   query.SetFilter("bazOnly", func(node interface{}) bool{
+//       if tree, ok := node.(*TomlTree); ok {
+//           return tree.Has("baz")
+//       }
+//       return false  // reject all other node types
+//   })
+//
+//   // run the query
+//   query.Execute(tree)
+//
+package toml
diff --git a/doc_test.go b/doc_test.go
new file mode 100644
index 0000000..c5c27a0
--- /dev/null
+++ b/doc_test.go
@@ -0,0 +1,51 @@
+// code examples for godoc
+
+package toml
+
+import "fmt"
+
+func ExampleNodeFilterFn_filterExample() {
+	tree, _ := Load(`
+      [struct_one]
+      foo = "foo"
+      bar = "bar"
+
+      [struct_two]
+      baz = "baz"
+      gorf = "gorf"
+    `)
+
+	// create a query that references a user-defined-filter
+	query, _ := CompileQuery("$[?(bazOnly)]")
+
+	// define the filter, and assign it to the query
+	query.SetFilter("bazOnly", func(node interface{}) bool {
+		if tree, ok := node.(*TomlTree); ok {
+			return tree.Has("baz")
+		}
+		return false // reject all other node types
+	})
+
+	// results contain only the 'struct_two' TomlTree
+	query.Execute(tree)
+}
+
+func ExampleQuery_queryExample() {
+	config, _ := Load(`
+      [[book]]
+      title = "The Stand"
+      author = "Stephen King"
+      [[book]]
+      title = "For Whom the Bell Tolls"
+      author = "Ernest Hemmingway"
+      [[book]]
+      title = "Neuromancer"
+      author = "William Gibson"
+    `)
+
+	// find and print all the authors in the document
+	authors, _ := config.Query("$.book.author")
+	for _, name := range authors.Values() {
+		fmt.Println(name)
+	}
+}
diff --git a/match.go b/match.go
index 2b89be9..423a869 100644
--- a/match.go
+++ b/match.go
@@ -24,10 +24,10 @@
 
 // base match
 type matchBase struct {
-	next PathFn
+	next pathFn
 }
 
-func (f *matchBase) SetNext(next PathFn) {
+func (f *matchBase) setNext(next pathFn) {
 	f.next = next
 }
 
@@ -40,11 +40,11 @@
 	return &terminatingFn{}
 }
 
-func (f *terminatingFn) SetNext(next PathFn) {
+func (f *terminatingFn) setNext(next pathFn) {
 	// do nothing
 }
 
-func (f *terminatingFn) Call(node interface{}, ctx *queryContext) {
+func (f *terminatingFn) call(node interface{}, ctx *queryContext) {
 	switch castNode := node.(type) {
 	case *TomlTree:
 		ctx.result.appendResult(node, castNode.position)
@@ -66,11 +66,11 @@
 	return &matchKeyFn{Name: name}
 }
 
-func (f *matchKeyFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchKeyFn) call(node interface{}, ctx *queryContext) {
 	if tree, ok := node.(*TomlTree); ok {
 		item := tree.values[f.Name]
 		if item != nil {
-			f.next.Call(item, ctx)
+			f.next.call(item, ctx)
 		}
 	}
 }
@@ -85,10 +85,10 @@
 	return &matchIndexFn{Idx: idx}
 }
 
-func (f *matchIndexFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchIndexFn) call(node interface{}, ctx *queryContext) {
 	if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
 		if f.Idx < len(arr) && f.Idx >= 0 {
-			f.next.Call(arr[f.Idx], ctx)
+			f.next.call(arr[f.Idx], ctx)
 		}
 	}
 }
@@ -103,7 +103,7 @@
 	return &matchSliceFn{Start: start, End: end, Step: step}
 }
 
-func (f *matchSliceFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchSliceFn) call(node interface{}, ctx *queryContext) {
 	if arr, ok := tomlValueCheck(node, ctx).([]interface{}); ok {
 		// adjust indexes for negative values, reverse ordering
 		realStart, realEnd := f.Start, f.End
@@ -118,7 +118,7 @@
 		}
 		// loop and gather
 		for idx := realStart; idx < realEnd; idx += f.Step {
-			f.next.Call(arr[idx], ctx)
+			f.next.call(arr[idx], ctx)
 		}
 	}
 }
@@ -132,28 +132,28 @@
 	return &matchAnyFn{}
 }
 
-func (f *matchAnyFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchAnyFn) call(node interface{}, ctx *queryContext) {
 	if tree, ok := node.(*TomlTree); ok {
 		for _, v := range tree.values {
-			f.next.Call(v, ctx)
+			f.next.call(v, ctx)
 		}
 	}
 }
 
 // filter through union
 type matchUnionFn struct {
-	Union []PathFn
+	Union []pathFn
 }
 
-func (f *matchUnionFn) SetNext(next PathFn) {
+func (f *matchUnionFn) setNext(next pathFn) {
 	for _, fn := range f.Union {
-		fn.SetNext(next)
+		fn.setNext(next)
 	}
 }
 
-func (f *matchUnionFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchUnionFn) call(node interface{}, ctx *queryContext) {
 	for _, fn := range f.Union {
-		fn.Call(node, ctx)
+		fn.call(node, ctx)
 	}
 }
 
@@ -166,12 +166,12 @@
 	return &matchRecursiveFn{}
 }
 
-func (f *matchRecursiveFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchRecursiveFn) call(node interface{}, ctx *queryContext) {
 	if tree, ok := node.(*TomlTree); ok {
 		var visit func(tree *TomlTree)
 		visit = func(tree *TomlTree) {
 			for _, v := range tree.values {
-				f.next.Call(v, ctx)
+				f.next.call(v, ctx)
 				switch node := v.(type) {
 				case *TomlTree:
 					visit(node)
@@ -182,6 +182,7 @@
 				}
 			}
 		}
+		f.next.call(tree, ctx)
 		visit(tree)
 	}
 }
@@ -197,7 +198,7 @@
 	return &matchFilterFn{Name: name, Pos: pos}
 }
 
-func (f *matchFilterFn) Call(node interface{}, ctx *queryContext) {
+func (f *matchFilterFn) call(node interface{}, ctx *queryContext) {
 	fn, ok := (*ctx.filters)[f.Name]
 	if !ok {
 		panic(fmt.Sprintf("%s: query context does not have filter '%s'",
@@ -206,45 +207,21 @@
 	switch castNode := tomlValueCheck(node, ctx).(type) {
 	case *TomlTree:
 		for _, v := range castNode.values {
-			if fn(v) {
-				f.next.Call(v, ctx)
+			if tv, ok := v.(*tomlValue); ok {
+				if fn(tv.value) {
+					f.next.call(v, ctx)
+				}
+			} else {
+				if fn(v) {
+					f.next.call(v, ctx)
+				}
 			}
 		}
 	case []interface{}:
 		for _, v := range castNode {
 			if fn(v) {
-				f.next.Call(v, ctx)
+				f.next.call(v, ctx)
 			}
 		}
 	}
 }
-
-// match based using result of an externally provided functional filter
-type matchScriptFn struct {
-	matchBase
-	Pos  Position
-	Name string
-}
-
-func newMatchScriptFn(name string, pos Position) *matchScriptFn {
-	return &matchScriptFn{Name: name, Pos: pos}
-}
-
-func (f *matchScriptFn) Call(node interface{}, ctx *queryContext) {
-	fn, ok := (*ctx.scripts)[f.Name]
-	if !ok {
-		panic(fmt.Sprintf("%s: query context does not have script '%s'",
-			f.Pos, f.Name))
-	}
-	switch result := fn(tomlValueCheck(node, ctx)).(type) {
-	case string:
-		nextMatch := newMatchKeyFn(result)
-		nextMatch.SetNext(f.next)
-		nextMatch.Call(node, ctx)
-	case int:
-		nextMatch := newMatchIndexFn(result)
-		nextMatch.SetNext(f.next)
-		nextMatch.Call(node, ctx)
-		//TODO: support other return types?
-	}
-}
diff --git a/match_test.go b/match_test.go
index cd0390b..c594eb5 100644
--- a/match_test.go
+++ b/match_test.go
@@ -7,7 +7,7 @@
 )
 
 // dump path tree to a string
-func pathString(root PathFn) string {
+func pathString(root pathFn) string {
 	result := fmt.Sprintf("%T:", root)
 	switch fn := root.(type) {
 	case *terminatingFn:
@@ -37,9 +37,6 @@
 	case *matchFilterFn:
 		result += fmt.Sprintf("{%s}", fn.Name)
 		result += pathString(fn.next)
-	case *matchScriptFn:
-		result += fmt.Sprintf("{%s}", fn.Name)
-		result += pathString(fn.next)
 	}
 	return result
 }
@@ -61,7 +58,7 @@
 	assertPathMatch(t, path, ref)
 }
 
-func buildPath(parts ...PathFn) *Query {
+func buildPath(parts ...pathFn) *Query {
 	query := newQuery()
 	for _, v := range parts {
 		query.appendPath(v)
@@ -177,7 +174,7 @@
 	assertPath(t,
 		"$[foo, bar, baz]",
 		buildPath(
-			&matchUnionFn{[]PathFn{
+			&matchUnionFn{[]pathFn{
 				newMatchKeyFn("foo"),
 				newMatchKeyFn("bar"),
 				newMatchKeyFn("baz"),
@@ -197,20 +194,9 @@
 	assertPath(t,
 		"$[?('foo'),?(bar)]",
 		buildPath(
-			&matchUnionFn{[]PathFn{
+			&matchUnionFn{[]pathFn{
 				newMatchFilterFn("foo", Position{}),
 				newMatchFilterFn("bar", Position{}),
 			}},
 		))
 }
-
-func TestPathScriptExpr(t *testing.T) {
-	assertPath(t,
-		"$[('foo'),(bar)]",
-		buildPath(
-			&matchUnionFn{[]PathFn{
-				newMatchScriptFn("foo", Position{}),
-				newMatchScriptFn("bar", Position{}),
-			}},
-		))
-}
diff --git a/position.go b/position.go
index fd42a0d..1f26245 100644
--- a/position.go
+++ b/position.go
@@ -6,7 +6,13 @@
 	"fmt"
 )
 
-// Position within a TOML document
+/*
+  Position of a document element within a TOML document.
+
+  Line and Col are both 1-indexed positions for the element's line number and
+  column number, respectively.  Values of zero or less will cause Invalid(),
+  to return true.
+*/
 type Position struct {
 	Line int // line within the document
 	Col  int // column within the line
@@ -18,7 +24,7 @@
 	return fmt.Sprintf("(%d, %d)", p.Line, p.Col)
 }
 
-// Invalid returns wheter or not the position is valid (i.e. with negative or
+// Returns whether or not the position is valid (i.e. with negative or
 // null values)
 func (p *Position) Invalid() bool {
 	return p.Line <= 0 || p.Col <= 0
diff --git a/query.go b/query.go
index dfda4e8..597f4ff 100644
--- a/query.go
+++ b/query.go
@@ -1,22 +1,39 @@
 package toml
 
-type nodeFilterFn func(node interface{}) bool
-type nodeFn func(node interface{}) interface{}
+import (
+	"time"
+)
 
+//  Type of a user-defined filter function, for use with Query.SetFilter().
+//
+//  The return value of the function must indicate if 'node' is to be included
+//  at this stage of the TOML path.  Returning true will include the node, and
+//  returning false will exclude it.
+//
+//  NOTE: Care should be taken to write script callbacks such that they are safe
+//  to use from multiple goroutines.
+type NodeFilterFn func(node interface{}) bool
+
+// The result of Executing a Query
 type QueryResult struct {
 	items     []interface{}
 	positions []Position
 }
 
+// appends a value/position pair to the result set
 func (r *QueryResult) appendResult(node interface{}, pos Position) {
 	r.items = append(r.items, node)
 	r.positions = append(r.positions, pos)
 }
 
+// Set of values within a QueryResult.  The order of values is not guaranteed
+// to be in document order, and may be different each time a query is executed.
 func (r *QueryResult) Values() []interface{} {
 	return r.items
 }
 
+// Set of positions for values within a QueryResult.  Each index in Positions()
+// corresponds to the entry in Value() of the same index.
 func (r *QueryResult) Positions() []Position {
 	return r.positions
 }
@@ -24,23 +41,22 @@
 // runtime context for executing query paths
 type queryContext struct {
 	result       *QueryResult
-	filters      *map[string]nodeFilterFn
-	scripts      *map[string]nodeFn
+	filters      *map[string]NodeFilterFn
 	lastPosition Position
 }
 
 // generic path functor interface
-type PathFn interface {
-	SetNext(next PathFn)
-	Call(node interface{}, ctx *queryContext)
+type pathFn interface {
+	setNext(next pathFn)
+	call(node interface{}, ctx *queryContext)
 }
 
-// encapsulates a query functor chain and script callbacks
+// A Query is the representation of a compiled TOML path.  A Query is safe
+// for concurrent use by multiple goroutines.
 type Query struct {
-	root    PathFn
-	tail    PathFn
-	filters *map[string]nodeFilterFn
-	scripts *map[string]nodeFn
+	root    pathFn
+	tail    pathFn
+	filters *map[string]NodeFilterFn
 }
 
 func newQuery() *Query {
@@ -48,25 +64,26 @@
 		root:    nil,
 		tail:    nil,
 		filters: &defaultFilterFunctions,
-		scripts: &defaultScriptFunctions,
 	}
 }
 
-func (q *Query) appendPath(next PathFn) {
+func (q *Query) appendPath(next pathFn) {
 	if q.root == nil {
 		q.root = next
 	} else {
-		q.tail.SetNext(next)
+		q.tail.setNext(next)
 	}
 	q.tail = next
-	next.SetNext(newTerminatingFn()) // init the next functor
+	next.setNext(newTerminatingFn()) // init the next functor
 }
 
-// TODO: return (err,query) instead
-func Compile(path string) (*Query, error) {
+// Compiles a TOML path expression.  The returned Query can be used to match
+// elements within a TomlTree and its descendants.
+func CompileQuery(path string) (*Query, error) {
 	return parseQuery(lexQuery(path))
 }
 
+// Executes a query against a TomlTree, and returns the result of the query.
 func (q *Query) Execute(tree *TomlTree) *QueryResult {
 	result := &QueryResult{
 		items:     []interface{}{},
@@ -78,17 +95,18 @@
 		ctx := &queryContext{
 			result:  result,
 			filters: q.filters,
-			scripts: q.scripts,
 		}
-		q.root.Call(tree, ctx)
+		q.root.call(tree, ctx)
 	}
 	return result
 }
 
-func (q *Query) SetFilter(name string, fn nodeFilterFn) {
+// Sets a user-defined filter function.  These may be used inside "?(..)" query
+// expressions to filter TOML document elements within a query.
+func (q *Query) SetFilter(name string, fn NodeFilterFn) {
 	if q.filters == &defaultFilterFunctions {
 		// clone the static table
-		q.filters = &map[string]nodeFilterFn{}
+		q.filters = &map[string]NodeFilterFn{}
 		for k, v := range defaultFilterFunctions {
 			(*q.filters)[k] = v
 		}
@@ -96,37 +114,29 @@
 	(*q.filters)[name] = fn
 }
 
-func (q *Query) SetScript(name string, fn nodeFn) {
-	if q.scripts == &defaultScriptFunctions {
-		// clone the static table
-		q.scripts = &map[string]nodeFn{}
-		for k, v := range defaultScriptFunctions {
-			(*q.scripts)[k] = v
-		}
-	}
-	(*q.scripts)[name] = fn
-}
-
-var defaultFilterFunctions = map[string]nodeFilterFn{
-	"odd": func(node interface{}) bool {
-		if ii, ok := node.(int64); ok {
-			return (ii & 1) == 1
-		}
-		return false
+var defaultFilterFunctions = map[string]NodeFilterFn{
+	"tree": func(node interface{}) bool {
+		_, ok := node.(*TomlTree)
+		return ok
 	},
-	"even": func(node interface{}) bool {
-		if ii, ok := node.(int64); ok {
-			return (ii & 1) == 0
-		}
-		return false
+	"int": func(node interface{}) bool {
+		_, ok := node.(int64)
+		return ok
 	},
-}
-
-var defaultScriptFunctions = map[string]nodeFn{
-	"last": func(node interface{}) interface{} {
-		if arr, ok := node.([]interface{}); ok {
-			return len(arr) - 1
-		}
-		return nil
+	"float": func(node interface{}) bool {
+		_, ok := node.(float64)
+		return ok
+	},
+	"string": func(node interface{}) bool {
+		_, ok := node.(string)
+		return ok
+	},
+	"time": func(node interface{}) bool {
+		_, ok := node.(time.Time)
+		return ok
+	},
+	"bool": func(node interface{}) bool {
+		_, ok := node.(bool)
+		return ok
 	},
 }
diff --git a/queryparser.go b/queryparser.go
index 8fdb10b..be6954f 100644
--- a/queryparser.go
+++ b/queryparser.go
@@ -16,7 +16,7 @@
 	flow         chan token
 	tokensBuffer []token
 	query        *Query
-	union        []PathFn
+	union        []pathFn
 	err          error
 }
 
@@ -156,7 +156,7 @@
 	// this state can be traversed after some sub-expressions
 	// so be careful when setting up state in the parser
 	if p.union == nil {
-		p.union = []PathFn{}
+		p.union = []pathFn{}
 	}
 
 loop: // labeled loop for easy breaking
@@ -185,8 +185,6 @@
 			p.union = append(p.union, newMatchKeyFn(tok.val))
 		case tokenQuestion:
 			return p.parseFilterExpr
-		case tokenLeftParen:
-			return p.parseScriptExpr
 		default:
 			return p.parseError(tok, "expected union sub expression, not '%s', %d", tok.val, len(p.union))
 		}
@@ -266,20 +264,6 @@
 	return p.parseUnionExpr
 }
 
-func (p *queryParser) parseScriptExpr() queryParserStateFn {
-	tok := p.getToken()
-	if tok.typ != tokenKey && tok.typ != tokenString {
-		return p.parseError(tok, "expected key or string for script funciton name")
-	}
-	name := tok.val
-	tok = p.getToken()
-	if tok.typ != tokenRightParen {
-		return p.parseError(tok, "expected right-parenthesis for script expression")
-	}
-	p.union = append(p.union, newMatchScriptFn(name, tok.Position))
-	return p.parseUnionExpr
-}
-
 func parseQuery(flow chan token) (*Query, error) {
 	parser := &queryParser{
 		flow:         flow,
diff --git a/queryparser_test.go b/queryparser_test.go
index bd147a0..b2b85ce 100644
--- a/queryparser_test.go
+++ b/queryparser_test.go
@@ -2,9 +2,11 @@
 
 import (
 	"fmt"
+	"io/ioutil"
 	"sort"
 	"strings"
 	"testing"
+	"time"
 )
 
 type queryTestNode struct {
@@ -56,6 +58,12 @@
 		result += fmt.Sprintf("%d", node)
 	case string:
 		result += "'" + node + "'"
+	case float64:
+		result += fmt.Sprintf("%f", node)
+	case bool:
+		result += fmt.Sprintf("%t", node)
+	case time.Time:
+		result += fmt.Sprintf("'%v'", node)
 	}
 	return result
 }
@@ -76,7 +84,7 @@
 		t.Errorf("Non-nil toml parse error: %v", err)
 		return
 	}
-	q, err := Compile(query)
+	q, err := CompileQuery(query)
 	if err != nil {
 		t.Error(err)
 		return
@@ -223,6 +231,28 @@
 		[]interface{}{
 			queryTestNode{
 				map[string]interface{}{
+					"foo": map[string]interface{}{
+						"bar": map[string]interface{}{
+							"a": int64(1),
+							"b": int64(2),
+						},
+					},
+					"baz": map[string]interface{}{
+						"foo": map[string]interface{}{
+							"a": int64(3),
+							"b": int64(4),
+						},
+					},
+					"gorf": map[string]interface{}{
+						"foo": map[string]interface{}{
+							"a": int64(5),
+							"b": int64(6),
+						},
+					},
+				}, Position{1, 1},
+			},
+			queryTestNode{
+				map[string]interface{}{
 					"bar": map[string]interface{}{
 						"a": int64(1),
 						"b": int64(2),
@@ -291,8 +321,10 @@
 		[]interface{}{
 			queryTestNode{
 				map[string]interface{}{
-					"a": int64(1),
-					"b": int64(2),
+					"bar": map[string]interface{}{
+						"a": int64(1),
+						"b": int64(2),
+					},
 				}, Position{1, 1},
 			},
 			queryTestNode{
@@ -303,6 +335,12 @@
 			},
 			queryTestNode{
 				map[string]interface{}{
+					"a": int64(1),
+					"b": int64(2),
+				}, Position{1, 1},
+			},
+			queryTestNode{
+				map[string]interface{}{
 					"a": int64(5),
 					"b": int64(6),
 				}, Position{7, 1},
@@ -310,59 +348,136 @@
 		})
 }
 
-func TestQueryScriptFnLast(t *testing.T) {
-	assertQueryPositions(t,
-		"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
-		"$.foo.a[(last)]",
+func TestQueryFilterFn(t *testing.T) {
+	buff, err := ioutil.ReadFile("example.toml")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	assertQueryPositions(t, string(buff),
+		"$..[?(int)]",
 		[]interface{}{
 			queryTestNode{
-				int64(9), Position{2, 1},
+				int64(8001), Position{13, 1},
+			},
+			queryTestNode{
+				int64(8001), Position{13, 1},
+			},
+			queryTestNode{
+				int64(8002), Position{13, 1},
+			},
+			queryTestNode{
+				int64(5000), Position{14, 1},
 			},
 		})
-}
 
-func TestQueryFilterFnOdd(t *testing.T) {
-	assertQueryPositions(t,
-		"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
-		"$.foo.a[?(odd)]",
+	assertQueryPositions(t, string(buff),
+		"$..[?(string)]",
 		[]interface{}{
 			queryTestNode{
-				int64(1), Position{2, 1},
+				"TOML Example", Position{3, 1},
 			},
 			queryTestNode{
-				int64(3), Position{2, 1},
+				"Tom Preston-Werner", Position{6, 1},
 			},
 			queryTestNode{
-				int64(5), Position{2, 1},
+				"GitHub", Position{7, 1},
 			},
 			queryTestNode{
-				int64(7), Position{2, 1},
+				"GitHub Cofounder & CEO\nLikes tater tots and beer.",
+				Position{8, 1},
 			},
 			queryTestNode{
-				int64(9), Position{2, 1},
+				"192.168.1.1", Position{12, 1},
+			},
+			queryTestNode{
+				"10.0.0.1", Position{21, 3},
+			},
+			queryTestNode{
+				"eqdc10", Position{22, 3},
+			},
+			queryTestNode{
+				"10.0.0.2", Position{25, 3},
+			},
+			queryTestNode{
+				"eqdc10", Position{26, 3},
 			},
 		})
-}
 
-func TestQueryFilterFnEven(t *testing.T) {
-	assertQueryPositions(t,
-		"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
-		"$.foo.a[?(even)]",
+	assertQueryPositions(t, string(buff),
+		"$..[?(float)]",
+		[]interface{}{
+		// no float values in document
+		})
+
+	tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
+	assertQueryPositions(t, string(buff),
+		"$..[?(tree)]",
 		[]interface{}{
 			queryTestNode{
-				int64(0), Position{2, 1},
+				map[string]interface{}{
+					"name":         "Tom Preston-Werner",
+					"organization": "GitHub",
+					"bio":          "GitHub Cofounder & CEO\nLikes tater tots and beer.",
+					"dob":          tv,
+				}, Position{5, 1},
 			},
 			queryTestNode{
-				int64(2), Position{2, 1},
+				map[string]interface{}{
+					"server":         "192.168.1.1",
+					"ports":          []interface{}{int64(8001), int64(8001), int64(8002)},
+					"connection_max": int64(5000),
+					"enabled":        true,
+				}, Position{11, 1},
 			},
 			queryTestNode{
-				int64(4), Position{2, 1},
+				map[string]interface{}{
+					"alpha": map[string]interface{}{
+						"ip": "10.0.0.1",
+						"dc": "eqdc10",
+					},
+					"beta": map[string]interface{}{
+						"ip": "10.0.0.2",
+						"dc": "eqdc10",
+					},
+				}, Position{17, 1},
 			},
 			queryTestNode{
-				int64(6), Position{2, 1},
+				map[string]interface{}{
+					"ip": "10.0.0.1",
+					"dc": "eqdc10",
+				}, Position{20, 3},
 			},
 			queryTestNode{
-				int64(8), Position{2, 1},
+				map[string]interface{}{
+					"ip": "10.0.0.2",
+					"dc": "eqdc10",
+				}, Position{24, 3},
+			},
+			queryTestNode{
+				map[string]interface{}{
+					"data": []interface{}{
+						[]interface{}{"gamma", "delta"},
+						[]interface{}{int64(1), int64(2)},
+					},
+				}, Position{28, 1},
+			},
+		})
+
+	assertQueryPositions(t, string(buff),
+		"$..[?(time)]",
+		[]interface{}{
+			queryTestNode{
+				tv, Position{9, 1},
+			},
+		})
+
+	assertQueryPositions(t, string(buff),
+		"$..[?(bool)]",
+		[]interface{}{
+			queryTestNode{
+				true, Position{15, 1},
 			},
 		})
 }
diff --git a/toml.go b/toml.go
index 2c7c55f..13ef35e 100644
--- a/toml.go
+++ b/toml.go
@@ -1,7 +1,3 @@
-// Package toml is a TOML markup language parser.
-//
-// This version supports the specification as described in
-// https://github.com/toml-lang/toml/blob/master/versions/toml-v0.2.0.md
 package toml
 
 import (
@@ -320,7 +316,7 @@
 }
 
 func (t *TomlTree) Query(query string) (*QueryResult, error) {
-	if q, err := Compile(query); err != nil {
+	if q, err := CompileQuery(query); err != nil {
 		return nil, err
 	} else {
 		return q.Execute(t), nil