mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Add references to the grammar
The special iterator variable ("_") is not supported yet. The _ variable will
be handled by mangling the variable name while parsing the rules (which are
still to come).
Also refactored terms to use type declaration and type switches. All terms are
now represented by underlying Go types without relying on extra structs. The
Kind attribute on Term has been removed in favour of type switches.
Lastly, removing the generated parser from the repository for now. Once the
grammar has stabilized, we can add the generated code back. The diffs were
unpleasant.
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
.vscode
|
||||
opa
|
||||
opalog/parser.go
|
||||
|
||||
@@ -2,3 +2,4 @@ language: go
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
install: make deps
|
||||
|
||||
@@ -18,17 +18,21 @@ GO := go
|
||||
GO15VENDOREXPERIMENT := 1
|
||||
export GO15VENDOREXPERIMENT
|
||||
|
||||
.PHONY: all generate build test clean
|
||||
.PHONY: all deps generate build test clean
|
||||
|
||||
all: build test
|
||||
|
||||
deps:
|
||||
$(GO) install ./vendor/github.com/PuerkitoBio/pigeon
|
||||
$(GO) install ./vendor/golang.org/x/tools/cmd/goimports
|
||||
|
||||
generate:
|
||||
$(GO) generate
|
||||
|
||||
build:
|
||||
build: generate
|
||||
$(GO) build -o opa $(LDFLAGS)
|
||||
|
||||
test:
|
||||
test: generate
|
||||
$(GO) test -v $(PACKAGES)
|
||||
|
||||
clean:
|
||||
|
||||
+5
-12
@@ -16,9 +16,10 @@ Requirements:
|
||||
|
||||
## Getting Started
|
||||
|
||||
After cloning the repository, you can run `make all` to build the project and
|
||||
execute all of the tests. If this succeeds, there should be a binary
|
||||
in the top directory (opa).
|
||||
After cloning the repository, run `make deps` to install the parser generator ("pigeon") into your workspace.
|
||||
|
||||
Next, run `make all` to build the project and execute all of the tests. If
|
||||
this succeeds, there should be a new binary in the top level directory ("opa").
|
||||
|
||||
Verify the build was successful by running `opa version`.
|
||||
|
||||
@@ -101,12 +102,4 @@ If you need to update the dependencies:
|
||||
|
||||
## Opalog
|
||||
|
||||
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
|
||||
> rebuild` to build and install the vendored dependencies (which include the
|
||||
> parser generator). Note, you will need to have [Glide](https://github.com/Masterminds/glide)
|
||||
> installed for this.
|
||||
|
||||
Commit the changes to the parser.peg and parser.go files.
|
||||
If you need to modify the Opalog syntax you must update opalog/opalog.peg. Both `make build` and `make test` will re-generate the parser but if you want to test the parser generation explicitly you can run `make generate`.
|
||||
+70
-52
@@ -4,6 +4,12 @@ package opalog
|
||||
//
|
||||
// BUGS: the escaped forward solidus (`\/`) is not currently handled for strings.
|
||||
//
|
||||
|
||||
func currentLocation(c *current) *Location {
|
||||
// TODO: Is it possible to propagate file names into the parser?
|
||||
return NewLocation(c.text, "", c.pos.line, c.pos.col)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Prog <- _ head:Term tail:( ws Term )* EOF {
|
||||
@@ -14,7 +20,7 @@ Prog <- _ head:Term tail:( ws Term )* EOF {
|
||||
return append([]interface{}{head}, tailSlice...), nil
|
||||
}
|
||||
|
||||
Term <- val:( Composite / Scalar / Var ) {
|
||||
Term <- val:( Composite / Scalar / Ref / Var ) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
@@ -22,92 +28,104 @@ Composite <- Object / Array
|
||||
|
||||
Scalar <- Number / String / Bool / Null
|
||||
|
||||
Key <- Scalar / Var
|
||||
Key <- Scalar / Ref / Var
|
||||
|
||||
Object <- '{' _ head:(Key _ ':' _ Term)? tail:( _ ',' _ Key _ ':' _ Term )* _ '}' {
|
||||
set := NewKeyValueSet()
|
||||
var buf [][2]*Term
|
||||
|
||||
// Empty object.
|
||||
if head == nil {
|
||||
return NewTerm(set, OBJECT, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
// Empty object.
|
||||
if head == nil {
|
||||
return ObjectTermWithLoc(buf, currentLocation(c)), 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, 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{})
|
||||
buf = append(buf, Item(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)))
|
||||
}
|
||||
// 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."
|
||||
buf = append(buf, Item(s[3].(*Term), s[len(s) - 1].(*Term)))
|
||||
}
|
||||
|
||||
result := NewTerm(set, OBJECT, c.text, "", c.pos.line, c.pos.col)
|
||||
return result, nil
|
||||
return ObjectTermWithLoc(buf, currentLocation(c)), nil
|
||||
}
|
||||
|
||||
Array <- '[' _ head:Term? tail:(_ ',' _ Term)* _ ']' {
|
||||
|
||||
// Empty array.
|
||||
if head == nil {
|
||||
return NewTerm([]*Term{}, ARRAY, c.text, "", c.pos.line, c.pos.col), nil
|
||||
}
|
||||
var buf []*Term
|
||||
|
||||
// Non-empty array, first element.
|
||||
var arr []*Term
|
||||
arr = append(arr, head.(*Term))
|
||||
// Empty array.
|
||||
if head == nil {
|
||||
return ArrayTermWithLoc(buf, currentLocation(c)), nil
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
// Non-empty array, first element.
|
||||
buf = append(buf, head.(*Term))
|
||||
|
||||
result := NewTerm(arr, ARRAY, c.text, "", c.pos.line, c.pos.col)
|
||||
return result, nil
|
||||
// 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.
|
||||
buf = append(buf, s[len(s) - 1].(*Term))
|
||||
}
|
||||
|
||||
return ArrayTermWithLoc(buf, currentLocation(c)), nil
|
||||
}
|
||||
|
||||
Ref <- head:Var tail:( RefDot / RefBracket )+ {
|
||||
buf := []*Term{head.(*Term)}
|
||||
|
||||
tailSlice := tail.([]interface{})
|
||||
for _, v := range tailSlice {
|
||||
buf = append(buf, v.(*Term))
|
||||
}
|
||||
|
||||
return RefTermWithLoc(buf, currentLocation(c)), nil
|
||||
}
|
||||
|
||||
RefDot <- "." val:Var {
|
||||
// Convert the Var into a string because 'foo.bar.baz' is equivalent to 'foo["bar"]["baz"]'.
|
||||
return StringTermWithLoc(string(val.(*Term).Value.(Var)), currentLocation(c)), nil
|
||||
}
|
||||
|
||||
RefBracket <- "[" val:(Scalar / Var) "]" {
|
||||
return val, 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
|
||||
return VarTermWithLoc(string(c.text), currentLocation(c)), 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
|
||||
return NumberTermWithLoc(v, currentLocation(c)), 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
|
||||
return StringTermWithLoc(v, currentLocation(c)), err
|
||||
}
|
||||
|
||||
Bool <- "true" {
|
||||
t := NewTerm(true, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
return BooleanTermWithLoc(true, currentLocation(c)), nil
|
||||
} / "false" {
|
||||
t := NewTerm(false, BOOLEAN, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
return BooleanTermWithLoc(false, currentLocation(c)), nil
|
||||
}
|
||||
|
||||
Null <- "null" {
|
||||
t := NewTerm(nil, NULL, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil
|
||||
return NullTermWithLoc(currentLocation(c)), nil
|
||||
}
|
||||
|
||||
Integer <- '0' / NonZeroDecimalDigit DecimalDigit*
|
||||
|
||||
-1770
File diff suppressed because it is too large
Load Diff
+48
-117
@@ -6,24 +6,22 @@ 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"))
|
||||
assertParseOneTerm(t, "null", "null", NullTerm())
|
||||
assertParseOneTerm(t, "true", "true", BooleanTerm(true))
|
||||
assertParseOneTerm(t, "false", "false", BooleanTerm(false))
|
||||
assertParseOneTerm(t, "integer", "53", NumberTerm(53))
|
||||
assertParseOneTerm(t, "integer2", "-53", NumberTerm(-53))
|
||||
assertParseOneTerm(t, "float", "16.7", NumberTerm(16.7))
|
||||
assertParseOneTerm(t, "float2", "-16.7", NumberTerm(-16.7))
|
||||
assertParseOneTerm(t, "exponent", "6e7", NumberTerm(6e7))
|
||||
assertParseOneTerm(t, "string", "\"a string\"", StringTerm("a string"))
|
||||
assertParseOneTerm(t, "string", "\"a string u6abc7def8abc0def with unicode\"", StringTerm("a string u6abc7def8abc0def with unicode"))
|
||||
assertParseOneTermFail(t, "hex", "6abc")
|
||||
assertParseOneTermFail(t, "non-string", "'a string'")
|
||||
assertParseOneTermFail(t, "non-number", "6zxy")
|
||||
@@ -33,69 +31,64 @@ func TestScalarTerms(t *testing.T) {
|
||||
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")
|
||||
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")))
|
||||
|
||||
assertParseOneTerm(t, "var", "foo", VarTerm("foo"))
|
||||
assertParseOneTerm(t, "var", "foo_bar", VarTerm("foo_bar"))
|
||||
assertParseOneTerm(t, "var", "foo0", VarTerm("foo0"))
|
||||
assertParseOneTermFail(t, "non-var", "foo-bar")
|
||||
assertParseOneTermFail(t, "non-var2", "foo-7")
|
||||
}
|
||||
|
||||
func TestRefTerms(t *testing.T) {
|
||||
assertParseOneTerm(t, "constants", "foo.bar.baz", RefTerm(VarTerm("foo"), StringTerm("bar"), StringTerm("baz")))
|
||||
assertParseOneTerm(t, "constants 2", "foo.bar[0].baz", RefTerm(VarTerm("foo"), StringTerm("bar"), NumberTerm(0), StringTerm("baz")))
|
||||
assertParseOneTerm(t, "variables", "foo.bar[0].baz[i]", RefTerm(VarTerm("foo"), StringTerm("bar"), NumberTerm(0), StringTerm("baz"), VarTerm("i")))
|
||||
}
|
||||
|
||||
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}))
|
||||
assertParseOneTerm(t, "number", "{\"abc\": 7, \"def\": 8}", ObjectTerm(Item(StringTerm("abc"), NumberTerm(7)), Item(StringTerm("def"), NumberTerm(8))))
|
||||
assertParseOneTerm(t, "bool", "{\"abc\": false, \"def\": true}", ObjectTerm(Item(StringTerm("abc"), BooleanTerm(false)), Item(StringTerm("def"), BooleanTerm(true))))
|
||||
assertParseOneTerm(t, "string", "{\"abc\": \"foo\", \"def\": \"bar\"}", ObjectTerm(Item(StringTerm("abc"), StringTerm("foo")), Item(StringTerm("def"), StringTerm("bar"))))
|
||||
assertParseOneTerm(t, "mixed", "{\"abc\": 7, \"def\": null}", ObjectTerm(Item(StringTerm("abc"), NumberTerm(7)), Item(StringTerm("def"), NullTerm())))
|
||||
assertParseOneTerm(t, "number key", "{8: 7, \"def\": null}", ObjectTerm(Item(NumberTerm(8), NumberTerm(7)), Item(StringTerm("def"), NullTerm())))
|
||||
assertParseOneTerm(t, "number key 2", "{8.5: 7, \"def\": null}", ObjectTerm(Item(NumberTerm(8.5), NumberTerm(7)), Item(StringTerm("def"), NullTerm())))
|
||||
assertParseOneTerm(t, "bool key", "{true: false}", ObjectTerm(Item(BooleanTerm(true), BooleanTerm(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"))),
|
||||
})),
|
||||
}))
|
||||
assertParseOneTerm(t, "var keys", "{foo: \"bar\", bar: 64}", ObjectTerm(Item(VarTerm("foo"), StringTerm("bar")), Item(VarTerm("bar"), NumberTerm(64))))
|
||||
assertParseOneTerm(t, "nested var keys", "{baz: {foo: \"bar\", bar: qux}}", ObjectTerm(Item(VarTerm("baz"), ObjectTerm(Item(VarTerm("foo"), StringTerm("bar")), Item(VarTerm("bar"), VarTerm("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}))
|
||||
assertParseOneTerm(t, "number", "[1,2,3,4.5]", ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(3), NumberTerm(4.5)))
|
||||
assertParseOneTerm(t, "bool", "[true, false, true]", ArrayTerm(BooleanTerm(true), BooleanTerm(false), BooleanTerm(true)))
|
||||
assertParseOneTerm(t, "string", "[\"foo\", \"bar\"]", ArrayTerm(StringTerm("foo"), StringTerm("bar")))
|
||||
assertParseOneTerm(t, "mixed", "[null, true, 42]", ArrayTerm(NullTerm(), BooleanTerm(true), NumberTerm(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),
|
||||
},
|
||||
))
|
||||
assertParseOneTerm(t, "var elements", "[foo, bar, 42]", ArrayTerm(VarTerm("foo"), VarTerm("bar"), NumberTerm(42)))
|
||||
assertParseOneTerm(t, "nested var elements", "[[foo, true], [null, bar], 42]", ArrayTerm(ArrayTerm(VarTerm("foo"), BooleanTerm(true)), ArrayTerm(NullTerm(), VarTerm("bar")), NumberTerm(42)))
|
||||
}
|
||||
|
||||
func TestEmptyComposites(t *testing.T) {
|
||||
assertParseOneTerm(t, "empty object", "{}", ObjectTerm())
|
||||
assertParseOneTerm(t, "emtpy array", "[]", ArrayTerm())
|
||||
}
|
||||
|
||||
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")),
|
||||
})),
|
||||
}),
|
||||
}))
|
||||
assertParseOneTerm(t, "nested composites", "[{foo: [\"bar\", baz]}]", ArrayTerm(ObjectTerm(Item(VarTerm("foo"), ArrayTerm(StringTerm("bar"), VarTerm("baz"))))))
|
||||
}
|
||||
|
||||
func TestCompositesWithRefs(t *testing.T) {
|
||||
ref1 := RefTerm(VarTerm("a"), VarTerm("i"), StringTerm("b"))
|
||||
ref2 := RefTerm(VarTerm("c"), NumberTerm(0), StringTerm("d"), StringTerm("e"), VarTerm("j"))
|
||||
assertParseOneTerm(t, "ref keys", "[{a[i].b: 8, c[0][\"d\"].e[j]: f}]", ArrayTerm(ObjectTerm(Item(ref1, NumberTerm(8)), Item(ref2, VarTerm("f")))))
|
||||
assertParseOneTerm(t, "ref values", "[{8: a[i].b, f: c[0][\"d\"].e[j]}]", ArrayTerm(ObjectTerm(Item(NumberTerm(8), ref1), Item(VarTerm("f"), ref2))))
|
||||
}
|
||||
|
||||
func assertTermEqual(t *testing.T, x *Term, y *Term) {
|
||||
@@ -143,65 +136,3 @@ func assertParseOneTermFail(t *testing.T, msg string, expr string) {
|
||||
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
@@ -1,102 +0,0 @@
|
||||
// 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,67 +0,0 @@
|
||||
// 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 TestSetAdd(t *testing.T) {
|
||||
eq := func(x interface{}, y interface{}) bool { return x == y }
|
||||
s1 := NewSet(eq)
|
||||
s1.Add(1)
|
||||
s1.Add(2)
|
||||
if !s1.Contains(1) || !s1.Contains(2) {
|
||||
t.Errorf("Failure on Contains 1 || Contains 2")
|
||||
}
|
||||
if s1.Length() != 2 {
|
||||
t.Errorf("Failure on set length")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDifference(t *testing.T) {
|
||||
eq := func(x interface{}, y interface{}) bool { return x == y }
|
||||
s1 := NewSet(eq)
|
||||
s2 := NewSet(eq)
|
||||
s1.Add(1)
|
||||
s1.Add(2)
|
||||
s2.Add(1)
|
||||
s12 := s1.Difference(s2)
|
||||
if s12.Length() != 1 {
|
||||
t.Errorf("Failure on set difference length")
|
||||
}
|
||||
if !s12.Contains(2) {
|
||||
t.Errorf("Failure on set difference containment")
|
||||
}
|
||||
s21 := s2.Difference(s1)
|
||||
if s21.Length() != 0 {
|
||||
t.Errorf("Failure on set difference inversion length")
|
||||
}
|
||||
if s1.Length() != 2 {
|
||||
t.Errorf("Set-difference modified s1")
|
||||
}
|
||||
if !s1.Contains(1) || !s1.Contains(2) {
|
||||
t.Errorf("Set-difference changed a value in s1")
|
||||
}
|
||||
if s2.Length() != 1 {
|
||||
t.Errorf("Set-difference modified s2")
|
||||
}
|
||||
if !s2.Contains(1) {
|
||||
t.Errorf("Set-difference changed a value in s2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEquality(t *testing.T) {
|
||||
eq := func(x interface{}, y interface{}) bool { return x == y }
|
||||
s1 := NewSet(eq)
|
||||
s2 := NewSet(eq)
|
||||
s1.Add(1)
|
||||
s1.Add(2)
|
||||
s2.Add(1)
|
||||
s2.Add(2)
|
||||
if !s1.Equal(s2) {
|
||||
t.Errorf("Equality on sets failed")
|
||||
}
|
||||
}
|
||||
+266
-93
@@ -4,115 +4,288 @@
|
||||
|
||||
package opalog
|
||||
|
||||
import "fmt"
|
||||
import "regexp"
|
||||
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
|
||||
Text []byte // The original text fragment from the source.
|
||||
File string // The name of the source file (which may be empty).
|
||||
Row int // The line in the source.
|
||||
Col int // The column in the row.
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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}
|
||||
}
|
||||
|
||||
// Term is an argument to a function
|
||||
// Value declares the common interface for all Term values. Every kind of Term value
|
||||
// in the language is represented as a type that implements this interface:
|
||||
//
|
||||
// - Null, Boolean, Number, String
|
||||
// - Object, Array
|
||||
// - Variables
|
||||
// - References
|
||||
//
|
||||
type Value interface {
|
||||
// Equal returns true if this value equals the other value.
|
||||
Equal(other Value) bool
|
||||
|
||||
// String returns a human readable string representation of the value.
|
||||
String() string
|
||||
}
|
||||
|
||||
// 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
|
||||
Value Value // the value of the Term as represented in Go
|
||||
Location *Location // the location of the Term in the 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 {
|
||||
// Equal returns true if this term equals the other term. Equality is
|
||||
// defined for each kind of term.
|
||||
func (term *Term) Equal(other *Term) bool {
|
||||
if term == other {
|
||||
return true
|
||||
}
|
||||
// wrong types
|
||||
if term1.Kind != term2.Kind {
|
||||
return term.Value.Equal(other.Value)
|
||||
}
|
||||
|
||||
func (term *Term) String() string {
|
||||
return term.Value.String()
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func NullTerm() *Term {
|
||||
return &Term{Value: Null{}}
|
||||
}
|
||||
|
||||
func NullTermWithLoc(loc *Location) *Term {
|
||||
return &Term{Value: Null{}, Location: loc}
|
||||
}
|
||||
|
||||
func (null Null) Equal(other Value) bool {
|
||||
switch other.(type) {
|
||||
case Null:
|
||||
return true
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
func (null Null) String() string {
|
||||
return "null"
|
||||
}
|
||||
|
||||
type Boolean bool
|
||||
|
||||
func BooleanTerm(b bool) *Term {
|
||||
return &Term{Value: Boolean(b)}
|
||||
}
|
||||
|
||||
func BooleanTermWithLoc(b bool, loc *Location) *Term {
|
||||
return &Term{Value: Boolean(b), Location: loc}
|
||||
}
|
||||
|
||||
func (bol Boolean) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Boolean:
|
||||
return bol == other
|
||||
default:
|
||||
return term1.Value == term2.Value
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (bol Boolean) String() string {
|
||||
return strconv.FormatBool(bool(bol))
|
||||
}
|
||||
|
||||
type Number float64
|
||||
|
||||
func NumberTerm(n float64) *Term {
|
||||
return &Term{Value: Number(n)}
|
||||
}
|
||||
|
||||
func NumberTermWithLoc(n float64, loc *Location) *Term {
|
||||
return &Term{Value: Number(n), Location: loc}
|
||||
}
|
||||
|
||||
func (num Number) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Number:
|
||||
return num == other
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (num Number) String() string {
|
||||
return strconv.FormatFloat(float64(num), 'G', -1, 64)
|
||||
}
|
||||
|
||||
type String string
|
||||
|
||||
func StringTerm(s string) *Term {
|
||||
return &Term{Value: String(s)}
|
||||
}
|
||||
|
||||
func StringTermWithLoc(s string, loc *Location) *Term {
|
||||
return &Term{Value: String(s), Location: loc}
|
||||
}
|
||||
|
||||
func (str String) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case String:
|
||||
return str == other
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (str String) String() string {
|
||||
return strconv.Quote(string(str))
|
||||
}
|
||||
|
||||
type Var string
|
||||
|
||||
func VarTerm(v string) *Term {
|
||||
return &Term{Value: Var(v)}
|
||||
}
|
||||
|
||||
func VarTermWithLoc(v string, loc *Location) *Term {
|
||||
return &Term{Value: Var(v), Location: loc}
|
||||
}
|
||||
|
||||
func (variable Var) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Var:
|
||||
return variable == other
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (variable Var) String() string {
|
||||
return string(variable)
|
||||
}
|
||||
|
||||
type Ref []*Term
|
||||
|
||||
func RefTerm(r ...*Term) *Term {
|
||||
return &Term{Value: Ref(r)}
|
||||
}
|
||||
|
||||
func RefTermWithLoc(r []*Term, loc *Location) *Term {
|
||||
return &Term{Value: Ref(r), Location: loc}
|
||||
}
|
||||
|
||||
func (ref Ref) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Ref:
|
||||
if len(ref) == len(other) {
|
||||
for i := range ref {
|
||||
if !ref[i].Equal(other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var varRegexp = regexp.MustCompile("[[:alpha:]_][[:alpha:][:digit:]_]+")
|
||||
|
||||
func (ref Ref) String() string {
|
||||
buf := []string{string(ref[0].Value.(Var))}
|
||||
for _, p := range ref[1:] {
|
||||
switch p := p.Value.(type) {
|
||||
case String:
|
||||
str := string(p)
|
||||
if varRegexp.MatchString(str) {
|
||||
buf = append(buf, "."+str)
|
||||
} else {
|
||||
buf = append(buf, "["+p.String()+"]")
|
||||
}
|
||||
default:
|
||||
buf = append(buf, "["+p.String()+"]")
|
||||
}
|
||||
}
|
||||
return strings.Join(buf, "")
|
||||
}
|
||||
|
||||
type Array []*Term
|
||||
|
||||
func ArrayTerm(a ...*Term) *Term {
|
||||
return &Term{Value: Array(a)}
|
||||
}
|
||||
|
||||
func ArrayTermWithLoc(a []*Term, loc *Location) *Term {
|
||||
return &Term{Value: Array(a), Location: loc}
|
||||
}
|
||||
|
||||
func (arr Array) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Array:
|
||||
if len(arr) == len(other) {
|
||||
for i := range arr {
|
||||
if !arr[i].Equal(other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (arr Array) String() string {
|
||||
var buf []string
|
||||
for _, e := range arr {
|
||||
buf = append(buf, e.String())
|
||||
}
|
||||
return "[" + strings.Join(buf, ", ") + "]"
|
||||
}
|
||||
|
||||
type Object [][2]*Term
|
||||
|
||||
func Item(key, value *Term) [2]*Term {
|
||||
return [2]*Term{key, value}
|
||||
}
|
||||
|
||||
func ObjectTerm(o ...[2]*Term) *Term {
|
||||
return &Term{Value: Object(o)}
|
||||
}
|
||||
|
||||
func ObjectTermWithLoc(o [][2]*Term, loc *Location) *Term {
|
||||
return &Term{Value: Object(o), Location: loc}
|
||||
}
|
||||
|
||||
func (obj Object) Equal(other Value) bool {
|
||||
switch other := other.(type) {
|
||||
case Object:
|
||||
if len(obj) == len(other) {
|
||||
for i := range obj {
|
||||
if !obj[i][0].Equal(other[i][0]) {
|
||||
return false
|
||||
}
|
||||
if !obj[i][1].Equal(other[i][1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (obj Object) String() string {
|
||||
var buf []string
|
||||
for _, p := range obj {
|
||||
buf = append(buf, fmt.Sprintf("%s: %s", p[0], p[1]))
|
||||
}
|
||||
return "{" + strings.Join(buf, ", ") + "}"
|
||||
}
|
||||
|
||||
+49
-21
@@ -7,25 +7,53 @@ 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")))
|
||||
assertTermEqual(t, NullTerm(), NullTerm())
|
||||
assertTermEqual(t, BooleanTerm(true), BooleanTerm(true))
|
||||
assertTermEqual(t, NumberTerm(5), NumberTerm(5))
|
||||
assertTermEqual(t, StringTerm("a string"), StringTerm("a string"))
|
||||
assertTermEqual(t, ObjectTerm(), ObjectTerm())
|
||||
assertTermEqual(t, ArrayTerm(), ArrayTerm())
|
||||
assertTermEqual(t, ObjectTerm(Item(NumberTerm(1), NumberTerm(2))), ObjectTerm(Item(NumberTerm(1), NumberTerm(2))))
|
||||
assertTermEqual(t, ObjectTerm(Item(NumberTerm(1), NumberTerm(2)), Item(NumberTerm(3), NumberTerm(4))), ObjectTerm(Item(NumberTerm(1), NumberTerm(2)), Item(NumberTerm(3), NumberTerm(4))))
|
||||
assertTermEqual(t, ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(3)), ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(3)))
|
||||
assertTermEqual(t, VarTerm("foo"), VarTerm("foo"))
|
||||
assertTermEqual(t, RefTerm(VarTerm("foo"), VarTerm("i"), NumberTerm(2)), RefTerm(VarTerm("foo"), VarTerm("i"), NumberTerm(2)))
|
||||
assertTermNotEqual(t, NullTerm(), BooleanTerm(true))
|
||||
assertTermNotEqual(t, BooleanTerm(true), BooleanTerm(false))
|
||||
assertTermNotEqual(t, NumberTerm(5), NumberTerm(7))
|
||||
assertTermNotEqual(t, StringTerm("a string"), StringTerm("abc"))
|
||||
assertTermNotEqual(t, ObjectTerm(Item(NumberTerm(3), NumberTerm(2))), ObjectTerm(Item(NumberTerm(1), NumberTerm(2))))
|
||||
assertTermNotEqual(t, ObjectTerm(Item(NumberTerm(1), NumberTerm(2)), Item(NumberTerm(3), NumberTerm(7))), ObjectTerm(Item(NumberTerm(1), NumberTerm(2)), Item(NumberTerm(3), NumberTerm(4))))
|
||||
assertTermNotEqual(t, NumberTerm(5), StringTerm("a string"))
|
||||
assertTermNotEqual(t, NumberTerm(1), BooleanTerm(true))
|
||||
assertTermNotEqual(t, ObjectTerm(Item(NumberTerm(1), NumberTerm(2)), Item(NumberTerm(3), NumberTerm(7))), ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(7)))
|
||||
assertTermNotEqual(t, ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(3)), ArrayTerm(NumberTerm(1), NumberTerm(2), NumberTerm(4)))
|
||||
assertTermNotEqual(t, VarTerm("foo"), VarTerm("bar"))
|
||||
assertTermNotEqual(t, RefTerm(VarTerm("foo"), VarTerm("i"), NumberTerm(2)), RefTerm(VarTerm("foo"), StringTerm("i"), NumberTerm(2)))
|
||||
}
|
||||
|
||||
func TestTermsToString(t *testing.T) {
|
||||
assertToString(t, Null{}, "null")
|
||||
assertToString(t, Boolean(true), "true")
|
||||
assertToString(t, Boolean(false), "false")
|
||||
assertToString(t, Number(4), "4")
|
||||
assertToString(t, Number(42.1), "42.1")
|
||||
assertToString(t, Number(6e7), "6E+07")
|
||||
assertToString(t, String("foo"), "\"foo\"")
|
||||
assertToString(t, String("\"foo\""), "\"\\\"foo\\\"\"")
|
||||
assertToString(t, String("foo bar"), "\"foo bar\"")
|
||||
assertToString(t, Var("foo"), "foo")
|
||||
assertToString(t, RefTerm(VarTerm("foo"), StringTerm("bar")).Value, "foo.bar")
|
||||
assertToString(t, RefTerm(VarTerm("foo"), StringTerm("bar"), VarTerm("i"), NumberTerm(0), StringTerm("baz")).Value, "foo.bar[i][0].baz")
|
||||
assertToString(t, RefTerm(VarTerm("foo"), BooleanTerm(false), NullTerm(), StringTerm("bar")).Value, "foo[false][null].bar")
|
||||
assertToString(t, ArrayTerm().Value, "[]")
|
||||
assertToString(t, ObjectTerm().Value, "{}")
|
||||
assertToString(t, ArrayTerm(ObjectTerm(Item(VarTerm("foo"), ArrayTerm(RefTerm(VarTerm("bar"), VarTerm("i"))))), StringTerm("foo"), BooleanTerm(true), NullTerm(), NumberTerm(42.1)).Value, "[{foo: [bar[i]]}, \"foo\", true, null, 42.1]")
|
||||
}
|
||||
|
||||
func assertToString(t *testing.T, val Value, expected string) {
|
||||
result := val.String()
|
||||
if result != expected {
|
||||
t.Errorf("Expected %v for %f but got %v", expected, val, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// 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