1178 lines
27 KiB
Go
1178 lines
27 KiB
Go
// Copyright 2017 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 (
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/open-policy-agent/opa/v1/util"
|
|
)
|
|
|
|
// RuleIndex defines the interface for rule indices.
|
|
type RuleIndex interface {
|
|
|
|
// Build tries to construct an index for the given rules. If the index was
|
|
// constructed, it returns true, otherwise false.
|
|
Build(rules []*Rule) bool
|
|
|
|
// Lookup searches the index for rules that will match the provided
|
|
// resolver. If the resolver returns an error, it is returned via err.
|
|
Lookup(resolver ValueResolver) (*IndexResult, error)
|
|
|
|
// AllRules traverses the index and returns all rules that will match
|
|
// the provided resolver without any optimizations (effectively with
|
|
// indexing disabled). If the resolver returns an error, it is returned
|
|
// via err.
|
|
AllRules(resolver ValueResolver) (*IndexResult, error)
|
|
}
|
|
|
|
// IndexResult contains the result of an index lookup.
|
|
type IndexResult struct {
|
|
Rules []*Rule
|
|
Else map[*Rule][]*Rule
|
|
Default *Rule
|
|
Kind RuleKind
|
|
EarlyExit bool
|
|
OnlyGroundRefs bool
|
|
}
|
|
|
|
// NewIndexResult returns a new IndexResult object.
|
|
func NewIndexResult(kind RuleKind) *IndexResult {
|
|
return &IndexResult{
|
|
Kind: kind,
|
|
}
|
|
}
|
|
|
|
// Empty returns true if there are no rules to evaluate.
|
|
func (ir *IndexResult) Empty() bool {
|
|
return len(ir.Rules) == 0 && ir.Default == nil
|
|
}
|
|
|
|
type baseDocEqIndex struct {
|
|
isVirtual func(Ref) bool
|
|
root *trieNode
|
|
defaultRule *Rule
|
|
kind RuleKind
|
|
onlyGroundRefs bool
|
|
}
|
|
|
|
var (
|
|
equalityRef = Equality.Ref()
|
|
equalRef = Equal.Ref()
|
|
globMatchRef = GlobMatch.Ref()
|
|
internalPrintRef = InternalPrint.Ref()
|
|
internalTestCaseRef = InternalTestCase.Ref()
|
|
internalMemberRef = Member.Ref()
|
|
|
|
skipIndexing = NewSet(NewTerm(internalPrintRef), NewTerm(internalTestCaseRef))
|
|
)
|
|
|
|
func newBaseDocEqIndex(isVirtual func(Ref) bool) *baseDocEqIndex {
|
|
return &baseDocEqIndex{
|
|
isVirtual: isVirtual,
|
|
root: newTrieNodeImpl(),
|
|
onlyGroundRefs: true,
|
|
}
|
|
}
|
|
|
|
func (i *baseDocEqIndex) Build(rules []*Rule) bool {
|
|
if len(rules) == 0 {
|
|
return false
|
|
}
|
|
|
|
i.kind = rules[0].Head.RuleKind()
|
|
indices := newrefindices(i.isVirtual)
|
|
values := make(map[Var]Value)
|
|
|
|
// build indices for each rule.
|
|
for idx := range rules {
|
|
WalkRules(rules[idx], func(rule *Rule) bool {
|
|
if rule.Default {
|
|
i.defaultRule = rule
|
|
return false
|
|
}
|
|
if i.onlyGroundRefs {
|
|
i.onlyGroundRefs = rule.Head.Reference.IsGround()
|
|
}
|
|
var skip bool
|
|
for i := range rule.Body {
|
|
if op := rule.Body[i].OperatorTerm(); op != nil && skipIndexing.Contains(op) {
|
|
skip = true
|
|
break
|
|
}
|
|
}
|
|
if !skip {
|
|
clear(values)
|
|
for i := range rule.Body {
|
|
indices.Update(rule, rule.Body[i], values)
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
// build trie out of indices.
|
|
for idx := range rules {
|
|
var prio int
|
|
WalkRules(rules[idx], func(rule *Rule) bool {
|
|
if rule.Default {
|
|
return false
|
|
}
|
|
node := i.root
|
|
if indices.Indexed(rule) {
|
|
for _, ref := range indices.Sorted() {
|
|
var values []*refindex
|
|
for _, ri := range indices.rules[rule] {
|
|
if ri.Ref.Equal(ref) {
|
|
values = append(values, ri)
|
|
}
|
|
}
|
|
if len(values) == 0 {
|
|
node = node.Insert(ref, nil, nil)
|
|
} else if len(values) == 1 {
|
|
node = node.Insert(ref, values[0].Value, values[0].Mapper)
|
|
} else {
|
|
var hasVar bool
|
|
for i := range values {
|
|
if _, isVar := values[i].Value.(Var); isVar {
|
|
hasVar = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if hasVar {
|
|
child := node.Insert(ref, anyValue, values[0].Mapper)
|
|
for i := range values {
|
|
if values[i].Mapper != nil {
|
|
node.next.addMapper(values[i].Mapper)
|
|
}
|
|
}
|
|
node = child
|
|
} else {
|
|
// When a rule has multiple scalar values (e.g., internal.member_2 with a set),
|
|
// each value should have its own child node, and the rule is appended to each.
|
|
// This creates separate paths for each value so different rules with overlapping
|
|
// values don't interfere with each other.
|
|
for _, val := range values {
|
|
child := node.Insert(ref, val.Value, val.Mapper)
|
|
child.append([...]int{idx, prio}, rule)
|
|
}
|
|
prio++
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Insert rule into trie with (insertion order, priority order)
|
|
// tuple. Retaining the insertion order allows us to return rules
|
|
// in the order they were passed to this function.
|
|
node.append([...]int{idx, prio}, rule)
|
|
prio++
|
|
return false
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (i *baseDocEqIndex) Lookup(resolver ValueResolver) (*IndexResult, error) {
|
|
tr := ttrPool.Get().(*trieTraversalResult)
|
|
|
|
defer func() {
|
|
clear(tr.unordered)
|
|
tr.ordering = tr.ordering[:0]
|
|
tr.multiple = false
|
|
tr.exist = nil
|
|
|
|
ttrPool.Put(tr)
|
|
}()
|
|
|
|
err := i.root.Traverse(resolver, tr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := IndexResultPool.Get()
|
|
|
|
result.Kind = i.kind
|
|
result.Default = i.defaultRule
|
|
result.OnlyGroundRefs = i.onlyGroundRefs
|
|
|
|
if result.Rules == nil {
|
|
result.Rules = make([]*Rule, 0, len(tr.ordering))
|
|
} else {
|
|
result.Rules = result.Rules[:0]
|
|
}
|
|
|
|
clear(result.Else)
|
|
|
|
for _, pos := range tr.ordering {
|
|
slices.SortFunc(tr.unordered[pos], func(a, b *ruleNode) int {
|
|
return a.prio[1] - b.prio[1]
|
|
})
|
|
nodes := tr.unordered[pos]
|
|
root := nodes[0].rule
|
|
|
|
result.Rules = append(result.Rules, root)
|
|
if len(nodes) > 1 {
|
|
if result.Else == nil {
|
|
result.Else = map[*Rule][]*Rule{}
|
|
}
|
|
|
|
result.Else[root] = make([]*Rule, len(nodes)-1)
|
|
for i := 1; i < len(nodes); i++ {
|
|
result.Else[root][i-1] = nodes[i].rule
|
|
}
|
|
}
|
|
}
|
|
|
|
if !tr.multiple {
|
|
// even when the indexer hasn't seen multiple values, the rule itself could be one
|
|
// where early exit shouldn't be applied.
|
|
var lastValue Value
|
|
for i := range result.Rules {
|
|
if result.Rules[i].Head.DocKind() != CompleteDoc {
|
|
tr.multiple = true
|
|
break
|
|
}
|
|
if result.Rules[i].Head.Value != nil {
|
|
if lastValue != nil && !ValueEqual(lastValue, result.Rules[i].Head.Value.Value) {
|
|
tr.multiple = true
|
|
break
|
|
}
|
|
lastValue = result.Rules[i].Head.Value.Value
|
|
}
|
|
}
|
|
}
|
|
|
|
result.EarlyExit = !tr.multiple
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (i *baseDocEqIndex) AllRules(ValueResolver) (*IndexResult, error) {
|
|
tr := newTrieTraversalResult()
|
|
|
|
// Walk over the rule trie and accumulate _all_ rules
|
|
rw := &ruleWalker{result: tr}
|
|
i.root.Do(rw)
|
|
|
|
result := NewIndexResult(i.kind)
|
|
result.Default = i.defaultRule
|
|
result.OnlyGroundRefs = i.onlyGroundRefs
|
|
result.Rules = make([]*Rule, 0, len(tr.ordering))
|
|
|
|
for _, pos := range tr.ordering {
|
|
slices.SortFunc(tr.unordered[pos], func(a, b *ruleNode) int {
|
|
return a.prio[1] - b.prio[1]
|
|
})
|
|
nodes := tr.unordered[pos]
|
|
root := nodes[0].rule
|
|
result.Rules = append(result.Rules, root)
|
|
if len(nodes) > 1 {
|
|
if result.Else == nil {
|
|
result.Else = map[*Rule][]*Rule{}
|
|
}
|
|
|
|
result.Else[root] = make([]*Rule, len(nodes)-1)
|
|
for i := 1; i < len(nodes); i++ {
|
|
result.Else[root][i-1] = nodes[i].rule
|
|
}
|
|
}
|
|
}
|
|
|
|
result.EarlyExit = !tr.multiple
|
|
|
|
return result, nil
|
|
}
|
|
|
|
type ruleWalker struct {
|
|
result *trieTraversalResult
|
|
}
|
|
|
|
func (r *ruleWalker) Do(x any) trieWalker {
|
|
tn := x.(*trieNode)
|
|
r.result.Add(tn)
|
|
return r
|
|
}
|
|
|
|
type valueMapper struct {
|
|
Key string
|
|
MapValue func(Value) Value
|
|
}
|
|
|
|
type refindex struct {
|
|
Ref Ref
|
|
Value Value
|
|
Mapper *valueMapper
|
|
}
|
|
|
|
type refindices struct {
|
|
isVirtual func(Ref) bool
|
|
rules map[*Rule][]*refindex
|
|
frequency *util.HasherMap[Ref, int]
|
|
sorted []Ref
|
|
}
|
|
|
|
func newrefindices(isVirtual func(Ref) bool) *refindices {
|
|
return &refindices{
|
|
isVirtual: isVirtual,
|
|
rules: map[*Rule][]*refindex{},
|
|
frequency: util.NewHasherMap[Ref, int](RefEqual),
|
|
}
|
|
}
|
|
|
|
// anyValue is a fake variable we used to put "naked ref" expressions
|
|
// into the rule index
|
|
var anyValue = Var("__any__")
|
|
|
|
// Update attempts to update the refindices for the given expression in the
|
|
// given rule. If the expression cannot be indexed the update does not affect
|
|
// the indices.
|
|
func (i *refindices) Update(rule *Rule, expr *Expr, values map[Var]Value) {
|
|
|
|
if len(expr.With) > 0 {
|
|
// NOTE(tsandall): In the future, we may need to consider expressions
|
|
// that have with statements applied to them.
|
|
return
|
|
}
|
|
|
|
if expr.Negated {
|
|
// NOTE(sr): We could try to cover simple expressions, like
|
|
// not input.funky => input.funky == false or undefined (two refindex?)
|
|
return
|
|
}
|
|
|
|
op := expr.Operator()
|
|
if op == nil {
|
|
if ts, ok := expr.Terms.(*Term); ok {
|
|
// NOTE(sr): If we wanted to cover function args, we'd need to also
|
|
// check for type "Var" here. But since it's impossible to call a
|
|
// function with a undefined argument, there's no point to recording
|
|
// "needs to be anything" for function args
|
|
if ref, ok := ts.Value.(Ref); ok { // "naked ref"
|
|
i.updateEq(rule, ref, anyValue, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
equalish := op.Equal(equalityRef) || // unification, no 3-operands version exists
|
|
// NOTE(tsandall): if equal() is called with more than two arguments the
|
|
// output value is being captured in which case the indexer cannot
|
|
// exclude the rule if the equal() call would return false (because the
|
|
// false value must still be produced.)
|
|
(op.Equal(equalRef) && len(expr.Operands()) == 2)
|
|
|
|
a, b := expr.Operand(0), expr.Operand(1)
|
|
switch {
|
|
case equalish:
|
|
if !i.updateEqWildcardRef(rule, a.Value, b.Value, values) {
|
|
i.updateEq(rule, a.Value, b.Value, values)
|
|
}
|
|
|
|
case op.Equal(globMatchRef) && len(expr.Operands()) == 3:
|
|
// NOTE(sr): Same as with equal() above -- 4 operands means the output
|
|
// of `glob.match` is captured and the rule can thus not be excluded.
|
|
i.updateGlobMatch(rule, expr)
|
|
|
|
case op.Equal(internalMemberRef) && len(expr.Operands()) == 2:
|
|
// NOTE(sr): Again, 3 operands means captured output (like above).
|
|
i.updateMember(rule, expr, values)
|
|
}
|
|
}
|
|
|
|
func (i *refindices) isValidIndexRef(ref Ref) bool {
|
|
// NB(sr): the ordering is intentional, cheapest-first
|
|
return RootDocumentNames.Contains(ref[0]) &&
|
|
!ref.IsNested() &&
|
|
ref.IsGround() &&
|
|
!i.isVirtual(ref)
|
|
}
|
|
|
|
// Sorted returns a sorted list of references that the indices were built from.
|
|
// References that appear more frequently in the indexed rules are ordered
|
|
// before less frequently appearing references.
|
|
func (i *refindices) Sorted() []Ref {
|
|
|
|
if i.sorted == nil {
|
|
counts := make([]int, 0, i.frequency.Len())
|
|
i.sorted = make([]Ref, 0, i.frequency.Len())
|
|
|
|
i.frequency.Iter(func(k Ref, v int) bool {
|
|
counts = append(counts, v)
|
|
i.sorted = append(i.sorted, k)
|
|
return false
|
|
})
|
|
|
|
sort.Slice(i.sorted, func(a, b int) bool {
|
|
if counts[a] > counts[b] {
|
|
return true
|
|
} else if counts[b] > counts[a] {
|
|
return false
|
|
}
|
|
return i.sorted[a][0].Loc().Compare(i.sorted[b][0].Loc()) < 0
|
|
})
|
|
}
|
|
|
|
return i.sorted
|
|
}
|
|
|
|
func (i *refindices) Indexed(rule *Rule) bool {
|
|
return len(i.rules[rule]) > 0
|
|
}
|
|
|
|
func (i *refindices) Value(rule *Rule, ref Ref) Value {
|
|
if index := i.index(rule, ref); index != nil {
|
|
return index.Value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *refindices) Mapper(rule *Rule, ref Ref) *valueMapper {
|
|
if index := i.index(rule, ref); index != nil {
|
|
return index.Mapper
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *refindices) updateEq(rule *Rule, a, b Value, constants map[Var]Value) {
|
|
args := rule.Head.Args
|
|
if !i.eqOperandsToRefAndValue(rule, args, a, b, constants) {
|
|
i.eqOperandsToRefAndValue(rule, args, b, a, constants)
|
|
}
|
|
}
|
|
|
|
func (i *refindices) updateEqWildcardRef(rule *Rule, a, b Value, constants map[Var]Value) bool {
|
|
return i.tryIndexWildcardRef(rule, a, b, constants) ||
|
|
i.tryIndexWildcardRef(rule, b, a, constants)
|
|
}
|
|
|
|
func (i *refindices) tryIndexWildcardRef(rule *Rule, a, b Value, constants map[Var]Value) bool {
|
|
ref, ok := a.(Ref)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
groundPrefix := ref.GroundPrefix()
|
|
if len(groundPrefix) != len(ref)-1 || !i.isValidIndexRef(groundPrefix) {
|
|
return false
|
|
}
|
|
|
|
resolvedValue := b
|
|
if bvar, ok := b.(Var); ok {
|
|
if resolved, ok := constants[bvar]; ok {
|
|
resolvedValue = resolved
|
|
}
|
|
} else if val, ok := indexValue(b); ok {
|
|
resolvedValue = val
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
if !IsScalar(resolvedValue) {
|
|
return false
|
|
}
|
|
|
|
i.insert(rule, &refindex{Ref: groundPrefix, Value: resolvedValue})
|
|
return true
|
|
}
|
|
|
|
func (i *refindices) updateGlobMatch(rule *Rule, expr *Expr) {
|
|
args := rule.Head.Args
|
|
|
|
delim, ok := globDelimiterToString(expr.Operand(1))
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if arr := globPatternToArray(expr.Operand(0), delim); arr != nil {
|
|
// The 3rd operand of glob.match is the value to match. We assume the
|
|
// 3rd operand was a reference that has been rewritten and bound to a
|
|
// variable earlier in the query OR a function argument variable.
|
|
match := expr.Operand(2)
|
|
if v, ok := match.Value.(Var); ok {
|
|
if ref := resolveVarToRef(i.rules[rule], args, v); ref != nil {
|
|
i.insert(rule, &refindex{
|
|
Ref: ref,
|
|
Value: arr.Value,
|
|
Mapper: &valueMapper{
|
|
Key: delim,
|
|
MapValue: func(v Value) Value {
|
|
if s, ok := v.(String); ok {
|
|
return stringSliceToArray(splitStringEscaped(string(s), delim))
|
|
}
|
|
return v
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (i *refindices) updateMember(rule *Rule, expr *Expr, constants map[Var]Value) {
|
|
args := rule.Head.Args
|
|
lhs, rhs := expr.Operand(0), expr.Operand(1)
|
|
|
|
lvar, ok := lhs.Value.(Var)
|
|
if ok {
|
|
lref := resolveVarToRef(i.rules[rule], args, lvar)
|
|
if lref != nil {
|
|
i.updateMemberRefInValue(rule, lref, rhs, constants) // `ref in value`
|
|
return
|
|
}
|
|
}
|
|
|
|
// `var0 in var1` case (var0 may be constant, var1 ref)
|
|
i.updateMemberValueInRef(rule, args, lhs.Value, rhs, constants)
|
|
}
|
|
|
|
func (i *refindices) updateMemberValueInRef(rule *Rule, args []*Term, lval Value, rhs *Term, constants map[Var]Value) {
|
|
if lvar, ok := lval.(Var); ok {
|
|
val, ok := constants[lvar]
|
|
if ok {
|
|
lval = val
|
|
}
|
|
} else if !IsScalar(lval) {
|
|
return
|
|
}
|
|
|
|
rref := i.resolveAndValidateRef(rule, args, rhs)
|
|
if rref == nil {
|
|
return
|
|
}
|
|
|
|
i.insert(rule, &refindex{Ref: rref, Value: lval})
|
|
}
|
|
|
|
func (i *refindices) updateMemberRefInValue(rule *Rule, ref Ref, rhs *Term, constants map[Var]Value) {
|
|
rval := rhs.Value
|
|
if rvar, ok := rval.(Var); ok { // rhs is var, try to resolve
|
|
if resolved, ok := constants[rvar]; ok {
|
|
rval = resolved
|
|
}
|
|
}
|
|
|
|
addRef := func(t *Term) error {
|
|
i.insert(rule, &refindex{Ref: ref, Value: t.Value})
|
|
return nil
|
|
}
|
|
|
|
switch rcol := rval.(type) {
|
|
case *Array:
|
|
_ = rcol.Iter(addRef)
|
|
case Set:
|
|
_ = rcol.Iter(addRef)
|
|
case Object:
|
|
_ = rcol.Iter(func(_, v *Term) error {
|
|
return addRef(v)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (i *refindices) resolveAndValidateRef(rule *Rule, args []*Term, term *Term) Ref {
|
|
var ref Ref
|
|
switch v := term.Value.(type) {
|
|
case Ref:
|
|
ref = v
|
|
case Var:
|
|
ref = resolveVarToRef(i.rules[rule], args, v)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
if ref == nil || !i.isValidIndexRef(ref) {
|
|
return nil
|
|
}
|
|
|
|
return ref
|
|
}
|
|
|
|
// resolveVarToRef checks the previously prepared `*refindex` slice for
|
|
// occurrences of the var `v`. Since we store `ref = var` expressions for
|
|
// "any" lookups (i.e. "return the rule if ref is anything"), we can
|
|
// resolve vars to refs in these simple cases:
|
|
//
|
|
// __local2__ = input.foo
|
|
// __local2__ = <something>
|
|
//
|
|
// This what builtin calls involving refs are rewritten to, so it is used
|
|
// for var -> ref lookup when buiding the RI for glob.match or `v in col`.
|
|
//
|
|
// For convenience, we also resolve function arg vars here.
|
|
//
|
|
// NB: This also covers explicit var assignments, like `role := input.rule`,
|
|
// but it is no help with chains of assignments, like
|
|
//
|
|
// x := input.role
|
|
// y := x
|
|
// <something with x>
|
|
//
|
|
// as we're not capturing `var = var` expressions in the index.
|
|
func resolveVarToRef(ri []*refindex, args []*Term, v Var) Ref {
|
|
for _, other := range ri {
|
|
if ov, ok := other.Value.(Var); ok && ov.Equal(v) {
|
|
return other.Ref
|
|
}
|
|
}
|
|
for j, arg := range args {
|
|
if arg.Value.Compare(v) == 0 {
|
|
return Ref{FunctionArgRootDocument, InternedTerm(j)}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *refindices) insert(rule *Rule, index *refindex) {
|
|
count, _ := i.frequency.Get(index.Ref)
|
|
i.frequency.Put(index.Ref, count+1)
|
|
|
|
_, indexValueIsVar := index.Value.(Var)
|
|
|
|
for pos, other := range i.rules[rule] {
|
|
if other.Ref.Equal(index.Ref) {
|
|
|
|
if ValueEqual(other.Value, index.Value) {
|
|
return
|
|
}
|
|
_, otherValueIsVar := other.Value.(Var)
|
|
if !indexValueIsVar && otherValueIsVar {
|
|
i.rules[rule][pos] = index
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
i.rules[rule] = append(i.rules[rule], index)
|
|
}
|
|
|
|
func (i *refindices) index(rule *Rule, ref Ref) *refindex {
|
|
for _, index := range i.rules[rule] {
|
|
if index.Ref.Equal(ref) {
|
|
return index
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type trieWalker interface {
|
|
Do(any) trieWalker
|
|
}
|
|
|
|
type trieTraversalResult struct {
|
|
unordered map[int][]*ruleNode
|
|
ordering []int
|
|
exist *Term
|
|
multiple bool
|
|
}
|
|
|
|
var ttrPool = sync.Pool{
|
|
New: func() any {
|
|
return newTrieTraversalResult()
|
|
},
|
|
}
|
|
|
|
func newTrieTraversalResult() *trieTraversalResult {
|
|
return &trieTraversalResult{
|
|
unordered: map[int][]*ruleNode{},
|
|
}
|
|
}
|
|
|
|
func (tr *trieTraversalResult) Add(t *trieNode) {
|
|
for _, node := range t.rules {
|
|
root := node.prio[0]
|
|
nodes, ok := tr.unordered[root]
|
|
if !ok {
|
|
tr.ordering = append(tr.ordering, root)
|
|
}
|
|
// Deduplicate: check if a ruleNode with this priority already exists
|
|
if !slices.ContainsFunc(nodes, func(existing *ruleNode) bool {
|
|
return existing.prio == node.prio
|
|
}) {
|
|
tr.unordered[root] = append(nodes, node)
|
|
}
|
|
}
|
|
if t.multiple {
|
|
tr.multiple = true
|
|
}
|
|
if tr.multiple || t.value == nil {
|
|
return
|
|
}
|
|
if t.value.IsGround() && tr.exist == nil || tr.exist.Equal(t.value) {
|
|
tr.exist = t.value
|
|
return
|
|
}
|
|
tr.multiple = true
|
|
}
|
|
|
|
type trieNode struct {
|
|
ref Ref
|
|
mappers []*valueMapper
|
|
next *trieNode
|
|
any *trieNode
|
|
undefined *trieNode
|
|
scalars *util.HasherMap[Value, *trieNode]
|
|
array *trieNode
|
|
rules []*ruleNode
|
|
value *Term
|
|
multiple bool
|
|
}
|
|
|
|
func (node *trieNode) append(prio [2]int, rule *Rule) {
|
|
node.rules = append(node.rules, &ruleNode{prio, rule})
|
|
|
|
if node.value != nil && rule.Head.Value != nil && !node.value.Equal(rule.Head.Value) {
|
|
node.multiple = true
|
|
}
|
|
|
|
if node.value == nil && rule.Head.DocKind() == CompleteDoc {
|
|
node.value = rule.Head.Value
|
|
}
|
|
}
|
|
|
|
type ruleNode struct {
|
|
prio [2]int
|
|
rule *Rule
|
|
}
|
|
|
|
func newTrieNodeImpl() *trieNode {
|
|
return &trieNode{
|
|
scalars: util.NewHasherMap[Value, *trieNode](ValueEqual),
|
|
}
|
|
}
|
|
|
|
func (node *trieNode) Do(walker trieWalker) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
next := walker.Do(node)
|
|
if next == nil {
|
|
return
|
|
}
|
|
|
|
node.any.Do(next)
|
|
node.undefined.Do(next)
|
|
|
|
node.scalars.Iter(func(_ Value, child *trieNode) bool {
|
|
child.Do(next)
|
|
return false
|
|
})
|
|
|
|
node.array.Do(next)
|
|
node.next.Do(next)
|
|
}
|
|
|
|
func (node *trieNode) Insert(ref Ref, value Value, mapper *valueMapper) *trieNode {
|
|
|
|
if node.next == nil {
|
|
node.next = newTrieNodeImpl()
|
|
node.next.ref = ref
|
|
}
|
|
|
|
if mapper != nil {
|
|
node.next.addMapper(mapper)
|
|
}
|
|
|
|
return node.next.insertValue(value)
|
|
}
|
|
|
|
func (node *trieNode) Traverse(resolver ValueResolver, tr *trieTraversalResult) error {
|
|
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
tr.Add(node)
|
|
|
|
return node.next.traverse(resolver, tr)
|
|
}
|
|
|
|
func (node *trieNode) addMapper(mapper *valueMapper) {
|
|
for i := range node.mappers {
|
|
if node.mappers[i].Key == mapper.Key {
|
|
return
|
|
}
|
|
}
|
|
node.mappers = append(node.mappers, mapper)
|
|
}
|
|
|
|
func (node *trieNode) insertValue(value Value) *trieNode {
|
|
|
|
switch value := value.(type) {
|
|
case nil:
|
|
if node.undefined == nil {
|
|
node.undefined = newTrieNodeImpl()
|
|
}
|
|
return node.undefined
|
|
case Var:
|
|
if node.any == nil {
|
|
node.any = newTrieNodeImpl()
|
|
}
|
|
return node.any
|
|
case Null, Boolean, Number, String:
|
|
child, ok := node.scalars.Get(value)
|
|
if !ok {
|
|
child = newTrieNodeImpl()
|
|
node.scalars.Put(value, child)
|
|
}
|
|
return child
|
|
case *Array:
|
|
if node.array == nil {
|
|
node.array = newTrieNodeImpl()
|
|
}
|
|
return node.array.insertArray(value)
|
|
}
|
|
|
|
panic("illegal value")
|
|
}
|
|
|
|
func (node *trieNode) insertArray(arr *Array) *trieNode {
|
|
|
|
if arr.Len() == 0 {
|
|
return node
|
|
}
|
|
|
|
switch head := arr.Elem(0).Value.(type) {
|
|
case Var:
|
|
if node.any == nil {
|
|
node.any = newTrieNodeImpl()
|
|
}
|
|
return node.any.insertArray(arr.Slice(1, -1))
|
|
case Null, Boolean, Number, String:
|
|
child, ok := node.scalars.Get(head)
|
|
if !ok {
|
|
child = newTrieNodeImpl()
|
|
node.scalars.Put(head, child)
|
|
}
|
|
return child.insertArray(arr.Slice(1, -1))
|
|
}
|
|
|
|
panic("illegal value")
|
|
}
|
|
|
|
func (node *trieNode) traverse(resolver ValueResolver, tr *trieTraversalResult) error {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
v, err := resolver.Resolve(node.ref)
|
|
if err != nil {
|
|
if IsUnknownValueErr(err) {
|
|
return node.traverseUnknown(resolver, tr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
err = node.undefined.Traverse(resolver, tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
err = node.any.Traverse(resolver, tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = node.traverseValue(resolver, tr, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range node.mappers {
|
|
mapped := node.mappers[i].MapValue(v)
|
|
if !ValueEqual(mapped, v) {
|
|
if err := node.traverseValue(resolver, tr, mapped); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (node *trieNode) traverseValue(resolver ValueResolver, tr *trieTraversalResult, value Value) error {
|
|
|
|
switch value := value.(type) {
|
|
case *Array, Set, Object:
|
|
if node.array != nil {
|
|
if arr, ok := value.(*Array); ok {
|
|
if err := node.array.traverseArray(resolver, tr, arr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if node.scalars.Len() > 0 {
|
|
return node.traverseCollectionMembership(resolver, tr, value)
|
|
}
|
|
|
|
return nil
|
|
|
|
case Null, Boolean, Number, String:
|
|
child, ok := node.scalars.Get(value)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return child.Traverse(resolver, tr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (node *trieNode) traverseCollectionMembership(resolver ValueResolver, tr *trieTraversalResult, collection Value) error {
|
|
checkMember := func(t *Term) error {
|
|
if IsScalar(t.Value) {
|
|
child, _ := node.scalars.Get(t.Value)
|
|
return child.Traverse(resolver, tr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
switch col := collection.(type) {
|
|
case *Array:
|
|
return col.Iter(checkMember)
|
|
case Set:
|
|
return col.Iter(checkMember)
|
|
case Object:
|
|
return col.Iter(func(_, v *Term) error {
|
|
return checkMember(v)
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (node *trieNode) traverseArray(resolver ValueResolver, tr *trieTraversalResult, arr *Array) error {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
if arr.Len() == 0 {
|
|
return node.Traverse(resolver, tr)
|
|
}
|
|
|
|
err := node.any.traverseArray(resolver, tr, arr.Slice(1, -1))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
head := arr.Elem(0).Value
|
|
|
|
if !IsScalar(head) {
|
|
return nil
|
|
}
|
|
|
|
switch head := head.(type) {
|
|
case Null, Boolean, Number, String:
|
|
child, _ := node.scalars.Get(head)
|
|
return child.traverseArray(resolver, tr, arr.Slice(1, -1))
|
|
}
|
|
|
|
panic("illegal value")
|
|
}
|
|
|
|
func (node *trieNode) traverseUnknown(resolver ValueResolver, tr *trieTraversalResult) error {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := node.Traverse(resolver, tr); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := node.undefined.traverseUnknown(resolver, tr); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := node.any.traverseUnknown(resolver, tr); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := node.array.traverseUnknown(resolver, tr); err != nil {
|
|
return err
|
|
}
|
|
|
|
var iterErr error
|
|
node.scalars.Iter(func(_ Value, child *trieNode) bool {
|
|
return child.traverseUnknown(resolver, tr) != nil
|
|
})
|
|
|
|
return iterErr
|
|
}
|
|
|
|
// If term `a` is one of the function's operands, we store a Ref: `args[0]`
|
|
// for the argument number. So for `f(x, y) { x = 10; y = 12 }`, we'll
|
|
// bind `args[0]` and `args[1]` to this rule when called for (x=10) and
|
|
// (y=12) respectively.
|
|
func (i *refindices) eqOperandsToRefAndValue(rule *Rule, args []*Term, a, b Value, constants map[Var]Value) bool {
|
|
switch v := a.(type) {
|
|
case Var:
|
|
// a is a var, but we have not been able to resolve it to a ref, save for later
|
|
if IsConstant(b) {
|
|
constants[v] = b
|
|
}
|
|
|
|
bval, ok := indexValue(b)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if ref := resolveVarToRef(i.rules[rule], args, v); ref != nil {
|
|
i.insert(rule, &refindex{Ref: ref, Value: bval})
|
|
return true
|
|
}
|
|
|
|
case Ref:
|
|
if !i.isValidIndexRef(v) {
|
|
return false
|
|
}
|
|
|
|
if bvar, ok := b.(Var); ok { // cheaper lookup first: constants
|
|
if resolved, ok := constants[bvar]; ok {
|
|
b = resolved
|
|
}
|
|
} else if bval, ok := indexValue(b); ok {
|
|
b = bval
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
i.insert(rule, &refindex{Ref: v, Value: b})
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func indexValue(b Value) (Value, bool) {
|
|
switch b := b.(type) {
|
|
case Null, Boolean, Number, String, Var:
|
|
return b, true
|
|
case *Array:
|
|
stop := false
|
|
first := true
|
|
vis := NewGenericVisitor(func(x any) bool {
|
|
if first {
|
|
first = false
|
|
return false
|
|
}
|
|
switch x.(type) {
|
|
// No nested structures or values that require evaluation (other than var).
|
|
case *Array, Object, Set, *ArrayComprehension, *ObjectComprehension, *SetComprehension, Ref:
|
|
stop = true
|
|
}
|
|
return stop
|
|
})
|
|
vis.Walk(b)
|
|
if !stop {
|
|
return b, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func globDelimiterToString(delim *Term) (string, bool) {
|
|
arr, ok := delim.Value.(*Array)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
var result string
|
|
|
|
if arr.Len() == 0 {
|
|
result = "."
|
|
} else {
|
|
sb := strings.Builder{}
|
|
for i := range arr.Len() {
|
|
term := arr.Elem(i)
|
|
s, ok := term.Value.(String)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
sb.WriteString(string(s))
|
|
}
|
|
result = sb.String()
|
|
}
|
|
|
|
return result, true
|
|
}
|
|
|
|
var globwildcard = VarTerm("$globwildcard")
|
|
|
|
func globPatternToArray(pattern *Term, delim string) *Term {
|
|
|
|
s, ok := pattern.Value.(String)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
parts := splitStringEscaped(string(s), delim)
|
|
arr := make([]*Term, len(parts))
|
|
|
|
for i := range parts {
|
|
if parts[i] == "*" {
|
|
arr[i] = globwildcard
|
|
} else {
|
|
var escaped bool
|
|
for _, c := range parts[i] {
|
|
if c == '\\' {
|
|
escaped = !escaped
|
|
continue
|
|
}
|
|
if !escaped {
|
|
switch c {
|
|
case '[', '?', '{', '*':
|
|
// TODO(tsandall): super glob and character pattern
|
|
// matching not supported yet.
|
|
return nil
|
|
}
|
|
}
|
|
escaped = false
|
|
}
|
|
arr[i] = StringTerm(parts[i])
|
|
}
|
|
}
|
|
|
|
return ArrayTerm(arr...)
|
|
}
|
|
|
|
// splits s on characters in delim except if delim characters have been escaped
|
|
// with reverse solidus.
|
|
func splitStringEscaped(s string, delim string) []string {
|
|
|
|
var last, curr int
|
|
var escaped bool
|
|
var result []string
|
|
|
|
for ; curr < len(s); curr++ {
|
|
if s[curr] == '\\' || escaped {
|
|
escaped = !escaped
|
|
continue
|
|
}
|
|
if strings.ContainsRune(delim, rune(s[curr])) {
|
|
result = append(result, s[last:curr])
|
|
last = curr + 1
|
|
}
|
|
}
|
|
|
|
result = append(result, s[last:])
|
|
|
|
return result
|
|
}
|
|
|
|
func stringSliceToArray(s []string) *Array {
|
|
arr := make([]*Term, len(s))
|
|
for i, v := range s {
|
|
arr[i] = StringTerm(v)
|
|
}
|
|
return NewArray(arr...)
|
|
}
|