Skip to content

Commit

Permalink
Implement parsing of partials syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
tstirrat15 committed Jan 27, 2025
1 parent 20d90b6 commit 4d072e8
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 7 deletions.
12 changes: 12 additions & 0 deletions pkg/composableschemadsl/dslshape/dslshape.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const (
NodeTypeCaveatTypeReference // A type reference for a caveat parameter.

NodeTypeImport
NodeTypePartial
NodeTypePartialReference // A location where a partial is referenced
)

const (
Expand Down Expand Up @@ -195,4 +197,14 @@ const (
// NodeTypeImport
//
NodeImportPredicatePath = "import-path"

//
// NodeTypePartial
//
NodePartialPredicateName = "partial-name"

//
// NodeTypePartialReference
//
NodePartialReferencePredicateName = "partial-reference-name"
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/composableschemadsl/lexer/lex_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var keywords = map[string]struct{}{
"import": {},
"all": {},
"any": {},
"partial": {},
}

// IsKeyword returns whether the specified input string is a reserved keyword.
Expand Down
1 change: 1 addition & 0 deletions pkg/composableschemadsl/lexer/lex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var lexerTests = []lexerTest{
{"keyword", "import", []Lexeme{{TokenTypeKeyword, 0, "import", ""}, tEOF}},
{"keyword", "all", []Lexeme{{TokenTypeKeyword, 0, "all", ""}, tEOF}},
{"keyword", "nil", []Lexeme{{TokenTypeKeyword, 0, "nil", ""}, tEOF}},
{"keyword", "partial", []Lexeme{{TokenTypeKeyword, 0, "partial", ""}, tEOF}},
{"identifier", "define", []Lexeme{{TokenTypeIdentifier, 0, "define", ""}, tEOF}},
{"typepath", "foo/bar", []Lexeme{
{TokenTypeIdentifier, 0, "foo", ""},
Expand Down
60 changes: 55 additions & 5 deletions pkg/composableschemadsl/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Loop:
case p.isKeyword("import"):
rootNode.Connect(dslshape.NodePredicateChild, p.consumeImport())

case p.isKeyword("partial"):
rootNode.Connect(dslshape.NodePredicateChild, p.consumePartial())

default:
p.emitErrorf("Unexpected token at root level: %v", p.currentToken.Kind)
break Loop
Expand Down Expand Up @@ -264,10 +267,39 @@ func (p *sourceParser) consumeDefinition() AstNode {

defNode.MustDecorate(dslshape.NodeDefinitionPredicateName, definitionName)

p.consumeDefinitionOrPartialImpl(defNode)

return defNode
}

// consumePartial attempts to consume a single schema partial.
// ```partial somepartial { ... }```
func (p *sourceParser) consumePartial() AstNode {
partialNode := p.startNode(dslshape.NodeTypePartial)
defer p.mustFinishNode()

// partial ...
p.consumeKeyword("partial")
definitionName, ok := p.consumeTypePath()
if !ok {
return partialNode
}

partialNode.MustDecorate(dslshape.NodePartialPredicateName, definitionName)

p.consumeDefinitionOrPartialImpl(partialNode)

return partialNode
}

// consumeDefinitionOrPartialImpl does the work of parsing and consuming
// the internals of a given definition or partial.
// Definitions and partials have the same set of allowable internals.
func (p *sourceParser) consumeDefinitionOrPartialImpl(node AstNode) AstNode {
// {
_, ok = p.consume(lexer.TokenTypeLeftBrace)
_, ok := p.consume(lexer.TokenTypeLeftBrace)
if !ok {
return defNode
return node
}

// Relations and permissions.
Expand All @@ -281,10 +313,13 @@ func (p *sourceParser) consumeDefinition() AstNode {
// permission ...
switch {
case p.isKeyword("relation"):
defNode.Connect(dslshape.NodePredicateChild, p.consumeRelation())
node.Connect(dslshape.NodePredicateChild, p.consumeRelation())

case p.isKeyword("permission"):
defNode.Connect(dslshape.NodePredicateChild, p.consumePermission())
node.Connect(dslshape.NodePredicateChild, p.consumePermission())

case p.isToken(lexer.TokenTypeEllipsis):
node.Connect(dslshape.NodePredicateChild, p.consumePartialReference())
}

ok := p.consumeStatementTerminator()
Expand All @@ -293,7 +328,7 @@ func (p *sourceParser) consumeDefinition() AstNode {
}
}

return defNode
return node
}

// consumeRelation consumes a relation.
Expand Down Expand Up @@ -597,6 +632,21 @@ func (p *sourceParser) tryConsumeNilExpression() (AstNode, bool) {
return node, true
}

func (p *sourceParser) consumePartialReference() AstNode {
partialReferenceNode := p.startNode(dslshape.NodeTypePartialReference)
defer p.mustFinishNode()

p.consume(lexer.TokenTypeEllipsis)
identifier, ok := p.consumeIdentifier()
if !ok {
return partialReferenceNode
}

partialReferenceNode.MustDecorate(dslshape.NodePartialReferencePredicateName, identifier)

return partialReferenceNode
}

func (p *sourceParser) consumeImport() AstNode {
importNode := p.startNode(dslshape.NodeTypeImport)
defer p.mustFinishNode()
Expand Down
5 changes: 5 additions & 0 deletions pkg/composableschemadsl/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ func TestParser(t *testing.T) {
{"local imports with unterminated string on import test", "localimport_with_unterminated_string"},
{"local imports with mismatched quotes on import test", "localimport_with_mismatched_quotes"},
{"local imports with keyword in import path test", "localimport_import_path_with_keyword"},
{"top-level block with unrecognized keyword", "nonsense_top_level_block"},
{"partials happy path", "partials"},
{"partials with malformed partial reference", "partials_with_malformed_partial_reference"},
{"partials with malformed reference splat", "partials_with_malformed_reference_splat"},
{"partials with malformed partial block", "partials_with_malformed_partial_block"},
}

for _, test := range parserTests {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nonsense thing {
relation: user
permission view = user
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
NodeTypeFile
end-rune = -1
input-source = top-level block with unrecognized keyword
start-rune = 0
child-node =>
NodeTypeError
end-rune = -1
error-message = Unexpected token at root level: TokenTypeIdentifier
error-source = nonsense
input-source = top-level block with unrecognized keyword
start-rune = 0
10 changes: 10 additions & 0 deletions pkg/composableschemadsl/parser/tests/partials.zed
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
partial view_partial {
relation user: user
permission view = user
}

definition user {}

definition resource {
...view_partial
}
54 changes: 54 additions & 0 deletions pkg/composableschemadsl/parser/tests/partials.zed.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
NodeTypeFile
end-rune = 134
input-source = partials happy path
start-rune = 0
child-node =>
NodeTypePartial
end-rune = 70
input-source = partials happy path
partial-name = view_partial
start-rune = 0
child-node =>
NodeTypeRelation
end-rune = 43
input-source = partials happy path
relation-name = user
start-rune = 25
allowed-types =>
NodeTypeTypeReference
end-rune = 43
input-source = partials happy path
start-rune = 40
type-ref-type =>
NodeTypeSpecificTypeReference
end-rune = 43
input-source = partials happy path
start-rune = 40
type-name = user
NodeTypePermission
end-rune = 68
input-source = partials happy path
relation-name = view
start-rune = 47
compute-expression =>
NodeTypeIdentifier
end-rune = 68
identifier-value = user
input-source = partials happy path
start-rune = 65
NodeTypeDefinition
definition-name = user
end-rune = 90
input-source = partials happy path
start-rune = 73
NodeTypeDefinition
definition-name = resource
end-rune = 133
input-source = partials happy path
start-rune = 93
child-node =>
NodeTypePartialReference
end-rune = 131
input-source = partials happy path
partial-reference-name = view_partial
start-rune = 117
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
partial view_partial {
relation user: user
permission view = user


definition user {}

definition resource {
...view_partial
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
NodeTypeFile
end-rune = 133
input-source = partials with malformed partial block
start-rune = 0
child-node =>
NodeTypePartial
end-rune = 69
input-source = partials with malformed partial block
partial-name = view_partial
start-rune = 0
child-node =>
NodeTypeRelation
end-rune = 43
input-source = partials with malformed partial block
relation-name = user
start-rune = 25
allowed-types =>
NodeTypeTypeReference
end-rune = 43
input-source = partials with malformed partial block
start-rune = 40
type-ref-type =>
NodeTypeSpecificTypeReference
end-rune = 43
input-source = partials with malformed partial block
start-rune = 40
type-name = user
NodeTypePermission
end-rune = 68
input-source = partials with malformed partial block
relation-name = view
start-rune = 47
compute-expression =>
NodeTypeIdentifier
end-rune = 68
identifier-value = user
input-source = partials with malformed partial block
start-rune = 65
NodeTypeError
end-rune = 69
error-message = Expected end of statement or definition, found: TokenTypeKeyword
error-source = definition
input-source = partials with malformed partial block
start-rune = 72
NodeTypeDefinition
definition-name = user
end-rune = 89
input-source = partials with malformed partial block
start-rune = 72
NodeTypeDefinition
definition-name = resource
end-rune = 132
input-source = partials with malformed partial block
start-rune = 92
child-node =>
NodeTypePartialReference
end-rune = 130
input-source = partials with malformed partial block
partial-reference-name = view_partial
start-rune = 116
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
partial view_partial {
relation user: user
permission view = user
}

definition user {}

definition resource {
...view_partial and_something_else
}
Loading

0 comments on commit 4d072e8

Please sign in to comment.