621 lines
14 KiB
Go
621 lines
14 KiB
Go
// 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.op
|
|
|
|
package ast
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
)
|
|
|
|
const (
|
|
// commentsKey is the global map key for the comments slice.
|
|
commentsKey = "comments"
|
|
|
|
// filenameKey is the global map key for the filename.
|
|
filenameKey = "filename"
|
|
)
|
|
|
|
type program struct {
|
|
buf []interface{}
|
|
comments interface{}
|
|
}
|
|
|
|
type ruleExt struct {
|
|
loc *Location
|
|
term *Term
|
|
body Body
|
|
}
|
|
|
|
// currentLocation converts the parser context to a Location object.
|
|
func currentLocation(c *current) *Location {
|
|
return NewLocation(c.text, c.globalStore[filenameKey].(string), c.pos.line, c.pos.col)
|
|
}
|
|
|
|
func makeProgram(c *current, vals interface{}) (interface{}, error) {
|
|
var buf []interface{}
|
|
if vals == nil {
|
|
return buf, nil
|
|
}
|
|
ifaceSlice := vals.([]interface{})
|
|
head := ifaceSlice[0]
|
|
buf = append(buf, head)
|
|
for _, tail := range ifaceSlice[1].([]interface{}) {
|
|
stmt := tail.([]interface{})[1]
|
|
buf = append(buf, stmt)
|
|
}
|
|
return program{buf, c.globalStore[commentsKey]}, nil
|
|
}
|
|
|
|
func makePackage(loc *Location, value interface{}) (interface{}, error) {
|
|
// All packages are implicitly declared under the default root document.
|
|
term := value.(*Term)
|
|
path := Ref{DefaultRootDocument.Copy().SetLocation(term.Location)}
|
|
switch v := term.Value.(type) {
|
|
case Ref:
|
|
// Convert head of package Ref to String because it will be prefixed
|
|
// with the root document variable.
|
|
head := StringTerm(string(v[0].Value.(Var))).SetLocation(v[0].Location)
|
|
tail := v[1:]
|
|
if !tail.IsGround() {
|
|
return nil, fmt.Errorf("package name cannot contain variables: %v", v)
|
|
}
|
|
|
|
// We do not allow non-string values in package names.
|
|
// Because documents are typically represented as JSON, non-string keys are
|
|
// not allowed for now.
|
|
// TODO(tsandall): consider special syntax for namespacing under arrays.
|
|
for _, p := range tail {
|
|
_, ok := p.Value.(String)
|
|
if !ok {
|
|
return nil, fmt.Errorf("package name cannot contain non-string values: %v", v)
|
|
}
|
|
}
|
|
path = append(path, head)
|
|
path = append(path, tail...)
|
|
case Var:
|
|
s := StringTerm(string(v)).SetLocation(term.Location)
|
|
path = append(path, s)
|
|
}
|
|
pkg := &Package{Location: loc, Path: path}
|
|
return pkg, nil
|
|
}
|
|
|
|
func makeImport(loc *Location, path, alias interface{}) (interface{}, error) {
|
|
imp := &Import{}
|
|
imp.Location = loc
|
|
imp.Path = path.(*Term)
|
|
if err := IsValidImportPath(imp.Path.Value); err != nil {
|
|
return nil, err
|
|
}
|
|
if alias == nil {
|
|
return imp, nil
|
|
}
|
|
aliasSlice := alias.([]interface{})
|
|
// Import definition above describes the "alias" slice. We only care about the "Var" element.
|
|
imp.Alias = aliasSlice[3].(*Term).Value.(Var)
|
|
return imp, nil
|
|
}
|
|
|
|
func makeDefaultRule(loc *Location, name, operator, value interface{}) (interface{}, error) {
|
|
|
|
if string(operator.([]uint8)) == Assign.Infix {
|
|
return nil, fmt.Errorf("default rules must use = operator (not := operator)")
|
|
}
|
|
|
|
term := value.(*Term)
|
|
var err error
|
|
|
|
vis := NewGenericVisitor(func(x interface{}) bool {
|
|
if err != nil {
|
|
return true
|
|
}
|
|
switch x.(type) {
|
|
case *ArrayComprehension, *ObjectComprehension, *SetComprehension: // skip closures
|
|
return true
|
|
case Ref, Var:
|
|
err = fmt.Errorf("default rule value cannot contain %v", TypeName(x))
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
vis.Walk(term)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := NewBody(NewExpr(BooleanTerm(true).SetLocation(loc)))
|
|
|
|
rule := &Rule{
|
|
Location: loc,
|
|
Default: true,
|
|
Head: &Head{
|
|
Location: loc,
|
|
Name: name.(*Term).Value.(Var),
|
|
Value: value.(*Term),
|
|
},
|
|
Body: body,
|
|
}
|
|
rule.Body[0].Location = loc
|
|
|
|
return []*Rule{rule}, nil
|
|
}
|
|
|
|
func makeRule(loc *Location, head, rest interface{}) (interface{}, error) {
|
|
|
|
if head == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
sl := rest.([]interface{})
|
|
|
|
rules := []*Rule{
|
|
{
|
|
Location: loc,
|
|
Head: head.(*Head),
|
|
Body: sl[0].(Body),
|
|
},
|
|
}
|
|
|
|
var ordered bool
|
|
prev := rules[0]
|
|
|
|
for i, elem := range sl[1].([]interface{}) {
|
|
|
|
next := elem.([]interface{})
|
|
re := next[1].(ruleExt)
|
|
|
|
if rules[0].Head.Assign {
|
|
return nil, errElseAssignOperator
|
|
}
|
|
|
|
if re.term == nil {
|
|
if ordered {
|
|
return nil, fmt.Errorf("expected 'else' keyword")
|
|
}
|
|
rules = append(rules, &Rule{
|
|
Location: re.loc,
|
|
Head: prev.Head.Copy(),
|
|
Body: re.body,
|
|
})
|
|
} else {
|
|
if (rules[0].Head.DocKind() != CompleteDoc) || (i != 0 && !ordered) {
|
|
return nil, fmt.Errorf("unexpected 'else' keyword")
|
|
}
|
|
ordered = true
|
|
curr := &Rule{
|
|
Location: re.loc,
|
|
Head: &Head{
|
|
Name: prev.Head.Name,
|
|
Args: prev.Head.Args.Copy(),
|
|
Value: re.term,
|
|
Location: re.term.Location,
|
|
},
|
|
Body: re.body,
|
|
}
|
|
prev.Else = curr
|
|
prev = curr
|
|
}
|
|
}
|
|
|
|
return rules, nil
|
|
}
|
|
|
|
func makeRuleHead(loc *Location, name, args, key, value interface{}) (interface{}, error) {
|
|
|
|
head := &Head{}
|
|
|
|
head.Location = loc
|
|
head.Name = name.(*Term).Value.(Var)
|
|
|
|
if args != nil && key != nil {
|
|
return nil, fmt.Errorf("partial rules cannot take arguments")
|
|
}
|
|
|
|
if args != nil {
|
|
argSlice := args.([]interface{})
|
|
head.Args = argSlice[3].(Args)
|
|
}
|
|
|
|
if key != nil {
|
|
keySlice := key.([]interface{})
|
|
// Head definition above describes the "key" slice. We care about the "Term" element.
|
|
head.Key = keySlice[3].(*Term)
|
|
}
|
|
|
|
if value != nil {
|
|
valueSlice := value.([]interface{})
|
|
operator := string(valueSlice[1].([]uint8))
|
|
|
|
if operator == Assign.Infix {
|
|
if head.Key != nil {
|
|
return nil, errPartialRuleAssignOperator
|
|
} else if len(head.Args) > 0 {
|
|
return nil, errFunctionAssignOperator
|
|
}
|
|
head.Assign = true
|
|
}
|
|
|
|
// Head definition above describes the "value" slice. We care about the "Term" element.
|
|
head.Value = valueSlice[len(valueSlice)-1].(*Term)
|
|
}
|
|
|
|
if key == nil && value == nil {
|
|
head.Value = BooleanTerm(true).SetLocation(head.Location)
|
|
}
|
|
|
|
if key != nil && value != nil {
|
|
switch head.Key.Value.(type) {
|
|
case Var, String, Ref: // nop
|
|
default:
|
|
return nil, fmt.Errorf("object key must be string, var, or ref, not %v", TypeName(head.Key.Value))
|
|
}
|
|
}
|
|
|
|
return head, nil
|
|
}
|
|
|
|
func makeArgs(list interface{}) (interface{}, error) {
|
|
termSlice := list.([]*Term)
|
|
args := make(Args, len(termSlice))
|
|
for i := 0; i < len(args); i++ {
|
|
args[i] = termSlice[i]
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func makeRuleExt(loc *Location, val, b interface{}) (interface{}, error) {
|
|
bs := b.([]interface{})
|
|
body := bs[1].(Body)
|
|
|
|
if val == nil {
|
|
term := BooleanTerm(true)
|
|
term.Location = loc
|
|
return ruleExt{term.Location, term, body}, nil
|
|
}
|
|
|
|
vs := val.([]interface{})
|
|
t := vs[3].(*Term)
|
|
return ruleExt{loc, t, body}, nil
|
|
}
|
|
|
|
func makeLiteral(negated, value, with interface{}) (interface{}, error) {
|
|
|
|
expr := value.(*Expr)
|
|
|
|
expr.Negated = negated.(bool)
|
|
|
|
if with != nil {
|
|
expr.With = with.([]*With)
|
|
}
|
|
|
|
return expr, nil
|
|
}
|
|
|
|
func makeLiteralExpr(loc *Location, lhs, rest interface{}) (interface{}, error) {
|
|
|
|
if rest == nil {
|
|
if call, ok := lhs.(*Term).Value.(Call); ok {
|
|
return NewExpr([]*Term(call)).SetLocation(loc), nil
|
|
}
|
|
return NewExpr(lhs).SetLocation(loc), nil
|
|
}
|
|
|
|
termSlice := rest.([]interface{})
|
|
terms := []*Term{
|
|
termSlice[1].(*Term),
|
|
lhs.(*Term),
|
|
termSlice[3].(*Term),
|
|
}
|
|
|
|
expr := NewExpr(terms).SetLocation(loc)
|
|
|
|
return expr, nil
|
|
}
|
|
|
|
func makeSomeDeclLiteral(loc *Location, sl interface{}) (interface{}, error) {
|
|
symbols := sl.([]*Term)
|
|
return NewExpr(&SomeDecl{Location: loc, Symbols: symbols}).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeSomeDeclSymbols(head interface{}, rest interface{}) (interface{}, error) {
|
|
|
|
var symbols []*Term
|
|
|
|
symbols = append(symbols, head.(*Term))
|
|
|
|
if sl1, ok := rest.([]interface{}); ok {
|
|
for i := range sl1 {
|
|
if sl2, ok := sl1[i].([]interface{}); ok {
|
|
symbols = append(symbols, sl2[3].(*Term))
|
|
}
|
|
}
|
|
}
|
|
|
|
return symbols, nil
|
|
}
|
|
|
|
func makeWithKeywordList(head, tail interface{}) (interface{}, error) {
|
|
var withs []*With
|
|
|
|
if head == nil {
|
|
return withs, nil
|
|
}
|
|
|
|
sl := tail.([]interface{})
|
|
|
|
withs = make([]*With, 0, len(sl)+1)
|
|
withs = append(withs, head.(*With))
|
|
|
|
for i := range sl {
|
|
withSlice := sl[i].([]interface{})
|
|
withs = append(withs, withSlice[1].(*With))
|
|
}
|
|
|
|
return withs, nil
|
|
}
|
|
|
|
func makeWithKeyword(loc *Location, target, value interface{}) (interface{}, error) {
|
|
w := &With{
|
|
Target: target.(*Term),
|
|
Value: value.(*Term),
|
|
}
|
|
return w.SetLocation(loc), nil
|
|
}
|
|
|
|
func makeExprTerm(loc *Location, lhs, rest interface{}) (interface{}, error) {
|
|
|
|
if rest == nil {
|
|
return lhs, nil
|
|
}
|
|
|
|
sl := rest.([]interface{})
|
|
|
|
if len(sl) == 0 {
|
|
return lhs, nil
|
|
}
|
|
|
|
for i := range sl {
|
|
termSlice := sl[i].([]interface{})
|
|
call := Call{
|
|
termSlice[1].(*Term),
|
|
lhs.(*Term),
|
|
termSlice[3].(*Term),
|
|
}
|
|
lhs = NewTerm(call).SetLocation(loc)
|
|
}
|
|
|
|
return lhs, nil
|
|
}
|
|
|
|
func makeCall(loc *Location, operator, args interface{}) (interface{}, error) {
|
|
|
|
termSlice := args.([]*Term)
|
|
termOperator := operator.(*Term)
|
|
|
|
call := make(Call, len(termSlice)+1)
|
|
|
|
if _, ok := termOperator.Value.(Var); ok {
|
|
termOperator = RefTerm(termOperator).SetLocation(loc)
|
|
}
|
|
|
|
call[0] = termOperator
|
|
|
|
for i := 1; i < len(call); i++ {
|
|
call[i] = termSlice[i-1]
|
|
}
|
|
|
|
return NewTerm(call).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeBraceEnclosedBody(loc *Location, body interface{}) (interface{}, error) {
|
|
if body != nil {
|
|
return body, nil
|
|
}
|
|
return NewBody(NewExpr(ObjectTerm().SetLocation(loc)).SetLocation(loc)), nil
|
|
}
|
|
|
|
func makeBody(head, tail interface{}, pos int) (interface{}, error) {
|
|
|
|
sl := tail.([]interface{})
|
|
body := make(Body, len(sl)+1)
|
|
body[0] = head.(*Expr)
|
|
|
|
for i := 1; i < len(body); i++ {
|
|
body[i] = sl[i-1].([]interface{})[pos].(*Expr)
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
func makeExprTermList(head, tail interface{}) (interface{}, error) {
|
|
|
|
var terms []*Term
|
|
|
|
if head == nil {
|
|
return terms, nil
|
|
}
|
|
|
|
sl := tail.([]interface{})
|
|
|
|
terms = make([]*Term, 0, len(sl)+1)
|
|
terms = append(terms, head.(*Term))
|
|
|
|
for i := range sl {
|
|
termSlice := sl[i].([]interface{})
|
|
terms = append(terms, termSlice[3].(*Term))
|
|
}
|
|
|
|
return terms, nil
|
|
}
|
|
|
|
func makeExprTermPairList(head, tail interface{}) (interface{}, error) {
|
|
|
|
var terms [][2]*Term
|
|
|
|
if head == nil {
|
|
return terms, nil
|
|
}
|
|
|
|
sl := tail.([]interface{})
|
|
|
|
terms = make([][2]*Term, 0, len(sl)+1)
|
|
terms = append(terms, head.([2]*Term))
|
|
|
|
for i := range sl {
|
|
termSlice := sl[i].([]interface{})
|
|
terms = append(terms, termSlice[3].([2]*Term))
|
|
}
|
|
|
|
return terms, nil
|
|
}
|
|
|
|
func makeExprTermPair(key, value interface{}) (interface{}, error) {
|
|
return [2]*Term{key.(*Term), value.(*Term)}, nil
|
|
}
|
|
|
|
func makeInfixOperator(loc *Location, text []byte) (interface{}, error) {
|
|
op := string(text)
|
|
for _, b := range Builtins {
|
|
if string(b.Infix) == op {
|
|
op = string(b.Name)
|
|
}
|
|
}
|
|
operator := RefTerm(VarTerm(op).SetLocation(loc)).SetLocation(loc)
|
|
return operator, nil
|
|
}
|
|
|
|
func makeArray(loc *Location, list interface{}) (interface{}, error) {
|
|
termSlice := list.([]*Term)
|
|
return ArrayTerm(termSlice...).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeObject(loc *Location, list interface{}) (interface{}, error) {
|
|
termPairSlice := list.([][2]*Term)
|
|
return ObjectTerm(termPairSlice...).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeSet(loc *Location, list interface{}) (interface{}, error) {
|
|
termSlice := list.([]*Term)
|
|
return SetTerm(termSlice...).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeArrayComprehension(loc *Location, head, body interface{}) (interface{}, error) {
|
|
return ArrayComprehensionTerm(head.(*Term), body.(Body)).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeSetComprehension(loc *Location, head, body interface{}) (interface{}, error) {
|
|
return SetComprehensionTerm(head.(*Term), body.(Body)).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeObjectComprehension(loc *Location, head, body interface{}) (interface{}, error) {
|
|
pair := head.([2]*Term)
|
|
return ObjectComprehensionTerm(pair[0], pair[1], body.(Body)).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeRef(loc *Location, head, rest interface{}) (interface{}, error) {
|
|
|
|
headTerm := head.(*Term)
|
|
ifaceSlice := rest.([]interface{})
|
|
|
|
if len(ifaceSlice) == 0 {
|
|
return headTerm, nil
|
|
}
|
|
|
|
ref := make(Ref, len(ifaceSlice)+1)
|
|
ref[0] = headTerm
|
|
|
|
for i := 1; i < len(ref); i++ {
|
|
ref[i] = ifaceSlice[i-1].(*Term)
|
|
}
|
|
|
|
return NewTerm(ref).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeRefOperandDot(loc *Location, val interface{}) (interface{}, error) {
|
|
return StringTerm(string(val.(*Term).Value.(Var))).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeVar(loc *Location, text interface{}) (interface{}, error) {
|
|
str := string(text.([]byte))
|
|
return VarTerm(str).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeNumber(loc *Location, text interface{}) (interface{}, error) {
|
|
f, ok := new(big.Float).SetString(string(text.([]byte)))
|
|
if !ok {
|
|
// This indicates the grammar is out-of-sync with what the string
|
|
// representation of floating point numbers. This should not be
|
|
// possible.
|
|
panic("illegal value")
|
|
}
|
|
|
|
// Put limit on size of exponent to prevent non-linear cost of String()
|
|
// function on big.Float from causing denial of service: https://github.com/golang/go/issues/11068
|
|
//
|
|
// n == sign * mantissa * 2^exp
|
|
// 0.5 <= mantissa < 1.0
|
|
//
|
|
// The limit is arbitrary.
|
|
exp := f.MantExp(nil)
|
|
if exp > 1e5 || exp < -1e5 {
|
|
return nil, fmt.Errorf("number too big")
|
|
}
|
|
|
|
return NumberTerm(json.Number(f.String())).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeString(loc *Location, text interface{}) (interface{}, error) {
|
|
var v string
|
|
err := json.Unmarshal(text.([]byte), &v)
|
|
return StringTerm(v).SetLocation(loc), err
|
|
}
|
|
|
|
func makeRawString(loc *Location, text interface{}) (interface{}, error) {
|
|
s := string(text.([]byte))
|
|
s = s[1 : len(s)-1] // Trim surrounding quotes.
|
|
return StringTerm(s).SetLocation(loc), nil
|
|
}
|
|
|
|
func makeNonterminatedString(loc *Location, s string) (interface{}, error) {
|
|
return StringTerm(s).SetLocation(loc), fmt.Errorf("found non-terminated string literal")
|
|
}
|
|
|
|
func makeBool(loc *Location, text interface{}) (interface{}, error) {
|
|
var term *Term
|
|
if string(text.([]byte)) == "true" {
|
|
term = BooleanTerm(true)
|
|
} else {
|
|
term = BooleanTerm(false)
|
|
}
|
|
return term.SetLocation(loc), nil
|
|
}
|
|
|
|
func makeNull(loc *Location) (interface{}, error) {
|
|
return NullTerm().SetLocation(loc), nil
|
|
}
|
|
|
|
func makeComments(c *current, text interface{}) (interface{}, error) {
|
|
|
|
var buf bytes.Buffer
|
|
for _, x := range text.([]interface{}) {
|
|
buf.Write(x.([]byte))
|
|
}
|
|
|
|
comment := NewComment(buf.Bytes())
|
|
comment.Location = currentLocation(c)
|
|
comments := c.globalStore[commentsKey].(map[commentKey]*Comment)
|
|
key := commentKey{
|
|
File: comment.Location.File,
|
|
Row: comment.Location.Row,
|
|
Col: comment.Location.Col,
|
|
}
|
|
comments[key] = comment
|
|
return comment, nil
|
|
}
|