| package jpath |
| |
| import ( |
| _ "github.com/pelletier/go-toml" |
| ) |
| |
| type nodeFilterFn func(node interface{}) bool |
| type nodeFn func(node interface{}) interface{} |
| |
| // runtime context for executing query paths |
| type queryContext struct { |
| filters *map[string]nodeFilterFn |
| scripts *map[string]nodeFn |
| results []interface{} |
| } |
| |
| func (c *queryContext) appendResult(value interface{}) { |
| c.results = append(c.results, value) |
| } |
| |
| // generic path functor interface |
| type PathFn interface { |
| SetNext(next PathFn) |
| Call(node interface{}, ctx *queryContext) |
| } |
| |
| // encapsulates a query functor chain and script callbacks |
| type Query struct { |
| root PathFn |
| tail PathFn |
| filters *map[string]nodeFilterFn |
| scripts *map[string]nodeFn |
| } |
| |
| func newQuery() *Query { |
| return &Query { |
| root: nil, |
| tail: nil, |
| filters: &defaultFilterFunctions, |
| scripts: &defaultScriptFunctions, |
| } |
| } |
| |
| func (q *Query) appendPath(next PathFn) { |
| if q.root == nil { |
| q.root = next |
| } else { |
| q.tail.SetNext(next) |
| } |
| q.tail = next |
| next.SetNext(newTerminatingFn()) // init the next functor |
| } |
| |
| func Compile(path string) *Query { |
| _, flow := lex(path) |
| return parse(flow) |
| } |
| |
| func (q *Query) Execute(node interface{}) interface{} { |
| if q.root == nil { |
| return []interface{}{node} // identity query for no predicates |
| } |
| ctx := &queryContext { |
| filters: q.filters, |
| scripts: q.scripts, |
| results: []interface{}{}, |
| } |
| q.root.Call(node, ctx) |
| return ctx.results |
| } |
| |
| func (q *Query) SetFilter(name string, fn nodeFilterFn) { |
| if q.filters == &defaultFilterFunctions { |
| // clone the static table |
| q.filters = &map[string]nodeFilterFn{} |
| for k, v := range defaultFilterFunctions { |
| (*q.filters)[k] = v |
| } |
| } |
| (*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 |
| }, |
| "even": func(node interface{}) bool { |
| if ii, ok := node.(int64); ok { |
| return (ii & 1) == 0 |
| } |
| return false |
| }, |
| } |
| |
| var defaultScriptFunctions = map[string]nodeFn { |
| "last": func(node interface{}) interface{} { |
| if arr, ok := node.([]interface{}); ok { |
| return len(arr)-1 |
| } |
| return nil |
| }, |
| } |
| |