mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
Add jsonlog parser
Just the basic JSON types
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
go get -u github.com/PuerkitoBio/pigeon
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
# generate source code for parser
|
||||
# generate source code for parser. Delete first so no silent errors.
|
||||
rm src/jsonlog/parser.go
|
||||
pigeon src/jsonlog/jsonlog.peg | goimports > src/jsonlog/parser.go
|
||||
|
||||
|
||||
|
||||
+132
-48
@@ -1,26 +1,55 @@
|
||||
// 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.
|
||||
|
||||
{
|
||||
|
||||
// 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
|
||||
|
||||
// part of the initializer code block omitted for brevity
|
||||
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
|
||||
)
|
||||
|
||||
var ops = map[string]func(int, int) int {
|
||||
"+": func(l, r int) int {
|
||||
return l + r
|
||||
},
|
||||
"-": func(l, r int) int {
|
||||
return l - r
|
||||
},
|
||||
"*": func(l, r int) int {
|
||||
return l * r
|
||||
},
|
||||
"/": func(l, r int) int {
|
||||
return l / r
|
||||
},
|
||||
// Location records a position in source code
|
||||
type Location struct {
|
||||
File string
|
||||
Row int
|
||||
Col int
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Equal checks if two terms are equal
|
||||
func (t1 *Term) Equal (t2 *Term) bool {
|
||||
return t1.Kind == t2.Kind && t1.Value == t2.Value
|
||||
}
|
||||
|
||||
func toIfaceSlice(v interface{}) []interface{} {
|
||||
@@ -29,52 +58,107 @@ func toIfaceSlice(v interface{}) []interface{} {
|
||||
}
|
||||
return v.([]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
func eval(first, rest interface{}) int {
|
||||
l := first.(int)
|
||||
restSl := toIfaceSlice(rest)
|
||||
for _, v := range restSl {
|
||||
restExpr := toIfaceSlice(v)
|
||||
r := restExpr[3].(int)
|
||||
op := restExpr[1].(string)
|
||||
l = ops[op](l, r)
|
||||
Prog <- _ vals:Term+ EOF {
|
||||
if vals == nil {
|
||||
return make([]interface{}, 0), nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
return vals.([]interface{}), nil
|
||||
// valsSl := toIfaceSlice(vals)
|
||||
// return valsSl, nil
|
||||
// switch len(valsSl) {
|
||||
// case 0:
|
||||
// return nil, nil
|
||||
// case 1:
|
||||
// return valsSl[0], nil
|
||||
// default:
|
||||
// return valsSl, nil
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
Input <- expr:Expr EOF {
|
||||
return expr, nil
|
||||
Term <- val:( Dictionary / Array / Number / String / Bool / Null ) _ {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
Expr <- _ first:Term rest:( _ AddOp _ Term )* _ {
|
||||
return eval(first, rest), nil
|
||||
Dictionary <- '{' _ vals:( String _ ':' _ Value ( ',' _ String _ ':' _ Value )* )? '}' {
|
||||
res := make(map[string]interface{})
|
||||
valsSl := toIfaceSlice(vals)
|
||||
if len(valsSl) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
res[valsSl[0].(string)] = valsSl[4]
|
||||
restSl := toIfaceSlice(valsSl[5])
|
||||
for _, v := range restSl {
|
||||
vSl := toIfaceSlice(v)
|
||||
res[vSl[2].(string)] = vSl[6]
|
||||
}
|
||||
t := NewTerm(res, DICTIONARY, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //res, nil
|
||||
}
|
||||
|
||||
Term <- first:Factor rest:( _ MulOp _ Factor )* {
|
||||
return eval(first, rest), nil
|
||||
Array <- '[' _ vals:( Value ( ',' _ Value )* )? ']' {
|
||||
valsSl := toIfaceSlice(vals)
|
||||
if len(valsSl) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
res := []interface{}{valsSl[0]}
|
||||
restSl := toIfaceSlice(valsSl[1])
|
||||
for _, v := range restSl {
|
||||
vSl := toIfaceSlice(v)
|
||||
res = append(res, vSl[2])
|
||||
}
|
||||
t := NewTerm(res, ARRAY, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //res, nil
|
||||
}
|
||||
|
||||
Factor <- '(' expr:Expr ')' {
|
||||
return expr, nil
|
||||
} / integer:Integer {
|
||||
return integer, 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
|
||||
}
|
||||
|
||||
AddOp <- ( '+' / '-' ) {
|
||||
return string(c.text), nil
|
||||
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
|
||||
}
|
||||
|
||||
MulOp <- ( '*' / '/' ) {
|
||||
return string(c.text), nil
|
||||
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
|
||||
}
|
||||
|
||||
Integer <- '-'? [0-9]+ {
|
||||
return strconv.Atoi(string(c.text))
|
||||
Null <- "null" {
|
||||
t := NewTerm(nil, NULL, c.text, "", c.pos.line, c.pos.col)
|
||||
return t, nil //nil, nil
|
||||
}
|
||||
|
||||
_ "whitespace" <- [ \n\t\r]*
|
||||
_ "whitespace" <- [ \t\r\n]*
|
||||
|
||||
EOF <- !.
|
||||
|
||||
|
||||
+648
-265
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,72 @@ import (
|
||||
// "fmt"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
_, err := Parse("nonexistent", []byte("2 + 3"))
|
||||
func testParse1Term(t *testing.T, msg string, expr string, correct *Term) interface{} {
|
||||
p, err := Parse("", []byte(expr))
|
||||
if err != nil {
|
||||
t.Errorf("Error when parsing: %s", err)
|
||||
t.Errorf("Error on test %s: parse error on %s: %s", msg, expr, err)
|
||||
}
|
||||
parsed := p.([]interface{})
|
||||
if len(parsed) != 1 {
|
||||
t.Errorf("Error on test %s: failed to parse 1 element from %s: %v",
|
||||
msg, expr, parsed)
|
||||
}
|
||||
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 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", NewTerm1(nil, NULL))
|
||||
testParse1Term(t, "true", "true", NewTerm1(true, BOOLEAN))
|
||||
testParse1Term(t, "false", "false", NewTerm1(false, BOOLEAN))
|
||||
testParse1Term(t, "integer", "53", NewTerm1(53, NUMBER))
|
||||
testParse1Term(t, "integer2", "-53", NewTerm1(-53, NUMBER))
|
||||
testParse1Term(t, "float", "16.7", NewTerm1(16.7, NUMBER))
|
||||
testParse1Term(t, "float2", "-16.7", NewTerm1(-16.7, NUMBER))
|
||||
testParse1Term(t, "exponent", "6e7", NewTerm1(6e7, NUMBER))
|
||||
testParse1Term(t, "string", "\"a string\"", NewTerm1("a string", STRING))
|
||||
testParse1Term(t, "string", "\"a string u6abc7def8abc0def with unicode\"",
|
||||
NewTerm1("a string u6abc7def8abc0def with unicode", STRING))
|
||||
|
||||
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 TestVariables(t *testing.T) {
|
||||
// testParse(t, "variable", "\"a string\"")
|
||||
// }
|
||||
|
||||
// NewTerm1 creates a NewTerm for testing using a couple default values
|
||||
func NewTerm1(x interface{}, kind int) *Term {
|
||||
var val interface{}
|
||||
switch x.(type) {
|
||||
case uint, uint8, uint16, uint32, uint64, int8, int16, int32, int64, int:
|
||||
val = float64(x.(int))
|
||||
case float32:
|
||||
val = float64(x.(float32))
|
||||
default:
|
||||
val = x
|
||||
}
|
||||
return NewTerm(val, kind, []byte(""), "", 0, 0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user