Add jsonlog parser

Just the basic JSON types
This commit is contained in:
Tim Hinrichs
2016-01-06 09:51:24 -08:00
parent cecbc34247
commit 9cfea2c20c
4 changed files with 848 additions and 317 deletions
+2 -1
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+66 -3
View File
@@ -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)
}