From d995ace63f36600d895104c1ac1c17f6bd4da1f2 Mon Sep 17 00:00:00 2001 From: Steven Turturo Date: Wed, 25 Sep 2024 16:16:12 -0700 Subject: [PATCH] feat: add nil token to allow nil permission expressions TODO: add more tests to make sure this doesn't break anything --- pkg/dsl/lexer/lexer.go | 6 ++- pkg/dsl/lexer/lexer_test.go | 92 +++++++++++++++++++++++++++++++++++ pkg/dsl/parser/parser.go | 3 +- pkg/dsl/parser/parser_test.go | 17 +++++++ pkg/dsl/token/token.go | 1 + 5 files changed, 117 insertions(+), 2 deletions(-) diff --git a/pkg/dsl/lexer/lexer.go b/pkg/dsl/lexer/lexer.go index d6984e427..625285fa4 100644 --- a/pkg/dsl/lexer/lexer.go +++ b/pkg/dsl/lexer/lexer.go @@ -147,9 +147,13 @@ func (l *Lexer) NextToken() (tok token.Token) { if isLetter(l.ch) { tok.PositionInfo = positionInfo(l.linePosition, l.columnPosition) tok.Literal = l.lexIdent() - if tok.Literal == "true" || tok.Literal == "false" { + switch tok.Literal { + case "true", "false": tok.Type = token.BOOLEAN return + case "nil": + tok.Type = token.NIL + return } tok.Type = token.LookupKeywords(tok.Literal) return diff --git a/pkg/dsl/lexer/lexer_test.go b/pkg/dsl/lexer/lexer_test.go index 64a69755d..ae60b4910 100644 --- a/pkg/dsl/lexer/lexer_test.go +++ b/pkg/dsl/lexer/lexer_test.go @@ -980,5 +980,97 @@ rule check_ip_address(ip_addresses string[]) { Expect(index + lexeme.Literal).Should(Equal(index + tt.expectedLiteral)) } }) + + It("Case 10", func() { + str := ` +entity user {} + +entity organization { + relation admin @user + relation member @user + + action create_repository = nil + action delete = nil +} + +` + + tests := []struct { + expectedType token.Type + expectedLiteral string + }{ + {token.NEWLINE, "\n"}, + {token.ENTITY, "entity"}, + {token.SPACE, " "}, + {token.IDENT, "user"}, + {token.SPACE, " "}, + {token.LCB, "{"}, + {token.RCB, "}"}, + {token.NEWLINE, "\n"}, + {token.NEWLINE, "\n"}, + // -- + {token.ENTITY, "entity"}, + {token.SPACE, " "}, + {token.IDENT, "organization"}, + {token.SPACE, " "}, + {token.LCB, "{"}, + {token.NEWLINE, "\n"}, + // -- + {token.TAB, "\t"}, + {token.RELATION, "relation"}, + {token.SPACE, " "}, + {token.IDENT, "admin"}, + {token.SPACE, " "}, + {token.SIGN, "@"}, + {token.IDENT, "user"}, + {token.NEWLINE, "\n"}, + // -- + {token.SPACE, " "}, + {token.SPACE, " "}, + {token.SPACE, " "}, + {token.SPACE, " "}, + {token.RELATION, "relation"}, + {token.SPACE, " "}, + {token.IDENT, "member"}, + {token.SPACE, " "}, + {token.SIGN, "@"}, + {token.IDENT, "user"}, + {token.NEWLINE, "\n"}, + {token.NEWLINE, "\n"}, + // -- + {token.TAB, "\t"}, + {token.PERMISSION, "action"}, + {token.SPACE, " "}, + {token.IDENT, "create_repository"}, + {token.SPACE, " "}, + {token.ASSIGN, "="}, + {token.SPACE, " "}, + {token.NIL, "nil"}, + {token.NEWLINE, "\n"}, + // -- + {token.TAB, "\t"}, + {token.PERMISSION, "action"}, + {token.SPACE, " "}, + {token.IDENT, "delete"}, + {token.SPACE, " "}, + {token.ASSIGN, "="}, + {token.SPACE, " "}, + {token.NIL, "nil"}, + {token.NEWLINE, "\n"}, + {token.RCB, "}"}, + {token.NEWLINE, "\n"}, + {token.NEWLINE, "\n"}, + // -- + } + + l := NewLexer(str) + + for i, tt := range tests { + lexeme := l.NextToken() + index := strconv.Itoa(i) + ": " + Expect(index + lexeme.Type.String()).Should(Equal(index + tt.expectedType.String())) + Expect(index + lexeme.Literal).Should(Equal(index + tt.expectedLiteral)) + } + }) }) }) diff --git a/pkg/dsl/parser/parser.go b/pkg/dsl/parser/parser.go index 33a96d707..54a912d13 100644 --- a/pkg/dsl/parser/parser.go +++ b/pkg/dsl/parser/parser.go @@ -64,9 +64,10 @@ func NewParser(str string) (p *Parser) { references: ast.NewReferences(), // initialize an empty map for relational references } - // register prefix parsing functions for token types IDENT and NOT + // register prefix parsing functions for token types IDENT and NIL p.prefixParseFns = make(map[token.Type]prefixParseFn) // initialize an empty map for prefix parsing functions p.registerPrefix(token.IDENT, p.parseIdentifierOrCall) // associate the parseIdentifier function with the IDENT token type + p.registerPrefix(token.NIL, p.parseIdentifierOrCall) // register infix parsing functions for token types AND, OR, NOT p.infixParseFunc = make(map[token.Type]infixParseFn) // initialize an empty map for infix parsing functions diff --git a/pkg/dsl/parser/parser_test.go b/pkg/dsl/parser/parser_test.go index df6e5a521..c1c3fedbd 100644 --- a/pkg/dsl/parser/parser_test.go +++ b/pkg/dsl/parser/parser_test.go @@ -1041,5 +1041,22 @@ rule confidentiality_level_low(confidentiality_level double) { // Ensure the error message contains the expected string Expect(err.Error()).Should(ContainSubstring("7:15:expected token to be RELATION, PERMISSION, ATTRIBUTE, got OR instead")) }) + + It("Case 30 - Nil Permission", func() { + pr := NewParser(` + entity account { + relation owner @user + relation admin @user + + action check_balance = nil + + permission withdraw = nil + } + `) + + _, err := pr.Parse() + // Ensure an error is not returned + Expect(err).ShouldNot(HaveOccurred()) + }) }) }) diff --git a/pkg/dsl/token/token.go b/pkg/dsl/token/token.go index 7dbf18d5c..6f9a78da2 100644 --- a/pkg/dsl/token/token.go +++ b/pkg/dsl/token/token.go @@ -76,6 +76,7 @@ const ( INTEGER = "INTEGER" DOUBLE = "DOUBLE" BOOLEAN = "BOOLEAN" + NIL = "NIL" /* Symbols