Skip to content

Commit

Permalink
parser: Dummp Replace ast.Node on REPLACE token
Browse files Browse the repository at this point in the history
To implement the Replace operation the token REPLACE has to be
understood by the parser and construct the proper ast.Node.

Signed-off-by: Quique Llorente <[email protected]>
  • Loading branch information
qinqon committed Dec 2, 2021
1 parent f4ed96a commit 060fe1f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 6 deletions.
1 change: 1 addition & 0 deletions nmpolicy/internal/ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Terminal struct {
type Node struct {
Meta
EqFilter *TernaryOperator `json:"eqfilter,omitempty"`
Replace *TernaryOperator `json:"replace,omitempty"`
Path *VariadicOperator `json:"path,omitempty"`
Terminal
}
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 invalidEqualityFilterError(msg string) *parserError {
}
}

func invalidReplaceError(msg string) *parserError {
return &parserError{
prefix: "invalid replace",
msg: msg,
}
}

func invalidExpressionError(msg string) *parserError {
return &parserError{
prefix: "invalid expression",
Expand Down
39 changes: 37 additions & 2 deletions nmpolicy/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ func (p *parser) parse() (ast.Node, error) {
if err := p.parseEqFilter(); err != nil {
return ast.Node{}, err
}
} else if p.currentToken().Type == lexer.REPLACE {
if err := p.parseReplace(); err != nil {
return ast.Node{}, err
}
} else {
return ast.Node{}, invalidExpressionError(fmt.Sprintf("unexpected token `%+v`", p.currentToken().Literal))
}
Expand Down Expand Up @@ -165,7 +169,7 @@ func (p *parser) parsePath() error {
}
path := append(*operator.Path, *p.lastNode)
operator.Path = &path
} else if p.currentToken().Type != lexer.EOF && p.currentToken().Type != lexer.EQFILTER {
} else if p.currentToken().Type != lexer.EOF && p.currentToken().Type != lexer.EQFILTER && p.currentToken().Type != lexer.REPLACE {
return invalidPathError("missing dot")
} else {
// Token has not being consumed let's go back.
Expand Down Expand Up @@ -204,9 +208,40 @@ func (p *parser) parseEqFilter() error {
return err
}
operator.EqFilter[2] = *p.lastNode
} else if p.currentToken().Type != lexer.EOF {
} else if p.currentToken().Type == lexer.EOF {
return invalidEqualityFilterError("missing right hand argument")
} else {
return invalidEqualityFilterError("right hand argument is not a string or identity")
}
p.lastNode = operator
return nil
}

func (p *parser) parseReplace() error {
operator := &ast.Node{
Meta: ast.Meta{Position: p.currentToken().Position},
Replace: &ast.TernaryOperator{},
}
if p.lastNode == nil {
return invalidReplaceError("missing left hand argument")
}
if p.lastNode.Path == nil {
return invalidReplaceError("left hand argument is not a path")
}
operator.Replace[0].Terminal = ast.CurrentStateIdentity()
operator.Replace[1] = *p.lastNode

p.nextToken()
if p.currentToken().Type == lexer.STRING {
if err := p.parseString(); err != nil {
return err
}
operator.Replace[2] = *p.lastNode
} else if p.currentToken().Type == lexer.EOF {
return invalidReplaceError("missing right hand argument")
} else {
return invalidReplaceError("right hand argument is not a string")
}
p.lastNode = operator
return nil
}
127 changes: 123 additions & 4 deletions nmpolicy/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,32 @@ import (
)

func TestParser(t *testing.T) {
testParseFailures(t)
testParseSuccess(t)
testParsePath(t)
testParseEqFilter(t)
testParseReplace(t)

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

testParserReuse(t)
}

func testParseFailures(t *testing.T) {
func testParseBasicFailures(t *testing.T) {
var tests = []test{
expectError("invalid expression: unexpected token `.`",
fromTokens(
dot(),
eof(),
),
),
}
runTest(t, tests)
}

func testParsePathFailures(t *testing.T) {
var tests = []test{
expectError(`invalid path: missing identity or number after dot`,
fromTokens(
identity("routes"),
Expand All @@ -65,6 +78,12 @@ func testParseFailures(t *testing.T) {
eof(),
),
),
}
runTest(t, tests)
}

func testParseEqFilterFailure(t *testing.T) {
var tests = []test{
expectError(`invalid equality filter: missing left hand argument`,
fromTokens(
eqfilter(),
Expand All @@ -80,6 +99,18 @@ func testParseFailures(t *testing.T) {
eof(),
),
),
expectError(`invalid equality filter: missing right hand argument`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
eqfilter(),
eof(),
),
),

expectError(`invalid equality filter: right hand argument is not a string or identity`,
fromTokens(
identity("routes"),
Expand All @@ -96,7 +127,52 @@ func testParseFailures(t *testing.T) {
runTest(t, tests)
}

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

expectError(`invalid replace: right hand argument is not a string`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
replace(),
replace(),
eof(),
),
),
}
runTest(t, tests)
}

func testParsePath(t *testing.T) {
var tests = []test{
expectEmptyAST(fromTokens()),
expectEmptyAST(fromTokens(eof())),
Expand All @@ -118,6 +194,12 @@ path:
eof(),
),
),
}
runTest(t, tests)
}

func testParseEqFilter(t *testing.T) {
var tests = []test{
expectAST(t, `
pos: 26
eqfilter:
Expand Down Expand Up @@ -193,6 +275,39 @@ eqfilter:
runTest(t, tests)
}

func testParseReplace(t *testing.T) {
var tests = []test{
expectAST(t, `
pos: 33
replace:
- pos: 0
identity: currentState
- pos: 0
path:
- pos: 0
identity: routes
- pos: 7
identity: running
- pos: 15
identity: next-hop-interface
- pos: 35
string: br1
`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("next-hop-interface"),
replace(),
str("br1"),
eof(),
),
),
}
runTest(t, tests)
}

func testParserReuse(t *testing.T) {
p := parser.New()
testToRun1 := expectAST(t, `
Expand Down Expand Up @@ -333,3 +448,7 @@ func eof() lexer.Token {
func eqfilter() lexer.Token {
return lexer.Token{Type: lexer.EQFILTER, Literal: "=="}
}

func replace() lexer.Token {
return lexer.Token{Type: lexer.REPLACE, Literal: ":="}
}

0 comments on commit 060fe1f

Please sign in to comment.