Skip to content

Commit

Permalink
operators: Add inequality operator
Browse files Browse the repository at this point in the history
Signed-off-by: Enrique Llorente <ellorent@redhat.com>
  • Loading branch information
qinqon committed Jul 12, 2023
1 parent d619f00 commit b929b32
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 9 deletions.
4 changes: 4 additions & 0 deletions nmpolicy/internal/ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Terminal struct {
type Node struct {
Meta
EqFilter *TernaryOperator `json:"eqfilter,omitempty"`
NeFilter *TernaryOperator `json:"nefilter,omitempty"`
Replace *TernaryOperator `json:"replace,omitempty"`
Path *VariadicOperator `json:"path,omitempty"`
Terminal
Expand All @@ -43,6 +44,9 @@ func (n Node) String() string {
if n.EqFilter != nil {
return fmt.Sprintf("EqFilter(%s)", *n.EqFilter)
}
if n.NeFilter != nil {
return fmt.Sprintf("NeFilter(%s)", *n.EqFilter)
}
if n.Replace != nil {
return fmt.Sprintf("Replace(%s)", *n.Replace)
}
Expand Down
2 changes: 2 additions & 0 deletions nmpolicy/internal/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (l *lexer) lexCurrentRune() (*Token, error) {
return l.lexEqualAs(REPLACE)
} else if l.isEqual() {
return l.lexEqualAs(EQFILTER)
} else if l.isExclamationMark() {
return l.lexEqualAs(NEFILTER)
} else if l.isPlus() {
return &Token{l.scn.Position(), MERGE, string(l.scn.Rune())}, nil
} else if l.isPipe() {
Expand Down
5 changes: 3 additions & 2 deletions nmpolicy/internal/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func testBasicExpressions(t *testing.T) {
{47, lexer.IDENTITY, "doo3"},
{50, lexer.EOF, ""}},
}},
{" . foo1.dar1:=foo2 . dar2 ... moo3+boo3|doo3 == := :=", expected{tokens: []lexer.Token{
{" . foo1.dar1:=foo2 . dar2 ... moo3+boo3|doo3 == := := !=", expected{tokens: []lexer.Token{

Check failure on line 100 in nmpolicy/internal/lexer/lexer_test.go

View workflow job for this annotation

GitHub Actions / lint

100-122 lines are duplicate of `nmpolicy/internal/lexer/lexer_test.go:77-99` (dupl)

Check failure on line 100 in nmpolicy/internal/lexer/lexer_test.go

View workflow job for this annotation

GitHub Actions / lint

100-122 lines are duplicate of `nmpolicy/internal/lexer/lexer_test.go:77-99` (dupl)

Check failure on line 100 in nmpolicy/internal/lexer/lexer_test.go

View workflow job for this annotation

GitHub Actions / lint

100-122 lines are duplicate of `nmpolicy/internal/lexer/lexer_test.go:77-99` (dupl)
{1, lexer.DOT, "."},
{3, lexer.IDENTITY, "foo1"},
{7, lexer.DOT, "."},
Expand All @@ -117,7 +117,8 @@ func testBasicExpressions(t *testing.T) {
{45, lexer.EQFILTER, "=="},
{48, lexer.REPLACE, ":="},
{51, lexer.REPLACE, ":="},
{52, lexer.EOF, ""}},
{54, lexer.NEFILTER, "!="},
{55, lexer.EOF, ""}},
}},
{"foo1.3|foo2", expected{tokens: []lexer.Token{
{0, lexer.IDENTITY, "foo1"},
Expand Down
6 changes: 5 additions & 1 deletion nmpolicy/internal/lexer/rune.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (l *lexer) isPipe() bool {
return l.scn.Rune() == '|'
}

func (l *lexer) isExclamationMark() bool {
return l.scn.Rune() == '!'
}

func (l *lexer) isDelimiter() bool {
return l.isEOF() || l.isSpace() || l.isDot() || l.isEqual() || l.isColon() || l.isPlus() || l.isPipe()
return l.isEOF() || l.isSpace() || l.isDot() || l.isEqual() || l.isColon() || l.isPlus() || l.isPipe() || l.isExclamationMark()
}
2 changes: 2 additions & 0 deletions nmpolicy/internal/lexer/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
PIPE // |
REPLACE // :=
EQFILTER // ==
NEFILTER // !=
MERGE // +
operatorsEnd
)
Expand All @@ -47,6 +48,7 @@ var tokens = []string{

REPLACE: "REPLACE",
EQFILTER: "EQFILTER",
NEFILTER: "NEFILTER",
MERGE: "MERGE",
}

Expand Down
7 changes: 7 additions & 0 deletions nmpolicy/internal/parser/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ func wrapWithInvalidEqualityFilterError(err error) *parserError {
}
}

func wrapWithInvalidInequalityFilterError(err error) *parserError {
return &parserError{
prefix: "invalid inequality filter",
inner: err,
}
}

func wrapWithInvalidReplaceError(err error) *parserError {
return &parserError{
prefix: "invalid replace",
Expand Down
16 changes: 16 additions & 0 deletions nmpolicy/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (p *parser) parse() (ast.Node, error) {
if err := p.parseEqFilter(); err != nil {
return ast.Node{}, err
}
} else if p.currentToken().Type == lexer.NEFILTER {
if err := p.parseNeFilter(); err != nil {
return ast.Node{}, err
}
} else if p.currentToken().Type == lexer.REPLACE {
if err := p.parseReplace(); err != nil {
return ast.Node{}, err
Expand Down Expand Up @@ -227,6 +231,18 @@ func (p *parser) parseEqFilter() error {
return nil
}

func (p *parser) parseNeFilter() error {
operator := &ast.Node{
Meta: ast.Meta{Position: p.currentToken().Position},
NeFilter: &ast.TernaryOperator{},
}
if err := p.fillInTernaryOperator(operator.NeFilter); err != nil {
return wrapWithInvalidInequalityFilterError(err)
}
p.lastNode = operator
return nil
}

func (p *parser) parseReplace() error {
operator := &ast.Node{
Meta: ast.Meta{Position: p.currentToken().Position},
Expand Down
136 changes: 136 additions & 0 deletions nmpolicy/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ import (
func TestParser(t *testing.T) {
testParsePath(t)
testParseEqFilter(t)
testParseNeFilter(t)
testParseReplace(t)
testParseReplaceWithPath(t)
testParseCapturePipeReplace(t)

testParseBasicFailures(t)
testParsePathFailures(t)
testParseEqFilterFailure(t)
testParseNeFilterFailure(t)
testParseReplaceFailure(t)

testParserReuse(t)
Expand Down Expand Up @@ -188,6 +190,59 @@ func testParseEqFilterFailure(t *testing.T) {
runTest(t, tests)
}

func testParseNeFilterFailure(t *testing.T) {
var tests = []test{
expectError(`invalid inequality filter: missing left hand argument
| !=0.0.0.0/0
| ^`,
fromTokens(
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectError(`invalid inequality filter: left hand argument is not a path
| foo!=0.0.0.0/0
| ...^`,
fromTokens(
str("foo"),
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectError(`invalid inequality filter: missing right hand argument
| routes.running.destination!=
| ...........................^`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
eof(),
),
),

expectError(`invalid inequality filter: right hand argument is not a string or identity
| routes.running.destination!=!=
| ............................^`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
nefilter(),
eof(),
),
),
}
runTest(t, tests)
}

func testParseReplaceFailure(t *testing.T) {
var tests = []test{
expectError(`invalid replace: missing left hand argument
Expand Down Expand Up @@ -344,6 +399,83 @@ eqfilter:
runTest(t, tests)
}

func testParseNeFilter(t *testing.T) {
var tests = []test{
expectAST(t, `
pos: 26
nefilter:
- pos: 0
identity: currentState
- pos: 0
path:
- pos: 0
identity: routes
- pos: 7
identity: running
- pos: 15
identity: destination
- pos: 28
string: 0.0.0.0/0`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectAST(t, `
pos: 33
nefilter:
- pos: 0
identity: currentState
- pos: 0
path:
- pos: 0
identity: routes
- pos: 7
identity: running
- pos: 15
identity: next-hop-interface
- pos: 35
path:
- pos: 35
identity: capture
- pos: 43
identity: default-gw
- pos: 54
identity: routes
- pos: 61
number: 0
- pos: 63
identity: next-hop-interface
`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("next-hop-interface"),
nefilter(),
identity("capture"),
dot(),
identity("default-gw"),
dot(),
identity("routes"),
dot(),
number(0),
dot(),
identity("next-hop-interface"),
eof(),
),
),
}
runTest(t, tests)
}

func testParseReplace(t *testing.T) {
var tests = []test{
expectAST(t, `
Expand Down Expand Up @@ -641,6 +773,10 @@ func eqfilter() lexer.Token {
return lexer.Token{Type: lexer.EQFILTER, Literal: "=="}
}

func nefilter() lexer.Token {
return lexer.Token{Type: lexer.NEFILTER, Literal: "!="}
}

func replace() lexer.Token {
return lexer.Token{Type: lexer.REPLACE, Literal: ":="}
}
Expand Down
4 changes: 4 additions & 0 deletions nmpolicy/internal/resolver/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func wrapWithEqFilterError(err error) error {
return fmt.Errorf("eqfilter error: %w", err)
}

func wrapWithNeFilterError(err error) error {
return fmt.Errorf("nefilter error: %w", err)
}

func replaceError(format string, a ...interface{}) error {
return wrapWithReplaceError(fmt.Errorf(format, a...))
}
Expand Down
18 changes: 14 additions & 4 deletions nmpolicy/internal/resolver/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import (
"github.com/nmstate/nmpolicy/nmpolicy/internal/ast"
)

func filter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, expectedValue interface{}) (map[string]interface{}, error) {
filtered, err := visitState(newPath(pathSteps), inputState, &filterVisitor{expectedValue: expectedValue})
func filter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, operator func(interface{}, interface{}) bool, expectedValue interface{}) (map[string]interface{}, error) {

Check failure on line 26 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 185 characters (lll)

Check failure on line 26 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 185 characters (lll)

Check failure on line 26 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 185 characters (lll)
filtered, err := visitState(newPath(pathSteps), inputState, &filterVisitor{
operator: operator,
expectedValue: expectedValue,
})

if err != nil {
return nil, fmt.Errorf("failed applying operation on the path: %w", err)
Expand All @@ -40,9 +43,16 @@ func filter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, e
}
return filteredMap, nil
}
func eqfilter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, expectedValue interface{}) (map[string]interface{}, error) {

Check failure on line 46 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)

Check failure on line 46 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)

Check failure on line 46 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)
return filter(inputState, pathSteps, func(lhs, rhs interface{}) bool { return lhs == rhs }, expectedValue)
}
func nefilter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, expectedValue interface{}) (map[string]interface{}, error) {

Check failure on line 49 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)

Check failure on line 49 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)

Check failure on line 49 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 141 characters (lll)
return filter(inputState, pathSteps, func(lhs, rhs interface{}) bool { return lhs != rhs }, expectedValue)
}

type filterVisitor struct {
mergeVisitResult bool
operator func(interface{}, interface{}) bool
expectedValue interface{}
}

Expand All @@ -61,7 +71,7 @@ func (e filterVisitor) visitLastMap(p path, mapToFilter map[string]interface{})
return nil, pathError(p.currentStep, `type missmatch: the value in the path doesn't match the value to filter. `+
`"%T" != "%T" -> %+v != %+v`, obtainedValue, e.expectedValue, obtainedValue, e.expectedValue)
}
if obtainedValue == e.expectedValue {
if e.operator(obtainedValue, e.expectedValue) {
return mapToFilter, nil
}
return nil, nil
Expand Down Expand Up @@ -109,7 +119,7 @@ func (e filterVisitor) visitSlice(p path, sliceToVisit []interface{}) (interface
for _, interfaceToVisit := range sliceToVisit {
// Filter only the first slice by forcing "mergeVisitResult" to true
// for the the following ones.
visitResult, err := visitState(p, interfaceToVisit, &filterVisitor{mergeVisitResult: true, expectedValue: e.expectedValue})
visitResult, err := visitState(p, interfaceToVisit, &filterVisitor{mergeVisitResult: true, operator: e.operator, expectedValue: e.expectedValue})

Check failure on line 122 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 147 characters (lll)

Check failure on line 122 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 147 characters (lll)

Check failure on line 122 in nmpolicy/internal/resolver/filter.go

View workflow job for this annotation

GitHub Actions / lint

line is 147 characters (lll)
if err != nil {
return nil, err
}
Expand Down
15 changes: 13 additions & 2 deletions nmpolicy/internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func (r *resolver) resolveCaptureEntryName(captureEntryName string) (types.NMSta
func (r *resolver) resolveCaptureASTEntry() (types.NMState, error) {
if r.currentNode.EqFilter != nil {
return r.resolveEqFilter()
} else if r.currentNode.NeFilter != nil {
return r.resolveNeFilter()
} else if r.currentNode.Replace != nil {
return r.resolveReplace()
} else if r.currentNode.Path != nil {
Expand All @@ -124,13 +126,22 @@ func (r *resolver) resolveCaptureASTEntry() (types.NMState, error) {

func (r *resolver) resolveEqFilter() (types.NMState, error) {
operator := r.currentNode.EqFilter
filteredState, err := r.resolveTernaryOperator(operator, filter)
filteredState, err := r.resolveTernaryOperator(operator, eqfilter)
if err != nil {
return nil, wrapWithEqFilterError(err)
}
return filteredState, nil
}

func (r *resolver) resolveNeFilter() (types.NMState, error) {
operator := r.currentNode.NeFilter
filteredState, err := r.resolveTernaryOperator(operator, nefilter)
if err != nil {
return nil, wrapWithNeFilterError(err)
}
return filteredState, nil
}

func (r *resolver) resolveReplace() (types.NMState, error) {
operator := r.currentNode.Replace
replacedState, err := r.resolveTernaryOperator(operator, replace)
Expand All @@ -141,7 +152,7 @@ func (r *resolver) resolveReplace() (types.NMState, error) {
}

func (r *resolver) resolvePathFilter() (types.NMState, error) {
return filter(r.currentState, *r.currentNode.Path, nil)
return eqfilter(r.currentState, *r.currentNode.Path, nil)
}

func (r *resolver) resolveTernaryOperator(operator *ast.TernaryOperator,
Expand Down
Loading

0 comments on commit b929b32

Please sign in to comment.