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:
Torin Sandall
2016-03-31 15:32:32 -07:00
parent a2ff4234c3
commit 157e4f083a
13 changed files with 447 additions and 2269 deletions
+1
View File
@@ -1,2 +1,3 @@
.vscode
opa
opalog/parser.go
+1
View File
@@ -2,3 +2,4 @@ language: go
go:
- 1.5
- 1.6
install: make deps
+7 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+48 -117
View File
@@ -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
View File
@@ -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
}
-67
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
-20
View File
@@ -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
}
-12
View File
@@ -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")))
}