Skip to content

Commit

Permalink
Update Rego syntax
Browse files Browse the repository at this point in the history
This commit contains a few syntax/parsing changes:

1) Old head :- body rule syntax is no longer supported. All rules must
be declared as: head { body } now.

2) Semicolon has replaced comma as the conjunction operator. This avoids
the ambiguity between set literals and curly-brace enclosed bodies.

3) ast.ParseBody will concatenate bodies that it receives from the
parser. This way, callers can invoke ast.ParseBody without enclosing
multiple expressions in braces.

4) Trailing commas are allowed in sets, arrays, and objects. To declare
a set of size one a trailing comma must be used, e.g., {foo,} =>
set(foo) where as {foo} => body(expr(foo)).

The following commit will update all of the existing test cases to bring
them into compliance with the new syntax.

Fixes open-policy-agent#253
  • Loading branch information
tsandall committed Feb 10, 2017
1 parent a5f6e4a commit 5607b7f
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 34 deletions.
20 changes: 13 additions & 7 deletions ast/parser_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,20 @@ func ParseBody(input string) (Body, error) {
if err != nil {
return nil, err
}
if len(stmts) != 1 {
return nil, fmt.Errorf("expected exactly one statement (body)")
}
body, ok := stmts[0].(Body)
if !ok {
return nil, fmt.Errorf("expected body but got %T", stmts[0])

result := Body{}

for _, stmt := range stmts {
if body, ok := stmt.(Body); ok {
result = append(result, body...)
} else {
return nil, fmt.Errorf("expected body but got %T", stmt)
}
}
return body, nil

setExprIndices(result)

return result, nil
}

// ParseExpr returns exactly one expression.
Expand Down
5 changes: 3 additions & 2 deletions ast/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,9 @@ func (rule *Rule) String() string {
}
buf = append(buf, rule.Head.String())
if !rule.Default {
buf = append(buf, ":-")
buf = append(buf, "{")
buf = append(buf, rule.Body.String())
buf = append(buf, "}")
}
return strings.Join(buf, " ")
}
Expand Down Expand Up @@ -556,7 +557,7 @@ func (body Body) String() string {
for _, v := range body {
buf = append(buf, v.String())
}
return strings.Join(buf, ", ")
return strings.Join(buf, "; ")
}

// Vars returns a VarSet containing variables in body. The params can be set to
Expand Down
54 changes: 29 additions & 25 deletions ast/rego.peg
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ DefaultRules <- "default" ws name:Var _ "=" _ value:Term {
return []*Rule{rule}, nil
}

NormalRules <- head:RuleHead _ val:(IfBody / (BraceEnclosedBody (_ BraceEnclosedBody)*)) {
NormalRules <- head:RuleHead _ val:(NonEmptyBraceEnclosedBody (_ NonEmptyBraceEnclosedBody)*) {

if head == nil {
return nil, nil
Expand All @@ -160,21 +160,14 @@ NormalRules <- head:RuleHead _ val:(IfBody / (BraceEnclosedBody (_ BraceEnclosed
// Parser is expected to return []Statement. If the author has chained
// rule bodies together (disjunction) then multiple rules must be returned.
bodies := []Body{}
body, ok := val.(Body)

if ok {
bodies = append(bodies, body)
} else {
// This is just unpacking the EnclosedBody structures above.
sl := val.([]interface{})
bodies = append(bodies, sl[0].(Body))
for _, x := range sl[1].([]interface{}) {
bodies = append(bodies, x.([]interface{})[1].(Body))
}

sl := val.([]interface{})
bodies = append(bodies, sl[0].(Body))
for _, x := range sl[1].([]interface{}) {
bodies = append(bodies, x.([]interface{})[1].(Body))
}

rules := make([]*Rule, len(bodies))

for i := range bodies {
rules[i] = &Rule{
Head: head.(*Head).Copy(),
Expand Down Expand Up @@ -219,24 +212,35 @@ RuleHead <- name:Var key:( _ "[" _ Term _ "]" _ )? value:( _ "=" _ Term )? {
return head, nil
}

IfBody <- ":-" _ body:Body {
return body, nil
}
Body <- BraceEnclosedBody / NonWhitespaceBody

BraceEnclosedBody <- "{" _ val:EnclosedBody? _ "}" {
NonEmptyBraceEnclosedBody <- "{" _ val:WhitespaceBody? _ "}" {
if val == nil {
panic("body must be non-empty")
}
return val, nil
}

// When body is enclosed, literals are separated by newline or comma.
EnclosedBody <- head:Literal tail:( [ \t]* ( "," / [\r\n] ) _ Literal)* {
BraceEnclosedBody <- "{" _ val:WhitespaceBody? _ "}" {

if val == nil {
body := NewBody(NewExpr(ObjectTerm()))
body[0].Location = currentLocation(c)
return body, nil
}

return val, nil
}

// When body is enclosed, literals must be separated by newline or semicolon.
// This is used for rules and comprehensions.
WhitespaceBody <- head:Literal tail:( ([ \t]* / Comment) ( ";" / [\r\n] ) _ Literal)* {
return ifacesToBody(head, tail.([]interface{})...), nil
}

// When body is not closed, literals are separated by comma.
Body <- head:Literal tail:( _ "," _ (Literal / ParseError))* {
// When body is not enclosed, literals must be separated by semicolon This is
// used for parsing ad-hoc queries.
NonWhitespaceBody <- head:Literal tail:( _ ";" _ (Literal / ParseError))* {
return ifacesToBody(head, tail.([]interface{})...), nil
}

Expand Down Expand Up @@ -336,7 +340,7 @@ Term <- val:( Comprehension / Composite / Scalar / Ref / Var ) {

Comprehension <- ArrayComprehension

ArrayComprehension <- "[" _ term:Term _ "|" _ body:EnclosedBody _ "]" {
ArrayComprehension <- "[" _ term:Term _ "|" _ body:WhitespaceBody _ "]" {
ac := ArrayComprehensionTerm(term.(*Term), body.(Body))
ac.Location = currentLocation(c)
return ac, nil
Expand All @@ -348,7 +352,7 @@ Scalar <- Number / String / Bool / Null

Key <- Scalar / Ref / Var

Object <- '{' _ head:(Key _ ':' _ Term)? tail:( _ ',' _ Key _ ':' _ Term )* _ '}' {
Object <- '{' _ head:(Key _ ':' _ Term)? tail:( _ ',' _ Key _ ':' _ Term )* _ ','? _ '}' {
obj := ObjectTerm()
obj.Location = currentLocation(c)

Expand All @@ -372,7 +376,7 @@ Object <- '{' _ head:(Key _ ':' _ Term)? tail:( _ ',' _ Key _ ':' _ Term )* _ '}
return obj, nil
}

Array <- '[' _ head:Term? tail:(_ ',' _ Term)* _ ']' {
Array <- '[' _ head:Term? tail:(_ ',' _ Term)* _ ','? _ ']' {

arr := ArrayTerm()
arr.Location = currentLocation(c)
Expand Down Expand Up @@ -404,7 +408,7 @@ SetEmpty <- "set(" _ ")" {
return set, nil
}

SetNonEmpty <- '{' _ head:Term tail:(_ ',' _ Term)* _ '}' {
SetNonEmpty <- '{' _ head:Term tail:(_ ',' _ Term)* _ ','? _ '}' {
set := SetTerm()
set.Location = currentLocation(c)

Expand Down

0 comments on commit 5607b7f

Please sign in to comment.