diff --git a/Makefile b/Makefile index 7c9f6390d5..7c9bb52001 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a24c705359..a2ca2f4051 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -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 diff --git a/jsonlog/jsonlog.peg b/jsonlog/jsonlog.peg deleted file mode 100644 index 3648a8bef3..0000000000 --- a/jsonlog/jsonlog.peg +++ /dev/null @@ -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 <- !. diff --git a/jsonlog/parser_test.go b/jsonlog/parser_test.go deleted file mode 100644 index 73f0ff5ee2..0000000000 --- a/jsonlog/parser_test.go +++ /dev/null @@ -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) -} diff --git a/jsonlog/syntax.go b/jsonlog/syntax.go deleted file mode 100644 index ec000a785a..0000000000 --- a/jsonlog/syntax.go +++ /dev/null @@ -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", 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", 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 - } -} diff --git a/main.go b/main.go index 28db270ed8..6a36792c1c 100644 --- a/main.go +++ b/main.go @@ -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 diff --git a/opalog/opalog.peg b/opalog/opalog.peg new file mode 100644 index 0000000000..3726d88930 --- /dev/null +++ b/opalog/opalog.peg @@ -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 <- !. diff --git a/jsonlog/parser.go b/opalog/parser.go similarity index 64% rename from jsonlog/parser.go rename to opalog/parser.go index 3b0b879fbe..361d4204cf 100644 --- a/jsonlog/parser.go +++ b/opalog/parser.go @@ -1,12 +1,4 @@ -// 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 +package opalog import ( "bytes" @@ -21,41 +13,55 @@ import ( "unicode/utf8" ) -func toIfaceSlice(v interface{}) []interface{} { - if v == nil { - return nil - } - return v.([]interface{}) -} +// +// BUGS: the escaped forward solidus (`\/`) is not currently handled for strings. +// var g = &grammar{ rules: []*rule{ { name: "Prog", - pos: position{line: 20, col: 1, offset: 612}, + pos: position{line: 9, col: 1, offset: 109}, expr: &actionExpr{ - pos: position{line: 20, col: 9, offset: 620}, + pos: position{line: 9, col: 9, offset: 117}, run: (*parser).callonProg1, expr: &seqExpr{ - pos: position{line: 20, col: 9, offset: 620}, + pos: position{line: 9, col: 9, offset: 117}, exprs: []interface{}{ &ruleRefExpr{ - pos: position{line: 20, col: 9, offset: 620}, + pos: position{line: 9, col: 9, offset: 117}, name: "_", }, &labeledExpr{ - pos: position{line: 20, col: 11, offset: 622}, - label: "vals", - expr: &oneOrMoreExpr{ - pos: position{line: 20, col: 16, offset: 627}, - expr: &ruleRefExpr{ - pos: position{line: 20, col: 16, offset: 627}, - name: "Term", + pos: position{line: 9, col: 11, offset: 119}, + label: "head", + expr: &ruleRefExpr{ + pos: position{line: 9, col: 16, offset: 124}, + name: "Term", + }, + }, + &labeledExpr{ + pos: position{line: 9, col: 21, offset: 129}, + label: "tail", + expr: &zeroOrMoreExpr{ + pos: position{line: 9, col: 26, offset: 134}, + expr: &seqExpr{ + pos: position{line: 9, col: 28, offset: 136}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 9, col: 28, offset: 136}, + name: "ws", + }, + &ruleRefExpr{ + pos: position{line: 9, col: 31, offset: 139}, + name: "Term", + }, + }, }, }, }, &ruleRefExpr{ - pos: position{line: 20, col: 22, offset: 633}, + pos: position{line: 9, col: 40, offset: 148}, name: "EOF", }, }, @@ -64,145 +70,195 @@ var g = &grammar{ }, { name: "Term", - pos: position{line: 27, col: 1, offset: 749}, + pos: position{line: 17, col: 1, offset: 323}, expr: &actionExpr{ - pos: position{line: 27, col: 9, offset: 757}, + pos: position{line: 17, col: 9, offset: 331}, run: (*parser).callonTerm1, - expr: &seqExpr{ - pos: position{line: 27, col: 9, offset: 757}, - exprs: []interface{}{ - &labeledExpr{ - pos: position{line: 27, col: 9, offset: 757}, - label: "val", - expr: &choiceExpr{ - pos: position{line: 27, col: 15, offset: 763}, - alternatives: []interface{}{ - &ruleRefExpr{ - pos: position{line: 27, col: 15, offset: 763}, - name: "Dictionary", - }, - &ruleRefExpr{ - pos: position{line: 27, col: 28, offset: 776}, - name: "Array", - }, - &ruleRefExpr{ - pos: position{line: 27, col: 36, offset: 784}, - name: "Number", - }, - &ruleRefExpr{ - pos: position{line: 27, col: 45, offset: 793}, - name: "String", - }, - &ruleRefExpr{ - pos: position{line: 27, col: 54, offset: 802}, - name: "Bool", - }, - &ruleRefExpr{ - pos: position{line: 27, col: 61, offset: 809}, - name: "Null", - }, - }, + expr: &labeledExpr{ + pos: position{line: 17, col: 9, offset: 331}, + label: "val", + expr: &choiceExpr{ + pos: position{line: 17, col: 15, offset: 337}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 17, col: 15, offset: 337}, + name: "Composite", + }, + &ruleRefExpr{ + pos: position{line: 17, col: 27, offset: 349}, + name: "Scalar", + }, + &ruleRefExpr{ + pos: position{line: 17, col: 36, offset: 358}, + name: "Var", }, - }, - &ruleRefExpr{ - pos: position{line: 27, col: 68, offset: 816}, - name: "_", }, }, }, }, }, { - name: "Dictionary", - pos: position{line: 31, col: 1, offset: 843}, + name: "Composite", + pos: position{line: 21, col: 1, offset: 389}, + expr: &choiceExpr{ + pos: position{line: 21, col: 14, offset: 402}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 21, col: 14, offset: 402}, + name: "Object", + }, + &ruleRefExpr{ + pos: position{line: 21, col: 23, offset: 411}, + name: "Array", + }, + }, + }, + }, + { + name: "Scalar", + pos: position{line: 23, col: 1, offset: 418}, + expr: &choiceExpr{ + pos: position{line: 23, col: 11, offset: 428}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 23, col: 11, offset: 428}, + name: "Number", + }, + &ruleRefExpr{ + pos: position{line: 23, col: 20, offset: 437}, + name: "String", + }, + &ruleRefExpr{ + pos: position{line: 23, col: 29, offset: 446}, + name: "Bool", + }, + &ruleRefExpr{ + pos: position{line: 23, col: 36, offset: 453}, + name: "Null", + }, + }, + }, + }, + { + name: "Key", + pos: position{line: 25, col: 1, offset: 459}, + expr: &choiceExpr{ + pos: position{line: 25, col: 8, offset: 466}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 25, col: 8, offset: 466}, + name: "Scalar", + }, + &ruleRefExpr{ + pos: position{line: 25, col: 17, offset: 475}, + name: "Var", + }, + }, + }, + }, + { + name: "Object", + pos: position{line: 27, col: 1, offset: 480}, expr: &actionExpr{ - pos: position{line: 31, col: 15, offset: 857}, - run: (*parser).callonDictionary1, + pos: position{line: 27, col: 11, offset: 490}, + run: (*parser).callonObject1, expr: &seqExpr{ - pos: position{line: 31, col: 15, offset: 857}, + pos: position{line: 27, col: 11, offset: 490}, exprs: []interface{}{ &litMatcher{ - pos: position{line: 31, col: 15, offset: 857}, + pos: position{line: 27, col: 11, offset: 490}, val: "{", ignoreCase: false, }, &ruleRefExpr{ - pos: position{line: 31, col: 19, offset: 861}, + pos: position{line: 27, col: 15, offset: 494}, name: "_", }, &labeledExpr{ - pos: position{line: 31, col: 21, offset: 863}, - label: "vals", + pos: position{line: 27, col: 17, offset: 496}, + label: "head", expr: &zeroOrOneExpr{ - pos: position{line: 31, col: 26, offset: 868}, + pos: position{line: 27, col: 22, offset: 501}, expr: &seqExpr{ - pos: position{line: 31, col: 28, offset: 870}, + pos: position{line: 27, col: 23, offset: 502}, exprs: []interface{}{ &ruleRefExpr{ - pos: position{line: 31, col: 28, offset: 870}, - name: "String", + pos: position{line: 27, col: 23, offset: 502}, + name: "Key", }, &ruleRefExpr{ - pos: position{line: 31, col: 35, offset: 877}, + pos: position{line: 27, col: 27, offset: 506}, name: "_", }, &litMatcher{ - pos: position{line: 31, col: 37, offset: 879}, + pos: position{line: 27, col: 29, offset: 508}, val: ":", ignoreCase: false, }, &ruleRefExpr{ - pos: position{line: 31, col: 41, offset: 883}, + pos: position{line: 27, col: 33, offset: 512}, name: "_", }, &ruleRefExpr{ - pos: position{line: 31, col: 43, offset: 885}, + pos: position{line: 27, col: 35, offset: 514}, name: "Term", }, - &zeroOrMoreExpr{ - pos: position{line: 31, col: 48, offset: 890}, - expr: &seqExpr{ - pos: position{line: 31, col: 50, offset: 892}, - exprs: []interface{}{ - &litMatcher{ - pos: position{line: 31, col: 50, offset: 892}, - val: ",", - ignoreCase: false, - }, - &ruleRefExpr{ - pos: position{line: 31, col: 54, offset: 896}, - name: "_", - }, - &ruleRefExpr{ - pos: position{line: 31, col: 56, offset: 898}, - name: "String", - }, - &ruleRefExpr{ - pos: position{line: 31, col: 63, offset: 905}, - name: "_", - }, - &litMatcher{ - pos: position{line: 31, col: 65, offset: 907}, - val: ":", - ignoreCase: false, - }, - &ruleRefExpr{ - pos: position{line: 31, col: 69, offset: 911}, - name: "_", - }, - &ruleRefExpr{ - pos: position{line: 31, col: 71, offset: 913}, - name: "Term", - }, - }, - }, - }, }, }, }, }, + &labeledExpr{ + pos: position{line: 27, col: 42, offset: 521}, + label: "tail", + expr: &zeroOrMoreExpr{ + pos: position{line: 27, col: 47, offset: 526}, + expr: &seqExpr{ + pos: position{line: 27, col: 49, offset: 528}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 27, col: 49, offset: 528}, + name: "_", + }, + &litMatcher{ + pos: position{line: 27, col: 51, offset: 530}, + val: ",", + ignoreCase: false, + }, + &ruleRefExpr{ + pos: position{line: 27, col: 55, offset: 534}, + name: "_", + }, + &ruleRefExpr{ + pos: position{line: 27, col: 57, offset: 536}, + name: "Key", + }, + &ruleRefExpr{ + pos: position{line: 27, col: 61, offset: 540}, + name: "_", + }, + &litMatcher{ + pos: position{line: 27, col: 63, offset: 542}, + val: ":", + ignoreCase: false, + }, + &ruleRefExpr{ + pos: position{line: 27, col: 67, offset: 546}, + name: "_", + }, + &ruleRefExpr{ + pos: position{line: 27, col: 69, offset: 548}, + name: "Term", + }, + }, + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 27, col: 77, offset: 556}, + name: "_", + }, &litMatcher{ - pos: position{line: 31, col: 82, offset: 924}, + pos: position{line: 27, col: 79, offset: 558}, val: "}", ignoreCase: false, }, @@ -212,61 +268,68 @@ var g = &grammar{ }, { name: "Array", - pos: position{line: 49, col: 1, offset: 1591}, + pos: position{line: 54, col: 1, offset: 1548}, expr: &actionExpr{ - pos: position{line: 49, col: 10, offset: 1600}, + pos: position{line: 54, col: 10, offset: 1557}, run: (*parser).callonArray1, expr: &seqExpr{ - pos: position{line: 49, col: 10, offset: 1600}, + pos: position{line: 54, col: 10, offset: 1557}, exprs: []interface{}{ &litMatcher{ - pos: position{line: 49, col: 10, offset: 1600}, + pos: position{line: 54, col: 10, offset: 1557}, val: "[", ignoreCase: false, }, &ruleRefExpr{ - pos: position{line: 49, col: 14, offset: 1604}, + pos: position{line: 54, col: 14, offset: 1561}, name: "_", }, &labeledExpr{ - pos: position{line: 49, col: 16, offset: 1606}, - label: "vals", + pos: position{line: 54, col: 17, offset: 1564}, + label: "head", expr: &zeroOrOneExpr{ - pos: position{line: 49, col: 21, offset: 1611}, + pos: position{line: 54, col: 22, offset: 1569}, + expr: &ruleRefExpr{ + pos: position{line: 54, col: 22, offset: 1569}, + name: "Term", + }, + }, + }, + &labeledExpr{ + pos: position{line: 54, col: 28, offset: 1575}, + label: "tail", + expr: &zeroOrMoreExpr{ + pos: position{line: 54, col: 33, offset: 1580}, expr: &seqExpr{ - pos: position{line: 49, col: 23, offset: 1613}, + pos: position{line: 54, col: 34, offset: 1581}, exprs: []interface{}{ &ruleRefExpr{ - pos: position{line: 49, col: 23, offset: 1613}, - name: "Value", + pos: position{line: 54, col: 34, offset: 1581}, + name: "_", }, - &zeroOrMoreExpr{ - pos: position{line: 49, col: 29, offset: 1619}, - expr: &seqExpr{ - pos: position{line: 49, col: 31, offset: 1621}, - exprs: []interface{}{ - &litMatcher{ - pos: position{line: 49, col: 31, offset: 1621}, - val: ",", - ignoreCase: false, - }, - &ruleRefExpr{ - pos: position{line: 49, col: 35, offset: 1625}, - name: "_", - }, - &ruleRefExpr{ - pos: position{line: 49, col: 37, offset: 1627}, - name: "Value", - }, - }, - }, + &litMatcher{ + pos: position{line: 54, col: 36, offset: 1583}, + val: ",", + ignoreCase: false, + }, + &ruleRefExpr{ + pos: position{line: 54, col: 40, offset: 1587}, + name: "_", + }, + &ruleRefExpr{ + pos: position{line: 54, col: 42, offset: 1589}, + name: "Term", }, }, }, }, }, + &ruleRefExpr{ + pos: position{line: 54, col: 49, offset: 1596}, + name: "_", + }, &litMatcher{ - pos: position{line: 49, col: 49, offset: 1639}, + pos: position{line: 54, col: 51, offset: 1598}, val: "]", ignoreCase: false, }, @@ -275,40 +338,77 @@ var g = &grammar{ }, }, { - name: "Number", - pos: position{line: 64, col: 1, offset: 2067}, + name: "Var", + pos: position{line: 78, col: 1, offset: 2306}, expr: &actionExpr{ - pos: position{line: 64, col: 11, offset: 2077}, + pos: position{line: 78, col: 8, offset: 2313}, + run: (*parser).callonVar1, + expr: &labeledExpr{ + pos: position{line: 78, col: 8, offset: 2313}, + label: "vals", + expr: &seqExpr{ + pos: position{line: 78, col: 15, offset: 2320}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 78, col: 15, offset: 2320}, + name: "AsciiLetter", + }, + &zeroOrMoreExpr{ + pos: position{line: 78, col: 27, offset: 2332}, + expr: &choiceExpr{ + pos: position{line: 78, col: 28, offset: 2333}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 78, col: 28, offset: 2333}, + name: "AsciiLetter", + }, + &ruleRefExpr{ + pos: position{line: 78, col: 42, offset: 2347}, + name: "DecimalDigit", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Number", + pos: position{line: 84, col: 1, offset: 2477}, + expr: &actionExpr{ + pos: position{line: 84, col: 11, offset: 2487}, run: (*parser).callonNumber1, expr: &seqExpr{ - pos: position{line: 64, col: 11, offset: 2077}, + pos: position{line: 84, col: 11, offset: 2487}, exprs: []interface{}{ &zeroOrOneExpr{ - pos: position{line: 64, col: 11, offset: 2077}, + pos: position{line: 84, col: 11, offset: 2487}, expr: &litMatcher{ - pos: position{line: 64, col: 11, offset: 2077}, + pos: position{line: 84, col: 11, offset: 2487}, val: "-", ignoreCase: false, }, }, &ruleRefExpr{ - pos: position{line: 64, col: 16, offset: 2082}, + pos: position{line: 84, col: 16, offset: 2492}, name: "Integer", }, &zeroOrOneExpr{ - pos: position{line: 64, col: 24, offset: 2090}, + pos: position{line: 84, col: 24, offset: 2500}, expr: &seqExpr{ - pos: position{line: 64, col: 26, offset: 2092}, + pos: position{line: 84, col: 26, offset: 2502}, exprs: []interface{}{ &litMatcher{ - pos: position{line: 64, col: 26, offset: 2092}, + pos: position{line: 84, col: 26, offset: 2502}, val: ".", ignoreCase: false, }, &oneOrMoreExpr{ - pos: position{line: 64, col: 30, offset: 2096}, + pos: position{line: 84, col: 30, offset: 2506}, expr: &ruleRefExpr{ - pos: position{line: 64, col: 30, offset: 2096}, + pos: position{line: 84, col: 30, offset: 2506}, name: "DecimalDigit", }, }, @@ -316,9 +416,9 @@ var g = &grammar{ }, }, &zeroOrOneExpr{ - pos: position{line: 64, col: 47, offset: 2113}, + pos: position{line: 84, col: 47, offset: 2523}, expr: &ruleRefExpr{ - pos: position{line: 64, col: 47, offset: 2113}, + pos: position{line: 84, col: 47, offset: 2523}, name: "Exponent", }, }, @@ -327,27 +427,127 @@ var g = &grammar{ }, }, { - name: "Integer", - pos: position{line: 72, col: 1, offset: 2352}, + name: "String", + pos: position{line: 92, col: 1, offset: 2762}, + expr: &actionExpr{ + pos: position{line: 92, col: 11, offset: 2772}, + run: (*parser).callonString1, + expr: &seqExpr{ + pos: position{line: 92, col: 11, offset: 2772}, + exprs: []interface{}{ + &litMatcher{ + pos: position{line: 92, col: 11, offset: 2772}, + val: "\"", + ignoreCase: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 92, col: 15, offset: 2776}, + expr: &choiceExpr{ + pos: position{line: 92, col: 17, offset: 2778}, + alternatives: []interface{}{ + &seqExpr{ + pos: position{line: 92, col: 17, offset: 2778}, + exprs: []interface{}{ + ¬Expr{ + pos: position{line: 92, col: 17, offset: 2778}, + expr: &ruleRefExpr{ + pos: position{line: 92, col: 18, offset: 2779}, + name: "EscapedChar", + }, + }, + &anyMatcher{ + line: 92, col: 30, offset: 2791, + }, + }, + }, + &seqExpr{ + pos: position{line: 92, col: 34, offset: 2795}, + exprs: []interface{}{ + &litMatcher{ + pos: position{line: 92, col: 34, offset: 2795}, + val: "\\", + ignoreCase: false, + }, + &ruleRefExpr{ + pos: position{line: 92, col: 39, offset: 2800}, + name: "EscapeSequence", + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 92, col: 57, offset: 2818}, + val: "\"", + ignoreCase: false, + }, + }, + }, + }, + }, + { + name: "Bool", + pos: position{line: 100, col: 1, offset: 3084}, expr: &choiceExpr{ - pos: position{line: 72, col: 12, offset: 2363}, + pos: position{line: 100, col: 9, offset: 3092}, + alternatives: []interface{}{ + &actionExpr{ + pos: position{line: 100, col: 9, offset: 3092}, + run: (*parser).callonBool2, + expr: &litMatcher{ + pos: position{line: 100, col: 9, offset: 3092}, + val: "true", + ignoreCase: false, + }, + }, + &actionExpr{ + pos: position{line: 103, col: 5, offset: 3190}, + run: (*parser).callonBool4, + expr: &litMatcher{ + pos: position{line: 103, col: 5, offset: 3190}, + val: "false", + ignoreCase: false, + }, + }, + }, + }, + }, + { + name: "Null", + pos: position{line: 108, col: 1, offset: 3289}, + expr: &actionExpr{ + pos: position{line: 108, col: 9, offset: 3297}, + run: (*parser).callonNull1, + expr: &litMatcher{ + pos: position{line: 108, col: 9, offset: 3297}, + val: "null", + ignoreCase: false, + }, + }, + }, + { + name: "Integer", + pos: position{line: 113, col: 1, offset: 3390}, + expr: &choiceExpr{ + pos: position{line: 113, col: 12, offset: 3401}, alternatives: []interface{}{ &litMatcher{ - pos: position{line: 72, col: 12, offset: 2363}, + pos: position{line: 113, col: 12, offset: 3401}, val: "0", ignoreCase: false, }, &seqExpr{ - pos: position{line: 72, col: 18, offset: 2369}, + pos: position{line: 113, col: 18, offset: 3407}, exprs: []interface{}{ &ruleRefExpr{ - pos: position{line: 72, col: 18, offset: 2369}, + pos: position{line: 113, col: 18, offset: 3407}, name: "NonZeroDecimalDigit", }, &zeroOrMoreExpr{ - pos: position{line: 72, col: 38, offset: 2389}, + pos: position{line: 113, col: 38, offset: 3427}, expr: &ruleRefExpr{ - pos: position{line: 72, col: 38, offset: 2389}, + pos: position{line: 113, col: 38, offset: 3427}, name: "DecimalDigit", }, }, @@ -358,19 +558,19 @@ var g = &grammar{ }, { name: "Exponent", - pos: position{line: 74, col: 1, offset: 2404}, + pos: position{line: 115, col: 1, offset: 3442}, expr: &seqExpr{ - pos: position{line: 74, col: 13, offset: 2416}, + pos: position{line: 115, col: 13, offset: 3454}, exprs: []interface{}{ &litMatcher{ - pos: position{line: 74, col: 13, offset: 2416}, + pos: position{line: 115, col: 13, offset: 3454}, val: "e", ignoreCase: true, }, &zeroOrOneExpr{ - pos: position{line: 74, col: 18, offset: 2421}, + pos: position{line: 115, col: 18, offset: 3459}, expr: &charClassMatcher{ - pos: position{line: 74, col: 18, offset: 2421}, + pos: position{line: 115, col: 18, offset: 3459}, val: "[+-]", chars: []rune{'+', '-'}, ignoreCase: false, @@ -378,9 +578,9 @@ var g = &grammar{ }, }, &oneOrMoreExpr{ - pos: position{line: 74, col: 24, offset: 2427}, + pos: position{line: 115, col: 24, offset: 3465}, expr: &ruleRefExpr{ - pos: position{line: 74, col: 24, offset: 2427}, + pos: position{line: 115, col: 24, offset: 3465}, name: "DecimalDigit", }, }, @@ -388,70 +588,22 @@ var g = &grammar{ }, }, { - name: "String", - pos: position{line: 76, col: 1, offset: 2442}, - expr: &actionExpr{ - pos: position{line: 76, col: 11, offset: 2452}, - run: (*parser).callonString1, - expr: &seqExpr{ - pos: position{line: 76, col: 11, offset: 2452}, - exprs: []interface{}{ - &litMatcher{ - pos: position{line: 76, col: 11, offset: 2452}, - val: "\"", - ignoreCase: false, - }, - &zeroOrMoreExpr{ - pos: position{line: 76, col: 15, offset: 2456}, - expr: &choiceExpr{ - pos: position{line: 76, col: 17, offset: 2458}, - alternatives: []interface{}{ - &seqExpr{ - pos: position{line: 76, col: 17, offset: 2458}, - exprs: []interface{}{ - ¬Expr{ - pos: position{line: 76, col: 17, offset: 2458}, - expr: &ruleRefExpr{ - pos: position{line: 76, col: 18, offset: 2459}, - name: "EscapedChar", - }, - }, - &anyMatcher{ - line: 76, col: 30, offset: 2471, - }, - }, - }, - &seqExpr{ - pos: position{line: 76, col: 34, offset: 2475}, - exprs: []interface{}{ - &litMatcher{ - pos: position{line: 76, col: 34, offset: 2475}, - val: "\\", - ignoreCase: false, - }, - &ruleRefExpr{ - pos: position{line: 76, col: 39, offset: 2480}, - name: "EscapeSequence", - }, - }, - }, - }, - }, - }, - &litMatcher{ - pos: position{line: 76, col: 57, offset: 2498}, - val: "\"", - ignoreCase: false, - }, - }, - }, + name: "AsciiLetter", + pos: position{line: 117, col: 1, offset: 3480}, + expr: &charClassMatcher{ + pos: position{line: 117, col: 16, offset: 3495}, + val: "[A-Za-z_]", + chars: []rune{'_'}, + ranges: []rune{'A', 'Z', 'a', 'z'}, + ignoreCase: false, + inverted: false, }, }, { name: "EscapedChar", - pos: position{line: 84, col: 1, offset: 2764}, + pos: position{line: 119, col: 1, offset: 3506}, expr: &charClassMatcher{ - pos: position{line: 84, col: 16, offset: 2779}, + pos: position{line: 119, col: 16, offset: 3521}, val: "[\\x00-\\x1f\"\\\\]", chars: []rune{'"', '\\'}, ranges: []rune{'\x00', '\x1f'}, @@ -461,16 +613,16 @@ var g = &grammar{ }, { name: "EscapeSequence", - pos: position{line: 86, col: 1, offset: 2795}, + pos: position{line: 121, col: 1, offset: 3537}, expr: &choiceExpr{ - pos: position{line: 86, col: 19, offset: 2813}, + pos: position{line: 121, col: 19, offset: 3555}, alternatives: []interface{}{ &ruleRefExpr{ - pos: position{line: 86, col: 19, offset: 2813}, + pos: position{line: 121, col: 19, offset: 3555}, name: "SingleCharEscape", }, &ruleRefExpr{ - pos: position{line: 86, col: 38, offset: 2832}, + pos: position{line: 121, col: 38, offset: 3574}, name: "UnicodeEscape", }, }, @@ -478,9 +630,9 @@ var g = &grammar{ }, { name: "SingleCharEscape", - pos: position{line: 88, col: 1, offset: 2847}, + pos: position{line: 123, col: 1, offset: 3589}, expr: &charClassMatcher{ - pos: position{line: 88, col: 21, offset: 2867}, + pos: position{line: 123, col: 21, offset: 3609}, val: "[\"\\\\/bfnrt]", chars: []rune{'"', '\\', '/', 'b', 'f', 'n', 'r', 't'}, ignoreCase: false, @@ -489,29 +641,29 @@ var g = &grammar{ }, { name: "UnicodeEscape", - pos: position{line: 90, col: 1, offset: 2880}, + pos: position{line: 125, col: 1, offset: 3622}, expr: &seqExpr{ - pos: position{line: 90, col: 18, offset: 2897}, + pos: position{line: 125, col: 18, offset: 3639}, exprs: []interface{}{ &litMatcher{ - pos: position{line: 90, col: 18, offset: 2897}, + pos: position{line: 125, col: 18, offset: 3639}, val: "u", ignoreCase: false, }, &ruleRefExpr{ - pos: position{line: 90, col: 22, offset: 2901}, + pos: position{line: 125, col: 22, offset: 3643}, name: "HexDigit", }, &ruleRefExpr{ - pos: position{line: 90, col: 31, offset: 2910}, + pos: position{line: 125, col: 31, offset: 3652}, name: "HexDigit", }, &ruleRefExpr{ - pos: position{line: 90, col: 40, offset: 2919}, + pos: position{line: 125, col: 40, offset: 3661}, name: "HexDigit", }, &ruleRefExpr{ - pos: position{line: 90, col: 49, offset: 2928}, + pos: position{line: 125, col: 49, offset: 3670}, name: "HexDigit", }, }, @@ -519,9 +671,9 @@ var g = &grammar{ }, { name: "DecimalDigit", - pos: position{line: 92, col: 1, offset: 2938}, + pos: position{line: 127, col: 1, offset: 3680}, expr: &charClassMatcher{ - pos: position{line: 92, col: 17, offset: 2954}, + pos: position{line: 127, col: 17, offset: 3696}, val: "[0-9]", ranges: []rune{'0', '9'}, ignoreCase: false, @@ -530,9 +682,9 @@ var g = &grammar{ }, { name: "NonZeroDecimalDigit", - pos: position{line: 94, col: 1, offset: 2961}, + pos: position{line: 129, col: 1, offset: 3703}, expr: &charClassMatcher{ - pos: position{line: 94, col: 24, offset: 2984}, + pos: position{line: 129, col: 24, offset: 3726}, val: "[1-9]", ranges: []rune{'1', '9'}, ignoreCase: false, @@ -541,63 +693,38 @@ var g = &grammar{ }, { name: "HexDigit", - pos: position{line: 96, col: 1, offset: 2991}, + pos: position{line: 131, col: 1, offset: 3733}, expr: &charClassMatcher{ - pos: position{line: 96, col: 13, offset: 3003}, + pos: position{line: 131, col: 13, offset: 3745}, val: "[0-9a-f]i", ranges: []rune{'0', '9', 'a', 'f'}, ignoreCase: true, inverted: false, }, }, - { - name: "Bool", - pos: position{line: 98, col: 1, offset: 3014}, - expr: &choiceExpr{ - pos: position{line: 98, col: 9, offset: 3022}, - alternatives: []interface{}{ - &actionExpr{ - pos: position{line: 98, col: 9, offset: 3022}, - run: (*parser).callonBool2, - expr: &litMatcher{ - pos: position{line: 98, col: 9, offset: 3022}, - val: "true", - ignoreCase: false, - }, - }, - &actionExpr{ - pos: position{line: 101, col: 5, offset: 3133}, - run: (*parser).callonBool4, - expr: &litMatcher{ - pos: position{line: 101, col: 5, offset: 3133}, - val: "false", - ignoreCase: false, - }, - }, - }, - }, - }, - { - name: "Null", - pos: position{line: 106, col: 1, offset: 3246}, - expr: &actionExpr{ - pos: position{line: 106, col: 9, offset: 3254}, - run: (*parser).callonNull1, - expr: &litMatcher{ - pos: position{line: 106, col: 9, offset: 3254}, - val: "null", - ignoreCase: false, - }, - }, - }, { name: "_", displayName: "\"whitespace\"", - pos: position{line: 111, col: 1, offset: 3358}, + pos: position{line: 133, col: 1, offset: 3756}, expr: &zeroOrMoreExpr{ - pos: position{line: 111, col: 19, offset: 3376}, + pos: position{line: 133, col: 19, offset: 3774}, expr: &charClassMatcher{ - pos: position{line: 111, col: 19, offset: 3376}, + pos: position{line: 133, col: 19, offset: 3774}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + { + name: "ws", + displayName: "\"whitespace\"", + pos: position{line: 135, col: 1, offset: 3786}, + expr: &oneOrMoreExpr{ + pos: position{line: 135, col: 20, offset: 3805}, + expr: &charClassMatcher{ + pos: position{line: 135, col: 20, offset: 3805}, val: "[ \\t\\r\\n]", chars: []rune{' ', '\t', '\r', '\n'}, ignoreCase: false, @@ -607,28 +734,29 @@ var g = &grammar{ }, { name: "EOF", - pos: position{line: 113, col: 1, offset: 3388}, + pos: position{line: 137, col: 1, offset: 3817}, expr: ¬Expr{ - pos: position{line: 113, col: 8, offset: 3395}, + pos: position{line: 137, col: 8, offset: 3824}, expr: &anyMatcher{ - line: 113, col: 9, offset: 3396, + line: 137, col: 9, offset: 3825, }, }, }, }, } -func (c *current) onProg1(vals interface{}) (interface{}, error) { - if vals == nil { +func (c *current) onProg1(head, tail interface{}) (interface{}, error) { + if head == nil { return make([]interface{}, 0), nil } - return vals.([]interface{}), nil + tailSlice := tail.([]interface{}) + return append([]interface{}{head}, tailSlice...), nil } func (p *parser) callonProg1() (interface{}, error) { stack := p.vstack[len(p.vstack)-1] _ = stack - return p.cur.onProg1(stack["vals"]) + return p.cur.onProg1(stack["head"], stack["tail"]) } func (c *current) onTerm1(val interface{}) (interface{}, error) { @@ -641,49 +769,79 @@ func (p *parser) callonTerm1() (interface{}, error) { return p.cur.onTerm1(stack["val"]) } -func (c *current) onDictionary1(vals interface{}) (interface{}, error) { - valsSl := toIfaceSlice(vals) - if len(valsSl) == 0 { - return NewTerm([]*Term{}, DICTIONARY, c.text, "", c.pos.line, c.pos.col), nil +func (c *current) onObject1(head, tail interface{}) (interface{}, error) { + set := NewKeyValueSet() + + // Empty object. + if head == nil { + return NewTerm(set, OBJECT, 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))) + + // 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))) } - t := NewTerm(res, DICTIONARY, c.text, "", c.pos.line, c.pos.col) - return t, nil //res, nil + + result := NewTerm(set, OBJECT, c.text, "", c.pos.line, c.pos.col) + return result, nil } -func (p *parser) callonDictionary1() (interface{}, error) { +func (p *parser) callonObject1() (interface{}, error) { stack := p.vstack[len(p.vstack)-1] _ = stack - return p.cur.onDictionary1(stack["vals"]) + return p.cur.onObject1(stack["head"], stack["tail"]) } -func (c *current) onArray1(vals interface{}) (interface{}, error) { - valsSl := toIfaceSlice(vals) - if len(valsSl) == 0 { +func (c *current) onArray1(head, tail interface{}) (interface{}, error) { + + // Empty array. + if head == nil { 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) + + // 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)) } - t := NewTerm(res, ARRAY, c.text, "", c.pos.line, c.pos.col) - return t, nil //res, nil + + result := NewTerm(arr, ARRAY, c.text, "", c.pos.line, c.pos.col) + return result, nil } func (p *parser) callonArray1() (interface{}, error) { stack := p.vstack[len(p.vstack)-1] _ = stack - return p.cur.onArray1(stack["vals"]) + return p.cur.onArray1(stack["head"], stack["tail"]) +} + +func (c *current) onVar1(vals interface{}) (interface{}, error) { + v := &Var{string(c.text)} + t := NewTerm(v, VAR, c.text, "", c.pos.line, c.pos.col) + return t, nil +} + +func (p *parser) callonVar1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onVar1(stack["vals"]) } func (c *current) onNumber1() (interface{}, error) { @@ -716,7 +874,7 @@ func (p *parser) callonString1() (interface{}, error) { func (c *current) onBool2() (interface{}, error) { t := NewTerm(true, BOOLEAN, c.text, "", c.pos.line, c.pos.col) - return t, nil // true, nil + return t, nil } func (p *parser) callonBool2() (interface{}, error) { @@ -727,7 +885,7 @@ func (p *parser) callonBool2() (interface{}, error) { func (c *current) onBool4() (interface{}, error) { t := NewTerm(false, BOOLEAN, c.text, "", c.pos.line, c.pos.col) - return t, nil // false, nil + return t, nil } func (p *parser) callonBool4() (interface{}, error) { @@ -738,7 +896,7 @@ func (p *parser) callonBool4() (interface{}, error) { func (c *current) onNull1() (interface{}, error) { t := NewTerm(nil, NULL, c.text, "", c.pos.line, c.pos.col) - return t, nil //nil, nil + return t, nil } func (p *parser) callonNull1() (interface{}, error) { diff --git a/opalog/parser_test.go b/opalog/parser_test.go new file mode 100644 index 0000000000..e4ac121b40 --- /dev/null +++ b/opalog/parser_test.go @@ -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) +} diff --git a/opalog/set.go b/opalog/set.go new file mode 100644 index 0000000000..69c20c4324 --- /dev/null +++ b/opalog/set.go @@ -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 +} diff --git a/jsonlog/syntax_test.go b/opalog/set_test.go similarity index 94% rename from jsonlog/syntax_test.go rename to opalog/set_test.go index 4b48a3a96b..cfa04d012d 100644 --- a/jsonlog/syntax_test.go +++ b/opalog/set_test.go @@ -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") } } - diff --git a/opalog/term.go b/opalog/term.go new file mode 100644 index 0000000000..aff86301c9 --- /dev/null +++ b/opalog/term.go @@ -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 + } +} diff --git a/opalog/term_test.go b/opalog/term_test.go new file mode 100644 index 0000000000..27a60f1921 --- /dev/null +++ b/opalog/term_test.go @@ -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"))) +} diff --git a/opalog/var.go b/opalog/var.go new file mode 100644 index 0000000000..3d9eb6c628 --- /dev/null +++ b/opalog/var.go @@ -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 +} \ No newline at end of file diff --git a/opalog/var_test.go b/opalog/var_test.go new file mode 100644 index 0000000000..e9f19a3a9a --- /dev/null +++ b/opalog/var_test.go @@ -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"))) +}