mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Add variables to the grammar
This change set also includes a bit of refactoring: - Renamed jsonlog to opalog - Renamed Dictionary to Object - Split AST into separate files - Tweaked parser definition to separate terms with whitespace - Renamed helper functions in parser_test.go to distinguish from cases - Moved reflection helper into test suite and renamed - Modified Term.String() to make output more readable - Reorganized the grammar file - Allow scalars and variables as object keys. We will deal with this when serializing to JSON.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# Use of this source code is governed by an Apache2
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
PACKAGES := github.com/open-policy-agent/opa/jsonlog/.../ \
|
||||
PACKAGES := github.com/open-policy-agent/opa/opalog/.../ \
|
||||
github.com/open-policy-agent/opa/cmd/.../
|
||||
|
||||
BUILD_COMMIT := $(shell ./build/get-build-commit.sh)
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ If you need to update the dependencies:
|
||||
|
||||
## Opalog
|
||||
|
||||
If you need to modify the Opalog syntax you must update jsonlog/parser.peg
|
||||
If you need to modify the Opalog syntax you must update opalog/parser.peg
|
||||
and run `make generate` to re-generate the parser code.
|
||||
|
||||
> If you encounter an error because "pigeon" is not installed, run `glide
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
{
|
||||
// Command json parses JSON as defined by [1].
|
||||
//
|
||||
// BUGS: the escaped forward solidus (`\/`) is not currently handled for strings.
|
||||
//
|
||||
// TODO: check if JSON's numbers are a subset of Go's numbers, since we are
|
||||
// assuming they are. Currently representing all numbers as float64.
|
||||
// TODO: think about relaxing a Dictionary to allow numeric keys, as they are common in practice.
|
||||
// [1]: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
||||
package jsonlog
|
||||
|
||||
func toIfaceSlice(v interface{}) []interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return v.([]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
Prog <- _ vals:Term+ EOF {
|
||||
if vals == nil {
|
||||
return make([]interface{}, 0), nil
|
||||
}
|
||||
return vals.([]interface{}), nil
|
||||
}
|
||||
|
||||
Term <- val:( Dictionary / Array / Number / String / Bool / Null ) _ {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
Dictionary <- '{' _ vals:( String _ ':' _ Term ( ',' _ String _ ':' _ Term )* )? '}' {
|
||||
valsSl := toIfaceSlice(vals)
|
||||
if len(valsSl) == 0 {
|
||||
return NewTerm([]*Term{}, DICTIONARY, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
restSl := toIfaceSlice(valsSl[5])
|
||||
// Storing dictionary arguments as a set of KeyValue pairs since we may not be
|
||||
// able to evaluate the keys (e.g. the key may be a variable)
|
||||
res := NewKeyValueSet()
|
||||
res.Add(NewKeyValue(valsSl[0].(*Term), valsSl[4].(*Term)))
|
||||
for _, v := range restSl {
|
||||
vSl := toIfaceSlice(v)
|
||||
res.Add(NewKeyValue(vSl[2].(*Term), vSl[6].(*Term)))
|
||||
}
|
||||
t := NewTerm(res, DICTIONARY, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //res, nil
|
||||
}
|
||||
|
||||
Array <- '[' _ vals:( Value ( ',' _ Value )* )? ']' {
|
||||
valsSl := toIfaceSlice(vals)
|
||||
if len(valsSl) == 0 {
|
||||
return NewTerm([]*Term{}, ARRAY, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
restSl := toIfaceSlice(valsSl[1])
|
||||
res := make([]*Term, 1 + len(valsSl))
|
||||
for i, v := range restSl {
|
||||
vSl := toIfaceSlice(v)
|
||||
res[i] = vSl[2].(*Term)
|
||||
}
|
||||
t := NewTerm(res, ARRAY, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //res, nil
|
||||
}
|
||||
|
||||
Number <- '-'? Integer ( '.' DecimalDigit+ )? Exponent? {
|
||||
// JSON numbers have the same syntax as Go's, and are parseable using
|
||||
// strconv.
|
||||
v, err := strconv.ParseFloat(string(c.text), 64)
|
||||
t := NewTerm(v, NUMBER, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, err
|
||||
}
|
||||
|
||||
Integer <- '0' / NonZeroDecimalDigit DecimalDigit*
|
||||
|
||||
Exponent <- 'e'i [+-]? DecimalDigit+
|
||||
|
||||
String <- '"' ( !EscapedChar . / '\\' EscapeSequence )* '"' {
|
||||
// TODO : the forward slash (solidus) is not a valid escape in Go, it will
|
||||
// fail if there's one in the string
|
||||
v, err := strconv.Unquote(string(c.text))
|
||||
t := NewTerm(v, STRING, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, err // v, err
|
||||
}
|
||||
|
||||
EscapedChar <- [\x00-\x1f"\\]
|
||||
|
||||
EscapeSequence <- SingleCharEscape / UnicodeEscape
|
||||
|
||||
SingleCharEscape <- ["\\/bfnrt]
|
||||
|
||||
UnicodeEscape <- 'u' HexDigit HexDigit HexDigit HexDigit
|
||||
|
||||
DecimalDigit <- [0-9]
|
||||
|
||||
NonZeroDecimalDigit <- [1-9]
|
||||
|
||||
HexDigit <- [0-9a-f]i
|
||||
|
||||
Bool <- "true" {
|
||||
t := NewTerm(true, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil // true, nil
|
||||
} / "false" {
|
||||
t := NewTerm(false, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil // false, nil
|
||||
}
|
||||
|
||||
Null <- "null" {
|
||||
t := NewTerm(nil, NULL, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //nil, nil
|
||||
}
|
||||
|
||||
_ "whitespace" <- [ \t\r\n]*
|
||||
|
||||
EOF <- !.
|
||||
@@ -1,117 +0,0 @@
|
||||
// Copyright 2015 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 jsonlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Printf
|
||||
|
||||
func testTermEqual(t *testing.T, x *Term, y *Term) {
|
||||
if !x.Equal(y) {
|
||||
t.Errorf("Failure on equality: \n%s and \n%s\n", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func testTermNotEqual(t *testing.T, x *Term, y *Term) {
|
||||
if x.Equal(y) {
|
||||
t.Errorf("Failure on non-equality: \n%s and \n%s\n", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// Test equality on pure-json terms
|
||||
func TestEqualJsonTerms(t *testing.T) {
|
||||
testTermEqual(t, NewNull(), NewNull())
|
||||
testTermEqual(t, GoTerm(true), GoTerm(true))
|
||||
testTermEqual(t, GoTerm(5), GoTerm(5))
|
||||
testTermEqual(t, GoTerm("a string"), GoTerm("a string"))
|
||||
testTermEqual(t, GoTerm(map[int]int{1: 2}), GoTerm(map[int]int{1: 2}))
|
||||
testTermEqual(t, GoTerm(map[int]int{1: 2, 3: 4}), GoTerm(map[int]int{1: 2, 3: 4}))
|
||||
testTermEqual(t, GoTerm([]int{1, 2, 3}), GoTerm([]int{1, 2, 3}))
|
||||
|
||||
testTermNotEqual(t, NewNull(), GoTerm(true))
|
||||
testTermNotEqual(t, GoTerm(true), GoTerm(false))
|
||||
testTermNotEqual(t, GoTerm(5), GoTerm(7))
|
||||
testTermNotEqual(t, GoTerm("a string"), GoTerm("abc"))
|
||||
testTermNotEqual(t, GoTerm(map[int]int{3: 2}), GoTerm(map[int]int{1: 2}))
|
||||
testTermNotEqual(t, GoTerm(map[int]int{1: 2, 3: 7}), GoTerm(map[int]int{1: 2, 3: 4}))
|
||||
testTermNotEqual(t, GoTerm(5), GoTerm("a string"))
|
||||
testTermNotEqual(t, GoTerm(1), GoTerm(true))
|
||||
testTermNotEqual(t, GoTerm(map[int]int{1: 2, 3: 7}), GoTerm([]int{1, 2, 3, 7}))
|
||||
testTermNotEqual(t, GoTerm([]int{1, 2, 3}), GoTerm([]int{1, 2, 4}))
|
||||
}
|
||||
|
||||
func testParse1Term(t *testing.T, msg string, expr string, correct *Term) interface{} {
|
||||
p, err := Parse("", []byte(expr))
|
||||
if err != nil {
|
||||
t.Errorf("Error on test %s: parse error on %s: %s", msg, expr, err)
|
||||
return nil
|
||||
}
|
||||
parsed := p.([]interface{})
|
||||
if len(parsed) != 1 {
|
||||
t.Errorf("Error on test %s: failed to parse 1 element from %s: %v",
|
||||
msg, expr, parsed)
|
||||
return nil
|
||||
}
|
||||
term := parsed[0].(*Term)
|
||||
if !term.Equal(correct) {
|
||||
t.Errorf("Error on test %s: wrong result on %s. Actual = %v; Correct = %v",
|
||||
msg, expr, term, correct)
|
||||
return nil
|
||||
}
|
||||
return parsed[0]
|
||||
}
|
||||
|
||||
func testParse1TermFail(t *testing.T, msg string, expr string) {
|
||||
p, err := Parse("", []byte(expr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parsed := p.([]interface{})
|
||||
if len(parsed) != 1 {
|
||||
t.Errorf("Error on test %s: failed to parse 1 element from %s: %v", msg, expr, parsed)
|
||||
} else {
|
||||
t.Errorf("Error on test %s: failed to error when parsing %v: %v", msg, expr, parsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScalarTerms(t *testing.T) {
|
||||
testParse1Term(t, "null", "null", NewNull())
|
||||
testParse1Term(t, "true", "true", GoTerm(true))
|
||||
testParse1Term(t, "false", "false", GoTerm(false))
|
||||
testParse1Term(t, "integer", "53", GoTerm(53))
|
||||
testParse1Term(t, "integer2", "-53", GoTerm(-53))
|
||||
testParse1Term(t, "float", "16.7", GoTerm(16.7))
|
||||
testParse1Term(t, "float2", "-16.7", GoTerm(-16.7))
|
||||
testParse1Term(t, "exponent", "6e7", GoTerm(6e7))
|
||||
testParse1Term(t, "string", "\"a string\"", GoTerm("a string"))
|
||||
testParse1Term(t, "string", "\"a string u6abc7def8abc0def with unicode\"",
|
||||
GoTerm("a string u6abc7def8abc0def with unicode"))
|
||||
|
||||
testParse1TermFail(t, "hex", "6abc")
|
||||
testParse1TermFail(t, "non-string", "'a string'")
|
||||
testParse1TermFail(t, "non-bool", "True")
|
||||
testParse1TermFail(t, "non-bool", "False")
|
||||
testParse1TermFail(t, "non-number", "6zxy")
|
||||
testParse1TermFail(t, "non-number2", "6d7")
|
||||
}
|
||||
|
||||
func TestDictionaryTerms(t *testing.T) {
|
||||
correct := GoTerm(map[string]int{"abc": 7, "def": 8})
|
||||
testParse1Term(t, "simple dict", "{\"abc\": 7, \"def\": 8}", correct)
|
||||
}
|
||||
|
||||
// func TestVariables(t *testing.T) {
|
||||
// testParse(t, "variable", "\"a string\"")
|
||||
// }
|
||||
|
||||
// NewNull creates a new NULL term for testing.
|
||||
// Special case since nil could be either NULL or
|
||||
// an empty array.
|
||||
func NewNull() *Term {
|
||||
return NewTerm(nil, NULL, []byte(""), "", 0, 0)
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
// Copyright 2015 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 jsonlog
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// note: iota is built into Go and is auto-incremented
|
||||
NULL = iota
|
||||
BOOLEAN = iota
|
||||
NUMBER = iota
|
||||
STRING = iota
|
||||
VARIABLE = iota
|
||||
REFERENCE = iota
|
||||
ARRAY = iota
|
||||
DICTIONARY = iota
|
||||
)
|
||||
|
||||
// Set is a collection of objects that we can't use a map for.
|
||||
type Set struct {
|
||||
Values []interface{}
|
||||
EqualFunc func(interface{}, interface{}) bool
|
||||
}
|
||||
|
||||
// NewSet returns a new set
|
||||
func NewSet(equal func(interface{}, interface{}) bool) *Set {
|
||||
s := Set{Values: make([]interface{}, 0), EqualFunc: equal}
|
||||
return &s
|
||||
}
|
||||
|
||||
// NewKeyValueSet returns a set for KeyValue pairs
|
||||
func NewKeyValueSet() *Set {
|
||||
f := func(x interface{}, y interface{}) bool {
|
||||
kvx := x.(*KeyValue)
|
||||
kvy := y.(*KeyValue)
|
||||
return kvx.Equal(kvy)
|
||||
}
|
||||
return NewSet(f)
|
||||
}
|
||||
|
||||
// Add an element
|
||||
func (s *Set) Add (x interface{}) {
|
||||
if !s.Contains(x) {
|
||||
s.Values = append(s.Values, x)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the set contains element x
|
||||
func (s *Set) Contains (x interface{}) bool {
|
||||
for _, elem := range s.Values {
|
||||
if s.EqualFunc(x, elem) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Length returns number of elements
|
||||
func (s *Set) Length () int {
|
||||
return len(s.Values)
|
||||
}
|
||||
|
||||
// Equal returns True if the 2 sets have all the same elements
|
||||
func (set1 *Set) Equal (set2 *Set) bool {
|
||||
if len(set1.Values) != len(set2.Values) {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: reimplement natively so we don't use memory
|
||||
diff12 := set1.Difference(set2)
|
||||
if diff12.Length() > 0 {
|
||||
return false
|
||||
}
|
||||
diff21 := set2.Difference(set1)
|
||||
if diff21.Length() > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Difference returns a new set that has all the elements of set1 except those in set2
|
||||
func (set1 *Set) Difference (set2 *Set) *Set {
|
||||
newset := NewSet(set1.EqualFunc)
|
||||
for _, elem := range set1.Values {
|
||||
if !set2.Contains(elem) {
|
||||
newset.Add(elem)
|
||||
}
|
||||
}
|
||||
return newset
|
||||
}
|
||||
|
||||
// Location records a position in source code
|
||||
type Location struct {
|
||||
File string
|
||||
Row int
|
||||
Col int
|
||||
}
|
||||
|
||||
// NewLocation creates a new instance of a location
|
||||
func NewLocation(file string, row int, col int) *Location {
|
||||
l := Location{File: file, Row: row, Col: col}
|
||||
return &l
|
||||
}
|
||||
|
||||
// Term is an argument to a function
|
||||
type Term struct {
|
||||
Value interface{} // actual value, as represented by Go
|
||||
Kind int // type of Term: one of the consts defined above
|
||||
Name []byte // original string representation
|
||||
Location *Location // text location in original source
|
||||
}
|
||||
|
||||
// NewTerm creates a new Term
|
||||
func NewTerm(x interface{}, kind int, orig []byte, file string, row int, col int) *Term {
|
||||
t := Term{Value: x, Kind: kind, Name: orig, Location: NewLocation(file, row, col)}
|
||||
return &t
|
||||
}
|
||||
|
||||
// String prints out a string version of Term.
|
||||
func (t *Term) String() string {
|
||||
return fmt.Sprintf("Term<Value: %v, Kind: %v, Name: %s>", t.Value, t.Kind, t.Name)
|
||||
}
|
||||
|
||||
// KeyValue represents a single key-value pair for a dictionary
|
||||
type KeyValue struct {
|
||||
Key *Term
|
||||
Value *Term
|
||||
}
|
||||
|
||||
// NewKeyValue creates a key-value pair
|
||||
func NewKeyValue(key *Term, value *Term) *KeyValue {
|
||||
kv := KeyValue{Key: key, Value: value}
|
||||
return &kv
|
||||
}
|
||||
|
||||
// String converts a KeyValue into a string
|
||||
func (kv *KeyValue) String() string {
|
||||
return fmt.Sprintf("KeyValue<Key: %s, Value: %s>", kv.Key, kv.Value)
|
||||
}
|
||||
|
||||
// Equal returns T if the keys and values are the same
|
||||
func (kv1 *KeyValue) Equal(kv2 *KeyValue) bool {
|
||||
return kv1.Key.Equal(kv2.Key) && kv1.Value.Equal(kv2.Value)
|
||||
}
|
||||
|
||||
// Equal checks if two terms are equal for their Value and Kind fields.
|
||||
// Ignores differences in pointers.
|
||||
// Will infinite loop on circular Terms (which are never generated by the parser).
|
||||
func (term1 *Term) Equal (term2 *Term) bool {
|
||||
// pointer equality
|
||||
if term1 == term2 {
|
||||
return true
|
||||
}
|
||||
// wrong types
|
||||
if term1.Kind != term2.Kind {
|
||||
return false
|
||||
}
|
||||
// recursive cases
|
||||
switch term1.Kind {
|
||||
case DICTIONARY:
|
||||
// A dictionary is a list of key/value pairs because
|
||||
// the keys may not be simple strings in the language
|
||||
set1 := term1.Value.(*Set)
|
||||
set2 := term2.Value.(*Set)
|
||||
return set1.Equal(set2)
|
||||
case ARRAY:
|
||||
// Golang Value objs for each of the Terms' .Value fields
|
||||
arr1 := term1.Value.([]*Term)
|
||||
arr2 := term2.Value.([]*Term)
|
||||
if len(arr1) != len(arr2) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(arr1); i++ {
|
||||
if !arr1[i].Equal(arr2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return term1.Value == term2.Value
|
||||
}
|
||||
}
|
||||
|
||||
// GoTerm creates a Jsonlog Term from a Go object
|
||||
func GoTerm(x interface{}) *Term {
|
||||
var val interface{}
|
||||
var typ int
|
||||
switch reflect.TypeOf(x).Kind() {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val = float64(reflect.ValueOf(x).Int())
|
||||
typ = NUMBER
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val = float64(reflect.ValueOf(x).Float())
|
||||
typ = NUMBER
|
||||
case reflect.String:
|
||||
val = x
|
||||
typ = STRING
|
||||
case reflect.Bool:
|
||||
val = x
|
||||
typ = BOOLEAN
|
||||
case reflect.Map:
|
||||
kvset := NewKeyValueSet()
|
||||
xval := reflect.ValueOf(x)
|
||||
for _, key := range xval.MapKeys() {
|
||||
kvset.Add(NewKeyValue(GoTerm(key.Interface()), GoTerm(xval.MapIndex(key).Interface())))
|
||||
}
|
||||
val = kvset
|
||||
typ = DICTIONARY
|
||||
case reflect.Slice, reflect.Array:
|
||||
xval := reflect.ValueOf(x)
|
||||
length := xval.Len()
|
||||
arr := make([]*Term, length)
|
||||
for i := 0; i < length; i++ {
|
||||
arr[i] = GoTerm(xval.Index(i).Interface())
|
||||
}
|
||||
val = arr
|
||||
typ = ARRAY
|
||||
default:
|
||||
val = x
|
||||
typ = NULL
|
||||
}
|
||||
return NewTerm(val, typ, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
// returns the result of dereferencing val and
|
||||
// any pointers pointed to by val
|
||||
func dePointer(val reflect.Value) reflect.Value {
|
||||
switch val.Kind() {
|
||||
case reflect.Ptr:
|
||||
return dePointer(val.Elem())
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,5 @@ func main() {
|
||||
|
||||
// Opalog parser generation:
|
||||
//
|
||||
//go:generate pigeon -o jsonlog/parser.go jsonlog/jsonlog.peg
|
||||
//go:generate goimports -w jsonlog/parser.go
|
||||
//go:generate pigeon -o opalog/parser.go opalog/opalog.peg
|
||||
//go:generate goimports -w opalog/parser.go
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
package opalog
|
||||
|
||||
//
|
||||
// BUGS: the escaped forward solidus (`\/`) is not currently handled for strings.
|
||||
//
|
||||
}
|
||||
|
||||
Prog <- _ head:Term tail:( ws Term )* EOF {
|
||||
if head == nil {
|
||||
return make([]interface{}, 0), nil
|
||||
}
|
||||
tailSlice := tail.([]interface{})
|
||||
return append([]interface{}{head}, tailSlice...), nil
|
||||
}
|
||||
|
||||
Term <- val:( Composite / Scalar / Var ) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
Composite <- Object / Array
|
||||
|
||||
Scalar <- Number / String / Bool / Null
|
||||
|
||||
Key <- Scalar / Var
|
||||
|
||||
Object <- '{' _ head:(Key _ ':' _ Term)? tail:( _ ',' _ Key _ ':' _ Term )* _ '}' {
|
||||
set := NewKeyValueSet()
|
||||
|
||||
// Empty object.
|
||||
if head == nil {
|
||||
return NewTerm(set, OBJECT, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
|
||||
// Non-empty object, first key/value pair.
|
||||
// The "head" variable is a slice containing exactly 5 elements (see rule definition above):
|
||||
// [key whitespace colon whitespace key] where the whitespace elements may be nil.
|
||||
headSlice := head.([]interface{})
|
||||
set.Add(NewKeyValue(headSlice[0].(*Term), headSlice[len(headSlice) - 1].(*Term)))
|
||||
|
||||
// Non-empty object, remaining key/value pairs.
|
||||
tailSlice := tail.([]interface{})
|
||||
for _, v := range tailSlice {
|
||||
s := v.([]interface{})
|
||||
// The "s" variable is a slice containing exactly 8 elements (see rule definition above).
|
||||
// This is similar to the "head" variable."
|
||||
set.Add(NewKeyValue(s[3].(*Term), s[len(s) - 1].(*Term)))
|
||||
}
|
||||
|
||||
result := NewTerm(set, OBJECT, c.text, "", c.pos.line, c.pos.col)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Array <- '[' _ head:Term? tail:(_ ',' _ Term)* _ ']' {
|
||||
|
||||
// Empty array.
|
||||
if head == nil {
|
||||
return NewTerm([]*Term{}, ARRAY, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
|
||||
// Non-empty array, first element.
|
||||
var arr []*Term
|
||||
arr = append(arr, head.(*Term))
|
||||
|
||||
// Non-empty array, remaining elements.
|
||||
tailSlice := tail.([]interface{})
|
||||
for _, v := range tailSlice {
|
||||
s := v.([]interface{})
|
||||
// The "s" is a slice containing exactly 4 elements (see rule definition above).
|
||||
// [whitespace comma whitespace value] where the whitespace elements may be nil.
|
||||
arr = append(arr, s[len(s) - 1].(*Term))
|
||||
}
|
||||
|
||||
result := NewTerm(arr, ARRAY, c.text, "", c.pos.line, c.pos.col)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Var <- vals:( AsciiLetter (AsciiLetter / DecimalDigit)* ) {
|
||||
v := &Var{string(c.text)}
|
||||
t := NewTerm(v, VAR, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
Number <- '-'? Integer ( '.' DecimalDigit+ )? Exponent? {
|
||||
// JSON numbers have the same syntax as Go's, and are parseable using
|
||||
// strconv.
|
||||
v, err := strconv.ParseFloat(string(c.text), 64)
|
||||
t := NewTerm(v, NUMBER, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, err
|
||||
}
|
||||
|
||||
String <- '"' ( !EscapedChar . / '\\' EscapeSequence )* '"' {
|
||||
// TODO : the forward slash (solidus) is not a valid escape in Go, it will
|
||||
// fail if there's one in the string
|
||||
v, err := strconv.Unquote(string(c.text))
|
||||
t := NewTerm(v, STRING, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, err // v, err
|
||||
}
|
||||
|
||||
Bool <- "true" {
|
||||
t := NewTerm(true, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
} / "false" {
|
||||
t := NewTerm(false, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
Null <- "null" {
|
||||
t := NewTerm(nil, NULL, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
Integer <- '0' / NonZeroDecimalDigit DecimalDigit*
|
||||
|
||||
Exponent <- 'e'i [+-]? DecimalDigit+
|
||||
|
||||
AsciiLetter <- [A-Za-z_]
|
||||
|
||||
EscapedChar <- [\x00-\x1f"\\]
|
||||
|
||||
EscapeSequence <- SingleCharEscape / UnicodeEscape
|
||||
|
||||
SingleCharEscape <- ["\\/bfnrt]
|
||||
|
||||
UnicodeEscape <- 'u' HexDigit HexDigit HexDigit HexDigit
|
||||
|
||||
DecimalDigit <- [0-9]
|
||||
|
||||
NonZeroDecimalDigit <- [1-9]
|
||||
|
||||
HexDigit <- [0-9a-f]i
|
||||
|
||||
_ "whitespace" <- [ \t\r\n]*
|
||||
|
||||
ws "whitespace" <- [ \t\r\n]+
|
||||
|
||||
EOF <- !.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
// 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 opalog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Printf
|
||||
|
||||
func TestScalarTerms(t *testing.T) {
|
||||
assertParseOneTerm(t, "null", "null", reflectTerm(nil))
|
||||
assertParseOneTerm(t, "true", "true", reflectTerm(true))
|
||||
assertParseOneTerm(t, "false", "false", reflectTerm(false))
|
||||
assertParseOneTerm(t, "integer", "53", reflectTerm(53))
|
||||
assertParseOneTerm(t, "integer2", "-53", reflectTerm(-53))
|
||||
assertParseOneTerm(t, "float", "16.7", reflectTerm(16.7))
|
||||
assertParseOneTerm(t, "float2", "-16.7", reflectTerm(-16.7))
|
||||
assertParseOneTerm(t, "exponent", "6e7", reflectTerm(6e7))
|
||||
assertParseOneTerm(t, "string", "\"a string\"", reflectTerm("a string"))
|
||||
assertParseOneTerm(t, "string", "\"a string u6abc7def8abc0def with unicode\"",
|
||||
reflectTerm("a string u6abc7def8abc0def with unicode"))
|
||||
assertParseOneTermFail(t, "hex", "6abc")
|
||||
assertParseOneTermFail(t, "non-string", "'a string'")
|
||||
assertParseOneTermFail(t, "non-number", "6zxy")
|
||||
assertParseOneTermFail(t, "non-number2", "6d7")
|
||||
assertParseOneTermFail(t, "non-number3", "6\"foo\"")
|
||||
assertParseOneTermFail(t, "non-number4", "6true")
|
||||
assertParseOneTermFail(t, "non-number5", "6false")
|
||||
assertParseOneTermFail(t, "non-number6", "6[null, null]")
|
||||
assertParseOneTermFail(t, "non-number7", "6{\"foo\": \"bar\"}")
|
||||
assertParseOneTermFail(t, "out-of-range", "1e1000")
|
||||
}
|
||||
|
||||
func TestVarTerms(t *testing.T) {
|
||||
assertParseOneTerm(t, "var", "foo", reflectTerm(NewVar("foo")))
|
||||
assertParseOneTerm(t, "var", "foo_bar", reflectTerm(NewVar("foo_bar")))
|
||||
assertParseOneTerm(t, "var", "foo0", reflectTerm(NewVar("foo0")))
|
||||
|
||||
assertParseOneTermFail(t, "non-var", "foo-bar")
|
||||
assertParseOneTermFail(t, "non-var2", "foo-7")
|
||||
}
|
||||
|
||||
func TestObjectWithScalars(t *testing.T) {
|
||||
assertParseOneTerm(t, "number", "{\"abc\": 7, \"def\": 8}", reflectTerm(map[string]int{"abc": 7, "def": 8}))
|
||||
assertParseOneTerm(t, "bool", "{\"abc\": false, \"def\": true}", reflectTerm(map[string]bool{"abc": false, "def": true}))
|
||||
assertParseOneTerm(t, "string", "{\"abc\": \"foo\", \"def\": \"bar\"}", reflectTerm(map[string]string{"abc": "foo", "def": "bar"}))
|
||||
assertParseOneTerm(t, "mixed", "{\"abc\": 7, \"def\": null}", reflectTerm(map[string]interface{}{"abc": 7, "def": nil}))
|
||||
assertParseOneTerm(t, "number key", "{8: 7, \"def\": null}", reflectTerm(map[interface{}]interface{}{8: 7, "def": nil}))
|
||||
assertParseOneTerm(t, "number key 2", "{8.5: 7, \"def\": null}", reflectTerm(map[interface{}]interface{}{8.5: 7, "def": nil}))
|
||||
assertParseOneTerm(t, "bool key", "{true: false}", reflectTerm(map[bool]bool{true: false}))
|
||||
}
|
||||
|
||||
func TestObjectWithVars(t *testing.T) {
|
||||
|
||||
assertParseOneTerm(t, "var keys", "{foo: \"bar\", bar: 64}", newObjectTerm([]*KeyValue{
|
||||
NewKeyValue(reflectTerm(NewVar("foo")), reflectTerm("bar")),
|
||||
NewKeyValue(reflectTerm(NewVar("bar")), reflectTerm(64)),
|
||||
}))
|
||||
|
||||
assertParseOneTerm(t, "nested var keys", "{baz: {foo: \"bar\", bar: qux}}", newObjectTerm([]*KeyValue{
|
||||
NewKeyValue(reflectTerm(NewVar("baz")), newObjectTerm([]*KeyValue{
|
||||
NewKeyValue(reflectTerm(NewVar("foo")), reflectTerm("bar")),
|
||||
NewKeyValue(reflectTerm(NewVar("bar")), reflectTerm(NewVar("qux"))),
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestArrayWithScalars(t *testing.T) {
|
||||
assertParseOneTerm(t, "number", "[1,2,3,4.5]", reflectTerm([]float64{1, 2, 3, 4.5}))
|
||||
assertParseOneTerm(t, "bool", "[true, false, true]", reflectTerm([]bool{true, false, true}))
|
||||
assertParseOneTerm(t, "string", "[\"foo\", \"bar\"]", reflectTerm([]string{"foo", "bar"}))
|
||||
assertParseOneTerm(t, "mixed", "[null, true, 42]", reflectTerm([]interface{}{nil, true, 42}))
|
||||
}
|
||||
|
||||
func TestArrayWithVars(t *testing.T) {
|
||||
assertParseOneTerm(t, "var elements", "[foo, bar, 42]", newArrayTerm([]*Term{reflectTerm(NewVar("foo")), reflectTerm(NewVar("bar")), reflectTerm(42)}))
|
||||
assertParseOneTerm(t, "nested var elements", "[[foo, true], [null, bar], 42]", newArrayTerm(
|
||||
[]*Term{
|
||||
newArrayTerm([]*Term{reflectTerm(NewVar("foo")), reflectTerm(true)}),
|
||||
newArrayTerm([]*Term{reflectTerm(nil), reflectTerm(NewVar("bar"))}),
|
||||
reflectTerm(42),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func TestNestedComposites(t *testing.T) {
|
||||
assertParseOneTerm(t, "nested composites", "[{foo: [\"bar\", baz]}]", newArrayTerm([]*Term{
|
||||
newObjectTerm([]*KeyValue{
|
||||
NewKeyValue(reflectTerm(NewVar("foo")), newArrayTerm([]*Term{
|
||||
reflectTerm("bar"), reflectTerm(NewVar("baz")),
|
||||
})),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
func assertTermEqual(t *testing.T, x *Term, y *Term) {
|
||||
if !x.Equal(y) {
|
||||
t.Errorf("Failure on equality: \n%s and \n%s\n", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func assertTermNotEqual(t *testing.T, x *Term, y *Term) {
|
||||
if x.Equal(y) {
|
||||
t.Errorf("Failure on non-equality: \n%s and \n%s\n", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func assertParseOneTerm(t *testing.T, msg string, expr string, correct *Term) interface{} {
|
||||
p, err := Parse("", []byte(expr))
|
||||
if err != nil {
|
||||
t.Errorf("Error on test %s: parse error on %s: %s", msg, expr, err)
|
||||
return nil
|
||||
}
|
||||
parsed := p.([]interface{})
|
||||
if len(parsed) != 1 {
|
||||
t.Errorf("Error on test %s: failed to parse 1 element from %s: %v",
|
||||
msg, expr, parsed)
|
||||
return nil
|
||||
}
|
||||
term := parsed[0].(*Term)
|
||||
if !term.Equal(correct) {
|
||||
t.Errorf("Error on test %s: wrong result on %s. Actual = %v; Correct = %v",
|
||||
msg, expr, term, correct)
|
||||
return nil
|
||||
}
|
||||
return parsed[0]
|
||||
}
|
||||
|
||||
func assertParseOneTermFail(t *testing.T, msg string, expr string) {
|
||||
p, err := Parse("", []byte(expr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parsed := p.([]interface{})
|
||||
if len(parsed) != 1 {
|
||||
t.Errorf("Error on test %s: failed to parse 1 element from %s: %v", msg, expr, parsed)
|
||||
} else {
|
||||
t.Errorf("Error on test %s: failed to error when parsing %v: %v", msg, expr, parsed)
|
||||
}
|
||||
}
|
||||
|
||||
func newObjectTerm(o []*KeyValue) *Term {
|
||||
set := NewKeyValueSet()
|
||||
for _, v := range o {
|
||||
set.Add(v)
|
||||
}
|
||||
return NewTerm(set, OBJECT, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
func newArrayTerm(arr []*Term) *Term {
|
||||
return NewTerm(arr, ARRAY, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
func reflectTerm(x interface{}) *Term {
|
||||
|
||||
if x == nil {
|
||||
return NewTerm(nil, NULL, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
if v, ok := x.(*Var); ok {
|
||||
return NewTerm(v, VAR, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
var val interface{}
|
||||
var typ int
|
||||
switch reflect.TypeOf(x).Kind() {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val = float64(reflect.ValueOf(x).Int())
|
||||
typ = NUMBER
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val = float64(reflect.ValueOf(x).Float())
|
||||
typ = NUMBER
|
||||
case reflect.String:
|
||||
val = x
|
||||
typ = STRING
|
||||
case reflect.Bool:
|
||||
val = x
|
||||
typ = BOOLEAN
|
||||
case reflect.Map:
|
||||
kvset := NewKeyValueSet()
|
||||
xval := reflect.ValueOf(x)
|
||||
for _, key := range xval.MapKeys() {
|
||||
kvset.Add(NewKeyValue(reflectTerm(key.Interface()), reflectTerm(xval.MapIndex(key).Interface())))
|
||||
}
|
||||
val = kvset
|
||||
typ = OBJECT
|
||||
case reflect.Slice, reflect.Array:
|
||||
xval := reflect.ValueOf(x)
|
||||
length := xval.Len()
|
||||
arr := make([]*Term, length)
|
||||
for i := 0; i < length; i++ {
|
||||
arr[i] = reflectTerm(xval.Index(i).Interface())
|
||||
}
|
||||
val = arr
|
||||
typ = ARRAY
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected type of term: %v", x))
|
||||
}
|
||||
|
||||
return NewTerm(val, typ, []byte(""), "", 0, 0)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// 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 opalog
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Set is a collection of objects that we can't use a map for.
|
||||
type Set struct {
|
||||
Values []interface{}
|
||||
EqualFunc func(interface{}, interface{}) bool
|
||||
}
|
||||
|
||||
// KeyValue represents a single key-value pair for a dictionary
|
||||
type KeyValue struct {
|
||||
Key *Term
|
||||
Value *Term
|
||||
}
|
||||
|
||||
// NewKeyValue creates a key-value pair
|
||||
func NewKeyValue(key *Term, value *Term) *KeyValue {
|
||||
kv := KeyValue{Key: key, Value: value}
|
||||
return &kv
|
||||
}
|
||||
|
||||
// String converts a KeyValue into a string
|
||||
func (kv *KeyValue) String() string {
|
||||
return fmt.Sprintf("%s: %s", kv.Key.String(), kv.Value.String())
|
||||
}
|
||||
|
||||
// Equal returns T if the keys and values are the same
|
||||
func (kv1 *KeyValue) Equal(kv2 *KeyValue) bool {
|
||||
return kv1.Key.Equal(kv2.Key) && kv1.Value.Equal(kv2.Value)
|
||||
}
|
||||
|
||||
// NewSet returns a new set
|
||||
func NewSet(equal func(interface{}, interface{}) bool) *Set {
|
||||
s := Set{Values: make([]interface{}, 0), EqualFunc: equal}
|
||||
return &s
|
||||
}
|
||||
|
||||
// NewKeyValueSet returns a set for KeyValue pairs
|
||||
func NewKeyValueSet() *Set {
|
||||
f := func(x interface{}, y interface{}) bool {
|
||||
kvx := x.(*KeyValue)
|
||||
kvy := y.(*KeyValue)
|
||||
return kvx.Equal(kvy)
|
||||
}
|
||||
return NewSet(f)
|
||||
}
|
||||
|
||||
// Add an element
|
||||
func (s *Set) Add(x interface{}) {
|
||||
if !s.Contains(x) {
|
||||
s.Values = append(s.Values, x)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the set contains element x
|
||||
func (s *Set) Contains(x interface{}) bool {
|
||||
for _, elem := range s.Values {
|
||||
if s.EqualFunc(x, elem) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Length returns number of elements
|
||||
func (s *Set) Length() int {
|
||||
return len(s.Values)
|
||||
}
|
||||
|
||||
// Equal returns True if the 2 sets have all the same elements
|
||||
func (set1 *Set) Equal(set2 *Set) bool {
|
||||
if len(set1.Values) != len(set2.Values) {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: reimplement natively so we don't use memory
|
||||
diff12 := set1.Difference(set2)
|
||||
if diff12.Length() > 0 {
|
||||
return false
|
||||
}
|
||||
diff21 := set2.Difference(set1)
|
||||
if diff21.Length() > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Difference returns a new set that has all the elements of set1 except those in set2
|
||||
func (set1 *Set) Difference(set2 *Set) *Set {
|
||||
newset := NewSet(set1.EqualFunc)
|
||||
for _, elem := range set1.Values {
|
||||
if !set2.Contains(elem) {
|
||||
newset.Add(elem)
|
||||
}
|
||||
}
|
||||
return newset
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
// Copyright 2015 The OPA Authors. All rights reserved.
|
||||
// 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 jsonlog
|
||||
package opalog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func TestSetAdd(t *testing.T) {
|
||||
eq := func(x interface{}, y interface{}) bool { return x == y }
|
||||
s1 := NewSet(eq)
|
||||
@@ -67,4 +65,3 @@ func TestSetEquality(t *testing.T) {
|
||||
t.Errorf("Equality on sets failed")
|
||||
}
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// 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 opalog
|
||||
|
||||
import "strconv"
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
NULL = iota
|
||||
BOOLEAN = iota
|
||||
NUMBER = iota
|
||||
STRING = iota
|
||||
ARRAY = iota
|
||||
OBJECT = iota
|
||||
VAR = iota
|
||||
)
|
||||
|
||||
// Location records a position in source code
|
||||
type Location struct {
|
||||
File string
|
||||
Row int
|
||||
Col int
|
||||
}
|
||||
|
||||
// NewLocation creates a new instance of a location
|
||||
func NewLocation(file string, row int, col int) *Location {
|
||||
l := Location{File: file, Row: row, Col: col}
|
||||
return &l
|
||||
}
|
||||
|
||||
// Term is an argument to a function
|
||||
type Term struct {
|
||||
Value interface{} // actual value, as represented by Go
|
||||
Kind int // type of Term: one of the consts defined above
|
||||
Name []byte // original string representation
|
||||
Location *Location // text location in original source
|
||||
}
|
||||
|
||||
// NewTerm creates a new Term
|
||||
func NewTerm(x interface{}, kind int, orig []byte, file string, row int, col int) *Term {
|
||||
t := Term{Value: x, Kind: kind, Name: orig, Location: NewLocation(file, row, col)}
|
||||
return &t
|
||||
}
|
||||
|
||||
// String returns the string representation of the Term.
|
||||
func (t *Term) String() string {
|
||||
switch t.Kind {
|
||||
case NULL:
|
||||
return "null"
|
||||
case BOOLEAN:
|
||||
return strconv.FormatBool(t.Value.(bool))
|
||||
case NUMBER:
|
||||
return strconv.FormatFloat(t.Value.(float64), 'G', -1, 64)
|
||||
case STRING:
|
||||
return "\"" + t.Value.(string) + "\""
|
||||
case VAR:
|
||||
return t.Value.(Var).Name
|
||||
case ARRAY:
|
||||
var buf []string
|
||||
for _, v := range t.Value.([]*Term) {
|
||||
buf = append(buf, v.String())
|
||||
}
|
||||
return "[" + strings.Join(buf, ", ") + "]"
|
||||
case OBJECT:
|
||||
set := t.Value.(*Set)
|
||||
var buf []string
|
||||
for _, v := range set.Values {
|
||||
buf = append(buf, v.(*KeyValue).String())
|
||||
}
|
||||
return "{" + strings.Join(buf, ", ") + "}"
|
||||
}
|
||||
panic("unreachable")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Equal checks if two terms are equal for their Value and Kind fields.
|
||||
// Ignores differences in pointers.
|
||||
// Will infinite loop on circular Terms (which are never generated by the parser).
|
||||
func (term1 *Term) Equal(term2 *Term) bool {
|
||||
// pointer equality
|
||||
if term1 == term2 {
|
||||
return true
|
||||
}
|
||||
// wrong types
|
||||
if term1.Kind != term2.Kind {
|
||||
return false
|
||||
}
|
||||
// recursive cases
|
||||
switch term1.Kind {
|
||||
case OBJECT:
|
||||
// A dictionary is a list of key/value pairs because
|
||||
// the keys may not be simple strings in the language
|
||||
set1 := term1.Value.(*Set)
|
||||
set2 := term2.Value.(*Set)
|
||||
return set1.Equal(set2)
|
||||
case ARRAY:
|
||||
// Golang Value objs for each of the Terms' .Value fields
|
||||
arr1 := term1.Value.([]*Term)
|
||||
arr2 := term2.Value.([]*Term)
|
||||
if len(arr1) != len(arr2) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(arr1); i++ {
|
||||
if !arr1[i].Equal(arr2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case VAR:
|
||||
var1 := term1.Value.(*Var)
|
||||
var2 := term2.Value.(*Var)
|
||||
return var1.Name == var2.Name
|
||||
default:
|
||||
return term1.Value == term2.Value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 opalog
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEqualTerms(t *testing.T) {
|
||||
assertTermEqual(t, reflectTerm(nil), reflectTerm(nil))
|
||||
assertTermEqual(t, reflectTerm(true), reflectTerm(true))
|
||||
assertTermEqual(t, reflectTerm(5), reflectTerm(5))
|
||||
assertTermEqual(t, reflectTerm("a string"), reflectTerm("a string"))
|
||||
assertTermEqual(t, reflectTerm(map[int]int{1: 2}), reflectTerm(map[int]int{1: 2}))
|
||||
assertTermEqual(t, reflectTerm(map[int]int{1: 2, 3: 4}), reflectTerm(map[int]int{1: 2, 3: 4}))
|
||||
assertTermEqual(t, reflectTerm([]int{1, 2, 3}), reflectTerm([]int{1, 2, 3}))
|
||||
|
||||
assertTermNotEqual(t, reflectTerm(nil), reflectTerm(true))
|
||||
assertTermNotEqual(t, reflectTerm(true), reflectTerm(false))
|
||||
assertTermNotEqual(t, reflectTerm(5), reflectTerm(7))
|
||||
assertTermNotEqual(t, reflectTerm("a string"), reflectTerm("abc"))
|
||||
assertTermNotEqual(t, reflectTerm(map[int]int{3: 2}), reflectTerm(map[int]int{1: 2}))
|
||||
assertTermNotEqual(t, reflectTerm(map[int]int{1: 2, 3: 7}), reflectTerm(map[int]int{1: 2, 3: 4}))
|
||||
assertTermNotEqual(t, reflectTerm(5), reflectTerm("a string"))
|
||||
assertTermNotEqual(t, reflectTerm(1), reflectTerm(true))
|
||||
assertTermNotEqual(t, reflectTerm(map[int]int{1: 2, 3: 7}), reflectTerm([]int{1, 2, 3, 7}))
|
||||
assertTermNotEqual(t, reflectTerm([]int{1, 2, 3}), reflectTerm([]int{1, 2, 4}))
|
||||
|
||||
assertTermEqual(t, reflectTerm(NewVar("foo")), reflectTerm(NewVar("foo")))
|
||||
assertTermNotEqual(t, reflectTerm(NewVar("foo")), reflectTerm(NewVar("bar")))
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 opalog
|
||||
|
||||
// Var is the AST type representing a variable.
|
||||
type Var struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewVar returns a new variable named "name".
|
||||
func NewVar(name string) *Var {
|
||||
return &Var{name}
|
||||
}
|
||||
|
||||
// Equal returns true if two variables have the same name.
|
||||
func (v *Var) Equal(other *Var) bool {
|
||||
return v.Name == other.Name
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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 opalog
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEqualVarTerms(t *testing.T) {
|
||||
assertTermEqual(t, reflectTerm(NewVar("foo")), reflectTerm(NewVar("foo")))
|
||||
assertTermNotEqual(t, reflectTerm(NewVar("foo")), reflectTerm(NewVar("foobar")))
|
||||
}
|
||||
Reference in New Issue
Block a user