tile38/internal/server/expr.go

159 lines
3.5 KiB
Go

package server
import (
"sync"
"github.com/tidwall/expr"
"github.com/tidwall/geojson"
"github.com/tidwall/gjson"
"github.com/tidwall/match"
"github.com/tidwall/tile38/internal/field"
"github.com/tidwall/tile38/internal/object"
)
type exprPool struct {
pool *sync.Pool
}
func typeForObject(o *object.Object) expr.Value {
switch o.Geo().(type) {
case *geojson.Point, *geojson.SimplePoint:
return expr.String("Point")
case *geojson.LineString:
return expr.String("LineString")
case *geojson.Polygon, *geojson.Circle, *geojson.Rect:
return expr.String("Polygon")
case *geojson.MultiPoint:
return expr.String("MultiPoint")
case *geojson.MultiLineString:
return expr.String("MultiLineString")
case *geojson.MultiPolygon:
return expr.String("MultiPolygon")
case *geojson.GeometryCollection:
return expr.String("GeometryCollection")
case *geojson.Feature:
return expr.String("Feature")
case *geojson.FeatureCollection:
return expr.String("FeatureCollection")
default:
return expr.Undefined
}
}
func resultToValue(r gjson.Result) expr.Value {
if !r.Exists() {
return expr.Undefined
}
switch r.Type {
case gjson.String:
return expr.String(r.String())
case gjson.False:
return expr.Bool(false)
case gjson.True:
return expr.Bool(true)
case gjson.Number:
return expr.Number(r.Float())
case gjson.JSON:
return expr.Object(r)
default:
return expr.Null
}
}
func newExprPool(s *Server) *exprPool {
ext := expr.NewExtender(
// ref
func(info expr.RefInfo, ctx *expr.Context) (expr.Value, error) {
o := ctx.UserData.(*object.Object)
if !info.Chain {
// root
if r := gjson.Get(o.Geo().Members(), info.Ident); r.Exists() {
return resultToValue(r), nil
}
switch info.Ident {
case "id":
return expr.String(o.ID()), nil
case "type":
return typeForObject(o), nil
default:
var rf field.Field
var ok bool
o.Fields().Scan(func(f field.Field) bool {
if f.Name() == info.Ident {
rf = f
ok = true
return false
}
return true
})
if ok {
r := gjson.Parse(rf.Value().JSON())
return resultToValue(r), nil
}
}
return expr.Number(0), nil
} else {
switch v := info.Value.Value().(type) {
case gjson.Result:
return resultToValue(v.Get(info.Ident)), nil
default:
// object methods
switch info.Ident {
case "match":
return expr.Function("match"), nil
}
}
return expr.Undefined, nil
}
},
// call
func(info expr.CallInfo, ctx *expr.Context) (expr.Value, error) {
if info.Chain {
switch info.Ident {
case "match":
args, err := info.Args.Compute()
if err != nil {
return expr.Undefined, err
}
t := match.Match(info.Value.String(), args.Get(0).String())
return expr.Bool(t), nil
}
}
return expr.Undefined, nil
},
// op
func(info expr.OpInfo, ctx *expr.Context) (expr.Value, error) {
// No custom operations
return expr.Undefined, nil
},
)
return &exprPool{
pool: &sync.Pool{
New: func() any {
ctx := &expr.Context{
Extender: ext,
}
return ctx
},
},
}
}
func (p *exprPool) Get(o *object.Object) *expr.Context {
ctx := p.pool.Get().(*expr.Context)
ctx.UserData = o
ctx.NoCase = true
return ctx
}
func (p *exprPool) Put(ctx *expr.Context) {
p.pool.Put(ctx)
}
func (where whereT) matchExpr(s *Server, o *object.Object) bool {
ctx := s.epool.Get(o)
res, _ := expr.Eval(where.name, ctx)
s.epool.Put(ctx)
return res.Bool()
}