Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement partials syntax parsing #2211

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading