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 parsing of local imports #2103

Merged
merged 1 commit into from
Oct 28, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
with:
distribution: "goreleaser-pro"
version: "2.3.2"
args: "release --clean --split --snapshot --single-target --skip=chocolatey"
args: "release --clean --split --snapshot --single-target"
env:
GORELEASER_KEY: "${{ secrets.GORELEASER_KEY }}"
- name: "Obtain container image to scan"
Expand Down
10 changes: 10 additions & 0 deletions pkg/composableschemadsl/dslshape/dslshape.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const (
NodeTypeNilExpression // A nil keyword

NodeTypeCaveatTypeReference // A type reference for a caveat parameter.

NodeTypeImport
)

const (
Expand Down Expand Up @@ -188,4 +190,12 @@ const (
//
NodeExpressionPredicateLeftExpr = "left-expr"
NodeExpressionPredicateRightExpr = "right-expr"

//
// NodeTypeImport
//
// TODO: still need to figure out what form this should take - full path? relative path?
NodeImportPredicateSource = "import-source"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Match whatever we do for definition name paths

NodeImportPredicatePathSegment = "path-segment"
NodeImportPredicateDefinitionName = "imported-definition"
)

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

2 changes: 2 additions & 0 deletions pkg/composableschemadsl/lexer/lex_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ var keywords = map[string]struct{}{
"permission": {},
"nil": {},
"with": {},
"from": {},
"import": {},
}

// IsKeyword returns whether the specified input string is a reserved keyword.
Expand Down
72 changes: 72 additions & 0 deletions pkg/composableschemadsl/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ Loop:
case p.isKeyword("caveat"):
rootNode.Connect(dslshape.NodePredicateChild, p.consumeCaveat())

case p.isKeyword("from"):
rootNode.Connect(dslshape.NodePredicateChild, p.consumeImport())

default:
p.emitErrorf("Unexpected token at root level: %v", p.currentToken.Kind)
break Loop
Expand Down Expand Up @@ -574,6 +577,20 @@ func (p *sourceParser) tryConsumeIdentifierLiteral() (AstNode, bool) {
return identNode, true
}

// consumeIdentifierLiteral is similar to the above, but attempts and errors
// rather than checking the token type beforehand
func (p *sourceParser) consumeIdentifierLiteral() (AstNode, bool) {
identNode := p.startNode(dslshape.NodeTypeIdentifier)
defer p.mustFinishNode()

identifier, ok := p.consumeIdentifier()
if !ok {
return identNode, false
}
identNode.MustDecorate(dslshape.NodeIdentiferPredicateValue, identifier)
return identNode, true
}

func (p *sourceParser) tryConsumeNilExpression() (AstNode, bool) {
if !p.isKeyword("nil") {
return nil, false
Expand All @@ -584,3 +601,58 @@ func (p *sourceParser) tryConsumeNilExpression() (AstNode, bool) {
defer p.mustFinishNode()
return node, true
}

func (p *sourceParser) consumeImport() AstNode {
importNode := p.startNode(dslshape.NodeTypeImport)
defer p.mustFinishNode()

// from ...
// NOTE: error handling isn't necessary here because this function is only
// invoked if the `from` keyword is found in the function above.
p.consumeKeyword("from")

// Consume alternating periods and identifiers
for {
if _, ok := p.consume(lexer.TokenTypePeriod); !ok {
return importNode
}

segmentNode, ok := p.consumeIdentifierLiteral()
// We connect the node so that the error information is retained, then break the loop
// so that we aren't continuing to attempt to consume.
importNode.Connect(dslshape.NodeImportPredicatePathSegment, segmentNode)
if !ok {
break
}

if !p.isToken(lexer.TokenTypePeriod) {
// If we don't have a period as our next token, we move
// to the next step of parsing.
break
}
}

if ok := p.consumeKeyword("import"); !ok {
return importNode
}

// Consume alternating identifiers and commas until we reach the end of the import statement
for {
definitionNode, ok := p.consumeIdentifierLiteral()
// We connect the node so that the error information is retained, then break the loop
// so that we aren't continuing to attempt to consume.
importNode.Connect(dslshape.NodeImportPredicateDefinitionName, definitionNode)
if !ok {
break
}

if _, ok := p.tryConsumeStatementTerminator(); ok {
break
}
if _, ok := p.consume(lexer.TokenTypeComma); !ok {
return importNode
}
}

return importNode
}
7 changes: 7 additions & 0 deletions pkg/composableschemadsl/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ func TestParser(t *testing.T) {
{"arrow illegal operations test", "arrowillegalops"},
{"arrow illegal function test", "arrowillegalfunc"},
{"caveat with keyword parameter test", "caveatwithkeywordparam"},
{"local imports test", "localimport"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a bunch of error cases as well

{"local imports with keyword in import path test", "localimport_import_path_with_keyword"},
{"local imports with keyword in identifiers test", "localimport_keyword_in_identifiers"},
{"local imports with malformed identifiers set test", "localimport_malformed_identifier_set"},
{"local imports with malformed import path test", "localimport_malformed_import_path"},
{"local imports with path missing leading period test", "localimport_path_missing_leading_period"},
{"local imports with typo in import separator test", "localimport_typo_in_import_separator"},
}

for _, test := range parserTests {
Expand Down
7 changes: 7 additions & 0 deletions pkg/composableschemadsl/parser/tests/localimport.zed
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .path.to.user import user, persona

definition resource {
relation user: user
relation persona: persona
permission view = user + persona
}
96 changes: 96 additions & 0 deletions pkg/composableschemadsl/parser/tests/localimport.zed.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
NodeTypeFile
end-rune = 155
input-source = local imports test
start-rune = 0
child-node =>
NodeTypeImport
end-rune = 39
input-source = local imports test
start-rune = 0
imported-definition =>
NodeTypeIdentifier
end-rune = 29
identifier-value = user
input-source = local imports test
start-rune = 26
NodeTypeIdentifier
end-rune = 38
identifier-value = persona
input-source = local imports test
start-rune = 32
path-segment =>
NodeTypeIdentifier
end-rune = 9
identifier-value = path
input-source = local imports test
start-rune = 6
NodeTypeIdentifier
end-rune = 12
identifier-value = to
input-source = local imports test
start-rune = 11
NodeTypeIdentifier
end-rune = 17
identifier-value = user
input-source = local imports test
start-rune = 14
NodeTypeDefinition
definition-name = resource
end-rune = 154
input-source = local imports test
start-rune = 41
child-node =>
NodeTypeRelation
end-rune = 85
input-source = local imports test
relation-name = user
start-rune = 67
allowed-types =>
NodeTypeTypeReference
end-rune = 85
input-source = local imports test
start-rune = 82
type-ref-type =>
NodeTypeSpecificTypeReference
end-rune = 85
input-source = local imports test
start-rune = 82
type-name = user
NodeTypeRelation
end-rune = 115
input-source = local imports test
relation-name = persona
start-rune = 91
allowed-types =>
NodeTypeTypeReference
end-rune = 115
input-source = local imports test
start-rune = 109
type-ref-type =>
NodeTypeSpecificTypeReference
end-rune = 115
input-source = local imports test
start-rune = 109
type-name = persona
NodeTypePermission
end-rune = 152
input-source = local imports test
relation-name = view
start-rune = 121
compute-expression =>
NodeTypeUnionExpression
end-rune = 152
input-source = local imports test
start-rune = 139
left-expr =>
NodeTypeIdentifier
end-rune = 142
identifier-value = user
input-source = local imports test
start-rune = 139
right-expr =>
NodeTypeIdentifier
end-rune = 152
identifier-value = persona
input-source = local imports test
start-rune = 146
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .path.definition.user import user, persona

definition resource {
relation user: user
relation persona: persona
permission view = user + persona
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
NodeTypeFile
end-rune = 20
input-source = local imports with keyword in import path test
start-rune = 0
child-node =>
NodeTypeImport
end-rune = 10
input-source = local imports with keyword in import path test
start-rune = 0
child-node =>
NodeTypeError
end-rune = 10
error-message = Expected keyword import, found token TokenTypeKeyword
error-source = definition
input-source = local imports with keyword in import path test
start-rune = 11
path-segment =>
NodeTypeIdentifier
end-rune = 9
identifier-value = path
input-source = local imports with keyword in import path test
start-rune = 6
NodeTypeIdentifier
end-rune = 10
input-source = local imports with keyword in import path test
start-rune = 11
child-node =>
NodeTypeError
end-rune = 10
error-message = Expected identifier, found token TokenTypeKeyword
error-source = definition
input-source = local imports with keyword in import path test
start-rune = 11
NodeTypeDefinition
end-rune = 20
input-source = local imports with keyword in import path test
start-rune = 11
child-node =>
NodeTypeError
end-rune = 20
error-message = Expected identifier, found token TokenTypePeriod
error-source = .
input-source = local imports with keyword in import path test
start-rune = 21
NodeTypeError
end-rune = 20
error-message = Unexpected token at root level: TokenTypePeriod
error-source = .
input-source = local imports with keyword in import path test
start-rune = 21
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .path.to.user import user, caveat

definition resource {
relation user: user
relation persona: persona
permission view = user + persona
}
Loading
Loading