Initial QSfera import
This commit is contained in:
+1020
File diff suppressed because it is too large
Load Diff
+3663
File diff suppressed because it is too large
Load Diff
+293
@@ -0,0 +1,293 @@
|
||||
// Copyright 2020 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"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/semver"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities"
|
||||
caps "github.com/open-policy-agent/opa/v1/capabilities"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// VersonIndex contains an index from built-in function name, language feature,
|
||||
// and future rego keyword to version number. During the build, this is used to
|
||||
// create an index of the minimum version required for the built-in/feature/kw.
|
||||
type VersionIndex struct {
|
||||
Builtins map[string]semver.Version `json:"builtins"`
|
||||
Features map[string]semver.Version `json:"features"`
|
||||
Keywords map[string]semver.Version `json:"keywords"`
|
||||
}
|
||||
|
||||
// NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go
|
||||
// and run as part of go:generate. We generate the version index as part of the
|
||||
// build process because it's relatively expensive to build (it takes ~500ms on
|
||||
// my machine) and never changes.
|
||||
//
|
||||
//go:embed version_index.json
|
||||
var versionIndexBs []byte
|
||||
|
||||
// init only on demand, as JSON unmarshalling comes with some cost, and contributes
|
||||
// noise to things like pprof stats
|
||||
var minVersionIndexOnce = sync.OnceValue(func() VersionIndex {
|
||||
var vi VersionIndex
|
||||
if err := json.Unmarshal(versionIndexBs, &vi); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return vi
|
||||
})
|
||||
|
||||
// In the compiler, we used this to check that we're OK working with ref heads.
|
||||
// If this isn't present, we'll fail. This is to ensure that older versions of
|
||||
// OPA can work with policies that we're compiling -- if they don't know ref
|
||||
// heads, they wouldn't be able to parse them.
|
||||
const FeatureRefHeadStringPrefixes = "rule_head_ref_string_prefixes"
|
||||
const FeatureRefHeads = "rule_head_refs"
|
||||
const FeatureRegoV1 = "rego_v1"
|
||||
const FeatureRegoV1Import = "rego_v1_import"
|
||||
const FeatureKeywordsInRefs = "keywords_in_refs"
|
||||
const FeatureTemplateStrings = "template_strings"
|
||||
|
||||
// Features carries the default features supported by this version of OPA.
|
||||
// Use RegisterFeatures to add to them.
|
||||
var Features = []string{
|
||||
FeatureRegoV1,
|
||||
FeatureKeywordsInRefs,
|
||||
FeatureTemplateStrings,
|
||||
}
|
||||
|
||||
// RegisterFeatures lets applications wrapping OPA register features, to be
|
||||
// included in `ast.CapabilitiesForThisVersion()`.
|
||||
func RegisterFeatures(fs ...string) {
|
||||
for i := range fs {
|
||||
if slices.Contains(Features, fs[i]) {
|
||||
continue
|
||||
}
|
||||
Features = append(Features, fs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Capabilities defines a structure containing data that describes the capabilities
|
||||
// or features supported by a particular version of OPA.
|
||||
type Capabilities struct {
|
||||
Builtins []*Builtin `json:"builtins,omitempty"`
|
||||
FutureKeywords []string `json:"future_keywords,omitempty"`
|
||||
WasmABIVersions []WasmABIVersion `json:"wasm_abi_versions,omitempty"`
|
||||
|
||||
// Features is a bit of a mixed bag for checking that an older version of OPA
|
||||
// is able to do what needs to be done.
|
||||
// TODO(sr): find better words ^^
|
||||
Features []string `json:"features,omitempty"`
|
||||
|
||||
// allow_net is an array of hostnames or IP addresses, that an OPA instance is
|
||||
// allowed to connect to.
|
||||
// If omitted, ANY host can be connected to. If empty, NO host can be connected to.
|
||||
// As of now, this only controls fetching remote refs for using JSON Schemas in
|
||||
// the type checker.
|
||||
// TODO(sr): support ports to further restrict connection peers
|
||||
AllowNet []string `json:"allow_net,omitempty"`
|
||||
}
|
||||
|
||||
// WasmABIVersion captures the Wasm ABI version. Its `Minor` version is indicating
|
||||
// backwards-compatible changes.
|
||||
type WasmABIVersion struct {
|
||||
Version int `json:"version"`
|
||||
Minor int `json:"minor_version"`
|
||||
}
|
||||
|
||||
type CapabilitiesOptions struct {
|
||||
regoVersion RegoVersion
|
||||
}
|
||||
|
||||
func newCapabilitiesOptions(opts []CapabilitiesOption) CapabilitiesOptions {
|
||||
co := CapabilitiesOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&co)
|
||||
}
|
||||
return co
|
||||
}
|
||||
|
||||
type CapabilitiesOption func(*CapabilitiesOptions)
|
||||
|
||||
func CapabilitiesRegoVersion(regoVersion RegoVersion) CapabilitiesOption {
|
||||
return func(o *CapabilitiesOptions) {
|
||||
o.regoVersion = regoVersion
|
||||
}
|
||||
}
|
||||
|
||||
// CapabilitiesForThisVersion returns the capabilities of this version of OPA.
|
||||
func CapabilitiesForThisVersion(opts ...CapabilitiesOption) *Capabilities {
|
||||
co := newCapabilitiesOptions(opts)
|
||||
|
||||
f := &Capabilities{}
|
||||
|
||||
for _, vers := range capabilities.ABIVersions() {
|
||||
f.WasmABIVersions = append(f.WasmABIVersions, WasmABIVersion{Version: vers[0], Minor: vers[1]})
|
||||
}
|
||||
|
||||
f.Builtins = make([]*Builtin, len(Builtins))
|
||||
copy(f.Builtins, Builtins)
|
||||
|
||||
slices.SortFunc(f.Builtins, func(a, b *Builtin) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
switch co.regoVersion {
|
||||
case RegoV0, RegoV0CompatV1:
|
||||
for kw := range allFutureKeywords {
|
||||
f.FutureKeywords = append(f.FutureKeywords, kw)
|
||||
}
|
||||
|
||||
f.Features = []string{
|
||||
FeatureRefHeadStringPrefixes,
|
||||
FeatureRefHeads,
|
||||
FeatureRegoV1Import,
|
||||
FeatureRegoV1, // Included in v0 capabilities to allow v1 bundles in --v0-compatible mode
|
||||
FeatureKeywordsInRefs,
|
||||
}
|
||||
default:
|
||||
for kw := range futureKeywords {
|
||||
f.FutureKeywords = append(f.FutureKeywords, kw)
|
||||
}
|
||||
|
||||
f.Features = make([]string, len(Features))
|
||||
copy(f.Features, Features)
|
||||
}
|
||||
|
||||
sort.Strings(f.FutureKeywords)
|
||||
sort.Strings(f.Features)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// LoadCapabilitiesJSON loads a JSON serialized capabilities structure from the reader r.
|
||||
func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) {
|
||||
d := util.NewJSONDecoder(r)
|
||||
var c Capabilities
|
||||
return &c, d.Decode(&c)
|
||||
}
|
||||
|
||||
// LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version.
|
||||
func LoadCapabilitiesVersion(version string) (*Capabilities, error) {
|
||||
cvs, err := LoadCapabilitiesVersions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, cv := range cvs {
|
||||
if cv == version {
|
||||
cont, err := caps.FS.ReadFile(cv + ".json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return LoadCapabilitiesJSON(bytes.NewReader(cont))
|
||||
}
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("no capabilities version found %v", version)
|
||||
}
|
||||
|
||||
// LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file.
|
||||
func LoadCapabilitiesFile(file string) (*Capabilities, error) {
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
return LoadCapabilitiesJSON(fd)
|
||||
}
|
||||
|
||||
// LoadCapabilitiesVersions loads all capabilities versions
|
||||
func LoadCapabilitiesVersions() ([]string, error) {
|
||||
ents, err := caps.FS.ReadDir(".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
capabilitiesVersions := make([]string, 0, len(ents))
|
||||
for _, ent := range ents {
|
||||
capabilitiesVersions = append(capabilitiesVersions, strings.Replace(ent.Name(), ".json", "", 1))
|
||||
}
|
||||
|
||||
slices.SortStableFunc(capabilitiesVersions, semver.Compare)
|
||||
|
||||
return capabilitiesVersions, nil
|
||||
}
|
||||
|
||||
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
|
||||
// the built-ins, features, and keywords in c.
|
||||
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
|
||||
// this is the oldest OPA release that includes capabilities
|
||||
maxVersion := semver.MustParse("0.17.0")
|
||||
minVersionIndex := minVersionIndexOnce()
|
||||
|
||||
for _, bi := range c.Builtins {
|
||||
v, ok := minVersionIndex.Builtins[bi.Name]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, kw := range c.FutureKeywords {
|
||||
v, ok := minVersionIndex.Keywords[kw]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, feat := range c.Features {
|
||||
v, ok := minVersionIndex.Features[feat]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
return maxVersion.String(), true
|
||||
}
|
||||
|
||||
func (c *Capabilities) ContainsFeature(feature string) bool {
|
||||
return slices.Contains(c.Features, feature)
|
||||
}
|
||||
|
||||
func (c *Capabilities) ContainsBuiltin(name string) bool {
|
||||
return slices.ContainsFunc(c.Builtins, func(builtin *Builtin) bool {
|
||||
return builtin.Name == name
|
||||
})
|
||||
}
|
||||
|
||||
// addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name
|
||||
// will be overwritten.
|
||||
func (c *Capabilities) addBuiltinSorted(bi *Builtin) {
|
||||
i := sort.Search(len(c.Builtins), func(x int) bool {
|
||||
return c.Builtins[x].Name >= bi.Name
|
||||
})
|
||||
if i < len(c.Builtins) && bi.Name == c.Builtins[i].Name {
|
||||
c.Builtins[i] = bi
|
||||
return
|
||||
}
|
||||
c.Builtins = append(c.Builtins, nil)
|
||||
copy(c.Builtins[i+1:], c.Builtins[i:])
|
||||
c.Builtins[i] = bi
|
||||
}
|
||||
+1325
File diff suppressed because it is too large
Load Diff
+439
@@ -0,0 +1,439 @@
|
||||
// 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 (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Compare returns an integer indicating whether two AST values are less than,
|
||||
// equal to, or greater than each other.
|
||||
//
|
||||
// If a is less than b, the return value is negative. If a is greater than b,
|
||||
// the return value is positive. If a is equal to b, the return value is zero.
|
||||
//
|
||||
// Different types are never equal to each other. For comparison purposes, types
|
||||
// are sorted as follows:
|
||||
//
|
||||
// nil < Null < Boolean < Number < String < Var < Ref < Array < Object < Set <
|
||||
// ArrayComprehension < ObjectComprehension < SetComprehension < Expr < SomeDecl
|
||||
// < With < Body < Rule < Import < Package < Module.
|
||||
//
|
||||
// Arrays and Refs are equal if and only if both a and b have the same length
|
||||
// and all corresponding elements are equal. If one element is not equal, the
|
||||
// return value is the same as for the first differing element. If all elements
|
||||
// are equal but a and b have different lengths, the shorter is considered less
|
||||
// than the other.
|
||||
//
|
||||
// Objects are considered equal if and only if both a and b have the same sorted
|
||||
// (key, value) pairs and are of the same length. Other comparisons are
|
||||
// consistent but not defined.
|
||||
//
|
||||
// Sets are considered equal if and only if the symmetric difference of a and b
|
||||
// is empty.
|
||||
// Other comparisons are consistent but not defined.
|
||||
func Compare(a, b any) int {
|
||||
|
||||
if t, ok := a.(*Term); ok {
|
||||
if t == nil {
|
||||
a = nil
|
||||
} else {
|
||||
a = t.Value
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := b.(*Term); ok {
|
||||
if t == nil {
|
||||
b = nil
|
||||
} else {
|
||||
b = t.Value
|
||||
}
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if b == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
sortA := sortOrder(a)
|
||||
sortB := sortOrder(b)
|
||||
|
||||
if sortA < sortB {
|
||||
return -1
|
||||
} else if sortB < sortA {
|
||||
return 1
|
||||
}
|
||||
|
||||
switch a := a.(type) {
|
||||
case Null:
|
||||
return 0
|
||||
case Boolean:
|
||||
if a == b.(Boolean) {
|
||||
return 0
|
||||
}
|
||||
if !a {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
case Number:
|
||||
return NumberCompare(a, b.(Number))
|
||||
case String:
|
||||
b := b.(String)
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
if a < b {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
case *TemplateString:
|
||||
b := b.(*TemplateString)
|
||||
return a.Compare(b)
|
||||
case Var:
|
||||
return VarCompare(a, b.(Var))
|
||||
case Ref:
|
||||
return termSliceCompare(a, b.(Ref))
|
||||
case *Array:
|
||||
b := b.(*Array)
|
||||
return termSliceCompare(a.elems, b.elems)
|
||||
case *lazyObj:
|
||||
return Compare(a.force(), b)
|
||||
case *object:
|
||||
if x, ok := b.(*lazyObj); ok {
|
||||
b = x.force()
|
||||
}
|
||||
return a.Compare(b.(*object))
|
||||
case Set:
|
||||
return a.Compare(b.(Set))
|
||||
case *ArrayComprehension:
|
||||
b := b.(*ArrayComprehension)
|
||||
if cmp := Compare(a.Term, b.Term); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return a.Body.Compare(b.Body)
|
||||
case *ObjectComprehension:
|
||||
b := b.(*ObjectComprehension)
|
||||
if cmp := Compare(a.Key, b.Key); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
if cmp := Compare(a.Value, b.Value); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return a.Body.Compare(b.Body)
|
||||
case *SetComprehension:
|
||||
b := b.(*SetComprehension)
|
||||
if cmp := Compare(a.Term, b.Term); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return a.Body.Compare(b.Body)
|
||||
case Call:
|
||||
return termSliceCompare(a, b.(Call))
|
||||
case *Expr:
|
||||
return a.Compare(b.(*Expr))
|
||||
case *SomeDecl:
|
||||
return a.Compare(b.(*SomeDecl))
|
||||
case *Every:
|
||||
return a.Compare(b.(*Every))
|
||||
case *With:
|
||||
return a.Compare(b.(*With))
|
||||
case Body:
|
||||
return a.Compare(b.(Body))
|
||||
case *Head:
|
||||
return a.Compare(b.(*Head))
|
||||
case *Rule:
|
||||
return a.Compare(b.(*Rule))
|
||||
case Args:
|
||||
return termSliceCompare(a, b.(Args))
|
||||
case *Import:
|
||||
return a.Compare(b.(*Import))
|
||||
case *Package:
|
||||
return a.Compare(b.(*Package))
|
||||
case *Annotations:
|
||||
return a.Compare(b.(*Annotations))
|
||||
case *Module:
|
||||
return a.Compare(b.(*Module))
|
||||
}
|
||||
panic(fmt.Sprintf("illegal value: %T", a))
|
||||
}
|
||||
|
||||
type termSlice []*Term
|
||||
|
||||
func (s termSlice) Less(i, j int) bool { return Compare(s[i].Value, s[j].Value) < 0 }
|
||||
func (s termSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s termSlice) Len() int { return len(s) }
|
||||
|
||||
func sortOrder(x any) int {
|
||||
switch x.(type) {
|
||||
case Null:
|
||||
return 0
|
||||
case Boolean:
|
||||
return 1
|
||||
case Number:
|
||||
return 2
|
||||
case String:
|
||||
return 3
|
||||
case *TemplateString:
|
||||
return 4
|
||||
case Var:
|
||||
return 5
|
||||
case Ref:
|
||||
return 6
|
||||
case *Array:
|
||||
return 7
|
||||
case Object:
|
||||
return 8
|
||||
case Set:
|
||||
return 9
|
||||
case *ArrayComprehension:
|
||||
return 10
|
||||
case *ObjectComprehension:
|
||||
return 11
|
||||
case *SetComprehension:
|
||||
return 12
|
||||
case Call:
|
||||
return 13
|
||||
case Args:
|
||||
return 14
|
||||
case *Expr:
|
||||
return 100
|
||||
case *SomeDecl:
|
||||
return 101
|
||||
case *Every:
|
||||
return 102
|
||||
case *With:
|
||||
return 110
|
||||
case *Head:
|
||||
return 120
|
||||
case Body:
|
||||
return 200
|
||||
case *Rule:
|
||||
return 1000
|
||||
case *Import:
|
||||
return 1001
|
||||
case *Package:
|
||||
return 1002
|
||||
case *Annotations:
|
||||
return 1003
|
||||
case *Module:
|
||||
return 10000
|
||||
}
|
||||
panic(fmt.Sprintf("illegal value: %T", x))
|
||||
}
|
||||
|
||||
func importsCompare(a, b []*Import) int {
|
||||
minLen := min(len(b), len(a))
|
||||
for i := range minLen {
|
||||
if cmp := a[i].Compare(b[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
}
|
||||
if len(b) < len(a) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func annotationsCompare(a, b []*Annotations) int {
|
||||
minLen := min(len(b), len(a))
|
||||
for i := range minLen {
|
||||
if cmp := a[i].Compare(b[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
}
|
||||
if len(b) < len(a) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func rulesCompare(a, b []*Rule) int {
|
||||
minLen := min(len(b), len(a))
|
||||
for i := range minLen {
|
||||
if cmp := a[i].Compare(b[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
}
|
||||
if len(b) < len(a) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func termSliceCompare(a, b []*Term) int {
|
||||
minLen := min(len(b), len(a))
|
||||
for i := range minLen {
|
||||
if cmp := Compare(a[i], b[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
} else if len(b) < len(a) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func withSliceCompare(a, b []*With) int {
|
||||
minLen := min(len(b), len(a))
|
||||
for i := range minLen {
|
||||
if cmp := Compare(a[i], b[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
} else if len(b) < len(a) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func VarCompare(a, b Var) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
if a < b {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func TermValueCompare(a, b *Term) int {
|
||||
return a.Value.Compare(b.Value)
|
||||
}
|
||||
|
||||
func TermValueEqual(a, b *Term) bool {
|
||||
return ValueEqual(a.Value, b.Value)
|
||||
}
|
||||
|
||||
func ValueEqual(a, b Value) bool {
|
||||
switch v := a.(type) {
|
||||
case Null:
|
||||
return v.Equal(b)
|
||||
case Boolean:
|
||||
return v.Equal(b)
|
||||
case Number:
|
||||
return v.Equal(b)
|
||||
case String:
|
||||
return v.Equal(b)
|
||||
case Var:
|
||||
return v.Equal(b)
|
||||
case Ref:
|
||||
return v.Equal(b)
|
||||
case *Array:
|
||||
return v.Equal(b)
|
||||
case *TemplateString:
|
||||
return v.Equal(b)
|
||||
}
|
||||
|
||||
return a.Compare(b) == 0
|
||||
}
|
||||
|
||||
func RefCompare(a, b Ref) int {
|
||||
return termSliceCompare(a, b)
|
||||
}
|
||||
|
||||
func RefEqual(a, b Ref) bool {
|
||||
return termSliceEqual(a, b)
|
||||
}
|
||||
|
||||
func NumberCompare(x, y Number) int {
|
||||
xs, ys := string(x), string(y)
|
||||
|
||||
var xIsF, yIsF bool
|
||||
|
||||
// Treat "1" and "1.0", "1.00", etc as "1"
|
||||
if strings.Contains(xs, ".") {
|
||||
if tx := strings.TrimRight(xs, ".0"); tx != xs {
|
||||
// Still a float after trimming?
|
||||
xIsF = strings.Contains(tx, ".")
|
||||
xs = tx
|
||||
}
|
||||
}
|
||||
if strings.Contains(ys, ".") {
|
||||
if ty := strings.TrimRight(ys, ".0"); ty != ys {
|
||||
yIsF = strings.Contains(ty, ".")
|
||||
ys = ty
|
||||
}
|
||||
}
|
||||
if xs == ys {
|
||||
return 0
|
||||
}
|
||||
|
||||
var xi, yi int64
|
||||
var xf, yf float64
|
||||
var xiOK, yiOK, xfOK, yfOK bool
|
||||
|
||||
if xi, xiOK = x.Int64(); xiOK {
|
||||
if yi, yiOK = y.Int64(); yiOK {
|
||||
return cmp.Compare(xi, yi)
|
||||
}
|
||||
}
|
||||
|
||||
if xIsF && yIsF {
|
||||
if xf, xfOK = x.Float64(); xfOK {
|
||||
if yf, yfOK = y.Float64(); yfOK {
|
||||
if xf == yf {
|
||||
return 0
|
||||
}
|
||||
// could still be "equal" depending on precision, so we continue?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var a *big.Rat
|
||||
fa, ok := new(big.Float).SetString(string(x))
|
||||
if !ok {
|
||||
panic("illegal value")
|
||||
}
|
||||
if fa.IsInt() {
|
||||
if i, _ := fa.Int64(); i == 0 {
|
||||
a = new(big.Rat).SetInt64(0)
|
||||
}
|
||||
}
|
||||
if a == nil {
|
||||
a, ok = new(big.Rat).SetString(string(x))
|
||||
if !ok {
|
||||
panic("illegal value")
|
||||
}
|
||||
}
|
||||
|
||||
var b *big.Rat
|
||||
fb, ok := new(big.Float).SetString(string(y))
|
||||
if !ok {
|
||||
panic("illegal value")
|
||||
}
|
||||
if fb.IsInt() {
|
||||
if i, _ := fb.Int64(); i == 0 {
|
||||
b = new(big.Rat).SetInt64(0)
|
||||
}
|
||||
}
|
||||
if b == nil {
|
||||
b, ok = new(big.Rat).SetString(string(y))
|
||||
if !ok {
|
||||
panic("illegal value")
|
||||
}
|
||||
}
|
||||
|
||||
return a.Cmp(b)
|
||||
}
|
||||
+6704
File diff suppressed because it is too large
Load Diff
+63
@@ -0,0 +1,63 @@
|
||||
// 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
|
||||
|
||||
// CompileModules takes a set of Rego modules represented as strings and
|
||||
// compiles them for evaluation. The keys of the map are used as filenames.
|
||||
func CompileModules(modules map[string]string) (*Compiler, error) {
|
||||
return CompileModulesWithOpt(modules, CompileOpts{})
|
||||
}
|
||||
|
||||
// CompileOpts defines a set of options for the compiler.
|
||||
type CompileOpts struct {
|
||||
EnablePrintStatements bool
|
||||
ParserOptions ParserOptions
|
||||
}
|
||||
|
||||
// CompileModulesWithOpt takes a set of Rego modules represented as strings and
|
||||
// compiles them for evaluation. The keys of the map are used as filenames.
|
||||
func CompileModulesWithOpt(modules map[string]string, opts CompileOpts) (*Compiler, error) {
|
||||
|
||||
parsed := make(map[string]*Module, len(modules))
|
||||
|
||||
for f, module := range modules {
|
||||
var pm *Module
|
||||
var err error
|
||||
if pm, err = ParseModuleWithOpts(f, module, opts.ParserOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed[f] = pm
|
||||
}
|
||||
|
||||
compiler := NewCompiler().
|
||||
WithDefaultRegoVersion(opts.ParserOptions.RegoVersion).
|
||||
WithEnablePrintStatements(opts.EnablePrintStatements).
|
||||
WithCapabilities(opts.ParserOptions.Capabilities)
|
||||
compiler.Compile(parsed)
|
||||
|
||||
if compiler.Failed() {
|
||||
return nil, compiler.Errors
|
||||
}
|
||||
|
||||
return compiler, nil
|
||||
}
|
||||
|
||||
// MustCompileModules compiles a set of Rego modules represented as strings. If
|
||||
// the compilation process fails, this function panics.
|
||||
func MustCompileModules(modules map[string]string) *Compiler {
|
||||
return MustCompileModulesWithOpts(modules, CompileOpts{})
|
||||
}
|
||||
|
||||
// MustCompileModulesWithOpts compiles a set of Rego modules represented as strings. If
|
||||
// the compilation process fails, this function panics.
|
||||
func MustCompileModulesWithOpts(modules map[string]string, opts CompileOpts) *Compiler {
|
||||
|
||||
compiler, err := CompileModulesWithOpt(modules, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return compiler
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020 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
|
||||
|
||||
const (
|
||||
compileStageComprehensionIndexBuild = "compile_stage_comprehension_index_build"
|
||||
)
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckPathConflicts returns a set of errors indicating paths that
|
||||
// are in conflict with the result of the provided callable.
|
||||
func CheckPathConflicts(c *Compiler, exists func([]string) (bool, error)) Errors {
|
||||
var errs Errors
|
||||
|
||||
root := c.RuleTree.Child(DefaultRootDocument.Value)
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(c.pathConflictCheckRoots) == 0 || slices.Contains(c.pathConflictCheckRoots, "") {
|
||||
for _, child := range root.Children {
|
||||
errs = append(errs, checkDocumentConflicts(child, exists, nil)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
for _, rootPath := range c.pathConflictCheckRoots {
|
||||
// traverse AST from `path` to go to the new root
|
||||
paths := strings.Split(rootPath, "/")
|
||||
node := root
|
||||
for _, key := range paths {
|
||||
node = node.Child(String(key))
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
// could not find the node from the AST (e.g. `path` is from a data file)
|
||||
// then no conflict is possible
|
||||
continue
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
errs = append(errs, checkDocumentConflicts(child, exists, paths)...)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func checkDocumentConflicts(node *TreeNode, exists func([]string) (bool, error), path []string) Errors {
|
||||
|
||||
switch key := node.Key.(type) {
|
||||
case String:
|
||||
path = append(path, string(key))
|
||||
default: // other key types cannot conflict with data
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(node.Values) > 0 {
|
||||
s := strings.Join(path, "/")
|
||||
if ok, err := exists(path); err != nil {
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflict check for data path %v: %v", s, err.Error())}
|
||||
} else if ok {
|
||||
return Errors{NewError(CompileErr, node.Values[0].Loc(), "conflicting rule for data path %v found", s)}
|
||||
}
|
||||
}
|
||||
|
||||
var errs Errors
|
||||
|
||||
for _, child := range node.Children {
|
||||
errs = append(errs, checkDocumentConflicts(child, exists, path)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 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
|
||||
|
||||
var defaultModuleLoader ModuleLoader
|
||||
|
||||
// DefaultModuleLoader lets you inject an `ast.ModuleLoader` that will
|
||||
// always be used. If another one is provided with the ast package,
|
||||
// they will both be consulted to enrich the set of modules dynamically.
|
||||
func DefaultModuleLoader(ml ModuleLoader) {
|
||||
defaultModuleLoader = ml
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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 declares Rego syntax tree types and also includes a parser and compiler for preparing policies for execution in the policy engine.
|
||||
//
|
||||
// Rego policies are defined using a relatively small set of types: modules, package and import declarations, rules, expressions, and terms. At their core, policies consist of rules that are defined by one or more expressions over documents available to the policy engine. The expressions are defined by intrinsic values (terms) such as strings, objects, variables, etc.
|
||||
//
|
||||
// Rego policies are typically defined in text files and then parsed and compiled by the policy engine at runtime. The parsing stage takes the text or string representation of the policy and converts it into an abstract syntax tree (AST) that consists of the types mentioned above. The AST is organized as follows:
|
||||
//
|
||||
// Module
|
||||
// |
|
||||
// +--- Package (Reference)
|
||||
// |
|
||||
// +--- Imports
|
||||
// | |
|
||||
// | +--- Import (Term)
|
||||
// |
|
||||
// +--- Rules
|
||||
// |
|
||||
// +--- Rule
|
||||
// |
|
||||
// +--- Head
|
||||
// | |
|
||||
// | +--- Name (Variable)
|
||||
// | |
|
||||
// | +--- Key (Term)
|
||||
// | |
|
||||
// | +--- Value (Term)
|
||||
// |
|
||||
// +--- Body
|
||||
// |
|
||||
// +--- Expression (Term | Terms | Variable Declaration)
|
||||
//
|
||||
// At query time, the policy engine expects policies to have been compiled. The compilation stage takes one or more modules and compiles them into a format that the policy engine supports.
|
||||
package ast
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/types"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// TypeEnv contains type info for static analysis such as type checking.
|
||||
type TypeEnv struct {
|
||||
tree *typeTreeNode
|
||||
next *TypeEnv
|
||||
newChecker func() *typeChecker
|
||||
}
|
||||
|
||||
// newTypeEnv returns an empty TypeEnv. The constructor is not exported because
|
||||
// type environments should only be created by the type checker.
|
||||
func newTypeEnv(f func() *typeChecker) *TypeEnv {
|
||||
return &TypeEnv{
|
||||
tree: newTypeTree(),
|
||||
newChecker: f,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the type of x.
|
||||
//
|
||||
// Deprecated: Use GetByValue or GetByRef instead, as they are more efficient.
|
||||
func (env *TypeEnv) Get(x any) types.Type {
|
||||
if term, ok := x.(*Term); ok {
|
||||
x = term.Value
|
||||
}
|
||||
|
||||
if v, ok := x.(Value); ok {
|
||||
return env.GetByValue(v)
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// GetByValue returns the type of v.
|
||||
func (env *TypeEnv) GetByValue(v Value) types.Type {
|
||||
switch x := v.(type) {
|
||||
|
||||
// Scalars.
|
||||
case Null:
|
||||
return types.Nl
|
||||
case Boolean:
|
||||
return types.B
|
||||
case Number:
|
||||
return types.N
|
||||
case String, *TemplateString:
|
||||
return types.S
|
||||
|
||||
// Composites.
|
||||
case *Array:
|
||||
static := make([]types.Type, x.Len())
|
||||
for i := range static {
|
||||
static[i] = env.GetByValue(x.Elem(i).Value)
|
||||
}
|
||||
|
||||
var dynamic types.Type
|
||||
if len(static) == 0 {
|
||||
dynamic = types.A
|
||||
}
|
||||
|
||||
return types.NewArray(static, dynamic)
|
||||
|
||||
case *lazyObj:
|
||||
return env.GetByValue(x.force())
|
||||
case *object:
|
||||
static := []*types.StaticProperty{}
|
||||
var dynamic *types.DynamicProperty
|
||||
|
||||
x.Foreach(func(k, v *Term) {
|
||||
if IsConstant(k.Value) {
|
||||
if kjson, err := JSON(k.Value); err == nil {
|
||||
static = append(static, types.NewStaticProperty(kjson, env.GetByValue(v.Value)))
|
||||
return
|
||||
}
|
||||
}
|
||||
// Can't handle it as a static property, fallback to dynamic
|
||||
dynamic = types.NewDynamicProperty(env.GetByValue(k.Value), env.GetByValue(v.Value))
|
||||
})
|
||||
|
||||
if len(static) == 0 && dynamic == nil {
|
||||
dynamic = types.NewDynamicProperty(types.A, types.A)
|
||||
}
|
||||
|
||||
return types.NewObject(static, dynamic)
|
||||
|
||||
case *set:
|
||||
var tpe types.Type
|
||||
x.Foreach(func(elem *Term) {
|
||||
tpe = types.Or(tpe, env.GetByValue(elem.Value))
|
||||
})
|
||||
if tpe == nil {
|
||||
tpe = types.A
|
||||
}
|
||||
return types.NewSet(tpe)
|
||||
|
||||
// Comprehensions.
|
||||
case *ArrayComprehension:
|
||||
cpy, errs := env.newChecker().CheckBody(env, x.Body)
|
||||
if len(errs) == 0 {
|
||||
return types.NewArray(nil, cpy.GetByValue(x.Term.Value))
|
||||
}
|
||||
return nil
|
||||
case *ObjectComprehension:
|
||||
cpy, errs := env.newChecker().CheckBody(env, x.Body)
|
||||
if len(errs) == 0 {
|
||||
return types.NewObject(nil, types.NewDynamicProperty(cpy.GetByValue(x.Key.Value), cpy.GetByValue(x.Value.Value)))
|
||||
}
|
||||
return nil
|
||||
case *SetComprehension:
|
||||
cpy, errs := env.newChecker().CheckBody(env, x.Body)
|
||||
if len(errs) == 0 {
|
||||
return types.NewSet(cpy.GetByValue(x.Term.Value))
|
||||
}
|
||||
return nil
|
||||
|
||||
// Refs.
|
||||
case Ref:
|
||||
return env.GetByRef(x)
|
||||
|
||||
// Vars.
|
||||
case Var:
|
||||
if node := env.tree.Child(v); node != nil {
|
||||
return node.Value()
|
||||
}
|
||||
if env.next != nil {
|
||||
return env.next.GetByValue(v)
|
||||
}
|
||||
return nil
|
||||
|
||||
// Calls.
|
||||
case Call:
|
||||
return nil
|
||||
}
|
||||
|
||||
return env.Get(v)
|
||||
}
|
||||
|
||||
// GetByRef returns the type of the value referred to by ref.
|
||||
func (env *TypeEnv) GetByRef(ref Ref) types.Type {
|
||||
node := env.tree.Child(ref[0].Value)
|
||||
if node == nil {
|
||||
return env.getRefFallback(ref)
|
||||
}
|
||||
|
||||
return env.getRefRec(node, ref, ref[1:])
|
||||
}
|
||||
|
||||
func (env *TypeEnv) getRefFallback(ref Ref) types.Type {
|
||||
if env.next != nil {
|
||||
return env.next.GetByRef(ref)
|
||||
}
|
||||
|
||||
if RootDocumentNames.Contains(ref[0]) {
|
||||
// types.A is an empty types.Any
|
||||
// this is used to represent a potential non-local reference
|
||||
return types.A
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *TypeEnv) getRefRec(node *typeTreeNode, ref, tail Ref) types.Type {
|
||||
if len(tail) == 0 {
|
||||
return env.getRefRecExtent(node)
|
||||
}
|
||||
|
||||
if node.Leaf() {
|
||||
if node.children.Len() > 0 {
|
||||
if child := node.Child(tail[0].Value); child != nil {
|
||||
return env.getRefRec(child, ref, tail[1:])
|
||||
}
|
||||
}
|
||||
return selectRef(node.Value(), tail)
|
||||
}
|
||||
|
||||
if !IsConstant(tail[0].Value) {
|
||||
return selectRef(env.getRefRecExtent(node), tail)
|
||||
}
|
||||
|
||||
child := node.Child(tail[0].Value)
|
||||
if child == nil {
|
||||
return env.getRefFallback(ref)
|
||||
}
|
||||
|
||||
return env.getRefRec(child, ref, tail[1:])
|
||||
}
|
||||
|
||||
func (env *TypeEnv) getRefRecExtent(node *typeTreeNode) types.Type {
|
||||
|
||||
if node.Leaf() {
|
||||
return node.Value()
|
||||
}
|
||||
|
||||
children := []*types.StaticProperty{}
|
||||
|
||||
node.Children().Iter(func(key Value, child *typeTreeNode) bool {
|
||||
tpe := env.getRefRecExtent(child)
|
||||
|
||||
// NOTE(sr): Converting to Golang-native types here is an extension of what we did
|
||||
// before -- only supporting strings. But since we cannot differentiate sets and arrays
|
||||
// that way, we could reconsider.
|
||||
switch key.(type) {
|
||||
case String, Number, Boolean: // skip anything else
|
||||
propKey, err := JSON(key)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unreachable, ValueToInterface: %w", err))
|
||||
}
|
||||
children = append(children, types.NewStaticProperty(propKey, tpe))
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// TODO(tsandall): for now, these objects can have any dynamic properties
|
||||
// because we don't have schema for base docs. Once schemas are supported
|
||||
// we can improve this.
|
||||
return types.NewObject(children, types.NewDynamicProperty(types.S, types.A))
|
||||
}
|
||||
|
||||
func (env *TypeEnv) wrap() *TypeEnv {
|
||||
cpy := *env
|
||||
cpy.next = env
|
||||
cpy.tree = newTypeTree()
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// typeTreeNode is used to store type information in a tree.
|
||||
type typeTreeNode struct {
|
||||
key Value
|
||||
value types.Type
|
||||
children *util.HasherMap[Value, *typeTreeNode]
|
||||
}
|
||||
|
||||
func newTypeTree() *typeTreeNode {
|
||||
return &typeTreeNode{
|
||||
key: nil,
|
||||
value: nil,
|
||||
children: util.NewHasherMap[Value, *typeTreeNode](ValueEqual),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Child(key Value) *typeTreeNode {
|
||||
value, ok := n.children.Get(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Children() *util.HasherMap[Value, *typeTreeNode] {
|
||||
return n.children
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Get(path Ref) types.Type {
|
||||
curr := n
|
||||
for _, term := range path {
|
||||
child, ok := curr.children.Get(term.Value)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
curr = child
|
||||
}
|
||||
return curr.Value()
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Leaf() bool {
|
||||
return n.value != nil
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) PutOne(key Value, tpe types.Type) {
|
||||
c, ok := n.children.Get(key)
|
||||
|
||||
var child *typeTreeNode
|
||||
if !ok {
|
||||
child = newTypeTree()
|
||||
child.key = key
|
||||
n.children.Put(key, child)
|
||||
} else {
|
||||
child = c
|
||||
}
|
||||
|
||||
child.value = tpe
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Put(path Ref, tpe types.Type) {
|
||||
curr := n
|
||||
for _, term := range path {
|
||||
child, ok := curr.children.Get(term.Value)
|
||||
if !ok {
|
||||
child = newTypeTree()
|
||||
child.key = term.Value
|
||||
curr.children.Put(child.key, child)
|
||||
}
|
||||
|
||||
curr = child
|
||||
}
|
||||
curr.value = tpe
|
||||
}
|
||||
|
||||
// Insert inserts tpe at path in the tree, but also merges the value into any types.Object present along that path.
|
||||
// If a types.Object is inserted, any leafs already present further down the tree are merged into the inserted object.
|
||||
// path must be ground.
|
||||
func (n *typeTreeNode) Insert(path Ref, tpe types.Type, env *TypeEnv) {
|
||||
curr := n
|
||||
for i, term := range path {
|
||||
child, ok := curr.children.Get(term.Value)
|
||||
if !ok {
|
||||
child = newTypeTree()
|
||||
child.key = term.Value
|
||||
curr.children.Put(child.key, child)
|
||||
} else if child.value != nil && i+1 < len(path) {
|
||||
// If child has an object value, merge the new value into it.
|
||||
if o, ok := child.value.(*types.Object); ok {
|
||||
var err error
|
||||
child.value, err = insertIntoObject(o, path[i+1:], tpe, env)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curr = child
|
||||
}
|
||||
|
||||
curr.value = mergeTypes(curr.value, tpe)
|
||||
|
||||
if _, ok := tpe.(*types.Object); ok && curr.children.Len() > 0 {
|
||||
// merge all leafs into the inserted object
|
||||
for p, t := range curr.Leafs() {
|
||||
var err error
|
||||
curr.value, err = insertIntoObject(curr.value.(*types.Object), *p, t, env)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unreachable, insertIntoObject: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mergeTypes merges the types of 'a' and 'b'. If both are sets, their 'of' types are joined with an types.Or.
|
||||
// If both are objects, the key types of their dynamic properties are joined with types.Or:s, and their value types
|
||||
// are recursively merged (using mergeTypes).
|
||||
// If 'a' and 'b' are both objects, and at least one of them have static properties, they are joined
|
||||
// with an types.Or, instead of being merged.
|
||||
// If 'a' is an Any containing an Object, and 'b' is an Object (or vice versa); AND both objects have no
|
||||
// static properties, they are merged.
|
||||
// If 'a' and 'b' are different types, they are joined with an types.Or.
|
||||
func mergeTypes(a, b types.Type) types.Type {
|
||||
if a == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
return a
|
||||
}
|
||||
|
||||
switch a := a.(type) {
|
||||
case *types.Object:
|
||||
if bObj, ok := b.(*types.Object); ok && len(a.StaticProperties()) == 0 && len(bObj.StaticProperties()) == 0 {
|
||||
if len(a.StaticProperties()) > 0 || len(bObj.StaticProperties()) > 0 {
|
||||
return types.Or(a, bObj)
|
||||
}
|
||||
|
||||
aDynProps := a.DynamicProperties()
|
||||
bDynProps := bObj.DynamicProperties()
|
||||
dynProps := types.NewDynamicProperty(
|
||||
types.Or(aDynProps.Key, bDynProps.Key),
|
||||
mergeTypes(aDynProps.Value, bDynProps.Value),
|
||||
)
|
||||
return types.NewObject(nil, dynProps)
|
||||
} else if bAny, ok := b.(types.Any); ok && len(a.StaticProperties()) == 0 {
|
||||
// If a is an object type with no static components ...
|
||||
for _, t := range bAny {
|
||||
if tObj, ok := t.(*types.Object); ok && len(tObj.StaticProperties()) == 0 {
|
||||
// ... and b is a types.Any containing an object with no static components, we merge them.
|
||||
aDynProps := a.DynamicProperties()
|
||||
tDynProps := tObj.DynamicProperties()
|
||||
tDynProps.Key = types.Or(tDynProps.Key, aDynProps.Key)
|
||||
tDynProps.Value = types.Or(tDynProps.Value, aDynProps.Value)
|
||||
return bAny
|
||||
}
|
||||
}
|
||||
}
|
||||
case *types.Set:
|
||||
if bSet, ok := b.(*types.Set); ok {
|
||||
return types.NewSet(types.Or(a.Of(), bSet.Of()))
|
||||
}
|
||||
case types.Any:
|
||||
if _, ok := b.(types.Any); !ok {
|
||||
return mergeTypes(b, a)
|
||||
}
|
||||
}
|
||||
|
||||
return types.Or(a, b)
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) String() string {
|
||||
b := &strings.Builder{}
|
||||
|
||||
key := "-"
|
||||
if k := n.key; k != nil {
|
||||
key = k.String()
|
||||
}
|
||||
|
||||
b.WriteString(key)
|
||||
if v := n.value; v != nil {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(v.String())
|
||||
}
|
||||
|
||||
n.children.Iter(func(_ Value, child *typeTreeNode) bool {
|
||||
b.WriteString("\n\t+ ")
|
||||
b.WriteString(strings.ReplaceAll(child.String(), "\n", "\n\t"))
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func insertIntoObject(o *types.Object, path Ref, tpe types.Type, env *TypeEnv) (*types.Object, error) {
|
||||
if len(path) == 0 {
|
||||
return o, nil
|
||||
}
|
||||
|
||||
key := env.GetByValue(path[0].Value)
|
||||
|
||||
if len(path) == 1 {
|
||||
var dynamicProps *types.DynamicProperty
|
||||
if dp := o.DynamicProperties(); dp != nil {
|
||||
dynamicProps = types.NewDynamicProperty(types.Or(o.DynamicProperties().Key, key), types.Or(o.DynamicProperties().Value, tpe))
|
||||
} else {
|
||||
dynamicProps = types.NewDynamicProperty(key, tpe)
|
||||
}
|
||||
return types.NewObject(o.StaticProperties(), dynamicProps), nil
|
||||
}
|
||||
|
||||
child, err := insertIntoObject(types.NewObject(nil, nil), path[1:], tpe, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dynamicProps *types.DynamicProperty
|
||||
if dp := o.DynamicProperties(); dp != nil {
|
||||
dynamicProps = types.NewDynamicProperty(types.Or(o.DynamicProperties().Key, key), types.Or(o.DynamicProperties().Value, child))
|
||||
} else {
|
||||
dynamicProps = types.NewDynamicProperty(key, child)
|
||||
}
|
||||
return types.NewObject(o.StaticProperties(), dynamicProps), nil
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Leafs() map[*Ref]types.Type {
|
||||
leafs := map[*Ref]types.Type{}
|
||||
n.children.Iter(func(_ Value, v *typeTreeNode) bool {
|
||||
collectLeafs(v, nil, leafs)
|
||||
return false
|
||||
})
|
||||
return leafs
|
||||
}
|
||||
|
||||
func collectLeafs(n *typeTreeNode, path Ref, leafs map[*Ref]types.Type) {
|
||||
nPath := append(path, NewTerm(n.key))
|
||||
if n.Leaf() {
|
||||
npc := nPath // copy of else nPath escapes to heap even if !n.Leaf()
|
||||
leafs[&npc] = n.Value()
|
||||
return
|
||||
}
|
||||
n.children.Iter(func(_ Value, v *typeTreeNode) bool {
|
||||
collectLeafs(v, nPath, leafs)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (n *typeTreeNode) Value() types.Type {
|
||||
return n.value
|
||||
}
|
||||
|
||||
// selectConstant returns the attribute of the type referred to by the term. If
|
||||
// the attribute type cannot be determined, nil is returned.
|
||||
func selectConstant(tpe types.Type, term *Term) types.Type {
|
||||
x, err := JSON(term.Value)
|
||||
if err == nil {
|
||||
return types.Select(tpe, x)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// selectRef returns the type of the nested attribute referred to by ref. If
|
||||
// the attribute type cannot be determined, nil is returned. If the ref
|
||||
// contains vars or refs, then the returned type will be a union of the
|
||||
// possible types.
|
||||
func selectRef(tpe types.Type, ref Ref) types.Type {
|
||||
if tpe == nil || len(ref) == 0 {
|
||||
return tpe
|
||||
}
|
||||
|
||||
head, tail := ref[0], ref[1:]
|
||||
|
||||
switch head.Value.(type) {
|
||||
case Var, Ref, *Array, Object, Set:
|
||||
return selectRef(types.Values(tpe), tail)
|
||||
default:
|
||||
return selectRef(selectConstant(tpe, head), tail)
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors represents a series of errors encountered during parsing, compiling,
|
||||
// etc.
|
||||
type Errors []*Error
|
||||
|
||||
func (e Errors) Error() string {
|
||||
|
||||
if len(e) == 0 {
|
||||
return "no error(s)"
|
||||
}
|
||||
|
||||
if len(e) == 1 {
|
||||
return fmt.Sprintf("1 error occurred: %v", e[0].Error())
|
||||
}
|
||||
|
||||
s := make([]string, len(e))
|
||||
for i, err := range e {
|
||||
s[i] = err.Error()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d errors occurred:\n%s", len(e), strings.Join(s, "\n"))
|
||||
}
|
||||
|
||||
// Sort sorts the error slice by location. If the locations are equal then the
|
||||
// error message is compared.
|
||||
func (e Errors) Sort() {
|
||||
slices.SortFunc(e, func(a, b *Error) int {
|
||||
if cmp := a.Location.Compare(b.Location); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
|
||||
return strings.Compare(a.Error(), b.Error())
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
// ParseErr indicates an unclassified parse error occurred.
|
||||
ParseErr = "rego_parse_error"
|
||||
|
||||
// CompileErr indicates an unclassified compile error occurred.
|
||||
CompileErr = "rego_compile_error"
|
||||
|
||||
// TypeErr indicates a type error was caught.
|
||||
TypeErr = "rego_type_error"
|
||||
|
||||
// UnsafeVarErr indicates an unsafe variable was found during compilation.
|
||||
UnsafeVarErr = "rego_unsafe_var_error"
|
||||
|
||||
// RecursionErr indicates recursion was found during compilation.
|
||||
RecursionErr = "rego_recursion_error"
|
||||
|
||||
// FormatErr indicates an error occurred during formatting.
|
||||
FormatErr = "rego_format_error"
|
||||
)
|
||||
|
||||
// IsError returns true if err is an AST error with code.
|
||||
func IsError(code string, err error) bool {
|
||||
if err, ok := err.(*Error); ok {
|
||||
return err.Code == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrorDetails defines the interface for detailed error messages.
|
||||
type ErrorDetails interface {
|
||||
Lines() []string
|
||||
}
|
||||
|
||||
// Error represents a single error caught during parsing, compiling, etc.
|
||||
type Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Details ErrorDetails `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
|
||||
var prefix string
|
||||
|
||||
if e.Location != nil {
|
||||
|
||||
if len(e.Location.File) > 0 {
|
||||
prefix += e.Location.File + ":" + strconv.Itoa(e.Location.Row)
|
||||
} else {
|
||||
prefix += strconv.Itoa(e.Location.Row) + ":" + strconv.Itoa(e.Location.Col)
|
||||
}
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
if len(prefix) > 0 {
|
||||
sb.WriteString(prefix)
|
||||
sb.WriteString(": ")
|
||||
}
|
||||
|
||||
sb.WriteString(e.Code)
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(e.Message)
|
||||
|
||||
if e.Details != nil {
|
||||
for _, line := range e.Details.Lines() {
|
||||
sb.WriteString("\n\t")
|
||||
sb.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// NewError returns a new Error object.
|
||||
func NewError(code string, loc *Location, f string, a ...any) *Error {
|
||||
return newErrorString(code, loc, fmt.Sprintf(f, a...))
|
||||
}
|
||||
|
||||
func newErrorString(code string, loc *Location, m string) *Error {
|
||||
return &Error{
|
||||
Code: code,
|
||||
Location: loc,
|
||||
Message: m,
|
||||
}
|
||||
}
|
||||
+1177
File diff suppressed because it is too large
Load Diff
+219
@@ -0,0 +1,219 @@
|
||||
// Copyright 2026 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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (node *trieNode) mermaid() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("graph TD\n")
|
||||
nodeCounter := 0
|
||||
nodeIDs := make(map[*trieNode]string)
|
||||
node.mermaidFormat(&sb, &nodeCounter, nodeIDs, "")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (node *trieNode) mermaidFormat(sb *strings.Builder, counter *int, nodeIDs map[*trieNode]string, parentID string) {
|
||||
currentID, exists := nodeIDs[node]
|
||||
if !exists {
|
||||
currentID = fmt.Sprintf("n%d", *counter)
|
||||
*counter++
|
||||
nodeIDs[node] = currentID
|
||||
label := node.mermaidLabel()
|
||||
fmt.Fprintf(sb, " %s[\"%s\"]\n", currentID, label)
|
||||
}
|
||||
|
||||
if parentID != "" {
|
||||
fmt.Fprintf(sb, " %s --> %s\n", parentID, currentID)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
|
||||
if node.undefined != nil {
|
||||
if childID, childExists := nodeIDs[node.undefined]; childExists {
|
||||
fmt.Fprintf(sb, " %s -->|undefined| %s\n", currentID, childID)
|
||||
} else {
|
||||
node.undefined.mermaidFormat(sb, counter, nodeIDs, "")
|
||||
fmt.Fprintf(sb, " %s -->|undefined| %s\n", currentID, nodeIDs[node.undefined])
|
||||
}
|
||||
}
|
||||
|
||||
if node.any != nil {
|
||||
if childID, childExists := nodeIDs[node.any]; childExists {
|
||||
fmt.Fprintf(sb, " %s -->|any| %s\n", currentID, childID)
|
||||
} else {
|
||||
node.any.mermaidFormat(sb, counter, nodeIDs, "")
|
||||
fmt.Fprintf(sb, " %s -->|any| %s\n", currentID, nodeIDs[node.any])
|
||||
}
|
||||
}
|
||||
|
||||
if node.scalars.Len() > 0 {
|
||||
type scalarPair struct {
|
||||
key Value
|
||||
node *trieNode
|
||||
}
|
||||
pairs := make([]scalarPair, 0, node.scalars.Len())
|
||||
node.scalars.Iter(func(key Value, val *trieNode) bool {
|
||||
pairs = append(pairs, scalarPair{key, val})
|
||||
return false
|
||||
})
|
||||
sort.Slice(pairs, func(a, b int) bool {
|
||||
return pairs[a].key.Compare(pairs[b].key) < 0
|
||||
})
|
||||
for _, pair := range pairs {
|
||||
var scalarLabel string
|
||||
if s, ok := pair.key.(String); ok {
|
||||
scalarLabel = string(s)
|
||||
} else {
|
||||
scalarLabel = pair.key.String()
|
||||
}
|
||||
if len(scalarLabel) > 20 {
|
||||
scalarLabel = scalarLabel[:20] + "..."
|
||||
}
|
||||
scalarLabel = mermaidEscape(scalarLabel)
|
||||
if childID, childExists := nodeIDs[pair.node]; childExists {
|
||||
fmt.Fprintf(sb, " %s -->|\"%s\"| %s\n", currentID, scalarLabel, childID)
|
||||
} else {
|
||||
pair.node.mermaidFormat(sb, counter, nodeIDs, "")
|
||||
fmt.Fprintf(sb, " %s -->|\"%s\"| %s\n", currentID, scalarLabel, nodeIDs[pair.node])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if node.array != nil {
|
||||
if childID, childExists := nodeIDs[node.array]; childExists {
|
||||
fmt.Fprintf(sb, " %s -->|array| %s\n", currentID, childID)
|
||||
} else {
|
||||
node.array.mermaidFormat(sb, counter, nodeIDs, "")
|
||||
fmt.Fprintf(sb, " %s -->|array| %s\n", currentID, nodeIDs[node.array])
|
||||
}
|
||||
}
|
||||
|
||||
if node.next != nil {
|
||||
node.next.mermaidFormat(sb, counter, nodeIDs, currentID)
|
||||
}
|
||||
}
|
||||
|
||||
func (node *trieNode) mermaidLabel() string {
|
||||
var parts []string
|
||||
|
||||
if len(node.ref) > 0 {
|
||||
parts = append(parts, node.ref.String())
|
||||
}
|
||||
|
||||
if len(node.rules) > 0 {
|
||||
for _, rn := range node.rules {
|
||||
bodyStr := ""
|
||||
if rn.rule.Body != nil {
|
||||
bodyStr = rn.rule.Body.String()
|
||||
if len(bodyStr) > 50 {
|
||||
bodyStr = bodyStr[:50] + "..."
|
||||
}
|
||||
}
|
||||
bodyStr = mermaidEscape(bodyStr)
|
||||
parts = append(parts, bodyStr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(node.mappers) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d mapper(s)", len(node.mappers)))
|
||||
}
|
||||
if node.multiple {
|
||||
parts = append(parts, "multiple")
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "·"
|
||||
}
|
||||
|
||||
return strings.Join(parts, "<br/>")
|
||||
}
|
||||
|
||||
func mermaidEscape(s string) string {
|
||||
s = strings.ReplaceAll(s, `"`, `"`)
|
||||
return s
|
||||
}
|
||||
|
||||
func (node *trieNode) String() string {
|
||||
var sb strings.Builder
|
||||
node.format(&sb, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (node *trieNode) format(sb *strings.Builder, depth int) {
|
||||
indent := strings.Repeat(" ", depth)
|
||||
|
||||
if len(node.ref) > 0 {
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(node.ref.String())
|
||||
} else if depth == 0 {
|
||||
sb.WriteString("root")
|
||||
}
|
||||
|
||||
if len(node.rules) > 0 {
|
||||
fmt.Fprintf(sb, " [%d rule(s)]", len(node.rules))
|
||||
}
|
||||
if len(node.mappers) > 0 {
|
||||
fmt.Fprintf(sb, " [%d mapper(s)]", len(node.mappers))
|
||||
}
|
||||
if node.value != nil {
|
||||
fmt.Fprintf(sb, " value=%v", node.value)
|
||||
}
|
||||
if node.multiple {
|
||||
sb.WriteString(" [multiple]")
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
|
||||
if node.undefined != nil {
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(" undefined:\n")
|
||||
node.undefined.format(sb, depth+2)
|
||||
}
|
||||
|
||||
if node.any != nil {
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(" any:\n")
|
||||
node.any.format(sb, depth+2)
|
||||
}
|
||||
|
||||
if node.scalars.Len() > 0 {
|
||||
scalars := make([]Value, 0, node.scalars.Len())
|
||||
nodes := make([]*trieNode, 0, node.scalars.Len())
|
||||
node.scalars.Iter(func(key Value, val *trieNode) bool {
|
||||
scalars = append(scalars, key)
|
||||
nodes = append(nodes, val)
|
||||
return false
|
||||
})
|
||||
sort.Slice(scalars, func(a, b int) bool {
|
||||
return scalars[a].Compare(scalars[b]) < 0
|
||||
})
|
||||
for i := range scalars {
|
||||
sb.WriteString(indent)
|
||||
fmt.Fprintf(sb, " %v:\n", scalars[i])
|
||||
for j := range nodes {
|
||||
if ValueEqual(scalars[i], scalars[j]) {
|
||||
nodes[j].format(sb, depth+2)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if node.array != nil {
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(" array:\n")
|
||||
node.array.format(sb, depth+2)
|
||||
}
|
||||
|
||||
if node.next != nil {
|
||||
node.next.format(sb, depth)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+623
@@ -0,0 +1,623 @@
|
||||
// Copyright 2020 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 scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/tokens"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
const bom = 0xFEFF
|
||||
|
||||
// Scanner is used to tokenize an input stream of
|
||||
// Rego source code.
|
||||
type Scanner struct {
|
||||
keywords map[string]tokens.Token
|
||||
bs []byte
|
||||
errors []Error
|
||||
tabs []int
|
||||
offset int
|
||||
row int
|
||||
col int
|
||||
width int
|
||||
curr rune
|
||||
regoV1Compatible bool
|
||||
}
|
||||
|
||||
// Error represents a scanner error.
|
||||
type Error struct {
|
||||
Message string
|
||||
Pos Position
|
||||
}
|
||||
|
||||
// Position represents a point in the scanned source code.
|
||||
type Position struct {
|
||||
Tabs []int // positions of any tabs preceding Col
|
||||
Offset int // start offset in bytes
|
||||
End int // end offset in bytes
|
||||
Row int // line number computed in bytes
|
||||
Col int // column number computed in bytes
|
||||
}
|
||||
|
||||
// New returns an initialized scanner that will scan
|
||||
// through the source code provided by the io.Reader.
|
||||
func New(r io.Reader) (*Scanner, error) {
|
||||
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Scanner{
|
||||
offset: 0,
|
||||
row: 1,
|
||||
col: 0,
|
||||
bs: bs,
|
||||
curr: -1,
|
||||
width: 0,
|
||||
keywords: tokens.Keywords(),
|
||||
tabs: []int{},
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if s.curr == bom {
|
||||
s.next()
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes for the full source
|
||||
// which the scanner has read in.
|
||||
func (s *Scanner) Bytes() []byte {
|
||||
return s.bs
|
||||
}
|
||||
|
||||
// String returns a human readable string of the current scanner state.
|
||||
func (s *Scanner) String() string {
|
||||
return fmt.Sprintf("<curr: %q, offset: %d, len: %d>", s.curr, s.offset, len(s.bs))
|
||||
}
|
||||
|
||||
// Keyword will return a token for the passed in
|
||||
// literal value. If the value is a Rego keyword
|
||||
// then the appropriate token is returned. Everything
|
||||
// else is an Ident.
|
||||
func (s *Scanner) Keyword(lit string) tokens.Token {
|
||||
if tok, ok := s.keywords[lit]; ok {
|
||||
return tok
|
||||
}
|
||||
return tokens.Ident
|
||||
}
|
||||
|
||||
// AddKeyword adds a string -> token mapping to this Scanner instance.
|
||||
func (s *Scanner) AddKeyword(kw string, tok tokens.Token) {
|
||||
s.keywords[kw] = tok
|
||||
|
||||
if tok == tokens.Every {
|
||||
// importing 'every' means also importing 'in'
|
||||
s.keywords["in"] = tokens.In
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) HasKeyword(keywords map[string]tokens.Token) bool {
|
||||
for kw := range s.keywords {
|
||||
if _, ok := keywords[kw]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Scanner) IsKeyword(str string) bool {
|
||||
_, ok := s.keywords[str]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Scanner) SetRegoV1Compatible() {
|
||||
s.regoV1Compatible = true
|
||||
}
|
||||
|
||||
func (s *Scanner) RegoV1Compatible() bool {
|
||||
return s.regoV1Compatible
|
||||
}
|
||||
|
||||
// WithKeywords returns a new copy of the Scanner struct `s`, with the set
|
||||
// of known keywords being that of `s` with `kws` added.
|
||||
func (s *Scanner) WithKeywords(kws map[string]tokens.Token) *Scanner {
|
||||
cpy := *s
|
||||
cpy.keywords = make(map[string]tokens.Token, len(s.keywords)+len(kws))
|
||||
for kw, tok := range s.keywords {
|
||||
cpy.AddKeyword(kw, tok)
|
||||
}
|
||||
for k, t := range kws {
|
||||
cpy.AddKeyword(k, t)
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// WithoutKeywords returns a new copy of the Scanner struct `s`, with the
|
||||
// set of known keywords being that of `s` with `kws` removed.
|
||||
// The previously known keywords are returned for a convenient reset.
|
||||
func (s *Scanner) WithoutKeywords(kws map[string]tokens.Token) (*Scanner, map[string]tokens.Token) {
|
||||
cpy := *s
|
||||
kw := s.keywords
|
||||
cpy.keywords = make(map[string]tokens.Token, len(s.keywords)-len(kws))
|
||||
for kw, tok := range s.keywords {
|
||||
if _, ok := kws[kw]; !ok {
|
||||
cpy.AddKeyword(kw, tok)
|
||||
}
|
||||
}
|
||||
return &cpy, kw
|
||||
}
|
||||
|
||||
type ScanOptions struct {
|
||||
continueTemplateString bool
|
||||
rawTemplateString bool
|
||||
}
|
||||
|
||||
type ScanOption func(*ScanOptions)
|
||||
|
||||
// ContinueTemplateString will continue scanning a template string
|
||||
func ContinueTemplateString(raw bool) ScanOption {
|
||||
return func(opts *ScanOptions) {
|
||||
opts.continueTemplateString = true
|
||||
opts.rawTemplateString = raw
|
||||
}
|
||||
}
|
||||
|
||||
// Scan will increment the scanners position in the source
|
||||
// code until the next token is found. The token, starting position
|
||||
// of the token, string literal, and any errors encountered are
|
||||
// returned. A token will always be returned, the caller must check
|
||||
// for any errors before using the other values.
|
||||
func (s *Scanner) Scan(opts ...ScanOption) (tokens.Token, Position, string, []Error) {
|
||||
scanOpts := &ScanOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(scanOpts)
|
||||
}
|
||||
|
||||
pos := Position{Offset: s.offset - s.width, Row: s.row, Col: s.col, Tabs: s.tabs}
|
||||
var tok tokens.Token
|
||||
var lit string
|
||||
if scanOpts.continueTemplateString {
|
||||
if scanOpts.rawTemplateString {
|
||||
lit, tok = s.scanRawTemplateString()
|
||||
} else {
|
||||
lit, tok = s.scanTemplateString()
|
||||
}
|
||||
} else if s.isWhitespace() {
|
||||
// string(rune) is an unnecessary heap allocation in this case as we know all
|
||||
// the possible whitespace values, and can simply translate to string ourselves
|
||||
switch s.curr {
|
||||
case ' ':
|
||||
lit = " "
|
||||
case '\t':
|
||||
lit = "\t"
|
||||
case '\n':
|
||||
lit = "\n"
|
||||
case '\r':
|
||||
lit = "\r"
|
||||
default:
|
||||
// unreachable unless isWhitespace changes
|
||||
lit = string(s.curr)
|
||||
}
|
||||
s.next()
|
||||
tok = tokens.Whitespace
|
||||
} else if isLetter(s.curr) {
|
||||
lit = s.scanIdentifier()
|
||||
tok = s.Keyword(lit)
|
||||
} else if isDecimal(s.curr) {
|
||||
lit = s.scanNumber()
|
||||
tok = tokens.Number
|
||||
} else {
|
||||
ch := s.curr
|
||||
s.next()
|
||||
switch ch {
|
||||
case -1:
|
||||
tok = tokens.EOF
|
||||
case '#':
|
||||
lit = s.scanComment()
|
||||
tok = tokens.Comment
|
||||
case '"':
|
||||
lit = s.scanString()
|
||||
tok = tokens.String
|
||||
case '`':
|
||||
lit = s.scanRawString()
|
||||
tok = tokens.String
|
||||
case '[':
|
||||
tok = tokens.LBrack
|
||||
case ']':
|
||||
tok = tokens.RBrack
|
||||
case '{':
|
||||
tok = tokens.LBrace
|
||||
case '}':
|
||||
tok = tokens.RBrace
|
||||
case '(':
|
||||
tok = tokens.LParen
|
||||
case ')':
|
||||
tok = tokens.RParen
|
||||
case ',':
|
||||
tok = tokens.Comma
|
||||
case ':':
|
||||
if s.curr == '=' {
|
||||
s.next()
|
||||
tok = tokens.Assign
|
||||
} else {
|
||||
tok = tokens.Colon
|
||||
}
|
||||
case '+':
|
||||
tok = tokens.Add
|
||||
case '-':
|
||||
tok = tokens.Sub
|
||||
case '*':
|
||||
tok = tokens.Mul
|
||||
case '/':
|
||||
tok = tokens.Quo
|
||||
case '%':
|
||||
tok = tokens.Rem
|
||||
case '&':
|
||||
tok = tokens.And
|
||||
case '|':
|
||||
tok = tokens.Or
|
||||
case '=':
|
||||
if s.curr == '=' {
|
||||
s.next()
|
||||
tok = tokens.Equal
|
||||
} else {
|
||||
tok = tokens.Unify
|
||||
}
|
||||
case '>':
|
||||
if s.curr == '=' {
|
||||
s.next()
|
||||
tok = tokens.Gte
|
||||
} else {
|
||||
tok = tokens.Gt
|
||||
}
|
||||
case '<':
|
||||
if s.curr == '=' {
|
||||
s.next()
|
||||
tok = tokens.Lte
|
||||
} else {
|
||||
tok = tokens.Lt
|
||||
}
|
||||
case '!':
|
||||
if s.curr == '=' {
|
||||
s.next()
|
||||
tok = tokens.Neq
|
||||
} else {
|
||||
s.error("illegal ! character")
|
||||
}
|
||||
case ';':
|
||||
tok = tokens.Semicolon
|
||||
case '.':
|
||||
tok = tokens.Dot
|
||||
case '$':
|
||||
switch s.curr {
|
||||
case '`':
|
||||
s.next()
|
||||
lit, tok = s.scanRawTemplateString()
|
||||
case '"':
|
||||
s.next()
|
||||
lit, tok = s.scanTemplateString()
|
||||
default:
|
||||
s.error("illegal $ character")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos.End = s.offset - s.width
|
||||
errs := s.errors
|
||||
s.errors = nil
|
||||
|
||||
return tok, pos, lit, errs
|
||||
}
|
||||
|
||||
func (s *Scanner) scanIdentifier() string {
|
||||
start := s.offset - 1
|
||||
for isLetter(s.curr) || isDigit(s.curr) {
|
||||
s.next()
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanNumber() string {
|
||||
|
||||
start := s.offset - 1
|
||||
|
||||
if s.curr != '.' {
|
||||
for isDecimal(s.curr) {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
if s.curr == '.' {
|
||||
s.next()
|
||||
var found bool
|
||||
for isDecimal(s.curr) {
|
||||
s.next()
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
s.error("expected fraction")
|
||||
}
|
||||
}
|
||||
|
||||
if lower(s.curr) == 'e' {
|
||||
s.next()
|
||||
if s.curr == '+' || s.curr == '-' {
|
||||
s.next()
|
||||
}
|
||||
var found bool
|
||||
for isDecimal(s.curr) {
|
||||
s.next()
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
s.error("expected exponent")
|
||||
}
|
||||
}
|
||||
|
||||
// Scan any digits following the decimals to get the
|
||||
// entire invalid number/identifier.
|
||||
// Example: 0a2b should be a single invalid number "0a2b"
|
||||
// rather than a number "0", followed by identifier "a2b".
|
||||
if isLetter(s.curr) {
|
||||
s.error("illegal number format")
|
||||
for isLetter(s.curr) || isDigit(s.curr) {
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanString() string {
|
||||
start := s.literalStart()
|
||||
for {
|
||||
ch := s.curr
|
||||
|
||||
if ch == '\n' || ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if ch == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch s.curr {
|
||||
case '\\', '"', '/', 'b', 'f', 'n', 'r', 't':
|
||||
s.next()
|
||||
case 'u':
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
default:
|
||||
s.error("illegal escape sequence")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanRawString() string {
|
||||
start := s.literalStart()
|
||||
for {
|
||||
ch := s.curr
|
||||
s.next()
|
||||
if ch == '`' {
|
||||
break
|
||||
} else if ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1])
|
||||
}
|
||||
|
||||
func (s *Scanner) scanTemplateString() (string, tokens.Token) {
|
||||
tok := tokens.TemplateStringPart
|
||||
start := s.literalStart()
|
||||
var escapes []int
|
||||
for {
|
||||
ch := s.curr
|
||||
|
||||
if ch == '\n' || ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if ch == '"' {
|
||||
tok = tokens.TemplateStringEnd
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch s.curr {
|
||||
case '\\', '"', '/', 'b', 'f', 'n', 'r', 't':
|
||||
s.next()
|
||||
case '{':
|
||||
escapes = append(escapes, s.offset-1)
|
||||
s.next()
|
||||
case 'u':
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
s.next()
|
||||
default:
|
||||
s.error("illegal escape sequence")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lazily remove escapes to not unnecessarily allocate a new byte slice
|
||||
if len(escapes) > 0 {
|
||||
return util.ByteSliceToString(removeEscapes(s, escapes, start)), tok
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1]), tok
|
||||
}
|
||||
|
||||
func (s *Scanner) scanRawTemplateString() (string, tokens.Token) {
|
||||
tok := tokens.RawTemplateStringPart
|
||||
start := s.literalStart()
|
||||
var escapes []int
|
||||
for {
|
||||
ch := s.curr
|
||||
|
||||
if ch < 0 {
|
||||
s.error("non-terminated string")
|
||||
break
|
||||
}
|
||||
|
||||
s.next()
|
||||
|
||||
if ch == '`' {
|
||||
tok = tokens.RawTemplateStringEnd
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '{' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch s.curr {
|
||||
case '{':
|
||||
escapes = append(escapes, s.offset-1)
|
||||
s.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lazily remove escapes to not unnecessarily allocate a new byte slice
|
||||
if len(escapes) > 0 {
|
||||
return util.ByteSliceToString(removeEscapes(s, escapes, start)), tok
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start : s.offset-1]), tok
|
||||
}
|
||||
|
||||
func removeEscapes(s *Scanner, escapes []int, start int) []byte {
|
||||
from := start
|
||||
bs := make([]byte, 0, s.offset-start-len(escapes))
|
||||
|
||||
for _, escape := range escapes {
|
||||
// Append the bytes before the escape sequence.
|
||||
if escape > from {
|
||||
bs = append(bs, s.bs[from:escape-1]...)
|
||||
}
|
||||
// Skip the escape character.
|
||||
from = escape
|
||||
}
|
||||
|
||||
// Append the remaining bytes after the last escape sequence.
|
||||
if from < s.offset-1 {
|
||||
bs = append(bs, s.bs[from:s.offset-1]...)
|
||||
}
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
func (s *Scanner) scanComment() string {
|
||||
start := s.literalStart()
|
||||
for s.curr != '\n' && s.curr != -1 {
|
||||
s.next()
|
||||
}
|
||||
end := s.offset - 1
|
||||
// Trim carriage returns that precede the newline
|
||||
if s.offset > 1 && s.bs[s.offset-2] == '\r' {
|
||||
end -= 1
|
||||
}
|
||||
|
||||
return util.ByteSliceToString(s.bs[start:end])
|
||||
}
|
||||
|
||||
func (s *Scanner) next() {
|
||||
|
||||
if s.offset >= len(s.bs) {
|
||||
s.curr = -1
|
||||
s.offset = len(s.bs) + 1
|
||||
return
|
||||
}
|
||||
|
||||
s.curr = rune(s.bs[s.offset])
|
||||
s.width = 1
|
||||
|
||||
if s.curr == 0 {
|
||||
s.error("illegal null character")
|
||||
} else if s.curr >= utf8.RuneSelf {
|
||||
s.curr, s.width = utf8.DecodeRune(s.bs[s.offset:])
|
||||
if s.curr == utf8.RuneError && s.width == 1 {
|
||||
s.error("illegal utf-8 character")
|
||||
} else if s.curr == bom && s.offset > 0 {
|
||||
s.error("illegal byte-order mark")
|
||||
}
|
||||
}
|
||||
|
||||
s.offset += s.width
|
||||
|
||||
if s.curr == '\n' {
|
||||
s.row++
|
||||
s.col = 0
|
||||
s.tabs = s.tabs[:0]
|
||||
} else {
|
||||
s.col++
|
||||
if s.curr == '\t' {
|
||||
s.tabs = append(s.tabs, s.col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) literalStart() int {
|
||||
// The current offset is at the first character past the literal delimiter (#, ", `, etc.)
|
||||
// Need to subtract width of first character (plus one for the delimiter).
|
||||
return s.offset - (s.width + 1)
|
||||
}
|
||||
|
||||
// From the Go scanner (src/go/scanner/scanner.go)
|
||||
|
||||
func isLetter(ch rune) bool {
|
||||
return 'a' <= lower(ch) && lower(ch) <= 'z' || ch == '_'
|
||||
}
|
||||
|
||||
func isDigit(ch rune) bool {
|
||||
return isDecimal(ch) || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
|
||||
}
|
||||
|
||||
func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' }
|
||||
|
||||
func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter
|
||||
|
||||
func (s *Scanner) isWhitespace() bool {
|
||||
return s.curr == ' ' || s.curr == '\t' || s.curr == '\n' || s.curr == '\r'
|
||||
}
|
||||
|
||||
func (s *Scanner) error(reason string) {
|
||||
s.errors = append(s.errors, Error{Pos: Position{
|
||||
Offset: s.offset,
|
||||
Row: s.row,
|
||||
Col: s.col,
|
||||
}, Message: reason})
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2020 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 tokens
|
||||
|
||||
import "maps"
|
||||
|
||||
// Token represents a single Rego source code token
|
||||
// for use by the Parser.
|
||||
type Token uint8
|
||||
|
||||
func (t Token) String() string {
|
||||
if int(t) >= len(strings) {
|
||||
return "unknown"
|
||||
}
|
||||
return strings[t]
|
||||
}
|
||||
|
||||
// All tokens must be defined here
|
||||
const (
|
||||
Illegal Token = iota
|
||||
EOF
|
||||
Whitespace
|
||||
Ident
|
||||
Comment
|
||||
|
||||
Package
|
||||
Import
|
||||
As
|
||||
Default
|
||||
Else
|
||||
Not
|
||||
Some
|
||||
With
|
||||
Null
|
||||
True
|
||||
False
|
||||
|
||||
Number
|
||||
String
|
||||
TemplateStringPart
|
||||
TemplateStringEnd
|
||||
RawTemplateStringPart
|
||||
RawTemplateStringEnd
|
||||
|
||||
LBrack
|
||||
RBrack
|
||||
LBrace
|
||||
RBrace
|
||||
LParen
|
||||
RParen
|
||||
Comma
|
||||
Colon
|
||||
|
||||
Add
|
||||
Sub
|
||||
Mul
|
||||
Quo
|
||||
Rem
|
||||
And
|
||||
Or
|
||||
Unify
|
||||
Equal
|
||||
Assign
|
||||
In
|
||||
Neq
|
||||
Gt
|
||||
Lt
|
||||
Gte
|
||||
Lte
|
||||
Dot
|
||||
Semicolon
|
||||
Dollar
|
||||
|
||||
Every
|
||||
Contains
|
||||
If
|
||||
)
|
||||
|
||||
var strings = [...]string{
|
||||
Illegal: "illegal",
|
||||
EOF: "eof",
|
||||
Whitespace: "whitespace",
|
||||
Comment: "comment",
|
||||
Ident: "identifier",
|
||||
Package: "package",
|
||||
Import: "import",
|
||||
As: "as",
|
||||
Default: "default",
|
||||
Else: "else",
|
||||
Not: "not",
|
||||
Some: "some",
|
||||
With: "with",
|
||||
Null: "null",
|
||||
True: "true",
|
||||
False: "false",
|
||||
Number: "number",
|
||||
String: "string",
|
||||
TemplateStringPart: "template-string-part",
|
||||
TemplateStringEnd: "template-string-end",
|
||||
RawTemplateStringPart: "raw-template-string-part",
|
||||
RawTemplateStringEnd: "raw-template-string-end",
|
||||
LBrack: "[",
|
||||
RBrack: "]",
|
||||
LBrace: "{",
|
||||
RBrace: "}",
|
||||
LParen: "(",
|
||||
RParen: ")",
|
||||
Comma: ",",
|
||||
Colon: ":",
|
||||
Add: "plus",
|
||||
Sub: "minus",
|
||||
Mul: "mul",
|
||||
Quo: "div",
|
||||
Rem: "rem",
|
||||
And: "and",
|
||||
Or: "or",
|
||||
Unify: "eq",
|
||||
Equal: "equal",
|
||||
Assign: "assign",
|
||||
In: "in",
|
||||
Neq: "neq",
|
||||
Gt: "gt",
|
||||
Lt: "lt",
|
||||
Gte: "gte",
|
||||
Lte: "lte",
|
||||
Dot: ".",
|
||||
Semicolon: ";",
|
||||
Dollar: "dollar",
|
||||
Every: "every",
|
||||
Contains: "contains",
|
||||
If: "if",
|
||||
}
|
||||
|
||||
var keywords = map[string]Token{
|
||||
"package": Package,
|
||||
"import": Import,
|
||||
"as": As,
|
||||
"default": Default,
|
||||
"else": Else,
|
||||
"not": Not,
|
||||
"some": Some,
|
||||
"with": With,
|
||||
"null": Null,
|
||||
"true": True,
|
||||
"false": False,
|
||||
}
|
||||
|
||||
// Keywords returns a copy of the default string -> Token keyword map.
|
||||
func Keywords() map[string]Token {
|
||||
return maps.Clone(keywords)
|
||||
}
|
||||
|
||||
// IsKeyword returns if a token is a keyword
|
||||
func IsKeyword(tok Token) bool {
|
||||
_, ok := keywords[strings[tok]]
|
||||
return ok
|
||||
}
|
||||
|
||||
func KeywordFor(tok Token) string {
|
||||
return strings[tok]
|
||||
}
|
||||
+1902
File diff suppressed because it is too large
Load Diff
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// This package provides options for JSON marshalling of AST nodes, and location
|
||||
// data in particular. Since location data occupies a significant portion of the
|
||||
// AST when included, it is excluded by default. The options provided here allow
|
||||
// changing that behavior — either for all nodes or for specific types. Since
|
||||
// JSONMarshaller implementations have access only to the node being marshaled,
|
||||
// our options are to either attach these settings to *all* nodes in the AST, or
|
||||
// to provide them via global state. The former is perhaps a little more elegant,
|
||||
// and is what we went with initially. The cost of attaching these settings to
|
||||
// every node however turned out to be non-negligible, and given that the number
|
||||
// of users who have an interest in AST serialization are likely to be few, we
|
||||
// have since switched to using global state, as provided here. Note that this
|
||||
// is mostly to provide an equivalent feature to what we had before, should
|
||||
// anyone depend on that. Users who need fine-grained control over AST
|
||||
// serialization are recommended to use external libraries for that purpose,
|
||||
// such as `github.com/json-iterator/go`.
|
||||
package json
|
||||
|
||||
import "sync"
|
||||
|
||||
// Options defines the options for JSON operations,
|
||||
// currently only marshaling can be configured
|
||||
type Options struct {
|
||||
MarshalOptions MarshalOptions
|
||||
}
|
||||
|
||||
// MarshalOptions defines the options for JSON marshaling,
|
||||
// currently only toggling the marshaling of location information is supported
|
||||
type MarshalOptions struct {
|
||||
// IncludeLocation toggles the marshaling of location information
|
||||
IncludeLocation NodeToggle
|
||||
// IncludeLocationText additionally/optionally includes the text of the location
|
||||
IncludeLocationText bool
|
||||
// ExcludeLocationFile additionally/optionally excludes the file of the location
|
||||
// Note that this is inverted (i.e. not "include" as the default needs to remain false)
|
||||
ExcludeLocationFile bool
|
||||
}
|
||||
|
||||
// NodeToggle is a generic struct to allow the toggling of
|
||||
// settings for different ast node types
|
||||
type NodeToggle struct {
|
||||
Term bool
|
||||
Package bool
|
||||
Comment bool
|
||||
Import bool
|
||||
Rule bool
|
||||
Head bool
|
||||
Expr bool
|
||||
SomeDecl bool
|
||||
Every bool
|
||||
With bool
|
||||
Annotations bool
|
||||
AnnotationsRef bool
|
||||
}
|
||||
|
||||
// configuredJSONOptions synchronizes access to the global JSON options
|
||||
type configuredJSONOptions struct {
|
||||
options Options
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
var options = &configuredJSONOptions{
|
||||
options: Defaults(),
|
||||
}
|
||||
|
||||
// SetOptions sets the global options for marshalling AST nodes to JSON
|
||||
func SetOptions(opts Options) {
|
||||
options.lock.Lock()
|
||||
defer options.lock.Unlock()
|
||||
options.options = opts
|
||||
}
|
||||
|
||||
// GetOptions returns (a copy of) the global options for marshalling AST nodes to JSON
|
||||
func GetOptions() Options {
|
||||
options.lock.RLock()
|
||||
defer options.lock.RUnlock()
|
||||
return options.options
|
||||
}
|
||||
|
||||
// Defaults returns the default JSON options, which is to exclude location
|
||||
// information in serialized JSON AST.
|
||||
func Defaults() Options {
|
||||
return Options{
|
||||
MarshalOptions: MarshalOptions{
|
||||
IncludeLocation: NodeToggle{
|
||||
Term: false,
|
||||
Package: false,
|
||||
Comment: false,
|
||||
Import: false,
|
||||
Rule: false,
|
||||
Head: false,
|
||||
Expr: false,
|
||||
SomeDecl: false,
|
||||
Every: false,
|
||||
With: false,
|
||||
Annotations: false,
|
||||
AnnotationsRef: false,
|
||||
},
|
||||
IncludeLocationText: false,
|
||||
ExcludeLocationFile: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
// Package location defines locations in Rego source code.
|
||||
package location
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// Location records a position in source code
|
||||
type Location struct {
|
||||
Text []byte `json:"-"` // The original text fragment from the source.
|
||||
File string `json:"file"` // The name of the source file (which may be empty).
|
||||
Row int `json:"row"` // The line in the source.
|
||||
Col int `json:"col"` // The column in the row.
|
||||
Offset int `json:"-"` // The byte offset for the location in the source.
|
||||
|
||||
Tabs []int `json:"-"` // The column offsets of tabs in the source.
|
||||
}
|
||||
|
||||
// NewLocation returns a new Location object.
|
||||
func NewLocation(text []byte, file string, row int, col int) *Location {
|
||||
return &Location{Text: text, File: file, Row: row, Col: col}
|
||||
}
|
||||
|
||||
// Equal checks if two locations are equal to each other.
|
||||
func (loc *Location) Equal(other *Location) bool {
|
||||
return loc.File == other.File &&
|
||||
loc.Row == other.Row &&
|
||||
loc.Col == other.Col &&
|
||||
bytes.Equal(loc.Text, other.Text)
|
||||
}
|
||||
|
||||
// Errorf returns a new error value with a message formatted to include the location
|
||||
// info (e.g., line, column, filename, etc.)
|
||||
func (loc *Location) Errorf(f string, a ...any) error {
|
||||
return errors.New(loc.Format(f, a...))
|
||||
}
|
||||
|
||||
// Wrapf returns a new error value that wraps an existing error with a message formatted
|
||||
// to include the location info (e.g., line, column, filename, etc.)
|
||||
func (loc *Location) Wrapf(err error, f string, a ...any) error {
|
||||
return fmt.Errorf(loc.Format(f, a...)+": %w", err)
|
||||
}
|
||||
|
||||
// Format returns a formatted string prefixed with the location information.
|
||||
func (loc *Location) Format(f string, a ...any) string {
|
||||
if len(loc.File) > 0 {
|
||||
f = fmt.Sprintf("%v:%v: %v", loc.File, loc.Row, f)
|
||||
} else {
|
||||
f = fmt.Sprintf("%v:%v: %v", loc.Row, loc.Col, f)
|
||||
}
|
||||
return fmt.Sprintf(f, a...)
|
||||
}
|
||||
|
||||
func (loc *Location) String() string {
|
||||
buf, _ := loc.AppendText(make([]byte, 0, loc.StringLength()))
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
|
||||
func (loc *Location) AppendText(buf []byte) ([]byte, error) {
|
||||
if loc != nil {
|
||||
switch {
|
||||
case len(loc.File) > 0:
|
||||
buf = util.AppendInt(append(append(buf, loc.File...), ':'), loc.Row)
|
||||
case len(loc.Text) > 0:
|
||||
buf = append(buf, loc.Text...)
|
||||
default:
|
||||
buf = util.AppendInt(append(util.AppendInt(buf, loc.Row), ':'), loc.Col)
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (loc *Location) StringLength() (n int) {
|
||||
if loc != nil {
|
||||
if l := len(loc.File); l > 0 {
|
||||
n = l + 1 + util.NumDigitsInt(loc.Row)
|
||||
} else if l := len(loc.Text); l > 0 {
|
||||
n = l
|
||||
} else {
|
||||
n = util.NumDigitsInt(loc.Row) + 1 + util.NumDigitsInt(loc.Col)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Compare returns -1, 0, or 1 to indicate if this loc is less than, equal to,
|
||||
// or greater than the other. Comparison is performed on the file, row, and
|
||||
// column of the Location (but not on the text.) Nil locations are greater than
|
||||
// non-nil locations.
|
||||
func (loc *Location) Compare(other *Location) int {
|
||||
if loc == other {
|
||||
return 0
|
||||
} else if loc == nil {
|
||||
return 1
|
||||
} else if other == nil {
|
||||
return -1
|
||||
} else if loc.File < other.File {
|
||||
return -1
|
||||
} else if loc.File > other.File {
|
||||
return 1
|
||||
} else if loc.Row < other.Row {
|
||||
return -1
|
||||
} else if loc.Row > other.Row {
|
||||
return 1
|
||||
} else if loc.Col < other.Col {
|
||||
return -1
|
||||
} else if loc.Col > other.Col {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (loc *Location) MarshalJSON() ([]byte, error) {
|
||||
// structs are used here to preserve the field ordering of the original Location struct
|
||||
jsonOptions := astJSON.GetOptions().MarshalOptions
|
||||
if jsonOptions.ExcludeLocationFile {
|
||||
data := struct {
|
||||
Row int `json:"row"`
|
||||
Col int `json:"col"`
|
||||
Text []byte `json:"text,omitempty"`
|
||||
}{
|
||||
Row: loc.Row,
|
||||
Col: loc.Col,
|
||||
}
|
||||
|
||||
if jsonOptions.IncludeLocationText {
|
||||
data.Text = loc.Text
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
File string `json:"file"`
|
||||
Row int `json:"row"`
|
||||
Col int `json:"col"`
|
||||
Text []byte `json:"text,omitempty"`
|
||||
}{
|
||||
Row: loc.Row,
|
||||
Col: loc.Col,
|
||||
File: loc.File,
|
||||
}
|
||||
|
||||
if jsonOptions.IncludeLocationText {
|
||||
data.Text = loc.Text
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// ValueMap represents a key/value map between AST term values. Any type of term
|
||||
// can be used as a key in the map.
|
||||
type ValueMap struct {
|
||||
hashMap *util.TypedHashMap[Value, Value]
|
||||
}
|
||||
|
||||
// NewValueMap returns a new ValueMap.
|
||||
func NewValueMap() *ValueMap {
|
||||
return &ValueMap{
|
||||
hashMap: util.NewTypedHashMap(ValueEqual, ValueEqual, Value.Hash, Value.Hash, nil),
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON provides a custom marshaller for the ValueMap which
|
||||
// will include the key, value, and value type.
|
||||
func (vs *ValueMap) MarshalJSON() ([]byte, error) {
|
||||
var tmp []map[string]any
|
||||
vs.Iter(func(k Value, v Value) bool {
|
||||
tmp = append(tmp, map[string]any{
|
||||
"name": k.String(),
|
||||
"type": ValueName(v),
|
||||
"value": v,
|
||||
})
|
||||
return false
|
||||
})
|
||||
return json.Marshal(tmp)
|
||||
}
|
||||
|
||||
// Equal returns true if this ValueMap equals the other.
|
||||
func (vs *ValueMap) Equal(other *ValueMap) bool {
|
||||
if vs == nil {
|
||||
return other == nil || other.Len() == 0
|
||||
}
|
||||
if other == nil {
|
||||
return vs.Len() == 0
|
||||
}
|
||||
return vs.hashMap.Equal(other.hashMap)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the map.
|
||||
func (vs *ValueMap) Len() int {
|
||||
if vs == nil {
|
||||
return 0
|
||||
}
|
||||
return vs.hashMap.Len()
|
||||
}
|
||||
|
||||
// Get returns the value in the map for k.
|
||||
func (vs *ValueMap) Get(k Value) Value {
|
||||
if vs != nil {
|
||||
if v, ok := vs.hashMap.Get(k); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hash returns a hash code for this ValueMap.
|
||||
func (vs *ValueMap) Hash() int {
|
||||
if vs == nil {
|
||||
return 0
|
||||
}
|
||||
return vs.hashMap.Hash()
|
||||
}
|
||||
|
||||
// Iter calls the iter function for each key/value pair in the map. If the iter
|
||||
// function returns true, iteration stops.
|
||||
func (vs *ValueMap) Iter(iter func(Value, Value) bool) bool {
|
||||
if vs == nil {
|
||||
return false
|
||||
}
|
||||
return vs.hashMap.Iter(iter)
|
||||
}
|
||||
|
||||
// Put inserts a key k into the map with value v.
|
||||
func (vs *ValueMap) Put(k, v Value) {
|
||||
if vs == nil {
|
||||
panic("put on nil value map")
|
||||
}
|
||||
vs.hashMap.Put(k, v)
|
||||
}
|
||||
|
||||
// Delete removes a key k from the map.
|
||||
func (vs *ValueMap) Delete(k Value) {
|
||||
if vs == nil {
|
||||
return
|
||||
}
|
||||
vs.hashMap.Delete(k)
|
||||
}
|
||||
|
||||
func (vs *ValueMap) String() string {
|
||||
if vs == nil {
|
||||
return "{}"
|
||||
}
|
||||
return vs.hashMap.String()
|
||||
}
|
||||
+3232
File diff suppressed because it is too large
Load Diff
+808
@@ -0,0 +1,808 @@
|
||||
// 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.
|
||||
|
||||
// This file contains extra functions for parsing Rego.
|
||||
// Most of the parsing is handled by the code in parser.go,
|
||||
// however, there are additional utilities that are
|
||||
// helpful for dealing with Rego source inputs (e.g., REPL
|
||||
// statements, source files, etc.)
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/tokens"
|
||||
)
|
||||
|
||||
// MustParseBody returns a parsed body.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseBody(input string) Body {
|
||||
return MustParseBodyWithOpts(input, ParserOptions{})
|
||||
}
|
||||
|
||||
// MustParseBodyWithOpts returns a parsed body.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseBodyWithOpts(input string, opts ParserOptions) Body {
|
||||
parsed, err := ParseBodyWithOpts(input, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseExpr returns a parsed expression.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseExpr(input string) *Expr {
|
||||
parsed, err := ParseExpr(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseImports returns a slice of imports.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseImports(input string) []*Import {
|
||||
parsed, err := ParseImports(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseModule returns a parsed module.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseModule(input string) *Module {
|
||||
return MustParseModuleWithOpts(input, ParserOptions{})
|
||||
}
|
||||
|
||||
// MustParseModuleWithOpts returns a parsed module.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseModuleWithOpts(input string, opts ParserOptions) *Module {
|
||||
parsed, err := ParseModuleWithOpts("", input, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParsePackage returns a Package.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParsePackage(input string) *Package {
|
||||
parsed, err := ParsePackage(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseStatements returns a slice of parsed statements.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseStatements(input string) []Statement {
|
||||
parsed, _, err := ParseStatements("", input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseStatement returns exactly one statement.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseStatement(input string) Statement {
|
||||
parsed, err := ParseStatement(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func MustParseStatementWithOpts(input string, popts ParserOptions) Statement {
|
||||
parsed, err := ParseStatementWithOpts(input, popts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseRef returns a parsed reference.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseRef(input string) Ref {
|
||||
parsed, err := ParseRef(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseRule returns a parsed rule.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseRule(input string) *Rule {
|
||||
parsed, err := ParseRule(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseRuleWithOpts returns a parsed rule.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseRuleWithOpts(input string, opts ParserOptions) *Rule {
|
||||
parsed, err := ParseRuleWithOpts(input, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// MustParseTerm returns a parsed term.
|
||||
// If an error occurs during parsing, panic.
|
||||
func MustParseTerm(input string) *Term {
|
||||
parsed, err := ParseTerm(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// ParseRuleFromBody returns a rule if the body can be interpreted as a rule
|
||||
// definition. Otherwise, an error is returned.
|
||||
func ParseRuleFromBody(module *Module, body Body) (*Rule, error) {
|
||||
|
||||
if len(body) != 1 {
|
||||
return nil, errors.New("multiple expressions cannot be used for rule head")
|
||||
}
|
||||
|
||||
return ParseRuleFromExpr(module, body[0])
|
||||
}
|
||||
|
||||
// ParseRuleFromExpr returns a rule if the expression can be interpreted as a
|
||||
// rule definition.
|
||||
func ParseRuleFromExpr(module *Module, expr *Expr) (*Rule, error) {
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
return nil, errors.New("expressions using with keyword cannot be used for rule head")
|
||||
}
|
||||
|
||||
if expr.Negated {
|
||||
return nil, errors.New("negated expressions cannot be used for rule head")
|
||||
}
|
||||
|
||||
if _, ok := expr.Terms.(*SomeDecl); ok {
|
||||
return nil, errors.New("'some' declarations cannot be used for rule head")
|
||||
}
|
||||
|
||||
if term, ok := expr.Terms.(*Term); ok {
|
||||
switch v := term.Value.(type) {
|
||||
case Ref:
|
||||
if len(v) > 2 { // 2+ dots
|
||||
return ParseCompleteDocRuleWithDotsFromTerm(module, term)
|
||||
}
|
||||
return ParsePartialSetDocRuleFromTerm(module, term)
|
||||
default:
|
||||
return nil, fmt.Errorf("%v cannot be used for rule name", ValueName(v))
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := expr.Terms.([]*Term); !ok {
|
||||
// This is a defensive check in case other kinds of expression terms are
|
||||
// introduced in the future.
|
||||
return nil, errors.New("expression cannot be used for rule head")
|
||||
}
|
||||
|
||||
if expr.IsEquality() {
|
||||
return parseCompleteRuleFromEq(module, expr)
|
||||
} else if expr.IsAssignment() {
|
||||
rule, err := parseCompleteRuleFromEq(module, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.Head.Assign = true
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
if _, ok := BuiltinMap[expr.Operator().String()]; ok {
|
||||
return nil, errors.New("rule name conflicts with built-in function")
|
||||
}
|
||||
|
||||
return ParseRuleFromCallExpr(module, expr.Terms.([]*Term))
|
||||
}
|
||||
|
||||
func parseCompleteRuleFromEq(module *Module, expr *Expr) (rule *Rule, err error) {
|
||||
|
||||
// ensure the rule location is set to the expr location
|
||||
// the helper functions called below try to set the location based
|
||||
// on the terms they've been provided but that is not as accurate.
|
||||
defer func() {
|
||||
if rule != nil {
|
||||
rule.Location = expr.Location
|
||||
rule.Head.Location = expr.Location
|
||||
}
|
||||
}()
|
||||
|
||||
lhs, rhs := expr.Operand(0), expr.Operand(1)
|
||||
if lhs == nil || rhs == nil {
|
||||
return nil, errors.New("assignment requires two operands")
|
||||
}
|
||||
|
||||
rule, err = ParseRuleFromCallEqExpr(module, lhs, rhs)
|
||||
if err == nil {
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
rule, err = ParsePartialObjectDocRuleFromEqExpr(module, lhs, rhs)
|
||||
if err == nil {
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
return ParseCompleteDocRuleFromEqExpr(module, lhs, rhs)
|
||||
}
|
||||
|
||||
// ParseCompleteDocRuleFromAssignmentExpr returns a rule if the expression can
|
||||
// be interpreted as a complete document definition declared with the assignment
|
||||
// operator.
|
||||
func ParseCompleteDocRuleFromAssignmentExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
|
||||
|
||||
rule, err := ParseCompleteDocRuleFromEqExpr(module, lhs, rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rule.Head.Assign = true
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParseCompleteDocRuleFromEqExpr returns a rule if the expression can be
|
||||
// interpreted as a complete document definition.
|
||||
func ParseCompleteDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
|
||||
var head *Head
|
||||
|
||||
if v, ok := lhs.Value.(Var); ok {
|
||||
// Modify the code to add the location to the head ref
|
||||
head = VarHead(v, lhs.Location, nil)
|
||||
} else if r, ok := lhs.Value.(Ref); ok { // groundness ?
|
||||
if _, ok := r[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", r)
|
||||
}
|
||||
head = RefHead(r)
|
||||
if len(r) > 1 && !r[len(r)-1].IsGround() {
|
||||
return nil, errors.New("ref not ground")
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("%v cannot be used for rule name", ValueName(lhs.Value))
|
||||
}
|
||||
head.Value = rhs
|
||||
head.Location = lhs.Location
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
|
||||
|
||||
return &Rule{
|
||||
Location: lhs.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
generatedBody: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseCompleteDocRuleWithDotsFromTerm(module *Module, term *Term) (*Rule, error) {
|
||||
ref, ok := term.Value.(Ref)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%v cannot be used for rule name", ValueName(term.Value))
|
||||
}
|
||||
|
||||
if _, ok := ref[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", ref)
|
||||
}
|
||||
head := RefHead(ref, BooleanTerm(true).SetLocation(term.Location))
|
||||
head.generatedValue = true
|
||||
head.Location = term.Location
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
|
||||
|
||||
return &Rule{
|
||||
Location: term.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParsePartialObjectDocRuleFromEqExpr returns a rule if the expression can be
|
||||
// interpreted as a partial object document definition.
|
||||
func ParsePartialObjectDocRuleFromEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
|
||||
ref, ok := lhs.Value.(Ref)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%v cannot be used as rule name", ValueName(lhs.Value))
|
||||
}
|
||||
|
||||
if _, ok := ref[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", ref)
|
||||
}
|
||||
|
||||
head := RefHead(ref, rhs)
|
||||
if len(ref) == 2 { // backcompat for naked `foo.bar = "baz"` statements
|
||||
head.Name = ref[0].Value.(Var)
|
||||
head.Key = ref[1]
|
||||
}
|
||||
head.Location = rhs.Location
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
|
||||
|
||||
rule := &Rule{
|
||||
Location: rhs.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParsePartialSetDocRuleFromTerm returns a rule if the term can be interpreted
|
||||
// as a partial set document definition.
|
||||
func ParsePartialSetDocRuleFromTerm(module *Module, term *Term) (*Rule, error) {
|
||||
|
||||
ref, ok := term.Value.(Ref)
|
||||
if !ok || len(ref) == 1 {
|
||||
return nil, fmt.Errorf("%vs cannot be used for rule head", ValueName(term.Value))
|
||||
}
|
||||
if _, ok := ref[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", ref)
|
||||
}
|
||||
|
||||
head := RefHead(ref)
|
||||
if len(ref) == 2 {
|
||||
v, ok := ref[0].Value.(Var)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%vs cannot be used for rule head", ValueName(term.Value))
|
||||
}
|
||||
// Modify the code to add the location to the head ref
|
||||
head = VarHead(v, ref[0].Location, nil)
|
||||
head.Key = ref[1]
|
||||
}
|
||||
head.Location = term.Location
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(term.Location)).SetLocation(term.Location))
|
||||
|
||||
rule := &Rule{
|
||||
Location: term.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParseRuleFromCallEqExpr returns a rule if the term can be interpreted as a
|
||||
// function definition (e.g., f(x) = y => f(x) = y { true }).
|
||||
func ParseRuleFromCallEqExpr(module *Module, lhs, rhs *Term) (*Rule, error) {
|
||||
|
||||
call, ok := lhs.Value.(Call)
|
||||
if !ok {
|
||||
return nil, errors.New("must be call")
|
||||
}
|
||||
|
||||
ref, ok := call[0].Value.(Ref)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%vs cannot be used in function signature", ValueName(call[0].Value))
|
||||
}
|
||||
if _, ok := ref[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", ref)
|
||||
}
|
||||
|
||||
head := RefHead(ref, rhs)
|
||||
head.Location = lhs.Location
|
||||
head.Args = Args(call[1:])
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(rhs.Location)).SetLocation(rhs.Location))
|
||||
|
||||
rule := &Rule{
|
||||
Location: lhs.Location,
|
||||
Head: head,
|
||||
Body: body,
|
||||
Module: module,
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParseRuleFromCallExpr returns a rule if the terms can be interpreted as a
|
||||
// function returning true or some value (e.g., f(x) => f(x) = true { true }).
|
||||
func ParseRuleFromCallExpr(module *Module, terms []*Term) (*Rule, error) {
|
||||
|
||||
if len(terms) <= 1 {
|
||||
return nil, errors.New("rule argument list must take at least one argument")
|
||||
}
|
||||
|
||||
loc := terms[0].Location
|
||||
ref := terms[0].Value.(Ref)
|
||||
if _, ok := ref[0].Value.(Var); !ok {
|
||||
return nil, fmt.Errorf("invalid rule head: %v", ref)
|
||||
}
|
||||
head := RefHead(ref, BooleanTerm(true).SetLocation(loc))
|
||||
head.Location = loc
|
||||
head.Args = terms[1:]
|
||||
|
||||
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)).SetLocation(loc))
|
||||
|
||||
rule := &Rule{
|
||||
Location: loc,
|
||||
Head: head,
|
||||
Module: module,
|
||||
Body: body,
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParseImports returns a slice of Import objects.
|
||||
func ParseImports(input string) ([]*Import, error) {
|
||||
stmts, _, err := ParseStatements("", input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := []*Import{}
|
||||
for _, stmt := range stmts {
|
||||
if imp, ok := stmt.(*Import); ok {
|
||||
result = append(result, imp)
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected import but got %T", stmt)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseModule returns a parsed Module object.
|
||||
// For details on Module objects and their fields, see policy.go.
|
||||
// Empty input will return nil, nil.
|
||||
func ParseModule(filename, input string) (*Module, error) {
|
||||
return ParseModuleWithOpts(filename, input, ParserOptions{})
|
||||
}
|
||||
|
||||
// ParseModuleWithOpts returns a parsed Module object, and has an additional input ParserOptions
|
||||
// For details on Module objects and their fields, see policy.go.
|
||||
// Empty input will return nil, nil.
|
||||
func ParseModuleWithOpts(filename, input string, popts ParserOptions) (*Module, error) {
|
||||
stmts, comments, err := ParseStatementsWithOpts(filename, input, popts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseModule(filename, stmts, comments, popts.RegoVersion)
|
||||
}
|
||||
|
||||
// ParseBody returns exactly one body.
|
||||
// If multiple bodies are parsed, an error is returned.
|
||||
func ParseBody(input string) (Body, error) {
|
||||
return ParseBodyWithOpts(input, ParserOptions{SkipRules: true})
|
||||
}
|
||||
|
||||
// ParseBodyWithOpts returns exactly one body. It does _not_ set SkipRules: true on its own,
|
||||
// but respects whatever ParserOptions it's been given.
|
||||
func ParseBodyWithOpts(input string, popts ParserOptions) (Body, error) {
|
||||
|
||||
stmts, _, err := ParseStatementsWithOpts("", input, popts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := Body{}
|
||||
|
||||
for _, stmt := range stmts {
|
||||
switch stmt := stmt.(type) {
|
||||
case Body:
|
||||
for i := range stmt {
|
||||
result.Append(stmt[i])
|
||||
}
|
||||
case *Comment:
|
||||
// skip
|
||||
default:
|
||||
return nil, fmt.Errorf("expected body but got %T", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseExpr returns exactly one expression.
|
||||
// If multiple expressions are parsed, an error is returned.
|
||||
func ParseExpr(input string) (*Expr, error) {
|
||||
body, err := ParseBody(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse expression: %w", err)
|
||||
}
|
||||
if len(body) != 1 {
|
||||
return nil, fmt.Errorf("expected exactly one expression but got: %v", body)
|
||||
}
|
||||
return body[0], nil
|
||||
}
|
||||
|
||||
// ParsePackage returns exactly one Package.
|
||||
// If multiple statements are parsed, an error is returned.
|
||||
func ParsePackage(input string) (*Package, error) {
|
||||
stmt, err := ParseStatement(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg, ok := stmt.(*Package)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected package but got %T", stmt)
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
// ParseTerm returns exactly one term.
|
||||
// If multiple terms are parsed, an error is returned.
|
||||
func ParseTerm(input string) (*Term, error) {
|
||||
body, err := ParseBody(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse term: %w", err)
|
||||
}
|
||||
if len(body) != 1 {
|
||||
return nil, fmt.Errorf("expected exactly one term but got: %v", body)
|
||||
}
|
||||
term, ok := body[0].Terms.(*Term)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected term but got %v", body[0].Terms)
|
||||
}
|
||||
return term, nil
|
||||
}
|
||||
|
||||
// ParseRef returns exactly one reference.
|
||||
func ParseRef(input string) (Ref, error) {
|
||||
term, err := ParseTerm(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ref: %w", err)
|
||||
}
|
||||
ref, ok := term.Value.(Ref)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected ref but got %v", term)
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// ParseRuleWithOpts returns exactly one rule.
|
||||
// If multiple rules are parsed, an error is returned.
|
||||
func ParseRuleWithOpts(input string, opts ParserOptions) (*Rule, error) {
|
||||
stmts, _, err := ParseStatementsWithOpts("", input, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stmts) != 1 {
|
||||
return nil, fmt.Errorf("expected exactly one statement (rule), got %v = %T, %T", stmts, stmts[0], stmts[1])
|
||||
}
|
||||
rule, ok := stmts[0].(*Rule)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected rule but got %T", stmts[0])
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// ParseRule returns exactly one rule.
|
||||
// If multiple rules are parsed, an error is returned.
|
||||
func ParseRule(input string) (*Rule, error) {
|
||||
return ParseRuleWithOpts(input, ParserOptions{})
|
||||
}
|
||||
|
||||
// ParseStatement returns exactly one statement.
|
||||
// A statement might be a term, expression, rule, etc. Regardless,
|
||||
// this function expects *exactly* one statement. If multiple
|
||||
// statements are parsed, an error is returned.
|
||||
func ParseStatement(input string) (Statement, error) {
|
||||
stmts, _, err := ParseStatements("", input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stmts) != 1 {
|
||||
return nil, errors.New("expected exactly one statement")
|
||||
}
|
||||
return stmts[0], nil
|
||||
}
|
||||
|
||||
func ParseStatementWithOpts(input string, popts ParserOptions) (Statement, error) {
|
||||
stmts, _, err := ParseStatementsWithOpts("", input, popts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stmts) != 1 {
|
||||
return nil, errors.New("expected exactly one statement")
|
||||
}
|
||||
return stmts[0], nil
|
||||
}
|
||||
|
||||
// ParseStatements is deprecated. Use ParseStatementWithOpts instead.
|
||||
func ParseStatements(filename, input string) ([]Statement, []*Comment, error) {
|
||||
return ParseStatementsWithOpts(filename, input, ParserOptions{})
|
||||
}
|
||||
|
||||
// ParseStatementsWithOpts returns a slice of parsed statements. This is the
|
||||
// default return value from the parser.
|
||||
func ParseStatementsWithOpts(filename, input string, popts ParserOptions) ([]Statement, []*Comment, error) {
|
||||
parser := NewParser().
|
||||
WithFilename(filename).
|
||||
WithReader(strings.NewReader(input)).
|
||||
WithProcessAnnotation(popts.ProcessAnnotation).
|
||||
WithFutureKeywords(popts.FutureKeywords...).
|
||||
WithAllFutureKeywords(popts.AllFutureKeywords).
|
||||
WithCapabilities(popts.Capabilities).
|
||||
WithSkipRules(popts.SkipRules).
|
||||
WithRegoVersion(popts.RegoVersion).
|
||||
withUnreleasedKeywords(popts.unreleasedKeywords)
|
||||
|
||||
stmts, comments, errs := parser.Parse()
|
||||
if len(errs) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
return stmts, comments, nil
|
||||
}
|
||||
|
||||
func parseModule(filename string, stmts []Statement, comments []*Comment, regoCompatibilityMode RegoVersion) (*Module, error) {
|
||||
if len(stmts) == 0 {
|
||||
return nil, NewError(ParseErr, &Location{File: filename}, "empty module")
|
||||
}
|
||||
|
||||
var errs Errors
|
||||
|
||||
pkg, ok := stmts[0].(*Package)
|
||||
if !ok {
|
||||
loc := stmts[0].Loc()
|
||||
errs = append(errs, NewError(ParseErr, loc, "package expected"))
|
||||
}
|
||||
|
||||
mod := &Module{
|
||||
Package: pkg,
|
||||
// The comments slice only holds comments that were not their own statements.
|
||||
Comments: comments,
|
||||
stmts: stmts,
|
||||
}
|
||||
|
||||
mod.regoVersion = regoCompatibilityMode
|
||||
if regoCompatibilityMode == RegoUndefined {
|
||||
mod.regoVersion = DefaultRegoVersion
|
||||
}
|
||||
|
||||
for i, stmt := range stmts[1:] {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Import:
|
||||
mod.Imports = append(mod.Imports, stmt)
|
||||
if mod.regoVersion == RegoV0 && RegoV1CompatibleRef.Equal(stmt.Path.Value) {
|
||||
mod.regoVersion = RegoV0CompatV1
|
||||
}
|
||||
case *Rule:
|
||||
setRuleModule(stmt, mod)
|
||||
mod.Rules = append(mod.Rules, stmt)
|
||||
case Body:
|
||||
rule, err := ParseRuleFromBody(mod, stmt)
|
||||
if err != nil {
|
||||
errs = append(errs, NewError(ParseErr, stmt[0].Location, "%s", err.Error()))
|
||||
continue
|
||||
}
|
||||
rule.generatedBody = true
|
||||
mod.Rules = append(mod.Rules, rule)
|
||||
|
||||
// NOTE(tsandall): the statement should now be interpreted as a
|
||||
// rule so update the statement list. This is important for the
|
||||
// logic below that associates annotations with statements.
|
||||
stmts[i+1] = rule
|
||||
case *Package:
|
||||
errs = append(errs, NewError(ParseErr, stmt.Loc(), "unexpected package"))
|
||||
case *Annotations:
|
||||
mod.Annotations = append(mod.Annotations, stmt)
|
||||
case *Comment:
|
||||
// Ignore comments, they're handled above.
|
||||
default:
|
||||
panic("illegal value") // Indicates grammar is out-of-sync with code.
|
||||
}
|
||||
}
|
||||
|
||||
if mod.regoVersion == RegoV0CompatV1 || mod.regoVersion == RegoV1 {
|
||||
for _, rule := range mod.Rules {
|
||||
for r := rule; r != nil; r = r.Else {
|
||||
errs = append(errs, CheckRegoV1(r)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
errs = append(errs, attachAnnotationsNodes(mod)...)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
attachRuleAnnotations(mod)
|
||||
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func ruleDeclarationHasKeyword(rule *Rule, keyword tokens.Token) bool {
|
||||
return slices.Contains(rule.Head.keywords, keyword)
|
||||
}
|
||||
|
||||
func newScopeAttachmentErr(a *Annotations, want string) *Error {
|
||||
var have string
|
||||
if a.node != nil {
|
||||
have = fmt.Sprintf(" (have %v)", TypeName(a.node))
|
||||
}
|
||||
return NewError(ParseErr, a.Loc(), "annotation scope '%v' must be applied to %v%v", a.Scope, want, have)
|
||||
}
|
||||
|
||||
func setRuleModule(rule *Rule, module *Module) {
|
||||
rule.Module = module
|
||||
if rule.Else != nil {
|
||||
setRuleModule(rule.Else, module)
|
||||
}
|
||||
}
|
||||
|
||||
// ParserErrorDetail holds additional details for parser errors.
|
||||
type ParserErrorDetail struct {
|
||||
Line string `json:"line"`
|
||||
Idx int `json:"idx"`
|
||||
}
|
||||
|
||||
func newParserErrorDetail(bs []byte, offset int) *ParserErrorDetail {
|
||||
|
||||
// Find first non-space character at or before offset position.
|
||||
if offset >= len(bs) {
|
||||
offset = len(bs) - 1
|
||||
} else if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
for offset > 0 && unicode.IsSpace(rune(bs[offset])) {
|
||||
offset--
|
||||
}
|
||||
|
||||
// Find beginning of line containing offset.
|
||||
begin := offset
|
||||
|
||||
for begin > 0 && !isNewLineChar(bs[begin]) {
|
||||
begin--
|
||||
}
|
||||
|
||||
if isNewLineChar(bs[begin]) {
|
||||
begin++
|
||||
}
|
||||
|
||||
// Find end of line containing offset.
|
||||
end := offset
|
||||
|
||||
for end < len(bs) && !isNewLineChar(bs[end]) {
|
||||
end++
|
||||
}
|
||||
|
||||
if begin > end {
|
||||
begin = end
|
||||
}
|
||||
|
||||
// Extract line and compute index of offset byte in line.
|
||||
line := bs[begin:end]
|
||||
index := offset - begin
|
||||
|
||||
return &ParserErrorDetail{
|
||||
Line: string(line),
|
||||
Idx: index,
|
||||
}
|
||||
}
|
||||
|
||||
// Lines returns the pretty formatted line output for the error details.
|
||||
func (d ParserErrorDetail) Lines() []string {
|
||||
line := strings.TrimLeft(d.Line, "\t") // remove leading tabs
|
||||
tabCount := len(d.Line) - len(line)
|
||||
indent := max(d.Idx-tabCount, 0)
|
||||
return []string{line, strings.Repeat(" ", indent) + "^"}
|
||||
}
|
||||
|
||||
func isNewLineChar(b byte) bool {
|
||||
return b == '\r' || b == '\n'
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2025 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 (
|
||||
"encoding"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var builtinNamesByNumParts = sync.OnceValue(func() map[int][]string {
|
||||
m := map[int][]string{}
|
||||
for name := range BuiltinMap {
|
||||
parts := strings.Count(name, ".") + 1
|
||||
if parts > 1 {
|
||||
m[parts] = append(m[parts], name)
|
||||
}
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
// BuiltinNameFromRef attempts to extract a known built-in function name from a ref,
|
||||
// in the most efficient way possible. I.e. without allocating memory for a new string.
|
||||
// If no built-in function name can be extracted, the second return value is false.
|
||||
func BuiltinNameFromRef(ref Ref) (string, bool) {
|
||||
reflen := len(ref)
|
||||
if reflen == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
_var, ok := ref[0].Value.(Var)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
varName := string(_var)
|
||||
if reflen == 1 {
|
||||
if _, ok := BuiltinMap[varName]; ok {
|
||||
return varName, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
totalLen := len(varName)
|
||||
for _, term := range ref[1:] {
|
||||
if _, ok = term.Value.(String); !ok {
|
||||
return "", false
|
||||
}
|
||||
totalLen += 1 + len(term.Value.(String)) // account for dot
|
||||
}
|
||||
|
||||
matched, ok := builtinNamesByNumParts()[reflen]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, name := range matched {
|
||||
// This check saves us a huge amount of work, as only very few built-in
|
||||
// names will have the exact same length as the ref we are checking.
|
||||
if len(name) != totalLen {
|
||||
continue
|
||||
}
|
||||
// Example: `name` is "io.jwt.decode" (and so is ref)
|
||||
// The first part is varName, which have already been established to be 'io':
|
||||
// io, jwt.decode io == io
|
||||
if curr, remaining, _ := strings.Cut(name, "."); curr == varName {
|
||||
// Loop over the remaining (now known to be string) terms in the ref, e.g. "jwt" and "decode"
|
||||
for _, term := range ref[1:] {
|
||||
ts := string(term.Value.(String))
|
||||
// First iteration: jwt.decode != jwt, so we continue cutting
|
||||
// Second iteration: remaining is "decode", and so is term
|
||||
if remaining == ts {
|
||||
return name, true
|
||||
}
|
||||
// Cutting remaining (e.g. jwt.decode), and we now get:
|
||||
// jwt, decode, false || jwt != jwt
|
||||
if curr, remaining, _ = strings.Cut(remaining, "."); remaining == "" || curr != ts {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func AppendDelimeted[T encoding.TextAppender](buf []byte, appenders []T, delim string) ([]byte, error) {
|
||||
for i, item := range appenders {
|
||||
if i > 0 {
|
||||
buf = append(buf, delim...)
|
||||
}
|
||||
var err error
|
||||
if buf, err = item.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
+1894
File diff suppressed because it is too large
Load Diff
+342
@@ -0,0 +1,342 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
func (m *Module) AppendText(buf []byte) ([]byte, error) {
|
||||
if m == nil {
|
||||
return append(buf, "<nil module>"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// NOTE(anderseknert): this DOES allocate still, and while that's unfortunate,
|
||||
// we'll be better off dealing with that when we have v2 JSON in the stdlib than
|
||||
// doing manual JSON marshalling (and string length calculations) here.
|
||||
for _, annotations := range m.Annotations {
|
||||
// rule annotations are attached to rules, so only check for package scoped ones here
|
||||
if annotations.Scope == "package" || annotations.Scope == "subpackages" {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
if buf, err = m.Package.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = imp.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
buf = append(buf, '\n')
|
||||
if buf, err = rule.appendWithOpts(toStringOpts{regoVersion: m.regoVersion}, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (pkg *Package) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if pkg == nil {
|
||||
return append(buf, "<illegal nil package>"...), nil
|
||||
}
|
||||
if len(pkg.Path) <= 1 {
|
||||
buf = append(buf, "package <illegal path \""...)
|
||||
if buf, err = pkg.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, "\">"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, "package "...)
|
||||
|
||||
path := pkg.Path[1:] // omit "data"
|
||||
|
||||
return path.AppendText(buf)
|
||||
}
|
||||
|
||||
func (imp *Import) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "import "...)
|
||||
var err error
|
||||
if buf, err = imp.Path.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if imp.Alias != "" {
|
||||
buf = append(buf, ' ', 'a', 's', ' ')
|
||||
buf = append(buf, imp.Alias...)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) AppendText(buf []byte) ([]byte, error) {
|
||||
regoVersion := DefaultRegoVersion
|
||||
if r.Module != nil {
|
||||
regoVersion = r.Module.RegoVersion()
|
||||
}
|
||||
return r.appendWithOpts(toStringOpts{regoVersion: regoVersion}, buf)
|
||||
}
|
||||
|
||||
func (r *Rule) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
// See note in [Module.AppendText] regarding annotations.
|
||||
for _, annotations := range r.Annotations {
|
||||
buf = append(buf, "# METADATA\n# "...)
|
||||
buf = append(buf, annotations.String()...)
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
|
||||
if r.Default {
|
||||
buf = append(buf, "default "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
if buf, err = r.Head.appendWithOpts(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
buf = append(buf, " if { "...)
|
||||
default:
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Rule) appendElse(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
buf = append(buf, " else "...)
|
||||
|
||||
var err error
|
||||
if r.Head.Value != nil {
|
||||
buf = append(buf, "= "...)
|
||||
if buf, err = r.Head.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v := opts.RegoVersion(); v == RegoV1 || v == RegoV0CompatV1 {
|
||||
buf = append(buf, " if { "...)
|
||||
} else {
|
||||
buf = append(buf, " { "...)
|
||||
}
|
||||
if buf, err = r.Body.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " }"...)
|
||||
|
||||
if r.Else != nil {
|
||||
if buf, err = r.Else.appendElse(opts, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (h *Head) AppendText(buf []byte) ([]byte, error) {
|
||||
return h.appendWithOpts(toStringOpts{}, buf)
|
||||
}
|
||||
|
||||
func (h *Head) appendWithOpts(opts toStringOpts, buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
if h.Reference == nil {
|
||||
buf = append(buf, h.Name...)
|
||||
} else {
|
||||
if buf, err = h.Reference.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
if buf, err = h.Args.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = h.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
buf = append(buf, " := "...)
|
||||
} else {
|
||||
buf = append(buf, " = "...)
|
||||
}
|
||||
if buf, err = h.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
if buf, err = h.Key.AppendText(append(buf, " contains "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (a Args) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, '(')
|
||||
if buf, err = AppendDelimeted(buf, a, ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (expr *Expr) AppendText(buf []byte) ([]byte, error) {
|
||||
if expr.Negated {
|
||||
buf = append(buf, "not "...)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch t := expr.Terms.(type) {
|
||||
case []*Term:
|
||||
if expr.IsEquality() && validEqAssignArgCount(expr) {
|
||||
if buf, err = t[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(append(append(buf, ' '), Equality.Infix...), ' ')
|
||||
if buf, err = t[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if buf, err = Call(t).AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case encoding.TextAppender:
|
||||
if buf, err = t.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported expr terms type: %T", expr.Terms)
|
||||
}
|
||||
|
||||
if len(expr.With) > 0 {
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
|
||||
return AppendDelimeted(buf, expr.With, " ")
|
||||
}
|
||||
|
||||
func (w *With) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "with "...)
|
||||
var err error
|
||||
if buf, err = w.Target.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, " as "...)
|
||||
if buf, err = w.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (w *Every) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "every "...)
|
||||
var err error
|
||||
if w.Key != nil {
|
||||
if buf, err = w.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = w.Value.AppendText(buf); err == nil {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = w.Domain.AppendText(buf); err == nil {
|
||||
buf = append(buf, " { "...)
|
||||
if buf, err = w.Body.AppendText(buf); err == nil {
|
||||
buf = append(buf, " }"...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (d *SomeDecl) AppendText(buf []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf = append(buf, "some "...)
|
||||
if call, ok := d.Symbols[0].Value.(Call); ok {
|
||||
if buf, err = call[1].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 3 {
|
||||
buf = append(buf, " in "...)
|
||||
} else {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
if buf, err = call[2].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(call) == 4 {
|
||||
buf = append(buf, " in "...)
|
||||
if buf, err = call[3].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
buf, err = AppendDelimeted(buf, d.Symbols, ", ")
|
||||
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func (c *Comment) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(append(buf, '#'), c.Text...), nil
|
||||
}
|
||||
|
||||
// RulePath returns the string representation of the rule's path, i.e. its package path followed by the rule head ref.
|
||||
func RulePath(r *Rule) string {
|
||||
if r == nil {
|
||||
return "<nil rule>"
|
||||
}
|
||||
if r.Module == nil {
|
||||
return "<rule " + r.Head.Reference.String() + " without module>"
|
||||
}
|
||||
buf := make([]byte, 0, r.Module.Package.Path.StringLength()+r.Head.Ref().StringLength()+1)
|
||||
buf, _ = r.Module.Package.Path.AppendText(buf)
|
||||
buf = append(buf, '.')
|
||||
buf, _ = r.Head.Ref().AppendText(buf)
|
||||
|
||||
return util.ByteSliceToString(buf)
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright 2018 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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Pretty writes a pretty representation of the AST rooted at x to w.
|
||||
//
|
||||
// This is function is intended for debug purposes when inspecting ASTs.
|
||||
func Pretty(w io.Writer, x any) {
|
||||
pp := &prettyPrinter{
|
||||
depth: -1,
|
||||
w: w,
|
||||
}
|
||||
NewBeforeAfterVisitor(pp.Before, pp.After).Walk(x)
|
||||
}
|
||||
|
||||
type prettyPrinter struct {
|
||||
depth int
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) Before(x any) bool {
|
||||
switch x.(type) {
|
||||
case *Term:
|
||||
default:
|
||||
pp.depth++
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
case *Term:
|
||||
return false
|
||||
case Args:
|
||||
if len(x) == 0 {
|
||||
return false
|
||||
}
|
||||
pp.writeType(x)
|
||||
case *Expr:
|
||||
extras := []string{}
|
||||
if x.Negated {
|
||||
extras = append(extras, "negated")
|
||||
}
|
||||
extras = append(extras, fmt.Sprintf("index=%d", x.Index))
|
||||
pp.writeIndent("%v %v", TypeName(x), strings.Join(extras, " "))
|
||||
case Null, Boolean, Number, String, Var:
|
||||
pp.writeValue(x)
|
||||
default:
|
||||
pp.writeType(x)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) After(x any) {
|
||||
switch x.(type) {
|
||||
case *Term:
|
||||
default:
|
||||
pp.depth--
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) writeValue(x any) {
|
||||
pp.writeIndent(fmt.Sprint(x))
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) writeType(x any) {
|
||||
pp.writeIndent(TypeName(x))
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) writeIndent(f string, a ...any) {
|
||||
pad := strings.Repeat(" ", pp.depth)
|
||||
pp.write(pad+f, a...)
|
||||
}
|
||||
|
||||
func (pp *prettyPrinter) write(f string, a ...any) {
|
||||
fmt.Fprintf(pp.w, f+"\n", a...)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package ast
|
||||
|
||||
import "context"
|
||||
|
||||
type regoCompileCtx struct{}
|
||||
|
||||
func WithCompiler(ctx context.Context, c *Compiler) context.Context {
|
||||
return context.WithValue(ctx, regoCompileCtx{}, c)
|
||||
}
|
||||
|
||||
func CompilerFromContext(ctx context.Context) (*Compiler, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
v, ok := ctx.Value(regoCompileCtx{}).(*Compiler)
|
||||
return v, ok
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/ast/internal/tokens"
|
||||
)
|
||||
|
||||
func checkDuplicateImports(modules []*Module) (errors Errors) {
|
||||
for _, module := range modules {
|
||||
processedImports := map[Var]*Import{}
|
||||
|
||||
for _, imp := range module.Imports {
|
||||
name := imp.Name()
|
||||
|
||||
if processed, conflict := processedImports[name]; conflict {
|
||||
errors = append(errors, NewError(CompileErr, imp.Location, "import must not shadow %v", processed))
|
||||
} else {
|
||||
processedImports[name] = imp
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkRootDocumentOverrides(node any) Errors {
|
||||
errors := Errors{}
|
||||
|
||||
WalkRules(node, func(rule *Rule) bool {
|
||||
name := rule.Head.Name
|
||||
if len(rule.Head.Reference) > 0 {
|
||||
name = rule.Head.Reference[0].Value.(Var)
|
||||
}
|
||||
|
||||
if ReservedVars.Contains(name) {
|
||||
errors = append(errors, NewError(CompileErr, rule.Location, "rules must not shadow %v (use a different rule name)", name))
|
||||
}
|
||||
|
||||
for _, arg := range rule.Head.Args {
|
||||
if _, ok := arg.Value.(Ref); ok {
|
||||
if RootDocumentRefs.Contains(arg) {
|
||||
errors = append(errors, NewError(CompileErr, arg.Location, "args must not shadow %v (use a different variable name)", arg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
WalkExprs(node, func(expr *Expr) bool {
|
||||
if expr.IsAssignment() {
|
||||
// assign() can be called directly, so we need to assert its given first operand exists before checking its name.
|
||||
if nameOp := expr.Operand(0); nameOp != nil {
|
||||
name := Var(nameOp.String())
|
||||
if ReservedVars.Contains(name) {
|
||||
errors = append(errors, NewError(CompileErr, expr.Location, "variables must not shadow %v (use a different variable name)", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func walkCalls(node any, f func(any) bool) {
|
||||
vis := NewGenericVisitor(func(x any) bool {
|
||||
switch y := x.(type) {
|
||||
case Call:
|
||||
return f(x)
|
||||
case *Expr:
|
||||
if y.IsCall() {
|
||||
return f(x)
|
||||
}
|
||||
case *Head:
|
||||
// GenericVisitor doesn't walk the rule head ref
|
||||
walkCalls(y.Reference, f)
|
||||
}
|
||||
return false
|
||||
})
|
||||
vis.Walk(node)
|
||||
}
|
||||
|
||||
func checkDeprecatedBuiltins(deprecatedBuiltinsMap map[string]struct{}, node any) (errs Errors) {
|
||||
walkCalls(node, func(x any) bool {
|
||||
var operator string
|
||||
var loc *Location
|
||||
|
||||
switch x := x.(type) {
|
||||
case *Expr:
|
||||
operator = x.Operator().String()
|
||||
loc = x.Loc()
|
||||
case Call:
|
||||
terms := []*Term(x)
|
||||
if len(terms) > 0 {
|
||||
operator = terms[0].Value.String()
|
||||
loc = terms[0].Loc()
|
||||
}
|
||||
}
|
||||
|
||||
if operator != "" {
|
||||
if _, ok := deprecatedBuiltinsMap[operator]; ok {
|
||||
errs = append(errs, NewError(TypeErr, loc, "deprecated built-in function calls in expression: %v", operator))
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func checkDeprecatedBuiltinsForCurrentVersion(node any) Errors {
|
||||
deprecatedBuiltins := make(map[string]struct{})
|
||||
capabilities := CapabilitiesForThisVersion()
|
||||
for _, bi := range capabilities.Builtins {
|
||||
if bi.IsDeprecated() {
|
||||
deprecatedBuiltins[bi.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return checkDeprecatedBuiltins(deprecatedBuiltins, node)
|
||||
}
|
||||
|
||||
type RegoCheckOptions struct {
|
||||
NoDuplicateImports bool
|
||||
NoRootDocumentOverrides bool
|
||||
NoDeprecatedBuiltins bool
|
||||
NoKeywordsAsRuleNames bool
|
||||
RequireIfKeyword bool
|
||||
RequireContainsKeyword bool
|
||||
RequireRuleBodyOrValue bool
|
||||
}
|
||||
|
||||
func NewRegoCheckOptions() RegoCheckOptions {
|
||||
// all options are enabled by default
|
||||
return RegoCheckOptions{
|
||||
NoDuplicateImports: true,
|
||||
NoRootDocumentOverrides: true,
|
||||
NoDeprecatedBuiltins: true,
|
||||
NoKeywordsAsRuleNames: true,
|
||||
RequireIfKeyword: true,
|
||||
RequireContainsKeyword: true,
|
||||
RequireRuleBodyOrValue: true,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckRegoV1 checks the given module or rule for errors that are specific to Rego v1.
|
||||
// Passing something other than an *ast.Rule or *ast.Module is considered a programming error, and will cause a panic.
|
||||
func CheckRegoV1(x any) Errors {
|
||||
return CheckRegoV1WithOptions(x, NewRegoCheckOptions())
|
||||
}
|
||||
|
||||
func CheckRegoV1WithOptions(x any, opts RegoCheckOptions) Errors {
|
||||
switch x := x.(type) {
|
||||
case *Module:
|
||||
return checkRegoV1Module(x, opts)
|
||||
case *Rule:
|
||||
return checkRegoV1Rule(x, opts)
|
||||
}
|
||||
panic(fmt.Sprintf("cannot check rego-v1 compatibility on type %T", x))
|
||||
}
|
||||
|
||||
func checkRegoV1Module(module *Module, opts RegoCheckOptions) Errors {
|
||||
var errors Errors
|
||||
if opts.NoDuplicateImports {
|
||||
errors = append(errors, checkDuplicateImports([]*Module{module})...)
|
||||
}
|
||||
if opts.NoRootDocumentOverrides {
|
||||
errors = append(errors, checkRootDocumentOverrides(module)...)
|
||||
}
|
||||
if opts.NoDeprecatedBuiltins {
|
||||
errors = append(errors, checkDeprecatedBuiltinsForCurrentVersion(module)...)
|
||||
}
|
||||
|
||||
for _, rule := range module.Rules {
|
||||
errors = append(errors, checkRegoV1Rule(rule, opts)...)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func checkRegoV1Rule(rule *Rule, opts RegoCheckOptions) Errors {
|
||||
t := "rule"
|
||||
if rule.isFunction() {
|
||||
t = "function"
|
||||
}
|
||||
|
||||
var errs Errors
|
||||
|
||||
if opts.NoKeywordsAsRuleNames && len(rule.Head.Reference) < 2 && IsKeywordInRegoVersion(rule.Head.Name.String(), RegoV1) {
|
||||
errs = append(errs, NewError(ParseErr, rule.Location, "%s keyword cannot be used for rule name", rule.Head.Name.String()))
|
||||
}
|
||||
if opts.RequireRuleBodyOrValue && rule.generatedBody && rule.Head.generatedValue {
|
||||
errs = append(errs, NewError(ParseErr, rule.Location, "%s must have value assignment and/or body declaration", t))
|
||||
}
|
||||
if opts.RequireIfKeyword && rule.Body != nil && !rule.generatedBody && !ruleDeclarationHasKeyword(rule, tokens.If) && !rule.Default {
|
||||
errs = append(errs, NewError(ParseErr, rule.Location, "`if` keyword is required before %s body", t))
|
||||
}
|
||||
if opts.RequireContainsKeyword && rule.Head.RuleKind() == MultiValue && !ruleDeclarationHasKeyword(rule, tokens.Contains) {
|
||||
errs = append(errs, NewError(ParseErr, rule.Location, "`contains` keyword is required for partial set rules"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/types"
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// SchemaSet holds a map from a path to a schema.
|
||||
type SchemaSet struct {
|
||||
m *util.HasherMap[Ref, any]
|
||||
}
|
||||
|
||||
// NewSchemaSet returns an empty SchemaSet.
|
||||
func NewSchemaSet() *SchemaSet {
|
||||
return &SchemaSet{
|
||||
m: util.NewHasherMap[Ref, any](RefEqual),
|
||||
}
|
||||
}
|
||||
|
||||
// Put inserts a raw schema into the set.
|
||||
func (ss *SchemaSet) Put(path Ref, raw any) {
|
||||
ss.m.Put(path, raw)
|
||||
}
|
||||
|
||||
// Get returns the raw schema identified by the path.
|
||||
func (ss *SchemaSet) Get(path Ref) any {
|
||||
if ss != nil {
|
||||
if x, ok := ss.m.Get(path); ok {
|
||||
return x
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSchema(raw any, allowNet []string) (types.Type, error) {
|
||||
|
||||
jsonSchema, err := compileSchema(raw, allowNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tpe, err := newSchemaParser().parseSchema(jsonSchema.RootSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type checking: %w", err)
|
||||
}
|
||||
|
||||
return tpe, nil
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright 2026 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
|
||||
|
||||
// CountFunc counts the number of items in a slice S that satisfy predicate function f.
|
||||
func CountFunc[T any, S ~[]T](items S, f func(T) bool) (n int) {
|
||||
for i := range items {
|
||||
if f(items[i]) {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// StringLengther is an interface for types that can report their string length without
|
||||
// actually constructing the string. This is useful for pre-allocating buffers, like those
|
||||
// used in AppendText, strings.Builder, bytes.Buffer, etc.
|
||||
type StringLengther interface {
|
||||
StringLength() int
|
||||
}
|
||||
|
||||
// TermSliceStringLength returns the total string length of the given terms, as reported
|
||||
// by the [StringLengther.StringLength] method implementation of each term's [Value]. The
|
||||
// delimLen value will be added between each term's length to account for a delimiter, or
|
||||
// no delimiter if delimLen is 0.
|
||||
// Implementation note: this function is optimized for inlining, and just meets the threshold
|
||||
// for that. Don't change without making sure that's still the case.
|
||||
func TermSliceStringLength(terms []*Term, delimLen int) (n int) {
|
||||
for i := range terms {
|
||||
n += terms[i].StringLength() + delimLen
|
||||
}
|
||||
return max(n-delimLen, 0)
|
||||
}
|
||||
|
||||
func (t *Term) StringLength() int {
|
||||
if sl, ok := t.Value.(StringLengther); ok {
|
||||
return sl.StringLength()
|
||||
}
|
||||
|
||||
panic("expected all ast.Value types to implement StringLenghter interface, got: " + ValueName(t.Value))
|
||||
}
|
||||
|
||||
func (s String) StringLength() int {
|
||||
n := 2 // surrounding quotes
|
||||
bs := util.StringToByteSlice(s)
|
||||
for i := 0; i < len(bs); {
|
||||
r, size := utf8.DecodeRune(bs[i:])
|
||||
switch r {
|
||||
case '\\', '"':
|
||||
n += 2 // escaped backslash or quote
|
||||
case '\b', '\f', '\n', '\r', '\t':
|
||||
n += 2 // escaped control characters
|
||||
default:
|
||||
if r < 0x20 {
|
||||
n += 6 // unicode escape for other control characters
|
||||
} else {
|
||||
n += size // normal rune
|
||||
}
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n Number) StringLength() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (b Boolean) StringLength() int {
|
||||
if b {
|
||||
return 4
|
||||
}
|
||||
return 5
|
||||
}
|
||||
|
||||
func (Null) StringLength() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (s *set) StringLength() int {
|
||||
if s.Len() == 0 {
|
||||
return 5 // set()
|
||||
}
|
||||
// surrounding {} + ", " for every element - 1
|
||||
return TermSliceStringLength(s.Slice(), 2) + 2
|
||||
}
|
||||
|
||||
func (a *Array) StringLength() int {
|
||||
if a.Len() == 0 {
|
||||
return 2 // []
|
||||
}
|
||||
// surrounding brackets + ", " for every element - 1
|
||||
return TermSliceStringLength(a.elems, 2) + 2
|
||||
}
|
||||
|
||||
func (o *object) StringLength() (n int) {
|
||||
if o.Len() == 0 {
|
||||
return 2 // {}
|
||||
}
|
||||
// ": " for every item + ", " for every item - 1
|
||||
o.Foreach(func(key, value *Term) {
|
||||
n += key.StringLength() + 4 + value.StringLength() // ": " and ", "
|
||||
})
|
||||
return n // surrounding {} but also minus last ", "
|
||||
}
|
||||
|
||||
func (l *lazyObj) StringLength() int {
|
||||
return l.force().(*object).StringLength()
|
||||
}
|
||||
|
||||
func (ts *TemplateString) StringLength() (n int) {
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
n += 2 + x.StringLength() // for {}
|
||||
case *Term:
|
||||
if s, ok := x.Value.(String); ok {
|
||||
n += len(s) + countUnescapedLeftCurly(string(s))
|
||||
} else {
|
||||
n += x.StringLength()
|
||||
}
|
||||
default:
|
||||
n += 9 // <invalid>
|
||||
}
|
||||
}
|
||||
return n + 3 // $"" or $``
|
||||
}
|
||||
|
||||
func (c Call) StringLength() int {
|
||||
return c[0].StringLength() + 2 + TermSliceStringLength(c[1:], 2)
|
||||
}
|
||||
|
||||
func (r Ref) StringLength() (n int) {
|
||||
rlen := len(r)
|
||||
if rlen == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
n = len(s) // first term should never be quoted
|
||||
} else {
|
||||
n = r[0].StringLength()
|
||||
}
|
||||
|
||||
if rlen == 1 {
|
||||
return n
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
n += 1 + len(str) // dot + name
|
||||
} else {
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
default:
|
||||
n += 2 + p.StringLength() // brackets
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (v Var) StringLength() int {
|
||||
if v.IsWildcard() {
|
||||
return 1
|
||||
}
|
||||
return len(v)
|
||||
}
|
||||
|
||||
func (s *SetComprehension) StringLength() int {
|
||||
return s.Term.StringLength() + s.Body.StringLength() + 5 // {} and " | "
|
||||
}
|
||||
|
||||
func (a *ArrayComprehension) StringLength() int {
|
||||
return a.Term.StringLength() + a.Body.StringLength() + 5 // [] and " | "
|
||||
}
|
||||
|
||||
func (o *ObjectComprehension) StringLength() (n int) {
|
||||
n += o.Key.StringLength()
|
||||
n += o.Value.StringLength()
|
||||
n += o.Body.StringLength()
|
||||
return n + 7 // "{}"", " | ", and ": "
|
||||
}
|
||||
|
||||
func (m *Module) StringLength() (n int) {
|
||||
if m.Package != nil {
|
||||
n += m.Package.StringLength() + 2 // newlines
|
||||
}
|
||||
|
||||
if len(m.Imports) > 0 {
|
||||
for _, imp := range m.Imports {
|
||||
n += imp.StringLength() + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.Rules) > 0 {
|
||||
for _, rule := range m.Rules {
|
||||
n += rule.stringLengthWithOpts(toStringOpts{regoVersion: m.regoVersion}) + 1 // newline
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *Package) StringLength() int {
|
||||
if p == nil {
|
||||
return 21 // <illegal nil package>
|
||||
}
|
||||
if len(p.Path) <= 1 {
|
||||
return 25 + p.Path.StringLength() // // package <illegal path " ... ">
|
||||
}
|
||||
|
||||
return 8 + p.Path[1:].StringLength() // "package ..."
|
||||
}
|
||||
|
||||
func (i *Import) StringLength() (n int) {
|
||||
n = 7 + i.Path.StringLength() // "import " and path
|
||||
if i.Alias != "" {
|
||||
n += 4 + i.Alias.StringLength() // " as " and alias
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *Rule) StringLength() int {
|
||||
return r.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (r *Rule) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := 0
|
||||
if r.Default {
|
||||
n += 8 // "default "
|
||||
}
|
||||
n += r.Head.stringLengthWithOpts(opts)
|
||||
if !r.Default {
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV1, RegoV0CompatV1:
|
||||
n += 6 // " if { "
|
||||
default:
|
||||
n += 3 // " { "
|
||||
}
|
||||
n += r.Body.StringLength() + 2 // body and closing " }"
|
||||
}
|
||||
if r.Else != nil {
|
||||
n += r.Else.stringLengthWithOpts(opts)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *Head) StringLength() int {
|
||||
return h.stringLengthWithOpts(toStringOpts{})
|
||||
}
|
||||
|
||||
func (h *Head) stringLengthWithOpts(opts toStringOpts) int {
|
||||
n := h.Reference.StringLength()
|
||||
containsAdded := false
|
||||
switch {
|
||||
case len(h.Args) != 0:
|
||||
n += h.Args.StringLength()
|
||||
case len(h.Reference) == 1 && h.Key != nil:
|
||||
switch opts.RegoVersion() {
|
||||
case RegoV0:
|
||||
n += 2 + h.Key.StringLength() // for []
|
||||
default:
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
containsAdded = true
|
||||
}
|
||||
}
|
||||
if h.Value != nil {
|
||||
if h.Assign {
|
||||
n += 4 // " := "
|
||||
} else {
|
||||
n += 3 // " = "
|
||||
}
|
||||
n += h.Value.StringLength()
|
||||
} else if !containsAdded && h.Name == "" && h.Key != nil {
|
||||
n += 10 + h.Key.StringLength() // " contains "
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (a Args) StringLength() (n int) {
|
||||
n = 2 // ()
|
||||
for _, t := range a {
|
||||
n += t.StringLength() + 2 // ", "
|
||||
}
|
||||
return n - 2 // minus last ", "
|
||||
}
|
||||
|
||||
func (b Body) StringLength() (n int) {
|
||||
for _, expr := range b {
|
||||
n += expr.StringLength() + 2 // "; "
|
||||
}
|
||||
return max(n-2, 0) // minus last "; " (if `n` isn't 0)
|
||||
}
|
||||
|
||||
func (e *Expr) StringLength() (n int) {
|
||||
if e.Negated {
|
||||
n += 4 // "not "
|
||||
}
|
||||
switch terms := e.Terms.(type) {
|
||||
case []*Term:
|
||||
if e.IsEquality() && validEqAssignArgCount(e) {
|
||||
n += terms[1].StringLength() + len(Equality.Infix) + terms[2].StringLength() + 2 // spaces around =
|
||||
} else {
|
||||
n += Call(terms).StringLength()
|
||||
}
|
||||
case StringLengther:
|
||||
n += terms.StringLength()
|
||||
default:
|
||||
panic(fmt.Sprintf("string length estimation not implemented for type: %T", e.Terms))
|
||||
}
|
||||
|
||||
for _, w := range e.With {
|
||||
n += w.StringLength() + 1 // space before with
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (w *With) StringLength() int {
|
||||
return w.Target.StringLength() + w.Value.StringLength() + 9 // "with " and " as "
|
||||
}
|
||||
|
||||
func (e *Every) StringLength() int {
|
||||
n := 6 // "every "
|
||||
if e.Key != nil {
|
||||
n += e.Key.StringLength() + 2 // ", "
|
||||
}
|
||||
n += e.Value.StringLength() + 4 // " in "
|
||||
n += e.Domain.StringLength() + 3 // " { "
|
||||
n += e.Body.StringLength() + 2 // " }"
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *SomeDecl) StringLength() int {
|
||||
n := 5 // "some "
|
||||
if call, ok := s.Symbols[0].Value.(Call); ok {
|
||||
n += 4 // " in "
|
||||
n += call[1].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += 2 // ", "
|
||||
}
|
||||
n += call[2].StringLength()
|
||||
if len(call) == 4 {
|
||||
n += call[3].StringLength()
|
||||
}
|
||||
return n
|
||||
}
|
||||
return n + TermSliceStringLength(s.Symbols, 2)
|
||||
}
|
||||
|
||||
func (c *Comment) StringLength() int {
|
||||
return 1 + len(c.Text) // '#' + text
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// 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 (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TypeName returns a human readable name for the AST element type.
|
||||
func TypeName(x any) string {
|
||||
if _, ok := x.(*lazyObj); ok {
|
||||
return "object"
|
||||
}
|
||||
return strings.ToLower(reflect.Indirect(reflect.ValueOf(x)).Type().Name())
|
||||
}
|
||||
|
||||
// ValueName returns a human readable name for the AST Value type.
|
||||
// This is preferrable over calling TypeName when the argument is known to be
|
||||
// a Value, as this doesn't require reflection (= heap allocations).
|
||||
func ValueName(x Value) string {
|
||||
switch x.(type) {
|
||||
case String:
|
||||
return "string"
|
||||
case Boolean:
|
||||
return "boolean"
|
||||
case Number:
|
||||
return "number"
|
||||
case Null:
|
||||
return "null"
|
||||
case Var:
|
||||
return "var"
|
||||
case Object:
|
||||
return "object"
|
||||
case Set:
|
||||
return "set"
|
||||
case Ref:
|
||||
return "ref"
|
||||
case Call:
|
||||
return "call"
|
||||
case *Array:
|
||||
return "array"
|
||||
case *ArrayComprehension:
|
||||
return "arraycomprehension"
|
||||
case *ObjectComprehension:
|
||||
return "objectcomprehension"
|
||||
case *SetComprehension:
|
||||
return "setcomprehension"
|
||||
case *TemplateString:
|
||||
return "templatestring"
|
||||
}
|
||||
|
||||
return TypeName(x)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
var (
|
||||
TermPtrPool = util.NewSyncPool[Term]()
|
||||
BytesReaderPool = util.NewSyncPool[bytes.Reader]()
|
||||
IndexResultPool = util.NewSyncPool[IndexResult]()
|
||||
|
||||
// Needs custom pool because of custom Put logic.
|
||||
varVisitorPool = &vvPool{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return NewVarVisitor()
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type vvPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
func (p *vvPool) Get() *VarVisitor {
|
||||
return p.pool.Get().(*VarVisitor)
|
||||
}
|
||||
|
||||
func (p *vvPool) Put(vv *VarVisitor) {
|
||||
if vv != nil {
|
||||
vv.Clear()
|
||||
p.pool.Put(vv)
|
||||
}
|
||||
}
|
||||
+3283
File diff suppressed because it is too large
Load Diff
+266
@@ -0,0 +1,266 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// AppendText appends the text representation of term (i.e. as printed in policy) to
|
||||
// buf and returns the extended buffer.
|
||||
func (term *Term) AppendText(buf []byte) ([]byte, error) {
|
||||
if app, ok := term.Value.(encoding.TextAppender); ok {
|
||||
return app.AppendText(buf)
|
||||
}
|
||||
|
||||
return append(buf, term.Value.String()...), nil
|
||||
}
|
||||
|
||||
func (v Var) AppendText(buf []byte) ([]byte, error) {
|
||||
if v.IsWildcard() {
|
||||
return append(buf, WildcardString...), nil
|
||||
}
|
||||
return append(buf, v...), nil
|
||||
}
|
||||
|
||||
func (b Boolean) AppendText(buf []byte) ([]byte, error) {
|
||||
if b {
|
||||
return append(buf, "true"...), nil
|
||||
}
|
||||
return append(buf, "false"...), nil
|
||||
}
|
||||
|
||||
func (Null) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, "null"...), nil
|
||||
}
|
||||
|
||||
func (str String) AppendText(buf []byte) ([]byte, error) {
|
||||
return strconv.AppendQuote(buf, string(str)), nil
|
||||
}
|
||||
|
||||
func (str String) appendNoQuote(buf []byte) []byte {
|
||||
// Append using strconv.AppendQuote for proper escaping, but trim off
|
||||
// the leading and trailing quotes afterwards.
|
||||
oldLen := len(buf)
|
||||
buf = strconv.AppendQuote(buf, string(str))
|
||||
newLen := len(buf)
|
||||
quoted := buf[oldLen:newLen]
|
||||
|
||||
return append(buf[:oldLen], quoted[1:len(quoted)-1]...)
|
||||
}
|
||||
|
||||
func (num Number) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, num...), nil
|
||||
}
|
||||
|
||||
func (arr *Array) AppendText(buf []byte) ([]byte, error) {
|
||||
buf, err := AppendDelimeted(append(buf, '['), arr.elems, ", ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (obj *object) AppendText(buf []byte) ([]byte, error) {
|
||||
olen := obj.Len()
|
||||
if olen == 0 {
|
||||
return append(buf, "{}"...), nil
|
||||
}
|
||||
|
||||
buf = append(buf, '{')
|
||||
|
||||
var err error
|
||||
|
||||
// first key-value pair
|
||||
keys := obj.sortedKeys()
|
||||
for i := range keys {
|
||||
if buf, err = keys[i].key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = keys[i].value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i < olen-1 {
|
||||
buf = append(buf, ", "...)
|
||||
}
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (obj *lazyObj) AppendText(buf []byte) ([]byte, error) {
|
||||
return append(buf, obj.force().String()...), nil
|
||||
}
|
||||
|
||||
func (s *set) AppendText(buf []byte) ([]byte, error) {
|
||||
slen := s.Len()
|
||||
if slen == 0 {
|
||||
return append(buf, "set()"...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
buf = append(buf, '{')
|
||||
if buf, err = AppendDelimeted(buf, s.sortedKeys(), ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (c Call) AppendText(buf []byte) ([]byte, error) {
|
||||
if len(c) == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if buf, err = c[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if buf, err = AppendDelimeted(append(buf, '('), c[1:], ", "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ')'), nil
|
||||
}
|
||||
|
||||
func (ts *TemplateString) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, "$\""...)
|
||||
for _, p := range ts.Parts {
|
||||
switch x := p.(type) {
|
||||
case *Expr:
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, '}')
|
||||
case *Term:
|
||||
if str, ok := x.Value.(String); ok {
|
||||
// TODO(anders): this is a bit of a mess, but as explained by the comment on
|
||||
// [EscapeTemplateStringStringPart], required as long as we rely on strconv for escaping, which adds
|
||||
// quotes around the string that we don't want here, and trying to "unappend" them is not nice at all..
|
||||
s := string(str)
|
||||
ulc := countUnescapedLeftCurly(s)
|
||||
sl := str.StringLength() + ulc - 2 // no surrounding quotes
|
||||
|
||||
if sl == len(s) { // no escaping needed
|
||||
buf = append(buf, s...)
|
||||
} else { // some escaping needed
|
||||
if sl == len(s)+ulc { // only unescaped {
|
||||
buf = AppendEscapedTemplateStringStringPart(buf, string(str))
|
||||
} else { // full escaping needed. this is expensive but luckily rare
|
||||
tmp := str.appendNoQuote(make([]byte, 0, sl))
|
||||
ets := EscapeTemplateStringStringPart(util.ByteSliceToString(tmp))
|
||||
buf = append(buf, ets...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
if buf, err = x.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
buf = append(buf, "<invalid>"...)
|
||||
}
|
||||
}
|
||||
return append(buf, '"'), nil
|
||||
}
|
||||
|
||||
func (r Ref) AppendText(buf []byte) ([]byte, error) {
|
||||
reflen := len(r)
|
||||
if reflen == 0 {
|
||||
return buf, nil
|
||||
}
|
||||
if reflen == 1 {
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
// While a ref head is typically a Var, a lone String term should not be quoted
|
||||
return append(buf, s...), nil
|
||||
}
|
||||
return r[0].AppendText(buf)
|
||||
}
|
||||
if name, ok := BuiltinNameFromRef(r); ok {
|
||||
return append(buf, name...), nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if s, ok := r[0].Value.(String); ok {
|
||||
buf = append(buf, s...)
|
||||
} else if buf, err = r[0].AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range r[1:] {
|
||||
switch v := p.Value.(type) {
|
||||
case String:
|
||||
str := string(v)
|
||||
if IsVarCompatibleString(str) && !IsKeyword(str) {
|
||||
buf = append(append(buf, '.'), str...)
|
||||
} else {
|
||||
buf = append(buf, '[')
|
||||
// Determine whether we need the full JSON-escaped form
|
||||
if strings.ContainsFunc(str, isControlOrBackslash) {
|
||||
if buf, err = v.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf = append(append(append(buf, '"'), str...), '"')
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
default:
|
||||
buf = append(buf, '[')
|
||||
if buf, err = p.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (sc *SetComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = sc.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = sc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
|
||||
func (ac *ArrayComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '[')
|
||||
var err error
|
||||
if buf, err = ac.Term.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = ac.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, ']'), nil
|
||||
}
|
||||
|
||||
func (oc *ObjectComprehension) AppendText(buf []byte) ([]byte, error) {
|
||||
buf = append(buf, '{')
|
||||
var err error
|
||||
if buf, err = oc.Key.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = append(buf, ": "...)
|
||||
if buf, err = oc.Value.AppendText(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf, err = oc.Body.AppendText(append(buf, " | "...)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, '}'), nil
|
||||
}
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Transformer defines the interface for transforming AST elements. If the
|
||||
// transformer returns nil and does not indicate an error, the AST element will
|
||||
// be set to nil and no transformations will be applied to children of the
|
||||
// element.
|
||||
type Transformer interface {
|
||||
Transform(any) (any, error)
|
||||
}
|
||||
|
||||
// Transform iterates the AST and calls the Transform function on the
|
||||
// Transformer t for x before recursing.
|
||||
func Transform(t Transformer, x any) (any, error) {
|
||||
if term, ok := x.(*Term); ok {
|
||||
return Transform(t, term.Value)
|
||||
}
|
||||
|
||||
y, err := t.Transform(x)
|
||||
if err != nil {
|
||||
return x, err
|
||||
}
|
||||
|
||||
if y == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
switch y := y.(type) {
|
||||
case *Module:
|
||||
p, err := Transform(t, y.Package)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Package, ok = p.(*Package); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Package, p)
|
||||
}
|
||||
for i := range y.Imports {
|
||||
imp, err := Transform(t, y.Imports[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Imports[i], ok = imp.(*Import); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Imports[i], imp)
|
||||
}
|
||||
}
|
||||
for i := range y.Rules {
|
||||
rule, err := Transform(t, y.Rules[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Rules[i], ok = rule.(*Rule); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Rules[i], rule)
|
||||
}
|
||||
}
|
||||
for i := range y.Annotations {
|
||||
a, err := Transform(t, y.Annotations[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Annotations[i], ok = a.(*Annotations); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Annotations[i], a)
|
||||
}
|
||||
}
|
||||
for i := range y.Comments {
|
||||
comment, err := Transform(t, y.Comments[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Comments[i], ok = comment.(*Comment); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Comments[i], comment)
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *Package:
|
||||
ref, err := Transform(t, y.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Path, ok = ref.(Ref); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Path, ref)
|
||||
}
|
||||
return y, nil
|
||||
case *Import:
|
||||
y.Path, err = transformTerm(t, y.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Alias, err = transformVar(t, y.Alias); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case *Rule:
|
||||
if y.Head, err = transformHead(t, y.Head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Body, err = transformBody(t, y.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Else != nil {
|
||||
rule, err := Transform(t, y.Else)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Else, ok = rule.(*Rule); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.Else, rule)
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *Head:
|
||||
if y.Reference, err = transformRef(t, y.Reference); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Name, err = transformVar(t, y.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Args, err = transformArgs(t, y.Args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Key != nil {
|
||||
if y.Key, err = transformTerm(t, y.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if y.Value != nil {
|
||||
if y.Value, err = transformTerm(t, y.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case Args:
|
||||
for i := range y {
|
||||
if y[i], err = transformTerm(t, y[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case Body:
|
||||
for i, e := range y {
|
||||
e, err := Transform(t, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y[i], ok = e.(*Expr); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y[i], e)
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *Expr:
|
||||
switch ts := y.Terms.(type) {
|
||||
case *SomeDecl:
|
||||
decl, err := Transform(t, ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Terms, ok = decl.(*SomeDecl); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y, decl)
|
||||
}
|
||||
return y, nil
|
||||
case []*Term:
|
||||
for i := range ts {
|
||||
if ts[i], err = transformTerm(t, ts[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case *Term:
|
||||
if y.Terms, err = transformTerm(t, ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *Every:
|
||||
if ts.Key != nil {
|
||||
ts.Key, err = transformTerm(t, ts.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ts.Value, err = transformTerm(t, ts.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.Domain, err = transformTerm(t, ts.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.Body, err = transformBody(t, ts.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y.Terms = ts
|
||||
}
|
||||
for i, w := range y.With {
|
||||
w, err := Transform(t, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.With[i], ok = w.(*With); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", y.With[i], w)
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *With:
|
||||
if y.Target, err = transformTerm(t, y.Target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Value, err = transformTerm(t, y.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case Ref:
|
||||
for i, term := range y {
|
||||
if y[i], err = transformTerm(t, term); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *object:
|
||||
return y.Map(func(k, v *Term) (*Term, *Term, error) {
|
||||
k, err := transformTerm(t, k)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
v, err = transformTerm(t, v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return k, v, nil
|
||||
})
|
||||
case *Array:
|
||||
for i := range y.Len() {
|
||||
v, err := transformTerm(t, y.Elem(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y.set(i, v)
|
||||
}
|
||||
return y, nil
|
||||
case Set:
|
||||
y, err = y.Map(func(term *Term) (*Term, error) {
|
||||
return transformTerm(t, term)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case *ArrayComprehension:
|
||||
if y.Term, err = transformTerm(t, y.Term); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Body, err = transformBody(t, y.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case *ObjectComprehension:
|
||||
if y.Key, err = transformTerm(t, y.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Value, err = transformTerm(t, y.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Body, err = transformBody(t, y.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case *SetComprehension:
|
||||
if y.Term, err = transformTerm(t, y.Term); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Body, err = transformBody(t, y.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y, nil
|
||||
case Call:
|
||||
for i := range y {
|
||||
if y[i], err = transformTerm(t, y[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
case *TemplateString:
|
||||
for i := range y.Parts {
|
||||
if expr, ok := y.Parts[i].(*Expr); ok {
|
||||
transformed, err := Transform(t, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if y.Parts[i], ok = transformed.(*Expr); !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", expr, transformed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return y, nil
|
||||
default:
|
||||
return y, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TransformRefs calls the function f on all references under x.
|
||||
func TransformRefs(x any, f func(Ref) (Value, error)) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
if r, ok := x.(Ref); ok {
|
||||
return f(r)
|
||||
}
|
||||
return x, nil
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
// TransformVars calls the function f on all vars under x.
|
||||
func TransformVars(x any, f func(Var) (Value, error)) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
if v, ok := x.(Var); ok {
|
||||
return f(v)
|
||||
}
|
||||
return x, nil
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
// TransformComprehensions calls the function f on all comprehensions under x.
|
||||
func TransformComprehensions(x any, f func(any) (Value, error)) (any, error) {
|
||||
t := NewGenericTransformer(func(x any) (any, error) {
|
||||
switch x := x.(type) {
|
||||
case *ArrayComprehension:
|
||||
return f(x)
|
||||
case *SetComprehension:
|
||||
return f(x)
|
||||
case *ObjectComprehension:
|
||||
return f(x)
|
||||
}
|
||||
return x, nil
|
||||
})
|
||||
return Transform(t, x)
|
||||
}
|
||||
|
||||
// GenericTransformer implements the Transformer interface to provide a utility
|
||||
// to transform AST nodes using a closure.
|
||||
type GenericTransformer struct {
|
||||
f func(any) (any, error)
|
||||
}
|
||||
|
||||
// NewGenericTransformer returns a new GenericTransformer that will transform
|
||||
// AST nodes using the function f.
|
||||
func NewGenericTransformer(f func(x any) (any, error)) *GenericTransformer {
|
||||
return &GenericTransformer{
|
||||
f: f,
|
||||
}
|
||||
}
|
||||
|
||||
// Transform calls the function f on the GenericTransformer.
|
||||
func (t *GenericTransformer) Transform(x any) (any, error) {
|
||||
return t.f(x)
|
||||
}
|
||||
|
||||
func transformHead(t Transformer, head *Head) (*Head, error) {
|
||||
y, err := Transform(t, head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, ok := y.(*Head)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", head, y)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func transformArgs(t Transformer, args Args) (Args, error) {
|
||||
y, err := Transform(t, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, ok := y.(Args)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", args, y)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func transformBody(t Transformer, body Body) (Body, error) {
|
||||
y, err := Transform(t, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, ok := y.(Body)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", body, y)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func transformTerm(t Transformer, term *Term) (*Term, error) {
|
||||
v, err := transformValue(t, term.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Term{Value: v, Location: term.Location}, nil
|
||||
}
|
||||
|
||||
func transformValue(t Transformer, v Value) (Value, error) {
|
||||
v1, err := Transform(t, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, ok := v1.(Value)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", v, v1)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func transformVar(t Transformer, v Var) (Var, error) {
|
||||
tv, err := t.Transform(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tv == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
r, ok := tv.(Var)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("illegal transform: %T != %T", v, tv)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func transformRef(t Transformer, r Ref) (Ref, error) {
|
||||
r1, err := Transform(t, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r2, ok := r1.(Ref)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("illegal transform: %T != %T", r, r2)
|
||||
}
|
||||
return r2, nil
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
// 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
|
||||
|
||||
func isRefSafe(ref Ref, safe VarSet) bool {
|
||||
switch head := ref[0].Value.(type) {
|
||||
case Var:
|
||||
return safe.Contains(head)
|
||||
case Call:
|
||||
return isCallSafe(head, safe)
|
||||
default:
|
||||
vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParams)
|
||||
vis.Walk(ref[0])
|
||||
isSafe := vis.Vars().DiffCount(safe) == 0
|
||||
varVisitorPool.Put(vis)
|
||||
return isSafe
|
||||
}
|
||||
}
|
||||
|
||||
func isCallSafe(call Call, safe VarSet) bool {
|
||||
vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParams)
|
||||
vis.Walk(call)
|
||||
isSafe := vis.Vars().DiffCount(safe) == 0
|
||||
varVisitorPool.Put(vis)
|
||||
|
||||
return isSafe
|
||||
}
|
||||
|
||||
// Unify returns a set of variables that will be unified when the equality expression defined by
|
||||
// terms a and b is evaluated. The unifier assumes that variables in the VarSet safe are already
|
||||
// unified.
|
||||
func Unify(safe VarSet, a *Term, b *Term) VarSet {
|
||||
u := &unifier{
|
||||
safe: safe,
|
||||
unified: VarSet{},
|
||||
unknown: map[Var]VarSet{},
|
||||
}
|
||||
u.unify(a, b)
|
||||
return u.unified
|
||||
}
|
||||
|
||||
type unifier struct {
|
||||
safe VarSet
|
||||
unified VarSet
|
||||
unknown map[Var]VarSet
|
||||
}
|
||||
|
||||
func (u *unifier) isSafe(x Var) bool {
|
||||
return u.safe.Contains(x) || u.unified.Contains(x)
|
||||
}
|
||||
|
||||
func (u *unifier) unify(a *Term, b *Term) {
|
||||
|
||||
switch a := a.Value.(type) {
|
||||
|
||||
case Var:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
if u.isSafe(b) {
|
||||
u.markSafe(a)
|
||||
} else if u.isSafe(a) {
|
||||
u.markSafe(b)
|
||||
} else {
|
||||
u.markUnknown(a, b)
|
||||
u.markUnknown(b, a)
|
||||
}
|
||||
case *Array, Object:
|
||||
u.unifyAll(a, b)
|
||||
case Ref:
|
||||
if isRefSafe(b, u.safe) {
|
||||
u.markSafe(a)
|
||||
}
|
||||
case Call:
|
||||
if isCallSafe(b, u.safe) {
|
||||
u.markSafe(a)
|
||||
}
|
||||
default:
|
||||
u.markSafe(a)
|
||||
}
|
||||
|
||||
case Ref:
|
||||
if isRefSafe(a, u.safe) {
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
case *Array, Object:
|
||||
u.markAllSafe(b)
|
||||
}
|
||||
}
|
||||
|
||||
case Call:
|
||||
if isCallSafe(a, u.safe) {
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
case *Array, Object:
|
||||
u.markAllSafe(b)
|
||||
}
|
||||
}
|
||||
|
||||
case *ArrayComprehension:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
case *Array:
|
||||
u.markAllSafe(b)
|
||||
}
|
||||
case *ObjectComprehension:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
case *object:
|
||||
u.markAllSafe(b)
|
||||
}
|
||||
case *SetComprehension:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
}
|
||||
|
||||
case *Array:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.unifyAll(b, a)
|
||||
case *ArrayComprehension, *ObjectComprehension, *SetComprehension:
|
||||
u.markAllSafe(a)
|
||||
case Ref:
|
||||
if isRefSafe(b, u.safe) {
|
||||
u.markAllSafe(a)
|
||||
}
|
||||
case Call:
|
||||
if isCallSafe(b, u.safe) {
|
||||
u.markAllSafe(a)
|
||||
}
|
||||
case *Array:
|
||||
if a.Len() == b.Len() {
|
||||
for i := range a.Len() {
|
||||
u.unify(a.Elem(i), b.Elem(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *object:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.unifyAll(b, a)
|
||||
case Ref:
|
||||
if isRefSafe(b, u.safe) {
|
||||
u.markAllSafe(a)
|
||||
}
|
||||
case Call:
|
||||
if isCallSafe(b, u.safe) {
|
||||
u.markAllSafe(a)
|
||||
}
|
||||
case *object:
|
||||
if a.Len() == b.Len() {
|
||||
_ = a.Iter(func(k, v *Term) error {
|
||||
if v2 := b.Get(k); v2 != nil {
|
||||
u.unify(v, v2)
|
||||
}
|
||||
return nil
|
||||
}) // impossible to return error
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
switch b := b.Value.(type) {
|
||||
case Var:
|
||||
u.markSafe(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unifier) markAllSafe(x Value) {
|
||||
vis := varVisitorPool.Get().WithParams(VarVisitorParams{
|
||||
SkipRefHead: true,
|
||||
SkipObjectKeys: true,
|
||||
SkipClosures: true,
|
||||
})
|
||||
vis.Walk(x)
|
||||
for v := range vis.Vars() {
|
||||
u.markSafe(v)
|
||||
}
|
||||
varVisitorPool.Put(vis)
|
||||
}
|
||||
|
||||
func (u *unifier) markSafe(x Var) {
|
||||
u.unified.Add(x)
|
||||
|
||||
// Add dependencies of 'x' to safe set
|
||||
vs := u.unknown[x]
|
||||
delete(u.unknown, x)
|
||||
for v := range vs {
|
||||
u.markSafe(v)
|
||||
}
|
||||
|
||||
// Add dependants of 'x' to safe set if they have no more
|
||||
// dependencies.
|
||||
for v, deps := range u.unknown {
|
||||
if deps.Contains(x) {
|
||||
delete(deps, x)
|
||||
if len(deps) == 0 {
|
||||
u.markSafe(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unifier) markUnknown(a, b Var) {
|
||||
if _, ok := u.unknown[a]; !ok {
|
||||
u.unknown[a] = NewVarSet(b)
|
||||
} else {
|
||||
u.unknown[a].Add(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unifier) unifyAll(a Var, b Value) {
|
||||
if u.isSafe(a) {
|
||||
u.markAllSafe(b)
|
||||
} else {
|
||||
vis := varVisitorPool.Get().WithParams(VarVisitorParams{
|
||||
SkipRefHead: true,
|
||||
SkipObjectKeys: true,
|
||||
SkipClosures: true,
|
||||
})
|
||||
vis.Walk(b)
|
||||
unsafe := vis.Vars().Diff(u.safe).Diff(u.unified)
|
||||
if len(unsafe) == 0 {
|
||||
u.markSafe(a)
|
||||
} else {
|
||||
for v := range unsafe {
|
||||
u.markUnknown(a, v)
|
||||
}
|
||||
}
|
||||
varVisitorPool.Put(vis)
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// VarSet represents a set of variables.
|
||||
type VarSet map[Var]struct{ *Location }
|
||||
|
||||
// NewVarSet returns a new VarSet containing the specified variables.
|
||||
func NewVarSet(vs ...Var) VarSet {
|
||||
s := make(VarSet, len(vs))
|
||||
for _, v := range vs {
|
||||
s.Add(v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewVarSet returns a new VarSet containing the specified variables.
|
||||
func NewVarSetOfSize(size int) VarSet {
|
||||
return make(VarSet, size)
|
||||
}
|
||||
|
||||
// Add updates the set to include the variable "v".
|
||||
func (s VarSet) Add(v Var) {
|
||||
if _, ok := s[v]; !ok {
|
||||
s[v] = struct{ *Location }{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s VarSet) AddLocation(v Var, l *Location) {
|
||||
if entry, ok := s[v]; ok {
|
||||
entry.Location = l
|
||||
s[v] = entry
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the set contains the variable "v".
|
||||
func (s VarSet) Contains(v Var) bool {
|
||||
_, ok := s[v]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Copy returns a shallow copy of the VarSet.
|
||||
func (s VarSet) Copy() VarSet {
|
||||
cpy := NewVarSetOfSize(len(s))
|
||||
for v := range s {
|
||||
cpy.Add(v)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// Diff returns a VarSet containing variables in s that are not in vs.
|
||||
func (s VarSet) Diff(vs VarSet) VarSet {
|
||||
r := NewVarSetOfSize(s.DiffCount(vs))
|
||||
for v := range s {
|
||||
if !vs.Contains(v) {
|
||||
r.Add(v)
|
||||
r.AddLocation(v, s[v].Location)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// DiffCount returns the number of variables in s that are not in vs.
|
||||
func (s VarSet) DiffCount(vs VarSet) (i int) {
|
||||
for v := range s {
|
||||
if !vs.Contains(v) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Equal returns true if s contains exactly the same elements as vs.
|
||||
func (s VarSet) Equal(vs VarSet) bool {
|
||||
if len(s) != len(vs) {
|
||||
return false
|
||||
}
|
||||
for v := range s {
|
||||
if !vs.Contains(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Intersect returns a VarSet containing variables in s that are in vs.
|
||||
func (s VarSet) Intersect(vs VarSet) VarSet {
|
||||
i := 0
|
||||
for v := range s {
|
||||
if vs.Contains(v) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
r := NewVarSetOfSize(i)
|
||||
for v := range s {
|
||||
if vs.Contains(v) {
|
||||
r.Add(v)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Sorted returns a new sorted slice of vars from s.
|
||||
func (s VarSet) Sorted() []Var {
|
||||
sorted := make([]Var, 0, len(s))
|
||||
for v := range s {
|
||||
sorted = append(sorted, v)
|
||||
}
|
||||
slices.SortFunc(sorted, VarCompare)
|
||||
return sorted
|
||||
}
|
||||
|
||||
// Update merges the other VarSet into this VarSet.
|
||||
func (s VarSet) Update(vs VarSet) {
|
||||
for v := range vs {
|
||||
s.Add(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s VarSet) String() string {
|
||||
return fmt.Sprintf("%v", util.KeysSorted(s))
|
||||
}
|
||||
+1079
File diff suppressed because it is too large
Load Diff
+1026
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user