Files
QSfera/Server/vendor/github.com/open-policy-agent/opa/v1/ast/term.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

3284 lines
78 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package ast
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net/url"
"slices"
"strconv"
"strings"
"sync"
"unicode"
"github.com/cespare/xxhash/v2"
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
"github.com/open-policy-agent/opa/v1/ast/location"
"github.com/open-policy-agent/opa/v1/util"
)
// maxBindingsEstimate is the cap for binding count estimates in comprehensions.
// This value aligns with maxLinearScan in topdown/bindings.go.
const maxBindingsEstimate = 16
// EstimateBodyBindingCount returns an estimate of the number of bindings needed
// for evaluating a comprehension body. It uses the body length as a heuristic,
// capped at maxBindingsEstimate.
func EstimateBodyBindingCount(body Body) (estimate int) {
return min(len(body), maxBindingsEstimate)
}
var (
NullValue Value = Null{}
errFindNotFound = errors.New("find: not found")
)
// Location records a position in source code.
type Location = location.Location
// NewLocation returns a new Location object.
func NewLocation(text []byte, file string, row int, col int) *Location {
return location.NewLocation(text, file, row, col)
}
// Value declares the common interface for all Term values. Every kind of Term value
// in the language is represented as a type that implements this interface:
//
// - Null, Boolean, Number, String
// - Object, Array, Set
// - Variables, References
// - Array, Set, and Object Comprehensions
// - Calls
// - Template Strings
type Value interface {
Compare(other Value) int // Compare returns <0, 0, or >0 if this Value is less than, equal to, or greater than other, respectively.
Find(path Ref) (Value, error) // Find returns value referred to by path or an error if path is not found.
Hash() int // Returns hash code of the value.
IsGround() bool // IsGround returns true if this value is not a variable or contains no variables.
String() string // String returns a human readable string representation of the value.
StringLengther // All Values must be able to report their string length during optimization.
}
// InterfaceToValue converts a native Go value x to a Value.
func InterfaceToValue(x any) (Value, error) {
switch x := x.(type) {
case Value:
return x, nil
case nil:
return NullValue, nil
case bool:
return InternedValue(x), nil
case json.Number:
if interned := InternedIntNumberTermFromString(string(x)); interned != nil {
return interned.Value, nil
}
return Number(x), nil
case int:
return InternedValueOr(x, newIntNumberValue), nil
case int64:
return InternedValueOr(x, newInt64NumberValue), nil
case uint64:
return InternedValueOr(x, newUint64NumberValue), nil
case float64:
return floatNumber(x), nil
case string:
return String(x), nil
case []any:
r := util.NewPtrSlice[Term](len(x))
for i, e := range x {
e, err := InterfaceToValue(e)
if err != nil {
return nil, err
}
r[i].Value = e
}
return NewArray(r...), nil
case []string:
r := util.NewPtrSlice[Term](len(x))
for i, e := range x {
r[i].Value = String(e)
}
return NewArray(r...), nil
case map[string]any:
kvs := util.NewPtrSlice[Term](len(x) * 2)
idx := 0
for k, v := range x {
kvs[idx].Value = String(k)
v, err := InterfaceToValue(v)
if err != nil {
return nil, err
}
kvs[idx+1].Value = v
idx += 2
}
tuples := make([][2]*Term, len(kvs)/2)
for i := 0; i < len(kvs); i += 2 {
tuples[i/2] = *(*[2]*Term)(kvs[i : i+2])
}
return NewObject(tuples...), nil
case map[string]string:
r := newobject(len(x))
for k, v := range x {
r.Insert(StringTerm(k), StringTerm(v))
}
return r, nil
default:
ptr := util.Reference(x)
if err := util.RoundTrip(ptr); err != nil {
return nil, fmt.Errorf("ast: interface conversion: %w", err)
}
return InterfaceToValue(*ptr)
}
}
// ValueFromReader returns an AST value from a JSON serialized value in the reader.
func ValueFromReader(r io.Reader) (Value, error) {
var x any
if err := util.NewJSONDecoder(r).Decode(&x); err != nil {
return nil, err
}
return InterfaceToValue(x)
}
// As converts v into a Go native type referred to by x.
func As(v Value, x any) error {
return util.NewJSONDecoder(strings.NewReader(v.String())).Decode(x)
}
// Resolver defines the interface for resolving references to native Go values.
type Resolver interface {
Resolve(Ref) (any, error)
}
// ValueResolver defines the interface for resolving references to AST values.
type ValueResolver interface {
Resolve(Ref) (Value, error)
}
// UnknownValueErr indicates a ValueResolver was unable to resolve a reference
// because the reference refers to an unknown value.
type UnknownValueErr struct{}
func (UnknownValueErr) Error() string {
return "unknown value"
}
// IsUnknownValueErr returns true if the err is an UnknownValueErr.
func IsUnknownValueErr(err error) bool {
_, ok := err.(UnknownValueErr)
return ok
}
type illegalResolver struct{}
func (illegalResolver) Resolve(ref Ref) (any, error) {
return nil, fmt.Errorf("illegal value: %v", ref)
}
// ValueToInterface returns the Go representation of an AST value. The AST
// value should not contain any values that require evaluation (e.g., vars,
// comprehensions, etc.)
func ValueToInterface(v Value, resolver Resolver) (any, error) {
return valueToInterface(v, resolver, JSONOpt{})
}
func valueToInterface(v Value, resolver Resolver, opt JSONOpt) (any, error) {
switch v := v.(type) {
case Null:
return nil, nil
case Boolean:
return bool(v), nil
case Number:
return json.Number(v), nil
case String:
return string(v), nil
case *Array:
buf := []any{}
for i := range v.Len() {
x1, err := valueToInterface(v.Elem(i).Value, resolver, opt)
if err != nil {
return nil, err
}
buf = append(buf, x1)
}
return buf, nil
case *object:
buf := make(map[string]any, v.Len())
err := v.Iter(func(k, v *Term) error {
ki, err := valueToInterface(k.Value, resolver, opt)
if err != nil {
return err
}
var str string
var ok bool
if str, ok = ki.(string); !ok {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(ki); err != nil {
return err
}
str = strings.TrimSpace(buf.String())
}
vi, err := valueToInterface(v.Value, resolver, opt)
if err != nil {
return err
}
buf[str] = vi
return nil
})
if err != nil {
return nil, err
}
return buf, nil
case *lazyObj:
if opt.CopyMaps {
return valueToInterface(v.force(), resolver, opt)
}
return v.native, nil
case Set:
buf := []any{}
iter := func(x *Term) error {
x1, err := valueToInterface(x.Value, resolver, opt)
if err != nil {
return err
}
buf = append(buf, x1)
return nil
}
var err error
if opt.SortSets {
err = v.Sorted().Iter(iter)
} else {
err = v.Iter(iter)
}
if err != nil {
return nil, err
}
return buf, nil
case Ref:
return resolver.Resolve(v)
default:
return nil, fmt.Errorf("%v requires evaluation", TypeName(v))
}
}
// JSON returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.)
func JSON(v Value) (any, error) {
return JSONWithOpt(v, JSONOpt{})
}
// JSONOpt defines parameters for AST to JSON conversion.
type JSONOpt struct {
SortSets bool // sort sets before serializing (this makes conversion more expensive)
CopyMaps bool // enforces copying of map[string]any read from the store
}
// JSONWithOpt returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.)
func JSONWithOpt(v Value, opt JSONOpt) (any, error) {
return valueToInterface(v, illegalResolver{}, opt)
}
// MustJSON returns the JSON representation of v. The value must not contain any
// refs or terms that require evaluation (e.g., vars, comprehensions, etc.) If
// the conversion fails, this function will panic. This function is mostly for
// test purposes.
func MustJSON(v Value) any {
r, err := JSON(v)
if err != nil {
panic(err)
}
return r
}
// MustInterfaceToValue converts a native Go value x to a Value. If the
// conversion fails, this function will panic. This function is mostly for test
// purposes.
func MustInterfaceToValue(x any) Value {
v, err := InterfaceToValue(x)
if err != nil {
panic(err)
}
return v
}
// Term is an argument to a function.
type Term struct {
Value Value `json:"value"` // the value of the Term as represented in Go
Location *Location `json:"location,omitempty"` // the location of the Term in the source
}
// NewTerm returns a new Term object.
func NewTerm(v Value) *Term {
return &Term{
Value: v,
}
}
// SetLocation updates the term's Location and returns the term itself.
func (term *Term) SetLocation(loc *Location) *Term {
term.Location = loc
return term
}
// Loc returns the Location of term.
func (term *Term) Loc() *Location {
if term == nil {
return nil
}
return term.Location
}
// SetLoc sets the location on term.
func (term *Term) SetLoc(loc *Location) {
term.SetLocation(loc)
}
// Copy returns a deep copy of term.
func (term *Term) Copy() *Term {
if term == nil {
return nil
}
cpy := *term
switch v := term.Value.(type) {
case Null, Boolean, Number, String, Var:
cpy.Value = v
case Ref:
cpy.Value = v.Copy()
case *Array:
cpy.Value = v.Copy()
case Set:
cpy.Value = v.Copy()
case *object:
cpy.Value = v.Copy()
case *ArrayComprehension:
cpy.Value = v.Copy()
case *ObjectComprehension:
cpy.Value = v.Copy()
case *SetComprehension:
cpy.Value = v.Copy()
case *TemplateString:
cpy.Value = v.Copy()
case Call:
cpy.Value = v.Copy()
}
return &cpy
}
// Equal returns true if this term equals the other term. Equality is
// defined for each kind of term, and does not compare the Location.
func (term *Term) Equal(other *Term) bool {
if term == nil && other != nil {
return false
}
if term != nil && other == nil {
return false
}
if term == other {
return true
}
return ValueEqual(term.Value, other.Value)
}
// Get returns a value referred to by name from the term.
func (term *Term) Get(name *Term) *Term {
switch v := term.Value.(type) {
case *object:
return v.Get(name)
case *Array:
return v.Get(name)
case interface {
Get(*Term) *Term
}:
return v.Get(name)
case Set:
if v.Contains(name) {
return name
}
}
return nil
}
// Hash returns the hash code of the Term's Value. Its Location
// is ignored.
func (term *Term) Hash() int {
return term.Value.Hash()
}
// IsGround returns true if this term's Value is ground.
func (term *Term) IsGround() bool {
return term.Value.IsGround()
}
// termJSON is used to serialize Term to JSON without map allocation.
type termJSON struct {
Location *Location `json:"location,omitempty"`
Type string `json:"type"`
Value Value `json:"value"`
}
// MarshalJSON returns the JSON encoding of the term.
//
// Specialized marshalling logic is required to include a type hint for Value.
func (term *Term) MarshalJSON() ([]byte, error) {
d := termJSON{
Type: ValueName(term.Value),
Value: term.Value,
}
jsonOptions := astJSON.GetOptions().MarshalOptions
if jsonOptions.IncludeLocation.Term {
d.Location = term.Location
}
return json.Marshal(d)
}
func (term *Term) String() string {
return term.Value.String()
}
// UnmarshalJSON parses the byte array and stores the result in term.
// Specialized unmarshalling is required to handle Value and Location.
func (term *Term) UnmarshalJSON(bs []byte) error {
v := map[string]any{}
if err := util.UnmarshalJSON(bs, &v); err != nil {
return err
}
val, err := unmarshalValue(v)
if err != nil {
return err
}
term.Value = val
if loc, ok := v["location"].(map[string]any); ok {
term.Location = &Location{}
err := unmarshalLocation(term.Location, loc)
if err != nil {
return err
}
}
return nil
}
// Vars returns a VarSet with variables contained in this term.
func (term *Term) Vars() VarSet {
vis := NewVarVisitor()
vis.Walk(term)
return vis.vars
}
// IsConstant returns true if the AST value is constant.
// Note that this is only a shallow check as we currently don't have a real
// notion of constant "vars" in the AST implementation. Meaning that while we could
// derive that a reference to a constant value is also constant, we currently don't.
func IsConstant(v Value) bool {
switch v.(type) {
case Null, Boolean, Number, String:
return true
case Var, Ref, *ArrayComprehension, *ObjectComprehension, *SetComprehension, Call:
return false
}
found := false
vis := GenericVisitor{
func(x any) bool {
switch x.(type) {
case Var, Ref, *ArrayComprehension, *ObjectComprehension, *SetComprehension, Call:
found = true
return true
}
return false
},
}
vis.Walk(v)
return !found
}
// IsComprehension returns true if the supplied value is a comprehension.
func IsComprehension(x Value) bool {
switch x.(type) {
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
return true
}
return false
}
// ContainsRefs returns true if the Value v contains refs.
func ContainsRefs(v any) bool {
found := false
WalkRefs(v, func(Ref) bool {
found = true
return found
})
return found
}
// ContainsComprehensions returns true if the Value v contains comprehensions.
func ContainsComprehensions(v any) bool {
found := false
WalkClosures(v, func(x any) bool {
switch x.(type) {
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
found = true
return found
}
return found
})
return found
}
// ContainsClosures returns true if the Value v contains closures.
func ContainsClosures(v any) bool {
found := false
WalkClosures(v, func(x any) bool {
switch x.(type) {
case *ArrayComprehension, *ObjectComprehension, *SetComprehension, *Every:
found = true
return found
}
return found
})
return found
}
// IsScalar returns true if the AST value is a scalar.
func IsScalar(v Value) bool {
switch v.(type) {
case String, Number, Boolean, Null:
return true
}
return false
}
// Null represents the null value defined by JSON.
type Null struct{}
// NullTerm creates a new Term with a Null value.
func NullTerm() *Term {
return &Term{Value: NullValue}
}
// Equal returns true if the other term Value is also Null.
func (Null) Equal(other Value) bool {
switch other.(type) {
case Null:
return true
default:
return false
}
}
// Compare compares null to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (Null) Compare(other Value) int {
if _, ok := other.(Null); ok {
return 0
}
return -1
}
// Find returns the current value or a not found error.
func (Null) Find(path Ref) (Value, error) {
if len(path) == 0 {
return NullValue, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (Null) Hash() int {
return 0
}
// IsGround always returns true.
func (Null) IsGround() bool {
return true
}
func (Null) String() string {
return "null"
}
// Boolean represents a boolean value defined by JSON.
type Boolean bool
// BooleanTerm creates a new Term with a Boolean value.
func BooleanTerm(b bool) *Term {
return &Term{Value: internedBooleanValue(b)}
}
// Equal returns true if the other Value is a Boolean and is equal.
func (bol Boolean) Equal(other Value) bool {
switch other := other.(type) {
case Boolean:
return bol == other
default:
return false
}
}
// Compare compares bol to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (bol Boolean) Compare(other Value) int {
switch other := other.(type) {
case Boolean:
if bol == other {
return 0
}
if !bol {
return -1
}
return 1
case Null:
return 1
}
return -1
}
// Find returns the current value or a not found error.
func (bol Boolean) Find(path Ref) (Value, error) {
if len(path) == 0 {
return InternedTerm(bool(bol)).Value, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (bol Boolean) Hash() int {
if bol {
return 1
}
return 0
}
// IsGround always returns true.
func (Boolean) IsGround() bool {
return true
}
func (bol Boolean) String() string {
return strconv.FormatBool(bool(bol))
}
// Number represents a numeric value as defined by JSON.
type Number json.Number
// NumberTerm creates a new Term with a Number value.
func NumberTerm(n json.Number) *Term {
return &Term{Value: Number(n)}
}
// IntNumberTerm creates a new Term with an integer Number value.
// For values between -1 and 512, returns a cached Term to reduce allocations.
func IntNumberTerm(i int) *Term {
return internedIntNumberTerm(i)
}
// UIntNumberTerm creates a new Term with an unsigned integer Number value.
func UIntNumberTerm(u uint64) *Term {
return &Term{Value: newUint64NumberValue(u)}
}
// FloatNumberTerm creates a new Term with a floating point Number value.
func FloatNumberTerm(f float64) *Term {
s := strconv.FormatFloat(f, 'g', -1, 64)
return &Term{Value: Number(s)}
}
// Equal returns true if the other Value is a Number and is equal.
func (num Number) Equal(other Value) bool {
if other, ok := other.(Number); ok {
return NumberCompare(num, other) == 0
}
return false
}
// Compare compares num to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (num Number) Compare(other Value) int {
// Optimize for the common case, as calling Compare allocates on heap.
if otherNum, yes := other.(Number); yes {
return NumberCompare(num, otherNum)
}
return Compare(num, other)
}
// Find returns the current value or a not found error.
func (num Number) Find(path Ref) (Value, error) {
if len(path) == 0 {
return num, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (num Number) Hash() int {
if len(num) < 4 {
if i, err := strconv.Atoi(string(num)); err == nil {
return i
}
}
if f, ok := num.Float64(); ok {
return int(f)
}
return int(xxhash.Sum64String(string(num)))
}
// Int returns the int representation of num if possible.
func (num Number) Int() (int, bool) {
i64, ok := num.Int64()
return int(i64), ok
}
// Int64 returns the int64 representation of num if possible.
func (num Number) Int64() (int64, bool) {
i, err := json.Number(num).Int64()
if err != nil {
return 0, false
}
return i, true
}
// Float64 returns the float64 representation of num if possible.
func (num Number) Float64() (float64, bool) {
f, err := json.Number(num).Float64()
if err != nil {
return 0, false
}
return f, true
}
// IsGround always returns true.
func (Number) IsGround() bool {
return true
}
// MarshalJSON returns JSON encoded bytes representing num.
func (num Number) MarshalJSON() ([]byte, error) {
return json.Marshal(json.Number(num))
}
func (num Number) String() string {
return string(num)
}
func newIntNumberValue(i int) Value {
return Number(strconv.Itoa(i))
}
func newInt64NumberValue(i int64) Value {
return Number(strconv.FormatInt(i, 10))
}
func newUint64NumberValue(u uint64) Value {
return Number(strconv.FormatUint(u, 10))
}
func floatNumber(f float64) Number {
return Number(strconv.FormatFloat(f, 'g', -1, 64))
}
// String represents a string value as defined by JSON.
type String string
// StringTerm creates a new Term with a String value.
func StringTerm(s string) *Term {
return &Term{Value: String(s)}
}
// Equal returns true if the other Value is a String and is equal.
func (str String) Equal(other Value) bool {
switch other := other.(type) {
case String:
return str == other
default:
return false
}
}
// Compare compares str to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (str String) Compare(other Value) int {
// Optimize for the common case of one string being compared to another by
// using a direct comparison of values. This avoids the allocation performed
// when calling Compare and its any argument conversion.
if otherStr, ok := other.(String); ok {
if str == otherStr {
return 0
}
if str < otherStr {
return -1
}
return 1
}
return Compare(str, other)
}
// Find returns the current value or a not found error.
func (str String) Find(path Ref) (Value, error) {
if len(path) == 0 {
return str, nil
}
return nil, errFindNotFound
}
// IsGround always returns true.
func (String) IsGround() bool {
return true
}
func (str String) String() string {
return strconv.Quote(string(str))
}
// Hash returns the hash code for the Value.
func (str String) Hash() int {
return int(xxhash.Sum64String(string(str)))
}
type TemplateString struct {
Parts []Node `json:"parts"`
MultiLine bool `json:"multi_line"`
}
func (ts *TemplateString) Copy() *TemplateString {
cpy := &TemplateString{MultiLine: ts.MultiLine, Parts: make([]Node, len(ts.Parts))}
for i, p := range ts.Parts {
switch v := p.(type) {
case *Expr:
cpy.Parts[i] = v.Copy()
case *Term:
cpy.Parts[i] = v.Copy()
}
}
return cpy
}
func (ts *TemplateString) Equal(other Value) bool {
if o, ok := other.(*TemplateString); ok && ts.MultiLine == o.MultiLine && len(ts.Parts) == len(o.Parts) {
for i, p := range ts.Parts {
switch v := p.(type) {
case *Expr:
if ope, ok := o.Parts[i].(*Expr); !ok || !v.Equal(ope) {
return false
}
case *Term:
if opt, ok := o.Parts[i].(*Term); !ok || !v.Equal(opt) {
return false
}
default:
return false
}
}
return true
}
return false
}
func (ts *TemplateString) Compare(other Value) int {
if ots, ok := other.(*TemplateString); ok {
if ts.MultiLine != ots.MultiLine {
if !ts.MultiLine {
return -1
}
return 1
}
if len(ts.Parts) != len(ots.Parts) {
return len(ts.Parts) - len(ots.Parts)
}
for i := range ts.Parts {
if cmp := Compare(ts.Parts[i], ots.Parts[i]); cmp != 0 {
return cmp
}
}
return 0
}
return Compare(ts, other)
}
func (ts *TemplateString) Find(path Ref) (Value, error) {
if len(path) == 0 {
return ts, nil
}
return nil, errFindNotFound
}
func (ts *TemplateString) Hash() int {
hash := 0
for _, p := range ts.Parts {
switch x := p.(type) {
case *Expr:
hash += x.Hash()
case *Term:
hash += x.Value.Hash()
default:
panic(fmt.Sprintf("invalid template part type %T", p))
}
}
return hash
}
func (*TemplateString) IsGround() bool {
return false
}
func (ts *TemplateString) String() string {
buf, _ := ts.AppendText(make([]byte, 0, ts.StringLength()))
return util.ByteSliceToString(buf)
}
func TemplateStringTerm(multiLine bool, parts ...Node) *Term {
return &Term{Value: &TemplateString{MultiLine: multiLine, Parts: parts}}
}
// EscapeTemplateStringStringPart escapes unescaped left curly braces in s - i.e "{" becomes "\{".
// The internal representation of string terms within a template string does **NOT**
// treat '{' as special, but expects code dealing with template strings to escape them when
// required, such as when serializing the complete template string. Code that programmatically
// constructs template strings should not pre-escape left curly braces in string term parts.
//
// // TODO(anders): a future optimization would be to combine this with the other escaping done
// // for strings (e.g. escaping quotes, backslashes, and JSON control characters) in a single operation
// // to avoid multiple passes and allocations over the same string. That's currently done by
// // strconv.Quote, so we would need to re-implement that logic in code of our own.
// // NOTE(anders): I would love to come up with a better name for this component than
// // "TemplateStringStringPart"..
func EscapeTemplateStringStringPart(s string) string {
numUnescaped := countUnescapedLeftCurly(s)
if numUnescaped == 0 {
return s
}
return util.ByteSliceToString(AppendEscapedTemplateStringStringPart(make([]byte, 0, len(s)+numUnescaped), s))
}
func AppendEscapedTemplateStringStringPart(buf []byte, s string) []byte {
if s[0] == '{' {
buf = append(buf, '\\', s[0])
} else {
buf = append(buf, s[0])
}
for i := 1; i < len(s); i++ {
if s[i] == '{' && s[i-1] != '\\' {
buf = append(buf, '\\', s[i])
} else {
buf = append(buf, s[i])
}
}
return buf
}
func countUnescapedLeftCurly(s string) (n int) {
// Note(anders): while not the functions I'd intuitively reach for to solve this,
// they are hands down the fastest option here, as they're done in assembly, which
// performs about an order of magnitude better than a manual loop in Go.
if n = strings.Count(s, "{"); n > 0 {
n -= strings.Count(s, `\{`)
}
return n
}
// Var represents a variable as defined by the language.
type Var string
// VarTerm creates a new Term with a Variable value.
func VarTerm(v string) *Term {
return &Term{Value: InternedVarValue(v)}
}
// Equal returns true if the other Value is a Variable and has the same value
// (name).
func (v Var) Equal(other Value) bool {
switch other := other.(type) {
case Var:
return v == other
default:
return false
}
}
// Compare compares v to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (v Var) Compare(other Value) int {
if otherVar, ok := other.(Var); ok {
return strings.Compare(string(v), string(otherVar))
}
return Compare(v, other)
}
// Find returns the current value or a not found error.
func (v Var) Find(path Ref) (Value, error) {
if len(path) == 0 {
return v, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (v Var) Hash() int {
return int(xxhash.Sum64String(string(v)))
}
// IsGround always returns false.
func (Var) IsGround() bool {
return false
}
// IsWildcard returns true if this is a wildcard variable.
func (v Var) IsWildcard() bool {
return strings.HasPrefix(string(v), WildcardPrefix)
}
// IsGenerated returns true if this variable was generated during compilation.
func (v Var) IsGenerated() bool {
return strings.HasPrefix(string(v), "__local")
}
func (v Var) String() string {
// Special case for wildcard so that string representation is parseable. The
// parser mangles wildcard variables to make their names unique and uses an
// illegal variable name character (WildcardPrefix) to avoid conflicts. When
// we serialize the variable here, we need to make sure it's parseable.
if v.IsWildcard() {
return WildcardString
}
return string(v)
}
// Ref represents a reference as defined by the language.
type Ref []*Term
// EmptyRef returns a new, empty reference.
func EmptyRef() Ref {
return Ref([]*Term{})
}
// PtrRef returns a new reference against the head for the pointer
// s. Path components in the pointer are unescaped.
func PtrRef(head *Term, s string) (Ref, error) {
s = strings.Trim(s, "/")
if s == "" {
return Ref{head}, nil
}
parts := strings.Split(s, "/")
if maxLen := math.MaxInt32; len(parts) >= maxLen {
return nil, fmt.Errorf("path too long: %s, %d > %d (max)", s, len(parts), maxLen)
}
ref := make(Ref, uint(len(parts))+1)
ref[0] = head
for i := range parts {
var err error
parts[i], err = url.PathUnescape(parts[i])
if err != nil {
return nil, err
}
ref[i+1] = StringTerm(parts[i])
}
return ref, nil
}
// RefTerm creates a new Term with a Ref value.
func RefTerm(r ...*Term) *Term {
return &Term{Value: Ref(r)}
}
// Append returns a copy of ref with the term appended to the end.
func (ref Ref) Append(term *Term) Ref {
n := len(ref)
dst := make(Ref, n+1)
copy(dst, ref)
dst[n] = term
return dst
}
// Insert returns a copy of the ref with x inserted at pos. If pos < len(ref),
// existing elements are shifted to the right. If pos > len(ref)+1 this
// function panics.
func (ref Ref) Insert(x *Term, pos int) Ref {
switch {
case pos == len(ref):
return ref.Append(x)
case pos > len(ref)+1:
panic("illegal index")
}
cpy := make(Ref, len(ref)+1)
copy(cpy, ref[:pos])
cpy[pos] = x
copy(cpy[pos+1:], ref[pos:])
return cpy
}
// Extend returns a copy of ref with the terms from other appended. The head of
// other will be converted to a string.
func (ref Ref) Extend(other Ref) Ref {
offset := len(ref)
dst := make(Ref, offset+len(other))
copy(dst, ref)
head := other[0].Copy()
head.Value = String(head.Value.(Var))
dst[offset] = head
copy(dst[offset+1:], other[1:])
return dst
}
// Concat returns a ref with the terms appended.
func (ref Ref) Concat(terms []*Term) Ref {
if len(terms) == 0 {
return ref
}
cpy := make(Ref, len(ref)+len(terms))
copy(cpy, ref)
copy(cpy[len(ref):], terms)
return cpy
}
// Dynamic returns the offset of the first non-constant operand of ref.
func (ref Ref) Dynamic() int {
switch ref[0].Value.(type) {
case Call:
return 0
}
for i := 1; i < len(ref); i++ {
if !IsConstant(ref[i].Value) {
return i
}
}
return -1
}
// Copy returns a deep copy of ref.
func (ref Ref) Copy() Ref {
return termSliceCopy(ref)
}
// CopyNonGround returns a new ref with deep copies of the non-ground parts and shallow
// copies of the ground parts. This is a *much* cheaper operation than Copy for operations
// that only intend to modify (e.g. plug) the non-ground parts. The head element of the ref
// is always shallow copied.
func (ref Ref) CopyNonGround() Ref {
cpy := make(Ref, len(ref))
cpy[0] = ref[0]
for i := 1; i < len(ref); i++ {
if ref[i].Value.IsGround() {
cpy[i] = ref[i]
} else {
cpy[i] = ref[i].Copy()
}
}
return cpy
}
// Equal returns true if ref is equal to other.
func (ref Ref) Equal(other Value) bool {
switch o := other.(type) {
case Ref:
if len(ref) == len(o) {
for i := range ref {
if !ref[i].Equal(o[i]) {
return false
}
}
return true
}
}
return false
}
// Compare compares ref to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (ref Ref) Compare(other Value) int {
if o, ok := other.(Ref); ok {
return termSliceCompare(ref, o)
}
return Compare(ref, other)
}
// Find returns the current value or a "not found" error.
func (ref Ref) Find(path Ref) (Value, error) {
if len(path) == 0 {
return ref, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (ref Ref) Hash() int {
return termSliceHash(ref)
}
// HasPrefix returns true if the other ref is a prefix of this ref.
func (ref Ref) HasPrefix(other Ref) bool {
if len(other) > len(ref) {
return false
}
for i := range other {
if !ref[i].Equal(other[i]) {
return false
}
}
return true
}
// ConstantPrefix returns the constant portion of the ref starting from the head.
func (ref Ref) ConstantPrefix() Ref {
i := ref.Dynamic()
if i < 0 {
return ref
}
return ref[:i]
}
// StringPrefix returns the string portion of the ref starting from the head.
func (ref Ref) StringPrefix() Ref {
for i := 1; i < len(ref); i++ {
switch ref[i].Value.(type) {
case String: // pass
default: // cut off
return ref[:i]
}
}
return ref
}
// GroundPrefix returns the ground portion of the ref starting from the head. By
// definition, the head of the reference is always ground.
func (ref Ref) GroundPrefix() Ref {
for i := range ref {
if i > 0 && !ref[i].IsGround() {
return ref[:i]
}
}
return ref
}
// DynamicSuffix returns the dynamic portion of the ref.
// If the ref is not dynamic, nil is returned.
func (ref Ref) DynamicSuffix() Ref {
i := ref.Dynamic()
if i < 0 {
return nil
}
return ref[i:]
}
// IsGround returns true if all of the parts of the Ref are ground.
func (ref Ref) IsGround() bool {
if len(ref) < 2 {
return true
}
return termSliceIsGround(ref[1:])
}
// IsNested returns true if this ref contains other Refs.
func (ref Ref) IsNested() bool {
for _, x := range ref {
if _, ok := x.Value.(Ref); ok {
return true
}
}
return false
}
// Ptr returns a slash-separated path string for this ref. If the ref
// contains non-string terms this function returns an error. Path
// components are escaped.
func (ref Ref) Ptr() (string, error) {
buf := &strings.Builder{}
tail := ref[1:]
l := max(len(tail)-1, 0) // number of '/' to add
for i := range tail {
str, ok := tail[i].Value.(String)
if !ok {
return "", errors.New("invalid path value type")
}
l += len(str)
}
buf.Grow(l)
for i := range tail {
if i > 0 {
buf.WriteByte('/')
}
str := string(tail[i].Value.(String))
// Sadly, the url package does not expose an appender for this.
buf.WriteString(url.PathEscape(str))
}
return buf.String(), nil
}
// IsVarCompatibleString returns true if s is a valid variable name. String s is a valid variable
// name if it starts with a letter (a-z or A-Z) or underscore (_) and is followed by
// letters (a-z or A-Z), digits (0-9), and underscores.
func IsVarCompatibleString(s string) bool {
l := len(s)
if l == 0 {
return false
}
// not exactly easy on the eyes, but often orders of magnitude faster
// than using a compiled regex (see benchmarks in term_bench_test.go)
is_letter := func(c byte) bool {
return (c > 96 && c < 123) || (c > 64 && c < 91)
}
is_digit := func(c byte) bool {
return c > 47 && c < 58
}
// first character must be a letter or underscore
c := s[0]
if !(is_letter(c) || c == 95) {
return false
}
// remaining characters must be letters, digits, or underscores
for i := 1; i < l; i++ {
if c = s[i]; !(is_letter(c) || is_digit(c) || c == 95) {
return false
}
}
return true
}
func (ref Ref) String() string {
l := len(ref)
// First check for zero-alloc options, as making the buffer for AppendText
// always costs an allocation.
if l == 0 {
return ""
}
if l == 1 {
if s, ok := ref[0].Value.(String); ok {
// Ref head should normally be a Var, but if for some reason
// it's a string, don't quote it.
return string(s)
}
return ref[0].Value.String()
}
if name, ok := BuiltinNameFromRef(ref); ok {
return name
}
buf, _ := ref.AppendText(make([]byte, 0, ref.StringLength()))
return util.ByteSliceToString(buf)
}
// OutputVars returns a VarSet containing variables that would be bound by evaluating
// this expression in isolation.
func (ref Ref) OutputVars() VarSet {
vis := NewVarVisitor().WithParams(VarVisitorParams{SkipRefHead: true})
vis.WalkRef(ref)
return vis.Vars()
}
func (ref Ref) toArray() *Array {
terms := make([]*Term, 0, len(ref))
for _, term := range ref {
if _, ok := term.Value.(String); ok {
terms = append(terms, term)
} else {
terms = append(terms, InternedTerm(term.Value.String()))
}
}
return NewArray(terms...)
}
// QueryIterator defines the interface for querying AST documents with references.
type QueryIterator func(map[Var]Value, Value) error
// ArrayTerm creates a new Term with an Array value.
func ArrayTerm(a ...*Term) *Term {
return NewTerm(NewArray(a...))
}
// NewArray creates an Array with the terms provided. The array will
// use the provided term slice.
func NewArray(a ...*Term) *Array {
hs := make([]int, len(a))
for i, e := range a {
hs[i] = e.Value.Hash()
}
arr := &Array{elems: a, hashs: hs, ground: termSliceIsGround(a)}
arr.rehash()
return arr
}
// NewArrayWithCapacity returns a new empty Array with the given capacity pre-allocated.
func NewArrayWithCapacity(capacity int) *Array {
return &Array{
elems: make([]*Term, 0, capacity),
hashs: make([]int, 0, capacity),
ground: true,
}
}
// Array represents an array as defined by the language. Arrays are similar to the
// same types as defined by JSON with the exception that they can contain Vars
// and References.
type Array struct {
elems []*Term
hashs []int // element hashes
hash int
ground bool
}
// Copy returns a deep copy of arr.
func (arr *Array) Copy() *Array {
return &Array{
elems: termSliceCopy(arr.elems),
hashs: slices.Clone(arr.hashs),
hash: arr.hash,
ground: arr.ground,
}
}
// Equal returns true if arr is equal to other.
func (arr *Array) Equal(other Value) bool {
if arr == other {
return true
}
if other, ok := other.(*Array); ok && len(arr.elems) == len(other.elems) {
for i := range arr.elems {
if !arr.elems[i].Equal(other.elems[i]) {
return false
}
}
return true
}
return false
}
// Compare compares arr to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (arr *Array) Compare(other Value) int {
if b, ok := other.(*Array); ok {
return termSliceCompare(arr.elems, b.elems)
}
sortA := sortOrder(arr)
sortB := sortOrder(other)
if sortA < sortB {
return -1
} else if sortB < sortA {
return 1
}
return Compare(arr, other)
}
// Find returns the value at the index or an out-of-range error.
func (arr *Array) Find(path Ref) (Value, error) {
if len(path) == 0 {
return arr, nil
}
num, ok := path[0].Value.(Number)
if !ok {
return nil, errFindNotFound
}
i, ok := num.Int()
if !ok || i < 0 || i >= arr.Len() {
return nil, errFindNotFound
}
term := arr.Elem(i)
// Using Find on scalar values costs an allocation (type -> Value conversion)
// and since we already have the Value here, we can avoid that.
if len(path) == 1 && IsScalar(term.Value) {
return term.Value, nil
}
return term.Value.Find(path[1:])
}
// Get returns the element at pos or nil if not possible.
func (arr *Array) Get(pos *Term) *Term {
num, ok := pos.Value.(Number)
if !ok {
return nil
}
if i, ok := num.Int(); ok && i >= 0 && i < len(arr.elems) {
return arr.elems[i]
}
return nil
}
// Sorted returns a new Array that contains the sorted elements of arr.
func (arr *Array) Sorted() *Array {
cpy := make([]*Term, len(arr.elems))
for i := range cpy {
cpy[i] = arr.elems[i]
}
slices.SortFunc(cpy, TermValueCompare)
a := NewArray(cpy...)
a.hashs = arr.hashs
return a
}
// Hash returns the hash code for the Value.
func (arr *Array) Hash() int {
return arr.hash
}
// IsGround returns true if all of the Array elements are ground.
func (arr *Array) IsGround() bool {
return arr.ground
}
// MarshalJSON returns JSON encoded bytes representing arr.
func (arr *Array) MarshalJSON() ([]byte, error) {
if len(arr.elems) == 0 {
return []byte(`[]`), nil
}
return json.Marshal(arr.elems)
}
func (arr *Array) String() string {
buf, _ := arr.AppendText(make([]byte, 0, arr.StringLength()))
return util.ByteSliceToString(buf)
}
// Len returns the number of elements in the array.
func (arr *Array) Len() int {
return len(arr.elems)
}
// Elem returns the element i of arr.
func (arr *Array) Elem(i int) *Term {
return arr.elems[i]
}
// Set sets the element i of arr.
func (arr *Array) Set(i int, v *Term) {
arr.set(i, v)
}
// rehash updates the cached hash of arr.
func (arr *Array) rehash() {
arr.hash = 0
for _, h := range arr.hashs {
arr.hash += h
}
}
// set sets the element i of arr.
func (arr *Array) set(i int, v *Term) {
arr.ground = arr.ground && v.IsGround()
arr.elems[i] = v
arr.hashs[i] = v.Value.Hash()
arr.rehash()
}
// Slice returns a slice of arr starting from i index to j. -1
// indicates the end of the array. The returned value array is not a
// copy and any modifications to either of arrays may be reflected to
// the other.
func (arr *Array) Slice(i, j int) *Array {
var elems []*Term
var hashs []int
if j == -1 {
elems = arr.elems[i:]
hashs = arr.hashs[i:]
} else {
elems = arr.elems[i:j]
hashs = arr.hashs[i:j]
}
// If arr is ground, the slice is, too.
// If it's not, the slice could still be.
gr := arr.ground || termSliceIsGround(elems)
s := &Array{elems: elems, hashs: hashs, ground: gr}
s.rehash()
return s
}
// Iter calls f on each element in arr. If f returns an error,
// iteration stops and the return value is the error.
func (arr *Array) Iter(f func(*Term) error) error {
for i := range arr.elems {
if err := f(arr.elems[i]); err != nil {
return err
}
}
return nil
}
// Until calls f on each element in arr. If f returns true, iteration stops.
func (arr *Array) Until(f func(*Term) bool) bool {
return slices.ContainsFunc(arr.elems, f)
}
// Foreach calls f on each element in arr.
func (arr *Array) Foreach(f func(*Term)) {
for _, term := range arr.elems {
f(term)
}
}
// Append appends a term to arr, returning the appended array.
func (arr *Array) Append(v *Term) *Array {
cpy := *arr
cpy.elems = append(arr.elems, v)
cpy.hashs = append(arr.hashs, v.Value.Hash())
cpy.hash = arr.hash + v.Value.Hash()
cpy.ground = arr.ground && v.IsGround()
return &cpy
}
// Set represents a set as defined by the language.
type Set interface {
Value
Len() int
Copy() Set
Diff(Set) Set
Intersect(Set) Set
Union(Set) Set
Add(*Term)
Iter(func(*Term) error) error
Until(func(*Term) bool) bool
Foreach(func(*Term))
Contains(*Term) bool
Map(func(*Term) (*Term, error)) (Set, error)
Reduce(*Term, func(*Term, *Term) (*Term, error)) (*Term, error)
Sorted() *Array
Slice() []*Term
}
// NewSet returns a new Set containing t.
func NewSet(t ...*Term) Set {
s := newset(len(t))
for _, term := range t {
s.insert(term, false)
}
return s
}
// NewSetWithCapacity returns a new empty Set with the given capacity pre-allocated.
func NewSetWithCapacity(capacity int) Set {
return newset(capacity)
}
func newset(n int) *set {
var keys []*Term
if n > 0 {
keys = make([]*Term, 0, n)
}
return &set{
elems: make(map[int]*Term, n),
keys: keys,
hash: 0,
ground: true,
sortGuard: sync.Once{},
}
}
// SetTerm returns a new Term representing a set containing terms t.
func SetTerm(t ...*Term) *Term {
set := NewSet(t...)
return &Term{
Value: set,
}
}
type set struct {
elems map[int]*Term
keys []*Term
hash int
ground bool
// Prevents race condition around sorting.
// We can avoid (the allocation cost of) using a pointer here as all
// methods of `set` use a pointer receiver, and the `sync.Once` value
// is never copied.
sortGuard sync.Once
}
// Copy returns a deep copy of s.
func (s *set) Copy() Set {
cpy := &set{
hash: s.hash,
ground: s.ground,
sortGuard: sync.Once{},
elems: make(map[int]*Term, len(s.elems)),
keys: make([]*Term, 0, len(s.keys)),
}
for hash := range s.elems {
cpy.elems[hash] = s.elems[hash].Copy()
cpy.keys = append(cpy.keys, cpy.elems[hash])
}
return cpy
}
// IsGround returns true if all terms in s are ground.
func (s *set) IsGround() bool {
return s.ground
}
// Hash returns a hash code for s.
func (s *set) Hash() int {
return s.hash
}
func (s *set) String() string {
buf, _ := s.AppendText(make([]byte, 0, s.StringLength()))
return util.ByteSliceToString(buf)
}
func (s *set) sortedKeys() []*Term {
s.sortGuard.Do(func() {
slices.SortFunc(s.keys, TermValueCompare)
})
return s.keys
}
// Compare compares s to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (s *set) Compare(other Value) int {
o1 := sortOrder(s)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o1 > o2 {
return 1
}
t := other.(*set)
return termSliceCompare(s.sortedKeys(), t.sortedKeys())
}
// Find returns the set or dereferences the element itself.
func (s *set) Find(path Ref) (Value, error) {
if len(path) == 0 {
return s, nil
}
if !s.Contains(path[0]) {
return nil, errFindNotFound
}
return path[0].Value.Find(path[1:])
}
// Diff returns elements in s that are not in other.
func (s *set) Diff(other Set) Set {
if s.Compare(other) == 0 {
return NewSet()
}
result := newset(len(s.keys))
for _, term := range s.keys {
if !other.Contains(term) {
result.insert(term, false)
}
}
return result
}
// Intersect returns the set containing elements in both s and other.
func (s *set) Intersect(other Set) Set {
o := other.(*set)
n, m := s.Len(), o.Len()
ss := s
so := o
if m < n {
ss = o
so = s
n = m
}
result := newset(n)
for _, term := range ss.keys {
if so.Contains(term) {
result.insert(term, false)
}
}
return result
}
// Union returns the set containing all elements of s and other.
func (s *set) Union(other Set) Set {
o := other.(*set)
// Pre-allocate with max size - avoids over-allocation for overlapping sets
// while only requiring one potential grow for disjoint sets.
r := newset(max(len(s.keys), len(o.keys)))
for _, term := range s.keys {
r.insert(term, false)
}
for _, term := range o.keys {
r.insert(term, false)
}
return r
}
// Add updates s to include t.
func (s *set) Add(t *Term) {
s.insert(t, true)
}
// Iter calls f on each element in s. If f returns an error, iteration stops
// and the return value is the error.
func (s *set) Iter(f func(*Term) error) error {
for _, term := range s.sortedKeys() {
if err := f(term); err != nil {
return err
}
}
return nil
}
// Until calls f on each element in s. If f returns true, iteration stops.
func (s *set) Until(f func(*Term) bool) bool {
return slices.ContainsFunc(s.sortedKeys(), f)
}
// Foreach calls f on each element in s.
func (s *set) Foreach(f func(*Term)) {
for _, term := range s.sortedKeys() {
f(term)
}
}
// Map returns a new Set obtained by applying f to each value in s.
func (s *set) Map(f func(*Term) (*Term, error)) (Set, error) {
mapped := make([]*Term, 0, len(s.keys))
for _, x := range s.sortedKeys() {
term, err := f(x)
if err != nil {
return nil, err
}
mapped = append(mapped, term)
}
return NewSet(mapped...), nil
}
// Reduce returns a Term produced by applying f to each value in s. The first
// argument to f is the reduced value (starting with i) and the second argument
// to f is the element in s.
func (s *set) Reduce(i *Term, f func(*Term, *Term) (*Term, error)) (*Term, error) {
err := s.Iter(func(x *Term) error {
var err error
i, err = f(i, x)
if err != nil {
return err
}
return nil
})
return i, err
}
// Contains returns true if t is in s.
func (s *set) Contains(t *Term) bool {
return s.get(t) != nil
}
// Len returns the number of elements in the set.
func (s *set) Len() int {
return len(s.keys)
}
// MarshalJSON returns JSON encoded bytes representing s.
func (s *set) MarshalJSON() ([]byte, error) {
if s.keys == nil {
return []byte(`[]`), nil
}
return json.Marshal(s.sortedKeys())
}
// Sorted returns an Array that contains the sorted elements of s.
func (s *set) Sorted() *Array {
cpy := make([]*Term, len(s.keys))
copy(cpy, s.sortedKeys())
return NewArray(cpy...)
}
// Slice returns a slice of terms contained in the set.
func (s *set) Slice() []*Term {
return s.sortedKeys()
}
// NOTE(philipc): We assume a many-readers, single-writer model here.
// This method should NOT be used concurrently, or else we risk data races.
func (s *set) insert(x *Term, resetSortGuard bool) {
hash := x.Hash()
insertHash := hash
for curr, ok := s.elems[insertHash]; ok; {
if KeyHashEqual(curr.Value, x.Value) {
return
}
insertHash++
curr, ok = s.elems[insertHash]
}
s.elems[insertHash] = x
// O(1) insertion, but we'll have to re-sort the keys later.
s.keys = append(s.keys, x)
if resetSortGuard {
// Reset the sync.Once instance.
// See https://github.com/golang/go/issues/25955 for why we do it this way.
// Note that this will always be the case when external code calls insert via
// Add, or otherwise. Internal code may however benefit from not having to
// re-create this pointer when it's known not to be needed.
s.sortGuard = sync.Once{}
}
s.hash += hash
s.ground = s.ground && x.IsGround()
}
func (s *set) get(x *Term) *Term {
if len(s.elems) == 0 {
return nil
}
hash := x.Hash()
for curr, ok := s.elems[hash]; ok; {
// Pointer equality check first
if curr == x {
return curr
}
if KeyHashEqual(curr.Value, x.Value) {
return curr
}
hash++
curr, ok = s.elems[hash]
}
return nil
}
// Object represents an object as defined by the language.
type Object interface {
Value
Len() int
Get(*Term) *Term
Copy() Object
Insert(*Term, *Term)
Iter(func(*Term, *Term) error) error
Until(func(*Term, *Term) bool) bool
Foreach(func(*Term, *Term))
Map(func(*Term, *Term) (*Term, *Term, error)) (Object, error)
Diff(other Object) Object
Intersect(other Object) [][3]*Term
Merge(other Object) (Object, bool)
MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool)
Filter(filter Object) (Object, error)
Keys() []*Term
KeysIterator() ObjectKeysIterator
get(k *Term) *objectElem // To prevent external implementations
}
// NewObject creates a new Object with t.
func NewObject(t ...[2]*Term) Object {
obj := newobject(len(t))
for i := range t {
obj.insert(t[i][0], t[i][1], false)
}
return obj
}
// NewObjectWithCapacity returns a new empty Object with the given capacity pre-allocated.
func NewObjectWithCapacity(capacity int) Object {
return newobject(capacity)
}
// ObjectTerm creates a new Term with an Object value.
func ObjectTerm(o ...[2]*Term) *Term {
return &Term{Value: NewObject(o...)}
}
func LazyObject(blob map[string]any) Object {
return &lazyObj{native: blob, cache: map[string]Value{}}
}
type lazyObj struct {
strict Object
cache map[string]Value
native map[string]any
}
func (l *lazyObj) force() Object {
if l.strict == nil {
l.strict = MustInterfaceToValue(l.native).(Object)
// NOTE(jf): a possible performance improvement here would be to check how many
// entries have been realized to AST in the cache, and if some threshold compared to the
// total number of keys is exceeded, realize the remaining entries and set l.strict to l.cache.
l.cache = map[string]Value{} // We don't need the cache anymore; drop it to free up memory.
}
return l.strict
}
func (l *lazyObj) Compare(other Value) int {
o1 := sortOrder(l)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o2 < o1 {
return 1
}
return l.force().Compare(other)
}
func (l *lazyObj) Copy() Object {
return l
}
func (l *lazyObj) Diff(other Object) Object {
return l.force().Diff(other)
}
func (l *lazyObj) Intersect(other Object) [][3]*Term {
return l.force().Intersect(other)
}
func (l *lazyObj) Iter(f func(*Term, *Term) error) error {
return l.force().Iter(f)
}
func (l *lazyObj) Until(f func(*Term, *Term) bool) bool {
// NOTE(sr): there could be benefits in not forcing here -- if we abort because
// `f` returns true, we could save us from converting the rest of the object.
return l.force().Until(f)
}
func (l *lazyObj) Foreach(f func(*Term, *Term)) {
l.force().Foreach(f)
}
func (l *lazyObj) Filter(filter Object) (Object, error) {
return l.force().Filter(filter)
}
func (l *lazyObj) Map(f func(*Term, *Term) (*Term, *Term, error)) (Object, error) {
return l.force().Map(f)
}
func (l *lazyObj) MarshalJSON() ([]byte, error) {
return l.force().(*object).MarshalJSON()
}
func (l *lazyObj) Merge(other Object) (Object, bool) {
return l.force().Merge(other)
}
func (l *lazyObj) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool) {
return l.force().MergeWith(other, conflictResolver)
}
func (l *lazyObj) Len() int {
return len(l.native)
}
func (l *lazyObj) String() string {
return l.force().String()
}
// get is merely there to implement the Object interface -- `get` there serves the
// purpose of prohibiting external implementations. It's never called for lazyObj.
func (*lazyObj) get(*Term) *objectElem {
return nil
}
func (l *lazyObj) Get(k *Term) *Term {
if l.strict != nil {
return l.strict.Get(k)
}
if s, ok := k.Value.(String); ok {
if v, ok := l.cache[string(s)]; ok {
return NewTerm(v)
}
if val, ok := l.native[string(s)]; ok {
var converted Value
switch val := val.(type) {
case map[string]any:
converted = LazyObject(val)
default:
converted = MustInterfaceToValue(val)
}
l.cache[string(s)] = converted
return NewTerm(converted)
}
}
return nil
}
func (l *lazyObj) Insert(k, v *Term) {
l.force().Insert(k, v)
}
func (*lazyObj) IsGround() bool {
return true
}
func (l *lazyObj) Hash() int {
return l.force().Hash()
}
func (l *lazyObj) Keys() []*Term {
if l.strict != nil {
return l.strict.Keys()
}
ret := make([]*Term, 0, len(l.native))
for k := range l.native {
ret = append(ret, StringTerm(k))
}
slices.SortFunc(ret, TermValueCompare)
return ret
}
func (l *lazyObj) KeysIterator() ObjectKeysIterator {
return &lazyObjKeysIterator{keys: l.Keys()}
}
type lazyObjKeysIterator struct {
current int
keys []*Term
}
func (ki *lazyObjKeysIterator) Next() (*Term, bool) {
if ki.current == len(ki.keys) {
return nil, false
}
ki.current++
return ki.keys[ki.current-1], true
}
func (l *lazyObj) Find(path Ref) (Value, error) {
if l.strict != nil {
return l.strict.Find(path)
}
if len(path) == 0 {
return l, nil
}
if p0, ok := path[0].Value.(String); ok {
if v, ok := l.cache[string(p0)]; ok {
return v.Find(path[1:])
}
if v, ok := l.native[string(p0)]; ok {
var converted Value
switch v := v.(type) {
case map[string]any:
converted = LazyObject(v)
default:
converted = MustInterfaceToValue(v)
}
l.cache[string(p0)] = converted
return converted.Find(path[1:])
}
}
return nil, errFindNotFound
}
type object struct {
elems map[int]*objectElem
keys []*objectElem
ground int // number of key and value grounds. Counting is required to support insert's key-value replace.
hash int
sortGuard sync.Once // Prevents race condition around sorting.
}
func newobject(n int) *object {
var keys []*objectElem
if n > 0 {
keys = make([]*objectElem, 0, n)
}
return &object{
elems: make(map[int]*objectElem, n),
keys: keys,
sortGuard: sync.Once{},
}
}
type objectElem struct {
key *Term
value *Term
next *objectElem
}
// Item is a helper for constructing an tuple containing two Terms
// representing a key/value pair in an Object.
func Item(key, value *Term) [2]*Term {
return [2]*Term{key, value}
}
func (obj *object) sortedKeys() []*objectElem {
obj.sortGuard.Do(func() {
slices.SortFunc(obj.keys, func(a, b *objectElem) int {
return a.key.Value.Compare(b.key.Value)
})
})
return obj.keys
}
// Compare compares obj to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (obj *object) Compare(other Value) int {
if x, ok := other.(*lazyObj); ok {
other = x.force()
}
o1 := sortOrder(obj)
o2 := sortOrder(other)
if o1 < o2 {
return -1
} else if o2 < o1 {
return 1
}
a := obj
b := other.(*object)
// Ensure that keys are in canonical sorted order before use!
akeys := a.sortedKeys()
bkeys := b.sortedKeys()
minLen := len(akeys)
if len(b.keys) < len(akeys) {
minLen = len(bkeys)
}
for i := range minLen {
keysCmp := Compare(akeys[i].key, bkeys[i].key)
if keysCmp < 0 {
return -1
}
if keysCmp > 0 {
return 1
}
valA := akeys[i].value
valB := bkeys[i].value
valCmp := Compare(valA, valB)
if valCmp != 0 {
return valCmp
}
}
if len(akeys) < len(bkeys) {
return -1
}
if len(bkeys) < len(akeys) {
return 1
}
return 0
}
// Find returns the value at the key or undefined.
func (obj *object) Find(path Ref) (Value, error) {
if len(path) == 0 {
return obj, nil
}
term := obj.Get(path[0])
if term == nil {
return nil, errFindNotFound
}
// Using Find on scalar values costs an allocation (type -> Value conversion)
// and since we already have the Value here, we can avoid that.
if len(path) == 1 && IsScalar(term.Value) {
return term.Value, nil
}
return term.Value.Find(path[1:])
}
func (obj *object) Insert(k, v *Term) {
obj.insert(k, v, true)
}
// Get returns the value of k in obj if k exists, otherwise nil.
func (obj *object) Get(k *Term) *Term {
if len(obj.elems) == 0 {
return nil
}
hash := k.Hash()
for curr := obj.elems[hash]; curr != nil; curr = curr.next {
// Pointer equality check always fastest, and not too unlikely with interning.
if curr.key == k {
return curr.value
}
if KeyHashEqual(curr.key.Value, k.Value) {
return curr.value
}
}
return nil
}
func KeyHashEqual(x, y Value) bool {
switch x := x.(type) {
case Null, Boolean, String, Var:
return x == y
case Number:
if y, ok := y.(Number); ok {
return x.Equal(y)
}
}
return Compare(x, y) == 0
}
// Hash returns the hash code for the Value.
func (obj *object) Hash() int {
return obj.hash
}
// IsGround returns true if all of the Object key/value pairs are ground.
func (obj *object) IsGround() bool {
return obj.ground == 2*len(obj.keys)
}
// Copy returns a deep copy of obj.
func (obj *object) Copy() Object {
cpy, _ := obj.Map(func(k, v *Term) (*Term, *Term, error) {
return k.Copy(), v.Copy(), nil
})
cpy.(*object).hash = obj.hash
return cpy
}
// Diff returns a new Object that contains only the key/value pairs that exist in obj.
func (obj *object) Diff(other Object) Object {
r := newobject(obj.Len())
for _, node := range obj.sortedKeys() {
if other.Get(node.key) == nil {
r.insert(node.key, node.value, false)
}
}
return r
}
// Intersect returns a slice of term triplets that represent the intersection of keys
// between obj and other. For each intersecting key, the values from obj and other are included
// as the last two terms in the triplet (respectively).
func (obj *object) Intersect(other Object) [][3]*Term {
r := [][3]*Term{}
obj.Foreach(func(k, v *Term) {
if v2 := other.Get(k); v2 != nil {
r = append(r, [3]*Term{k, v, v2})
}
})
return r
}
// Iter calls the function f for each key-value pair in the object. If f
// returns an error, iteration stops and the error is returned.
func (obj *object) Iter(f func(*Term, *Term) error) error {
for _, node := range obj.sortedKeys() {
if err := f(node.key, node.value); err != nil {
return err
}
}
return nil
}
// Until calls f for each key-value pair in the object. If f returns
// true, iteration stops and Until returns true. Otherwise, return
// false.
func (obj *object) Until(f func(*Term, *Term) bool) bool {
for _, node := range obj.sortedKeys() {
if f(node.key, node.value) {
return true
}
}
return false
}
// Foreach calls f for each key-value pair in the object.
func (obj *object) Foreach(f func(*Term, *Term)) {
for _, node := range obj.sortedKeys() {
f(node.key, node.value)
}
}
// Map returns a new Object constructed by mapping each element in the object
// using the function f. If f returns an error, the error is returned by Map.
// If f return a nil key, the element is skipped.
func (obj *object) Map(f func(*Term, *Term) (*Term, *Term, error)) (Object, error) {
cpy := newobject(obj.Len())
for _, node := range obj.sortedKeys() {
k, v, err := f(node.key, node.value)
if err != nil {
return nil, err
}
if k != nil {
cpy.insert(k, v, false)
}
}
return cpy, nil
}
// Keys returns the keys of obj.
func (obj *object) Keys() []*Term {
keys := make([]*Term, len(obj.keys))
for i, elem := range obj.sortedKeys() {
keys[i] = elem.key
}
return keys
}
// Returns an iterator over the obj's keys.
func (obj *object) KeysIterator() ObjectKeysIterator {
return newobjectKeysIterator(obj)
}
// MarshalJSON returns JSON encoded bytes representing obj.
func (obj *object) MarshalJSON() ([]byte, error) {
sl := make([][2]*Term, obj.Len())
for i, node := range obj.sortedKeys() {
sl[i] = Item(node.key, node.value)
}
return json.Marshal(sl)
}
// Merge returns a new Object containing the non-overlapping keys of obj and other. If there are
// overlapping keys between obj and other, the values of associated with the keys are merged. Only
// objects can be merged with other objects. If the values cannot be merged, the second turn value
// will be false.
func (obj *object) Merge(other Object) (Object, bool) {
return obj.MergeWith(other, func(v1, v2 *Term) (*Term, bool) {
obj1, ok1 := v1.Value.(Object)
obj2, ok2 := v2.Value.(Object)
if !ok1 || !ok2 {
return nil, true
}
obj3, ok := obj1.Merge(obj2)
if !ok {
return nil, true
}
return NewTerm(obj3), false
})
}
// MergeWith returns a new Object containing the merged keys of obj and other.
// If there are overlapping keys between obj and other, the conflictResolver
// is called. The conflictResolver can return a merged value and a boolean
// indicating if the merge has failed and should stop.
func (obj *object) MergeWith(other Object, conflictResolver func(v1, v2 *Term) (*Term, bool)) (Object, bool) {
// Might overallocate assuming no conflicts is the common case,
// but that's typically faster than iterating over each object twice.
result := newobject(obj.Len() + other.Len())
stop := obj.Until(func(k, v *Term) bool {
v2 := other.Get(k)
// The key didn't exist in other, keep the original value
if v2 == nil {
result.insert(k, v, false)
return false
}
// The key exists in both, resolve the conflict if possible
merged, stop := conflictResolver(v, v2)
if !stop {
result.insert(k, merged, false)
}
return stop
})
if stop {
return nil, false
}
// Copy in any values from other for keys that don't exist in obj
other.Foreach(func(k, v *Term) {
if v2 := obj.Get(k); v2 == nil {
result.insert(k, v, false)
}
})
return result, true
}
// Filter returns a new object from values in obj where the keys are
// found in filter. Array indices for values can be specified as
// number strings.
func (obj *object) Filter(filter Object) (Object, error) {
filtered, err := filterObject(obj, filter)
if err != nil {
return nil, err
}
return filtered.(Object), nil
}
// Len returns the number of elements in the object.
func (obj *object) Len() int {
return len(obj.keys)
}
func (obj *object) String() string {
buf, _ := obj.AppendText(make([]byte, 0, obj.StringLength()))
return util.ByteSliceToString(buf)
}
func (*object) get(*Term) *objectElem {
return nil
}
// NOTE(philipc): We assume a many-readers, single-writer model here.
// This method should NOT be used concurrently, or else we risk data races.
func (obj *object) insert(k, v *Term, resetSortGuard bool) {
hash := k.Hash()
head := obj.elems[hash]
for curr := head; curr != nil; curr = curr.next {
if KeyHashEqual(curr.key.Value, k.Value) {
if curr.value.IsGround() {
obj.ground--
}
if v.IsGround() {
obj.ground++
}
// Update hash based on the new value
curr.value = v
obj.elems[hash] = curr
obj.hash = 0
for ehash := range obj.elems {
obj.hash += ehash + obj.elems[ehash].value.Hash()
}
return
}
}
obj.elems[hash] = &objectElem{key: k, value: v, next: head}
// O(1) insertion, but we'll have to re-sort the keys later.
obj.keys = append(obj.keys, obj.elems[hash])
if resetSortGuard {
// Reset the sync.Once instance.
// See https://github.com/golang/go/issues/25955 for why we do it this way.
// Note that this will always be the case when external code calls insert via
// Add, or otherwise. Internal code may however benefit from not having to
// re-create this when it's known not to be needed.
obj.sortGuard = sync.Once{}
}
obj.hash += hash + v.Hash()
if k.IsGround() {
obj.ground++
}
if v.IsGround() {
obj.ground++
}
}
func filterObject(o Value, filter Value) (Value, error) {
if (Null{}).Equal(filter) {
return o, nil
}
filteredObj, ok := filter.(*object)
if !ok {
return nil, fmt.Errorf("invalid filter value %q, expected an object", filter)
}
switch v := o.(type) {
case String, Number, Boolean, Null:
return o, nil
case *Array:
values := make([]*Term, 0, v.Len())
for i := range v.Len() {
subFilter := filteredObj.Get(InternedIntegerString(i))
if subFilter != nil {
filteredValue, err := filterObject(v.Elem(i).Value, subFilter.Value)
if err != nil {
return nil, err
}
values = append(values, NewTerm(filteredValue))
}
}
return NewArray(values...), nil
case Set:
terms := make([]*Term, 0, v.Len())
for _, t := range v.Slice() {
if filteredObj.Get(t) != nil {
filteredValue, err := filterObject(t.Value, filteredObj.Get(t).Value)
if err != nil {
return nil, err
}
terms = append(terms, NewTerm(filteredValue))
}
}
return NewSet(terms...), nil
case *object:
values := NewObject()
iterObj := v
other := filteredObj
if v.Len() < filteredObj.Len() {
iterObj = filteredObj
other = v
}
err := iterObj.Iter(func(key *Term, _ *Term) error {
if other.Get(key) != nil {
filteredValue, err := filterObject(v.Get(key).Value, filteredObj.Get(key).Value)
if err != nil {
return err
}
values.Insert(key, NewTerm(filteredValue))
}
return nil
})
return values, err
default:
return nil, fmt.Errorf("invalid object value type %q", v)
}
}
// NOTE(philipc): The only way to get an ObjectKeyIterator should be
// from an Object. This ensures that the iterator can have implementation-
// specific details internally, with no contracts except to the very
// limited interface.
type ObjectKeysIterator interface {
Next() (*Term, bool)
}
type objectKeysIterator struct {
obj *object
numKeys int
index int
}
func newobjectKeysIterator(o *object) ObjectKeysIterator {
return &objectKeysIterator{
obj: o,
numKeys: o.Len(),
index: 0,
}
}
func (oki *objectKeysIterator) Next() (*Term, bool) {
if oki.index == oki.numKeys || oki.numKeys == 0 {
return nil, false
}
oki.index++
return oki.obj.sortedKeys()[oki.index-1].key, true
}
// ArrayComprehension represents an array comprehension as defined in the language.
type ArrayComprehension struct {
Term *Term `json:"term"`
Body Body `json:"body"`
}
// ArrayComprehensionTerm creates a new Term with an ArrayComprehension value.
func ArrayComprehensionTerm(term *Term, body Body) *Term {
return &Term{
Value: &ArrayComprehension{
Term: term,
Body: body,
},
}
}
// Copy returns a deep copy of ac.
func (ac *ArrayComprehension) Copy() *ArrayComprehension {
cpy := *ac
cpy.Body = ac.Body.Copy()
cpy.Term = ac.Term.Copy()
return &cpy
}
// Equal returns true if ac is equal to other.
func (ac *ArrayComprehension) Equal(other Value) bool {
return Compare(ac, other) == 0
}
// Compare compares ac to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (ac *ArrayComprehension) Compare(other Value) int {
return Compare(ac, other)
}
// Find returns the current value or a not found error.
func (ac *ArrayComprehension) Find(path Ref) (Value, error) {
if len(path) == 0 {
return ac, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code of the Value.
func (ac *ArrayComprehension) Hash() int {
return ac.Term.Hash() + ac.Body.Hash()
}
// IsGround returns true if the Term and Body are ground.
func (ac *ArrayComprehension) IsGround() bool {
return ac.Term.IsGround() && ac.Body.IsGround()
}
func (ac *ArrayComprehension) String() string {
buf, _ := ac.AppendText(make([]byte, 0, ac.StringLength()))
return util.ByteSliceToString(buf)
}
// ObjectComprehension represents an object comprehension as defined in the language.
type ObjectComprehension struct {
Key *Term `json:"key"`
Value *Term `json:"value"`
Body Body `json:"body"`
}
// ObjectComprehensionTerm creates a new Term with an ObjectComprehension value.
func ObjectComprehensionTerm(key, value *Term, body Body) *Term {
return &Term{
Value: &ObjectComprehension{
Key: key,
Value: value,
Body: body,
},
}
}
// Copy returns a deep copy of oc.
func (oc *ObjectComprehension) Copy() *ObjectComprehension {
cpy := *oc
cpy.Body = oc.Body.Copy()
cpy.Key = oc.Key.Copy()
cpy.Value = oc.Value.Copy()
return &cpy
}
// Equal returns true if oc is equal to other.
func (oc *ObjectComprehension) Equal(other Value) bool {
return Compare(oc, other) == 0
}
// Compare compares oc to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (oc *ObjectComprehension) Compare(other Value) int {
return Compare(oc, other)
}
// Find returns the current value or a not found error.
func (oc *ObjectComprehension) Find(path Ref) (Value, error) {
if len(path) == 0 {
return oc, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code of the Value.
func (oc *ObjectComprehension) Hash() int {
return oc.Key.Hash() + oc.Value.Hash() + oc.Body.Hash()
}
// IsGround returns true if the Key, Value and Body are ground.
func (oc *ObjectComprehension) IsGround() bool {
return oc.Key.IsGround() && oc.Value.IsGround() && oc.Body.IsGround()
}
func (oc *ObjectComprehension) String() string {
buf, _ := oc.AppendText(make([]byte, 0, oc.StringLength()))
return util.ByteSliceToString(buf)
}
// SetComprehension represents a set comprehension as defined in the language.
type SetComprehension struct {
Term *Term `json:"term"`
Body Body `json:"body"`
}
// SetComprehensionTerm creates a new Term with an SetComprehension value.
func SetComprehensionTerm(term *Term, body Body) *Term {
return &Term{
Value: &SetComprehension{
Term: term,
Body: body,
},
}
}
// Copy returns a deep copy of sc.
func (sc *SetComprehension) Copy() *SetComprehension {
cpy := *sc
cpy.Body = sc.Body.Copy()
cpy.Term = sc.Term.Copy()
return &cpy
}
// Equal returns true if sc is equal to other.
func (sc *SetComprehension) Equal(other Value) bool {
return Compare(sc, other) == 0
}
// Compare compares sc to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (sc *SetComprehension) Compare(other Value) int {
return Compare(sc, other)
}
// Find returns the current value or a not found error.
func (sc *SetComprehension) Find(path Ref) (Value, error) {
if len(path) == 0 {
return sc, nil
}
return nil, errFindNotFound
}
// Hash returns the hash code of the Value.
func (sc *SetComprehension) Hash() int {
return sc.Term.Hash() + sc.Body.Hash()
}
// IsGround returns true if the Term and Body are ground.
func (sc *SetComprehension) IsGround() bool {
return sc.Term.IsGround() && sc.Body.IsGround()
}
func (sc *SetComprehension) String() string {
buf, _ := sc.AppendText(make([]byte, 0, sc.StringLength()))
return util.ByteSliceToString(buf)
}
// Call represents as function call in the language.
type Call []*Term
// CallTerm returns a new Term with a Call value defined by terms. The first
// term is the operator and the rest are operands.
func CallTerm(terms ...*Term) *Term {
return NewTerm(Call(terms))
}
// Copy returns a deep copy of c.
func (c Call) Copy() Call {
return termSliceCopy(c)
}
// Compare compares c to other, return <0, 0, or >0 if it is less than, equal to,
// or greater than other.
func (c Call) Compare(other Value) int {
return Compare(c, other)
}
// Find returns the current value or a not found error.
func (Call) Find(Ref) (Value, error) {
return nil, errFindNotFound
}
// Hash returns the hash code for the Value.
func (c Call) Hash() int {
return termSliceHash(c)
}
// IsGround returns true if the Value is ground.
func (c Call) IsGround() bool {
return termSliceIsGround(c)
}
// MakeExpr returns a new Expr from this call.
func (c Call) MakeExpr(output *Term) *Expr {
terms := []*Term(c)
return NewExpr(append(terms, output))
}
func (c Call) Operator() Ref {
if len(c) == 0 {
return nil
}
return c[0].Value.(Ref)
}
func (c Call) Operands() []*Term {
if len(c) < 1 {
return nil
}
return c[1:]
}
func (c Call) String() string {
buf, _ := c.AppendText(make([]byte, 0, c.StringLength()))
return util.ByteSliceToString(buf)
}
func termSliceCopy(a []*Term) []*Term {
cpy := make([]*Term, len(a))
for i := range a {
cpy[i] = a[i].Copy()
}
return cpy
}
func termSliceEqual(a, b []*Term) bool {
if len(a) == len(b) {
for i := range a {
if !a[i].Equal(b[i]) {
return false
}
}
return true
}
return false
}
func termSliceHash(a []*Term) int {
var hash int
for _, v := range a {
hash += v.Value.Hash()
}
return hash
}
func termSliceIsGround(a []*Term) bool {
for _, v := range a {
if !v.IsGround() {
return false
}
}
return true
}
// Detect when String() need to use expensive JSONescaped form
func isControlOrBackslash(r rune) bool {
return r == '\\' || unicode.IsControl(r)
}
// NOTE(tsandall): The unmarshalling errors in these functions are not
// helpful for callers because they do not identify the source of the
// unmarshalling error. Because OPA doesn't accept JSON describing ASTs
// from callers, this is acceptable (for now). If that changes in the future,
// the error messages should be revisited. The current approach focuses
// on the happy path and treats all errors the same. If better error
// reporting is needed, the error paths will need to be fleshed out.
func unmarshalBody(b []any) (Body, error) {
buf := Body{}
for _, e := range b {
if m, ok := e.(map[string]any); ok {
expr := &Expr{}
if err := unmarshalExpr(expr, m); err == nil {
buf = append(buf, expr)
continue
}
}
goto unmarshal_error
}
return buf, nil
unmarshal_error:
return nil, errors.New("ast: unable to unmarshal body")
}
func unmarshalExpr(expr *Expr, v map[string]any) error {
if x, ok := v["negated"]; ok {
if b, ok := x.(bool); ok {
expr.Negated = b
} else {
return fmt.Errorf("ast: unable to unmarshal negated field with type: %T (expected true or false)", v["negated"])
}
}
if generatedRaw, ok := v["generated"]; ok {
if b, ok := generatedRaw.(bool); ok {
expr.Generated = b
} else {
return fmt.Errorf("ast: unable to unmarshal generated field with type: %T (expected true or false)", v["generated"])
}
}
if err := unmarshalExprIndex(expr, v); err != nil {
return err
}
switch ts := v["terms"].(type) {
case map[string]any:
t, err := unmarshalTerm(ts)
if err != nil {
return err
}
expr.Terms = t
case []any:
terms, err := unmarshalTermSlice(ts)
if err != nil {
return err
}
expr.Terms = terms
default:
return fmt.Errorf(`ast: unable to unmarshal terms field with type: %T (expected {"value": ..., "type": ...} or [{"value": ..., "type": ...}, ...])`, v["terms"])
}
if x, ok := v["with"]; ok {
if sl, ok := x.([]any); ok {
ws := make([]*With, len(sl))
for i := range sl {
var err error
ws[i], err = unmarshalWith(sl[i])
if err != nil {
return err
}
}
expr.With = ws
}
}
if loc, ok := v["location"].(map[string]any); ok {
expr.Location = &Location{}
if err := unmarshalLocation(expr.Location, loc); err != nil {
return err
}
}
return nil
}
func unmarshalLocation(loc *Location, v map[string]any) error {
if x, ok := v["file"]; ok {
if s, ok := x.(string); ok {
loc.File = s
} else {
return fmt.Errorf("ast: unable to unmarshal file field with type: %T (expected string)", v["file"])
}
}
if x, ok := v["row"]; ok {
if n, ok := x.(json.Number); ok {
i64, err := n.Int64()
if err != nil {
return err
}
loc.Row = int(i64)
} else {
return fmt.Errorf("ast: unable to unmarshal row field with type: %T (expected number)", v["row"])
}
}
if x, ok := v["col"]; ok {
if n, ok := x.(json.Number); ok {
i64, err := n.Int64()
if err != nil {
return err
}
loc.Col = int(i64)
} else {
return fmt.Errorf("ast: unable to unmarshal col field with type: %T (expected number)", v["col"])
}
}
return nil
}
func unmarshalExprIndex(expr *Expr, v map[string]any) error {
if x, ok := v["index"]; ok {
if n, ok := x.(json.Number); ok {
i, err := n.Int64()
if err == nil {
expr.Index = int(i)
return nil
}
}
}
return fmt.Errorf("ast: unable to unmarshal index field with type: %T (expected integer)", v["index"])
}
func unmarshalTerm(m map[string]any) (*Term, error) {
var term Term
v, err := unmarshalValue(m)
if err != nil {
return nil, err
}
term.Value = v
if loc, ok := m["location"].(map[string]any); ok {
term.Location = &Location{}
if err := unmarshalLocation(term.Location, loc); err != nil {
return nil, err
}
}
return &term, nil
}
func unmarshalTermSlice(s []any) ([]*Term, error) {
buf := []*Term{}
for _, x := range s {
if m, ok := x.(map[string]any); ok {
t, err := unmarshalTerm(m)
if err == nil {
buf = append(buf, t)
continue
}
return nil, err
}
return nil, errors.New("ast: unable to unmarshal term")
}
return buf, nil
}
func unmarshalTermSliceValue(d map[string]any) ([]*Term, error) {
if s, ok := d["value"].([]any); ok {
return unmarshalTermSlice(s)
}
return nil, errors.New(`ast: unable to unmarshal term (expected {"value": [...], "type": ...} where type is one of: ref, array, or set)`)
}
func unmarshalWith(i any) (*With, error) {
if m, ok := i.(map[string]any); ok {
tgt, _ := m["target"].(map[string]any)
target, err := unmarshalTerm(tgt)
if err == nil {
val, _ := m["value"].(map[string]any)
value, err := unmarshalTerm(val)
if err == nil {
return &With{
Target: target,
Value: value,
}, nil
}
return nil, err
}
return nil, err
}
return nil, errors.New(`ast: unable to unmarshal with modifier (expected {"target": {...}, "value": {...}})`)
}
func unmarshalValue(d map[string]any) (Value, error) {
v := d["value"]
switch d["type"] {
case "null":
return NullValue, nil
case "boolean":
if b, ok := v.(bool); ok {
return Boolean(b), nil
}
case "number":
if n, ok := v.(json.Number); ok {
return Number(n), nil
}
case "string":
if s, ok := v.(string); ok {
return String(s), nil
}
case "var":
if s, ok := v.(string); ok {
return Var(s), nil
}
case "ref":
if s, err := unmarshalTermSliceValue(d); err == nil {
return Ref(s), nil
}
case "array":
if s, err := unmarshalTermSliceValue(d); err == nil {
return NewArray(s...), nil
}
case "set":
if s, err := unmarshalTermSliceValue(d); err == nil {
return NewSet(s...), nil
}
case "object":
if s, ok := v.([]any); ok {
buf := NewObject()
for _, x := range s {
if i, ok := x.([]any); ok && len(i) == 2 {
p, err := unmarshalTermSlice(i)
if err == nil {
buf.Insert(p[0], p[1])
continue
}
}
goto unmarshal_error
}
return buf, nil
}
case "arraycomprehension", "setcomprehension":
if m, ok := v.(map[string]any); ok {
t, ok := m["term"].(map[string]any)
if !ok {
goto unmarshal_error
}
term, err := unmarshalTerm(t)
if err != nil {
goto unmarshal_error
}
b, ok := m["body"].([]any)
if !ok {
goto unmarshal_error
}
body, err := unmarshalBody(b)
if err != nil {
goto unmarshal_error
}
if d["type"] == "arraycomprehension" {
return &ArrayComprehension{Term: term, Body: body}, nil
}
return &SetComprehension{Term: term, Body: body}, nil
}
case "objectcomprehension":
if m, ok := v.(map[string]any); ok {
k, ok := m["key"].(map[string]any)
if !ok {
goto unmarshal_error
}
key, err := unmarshalTerm(k)
if err != nil {
goto unmarshal_error
}
v, ok := m["value"].(map[string]any)
if !ok {
goto unmarshal_error
}
value, err := unmarshalTerm(v)
if err != nil {
goto unmarshal_error
}
b, ok := m["body"].([]any)
if !ok {
goto unmarshal_error
}
body, err := unmarshalBody(b)
if err != nil {
goto unmarshal_error
}
return &ObjectComprehension{Key: key, Value: value, Body: body}, nil
}
case "call":
if s, err := unmarshalTermSliceValue(d); err == nil {
return Call(s), nil
}
}
unmarshal_error:
return nil, errors.New("ast: unable to unmarshal term")
}