github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/schemadsl/parser/parser.go (about)

     1  // parser package defines the parser for the Authzed Schema DSL.
     2  package parser
     3  
     4  import (
     5  	"strings"
     6  
     7  	"github.com/authzed/spicedb/pkg/schemadsl/dslshape"
     8  	"github.com/authzed/spicedb/pkg/schemadsl/input"
     9  	"github.com/authzed/spicedb/pkg/schemadsl/lexer"
    10  )
    11  
    12  // Parse parses the given Schema DSL source into a parse tree.
    13  func Parse(builder NodeBuilder, source input.Source, input string) AstNode {
    14  	lx := lexer.Lex(source, input)
    15  	parser := buildParser(lx, builder, source, input)
    16  	defer parser.close()
    17  	return parser.consumeTopLevel()
    18  }
    19  
    20  // ignoredTokenTypes are those tokens ignored when parsing.
    21  var ignoredTokenTypes = map[lexer.TokenType]bool{
    22  	lexer.TokenTypeWhitespace:        true,
    23  	lexer.TokenTypeNewline:           true,
    24  	lexer.TokenTypeSinglelineComment: true,
    25  	lexer.TokenTypeMultilineComment:  true,
    26  }
    27  
    28  // consumeTopLevel attempts to consume the top-level definitions.
    29  func (p *sourceParser) consumeTopLevel() AstNode {
    30  	rootNode := p.startNode(dslshape.NodeTypeFile)
    31  	defer p.mustFinishNode()
    32  
    33  	// Start at the first token.
    34  	p.consumeToken()
    35  
    36  	if p.currentToken.Kind == lexer.TokenTypeError {
    37  		p.emitErrorf("%s", p.currentToken.Value)
    38  		return rootNode
    39  	}
    40  
    41  Loop:
    42  	for {
    43  		if p.isToken(lexer.TokenTypeEOF) {
    44  			break Loop
    45  		}
    46  
    47  		// Consume a statement terminator if one was found.
    48  		p.tryConsumeStatementTerminator()
    49  
    50  		if p.isToken(lexer.TokenTypeEOF) {
    51  			break Loop
    52  		}
    53  
    54  		// The top level of the DSL is a set of definitions and caveats:
    55  		// definition foobar { ... }
    56  		// caveat somecaveat (...) { ... }
    57  
    58  		switch {
    59  		case p.isKeyword("definition"):
    60  			rootNode.Connect(dslshape.NodePredicateChild, p.consumeDefinition())
    61  
    62  		case p.isKeyword("caveat"):
    63  			rootNode.Connect(dslshape.NodePredicateChild, p.consumeCaveat())
    64  
    65  		default:
    66  			p.emitErrorf("Unexpected token at root level: %v", p.currentToken.Kind)
    67  			break Loop
    68  		}
    69  	}
    70  
    71  	return rootNode
    72  }
    73  
    74  // consumeCaveat attempts to consume a single caveat definition.
    75  // ```caveat somecaveat(param1 type, param2 type) { ... }```
    76  func (p *sourceParser) consumeCaveat() AstNode {
    77  	defNode := p.startNode(dslshape.NodeTypeCaveatDefinition)
    78  	defer p.mustFinishNode()
    79  
    80  	// caveat ...
    81  	p.consumeKeyword("caveat")
    82  	caveatName, ok := p.consumeTypePath()
    83  	if !ok {
    84  		return defNode
    85  	}
    86  
    87  	defNode.MustDecorate(dslshape.NodeCaveatDefinitionPredicateName, caveatName)
    88  
    89  	// Parameters:
    90  	// (
    91  	_, ok = p.consume(lexer.TokenTypeLeftParen)
    92  	if !ok {
    93  		return defNode
    94  	}
    95  
    96  	for {
    97  		paramNode, ok := p.consumeCaveatParameter()
    98  		if !ok {
    99  			return defNode
   100  		}
   101  
   102  		defNode.Connect(dslshape.NodeCaveatDefinitionPredicateParameters, paramNode)
   103  		if _, ok := p.tryConsume(lexer.TokenTypeComma); !ok {
   104  			break
   105  		}
   106  	}
   107  
   108  	// )
   109  	_, ok = p.consume(lexer.TokenTypeRightParen)
   110  	if !ok {
   111  		return defNode
   112  	}
   113  
   114  	// {
   115  	_, ok = p.consume(lexer.TokenTypeLeftBrace)
   116  	if !ok {
   117  		return defNode
   118  	}
   119  
   120  	exprNode, ok := p.consumeCaveatExpression()
   121  	if !ok {
   122  		return defNode
   123  	}
   124  
   125  	defNode.Connect(dslshape.NodeCaveatDefinitionPredicateExpession, exprNode)
   126  
   127  	// }
   128  	_, ok = p.consume(lexer.TokenTypeRightBrace)
   129  	if !ok {
   130  		return defNode
   131  	}
   132  
   133  	return defNode
   134  }
   135  
   136  func (p *sourceParser) consumeCaveatExpression() (AstNode, bool) {
   137  	exprNode := p.startNode(dslshape.NodeTypeCaveatExpression)
   138  	defer p.mustFinishNode()
   139  
   140  	// Special Logic Note: Since CEL is its own language, we consume here until we have a matching
   141  	// close brace, and then pass ALL the found tokens to CEL's own parser to attach the expression
   142  	// here.
   143  	braceDepth := 1 // Starting at 1 from the open brace above
   144  	var startToken *commentedLexeme
   145  	var endToken *commentedLexeme
   146  consumer:
   147  	for {
   148  		currentToken := p.currentToken
   149  
   150  		switch currentToken.Kind {
   151  		case lexer.TokenTypeLeftBrace:
   152  			braceDepth++
   153  
   154  		case lexer.TokenTypeRightBrace:
   155  			if braceDepth == 1 {
   156  				break consumer
   157  			}
   158  
   159  			braceDepth--
   160  
   161  		case lexer.TokenTypeError:
   162  			break consumer
   163  
   164  		case lexer.TokenTypeEOF:
   165  			break consumer
   166  		}
   167  
   168  		if startToken == nil {
   169  			startToken = &currentToken
   170  		}
   171  
   172  		endToken = &currentToken
   173  		p.consumeToken()
   174  	}
   175  
   176  	if startToken == nil {
   177  		p.emitErrorf("missing caveat expression")
   178  		return exprNode, false
   179  	}
   180  
   181  	caveatExpression := p.input[startToken.Position : int(endToken.Position)+len(endToken.Value)]
   182  	exprNode.MustDecorate(dslshape.NodeCaveatExpressionPredicateExpression, caveatExpression)
   183  	return exprNode, true
   184  }
   185  
   186  // consumeCaveatParameter attempts to consume a caveat parameter.
   187  // ```(paramName paramtype)```
   188  func (p *sourceParser) consumeCaveatParameter() (AstNode, bool) {
   189  	paramNode := p.startNode(dslshape.NodeTypeCaveatParameter)
   190  	defer p.mustFinishNode()
   191  
   192  	name, ok := p.consumeIdentifier()
   193  	if !ok {
   194  		return paramNode, false
   195  	}
   196  
   197  	paramNode.MustDecorate(dslshape.NodeCaveatParameterPredicateName, name)
   198  	paramNode.Connect(dslshape.NodeCaveatParameterPredicateType, p.consumeCaveatTypeReference())
   199  	return paramNode, true
   200  }
   201  
   202  // consumeCaveatTypeReference attempts to consume a caveat type reference.
   203  // ```typeName<childType>```
   204  func (p *sourceParser) consumeCaveatTypeReference() AstNode {
   205  	typeRefNode := p.startNode(dslshape.NodeTypeCaveatTypeReference)
   206  	defer p.mustFinishNode()
   207  
   208  	name, ok := p.consumeIdentifier()
   209  	if !ok {
   210  		return typeRefNode
   211  	}
   212  
   213  	typeRefNode.MustDecorate(dslshape.NodeCaveatTypeReferencePredicateType, name)
   214  
   215  	// Check for child type(s).
   216  	// <
   217  	if _, ok := p.tryConsume(lexer.TokenTypeLessThan); !ok {
   218  		return typeRefNode
   219  	}
   220  
   221  	for {
   222  		childTypeRef := p.consumeCaveatTypeReference()
   223  		typeRefNode.Connect(dslshape.NodeCaveatTypeReferencePredicateChildTypes, childTypeRef)
   224  		if _, ok := p.tryConsume(lexer.TokenTypeComma); !ok {
   225  			break
   226  		}
   227  	}
   228  
   229  	// >
   230  	p.consume(lexer.TokenTypeGreaterThan)
   231  	return typeRefNode
   232  }
   233  
   234  // consumeDefinition attempts to consume a single schema definition.
   235  // ```definition somedef { ... }```
   236  func (p *sourceParser) consumeDefinition() AstNode {
   237  	defNode := p.startNode(dslshape.NodeTypeDefinition)
   238  	defer p.mustFinishNode()
   239  
   240  	// definition ...
   241  	p.consumeKeyword("definition")
   242  	definitionName, ok := p.consumeTypePath()
   243  	if !ok {
   244  		return defNode
   245  	}
   246  
   247  	defNode.MustDecorate(dslshape.NodeDefinitionPredicateName, definitionName)
   248  
   249  	// {
   250  	_, ok = p.consume(lexer.TokenTypeLeftBrace)
   251  	if !ok {
   252  		return defNode
   253  	}
   254  
   255  	// Relations and permissions.
   256  	for {
   257  		// }
   258  		if _, ok := p.tryConsume(lexer.TokenTypeRightBrace); ok {
   259  			break
   260  		}
   261  
   262  		// relation ...
   263  		// permission ...
   264  		switch {
   265  		case p.isKeyword("relation"):
   266  			defNode.Connect(dslshape.NodePredicateChild, p.consumeRelation())
   267  
   268  		case p.isKeyword("permission"):
   269  			defNode.Connect(dslshape.NodePredicateChild, p.consumePermission())
   270  		}
   271  
   272  		ok := p.consumeStatementTerminator()
   273  		if !ok {
   274  			break
   275  		}
   276  	}
   277  
   278  	return defNode
   279  }
   280  
   281  // consumeRelation consumes a relation.
   282  // ```relation foo: sometype```
   283  func (p *sourceParser) consumeRelation() AstNode {
   284  	relNode := p.startNode(dslshape.NodeTypeRelation)
   285  	defer p.mustFinishNode()
   286  
   287  	// relation ...
   288  	p.consumeKeyword("relation")
   289  	relationName, ok := p.consumeIdentifier()
   290  	if !ok {
   291  		return relNode
   292  	}
   293  
   294  	relNode.MustDecorate(dslshape.NodePredicateName, relationName)
   295  
   296  	// :
   297  	_, ok = p.consume(lexer.TokenTypeColon)
   298  	if !ok {
   299  		return relNode
   300  	}
   301  
   302  	// Relation allowed type(s).
   303  	relNode.Connect(dslshape.NodeRelationPredicateAllowedTypes, p.consumeTypeReference())
   304  
   305  	return relNode
   306  }
   307  
   308  // consumeTypeReference consumes a reference to a type or types of relations.
   309  // ```sometype | anothertype | anothertype:* ```
   310  func (p *sourceParser) consumeTypeReference() AstNode {
   311  	refNode := p.startNode(dslshape.NodeTypeTypeReference)
   312  	defer p.mustFinishNode()
   313  
   314  	for {
   315  		refNode.Connect(dslshape.NodeTypeReferencePredicateType, p.consumeSpecificTypeWithCaveat())
   316  		if _, ok := p.tryConsume(lexer.TokenTypePipe); !ok {
   317  			break
   318  		}
   319  	}
   320  
   321  	return refNode
   322  }
   323  
   324  // tryConsumeWithCaveat tries to consume a caveat `with` expression.
   325  func (p *sourceParser) tryConsumeWithCaveat() (AstNode, bool) {
   326  	if !p.isKeyword("with") {
   327  		return nil, false
   328  	}
   329  
   330  	caveatNode := p.startNode(dslshape.NodeTypeCaveatReference)
   331  	defer p.mustFinishNode()
   332  
   333  	if ok := p.consumeKeyword("with"); !ok {
   334  		return nil, ok
   335  	}
   336  
   337  	consumed, ok := p.consumeTypePath()
   338  	if !ok {
   339  		return caveatNode, true
   340  	}
   341  
   342  	caveatNode.MustDecorate(dslshape.NodeCaveatPredicateCaveat, consumed)
   343  	return caveatNode, true
   344  }
   345  
   346  // consumeSpecificTypeWithCaveat consumes an identifier as a specific type reference, with optional caveat.
   347  func (p *sourceParser) consumeSpecificTypeWithCaveat() AstNode {
   348  	specificNode := p.consumeSpecificTypeWithoutFinish()
   349  	defer p.mustFinishNode()
   350  
   351  	caveatNode, ok := p.tryConsumeWithCaveat()
   352  	if ok {
   353  		specificNode.Connect(dslshape.NodeSpecificReferencePredicateCaveat, caveatNode)
   354  	}
   355  
   356  	return specificNode
   357  }
   358  
   359  // consumeSpecificTypeOpen consumes an identifier as a specific type reference.
   360  func (p *sourceParser) consumeSpecificTypeWithoutFinish() AstNode {
   361  	specificNode := p.startNode(dslshape.NodeTypeSpecificTypeReference)
   362  
   363  	typeName, ok := p.consumeTypePath()
   364  	if !ok {
   365  		return specificNode
   366  	}
   367  
   368  	specificNode.MustDecorate(dslshape.NodeSpecificReferencePredicateType, typeName)
   369  
   370  	// Check for a wildcard
   371  	if _, ok := p.tryConsume(lexer.TokenTypeColon); ok {
   372  		_, ok := p.consume(lexer.TokenTypeStar)
   373  		if !ok {
   374  			return specificNode
   375  		}
   376  
   377  		specificNode.MustDecorate(dslshape.NodeSpecificReferencePredicateWildcard, "true")
   378  		return specificNode
   379  	}
   380  
   381  	// Check for a relation specified.
   382  	if _, ok := p.tryConsume(lexer.TokenTypeHash); !ok {
   383  		return specificNode
   384  	}
   385  
   386  	// Consume an identifier or an ellipsis.
   387  	consumed, ok := p.consume(lexer.TokenTypeIdentifier, lexer.TokenTypeEllipsis)
   388  	if !ok {
   389  		return specificNode
   390  	}
   391  
   392  	specificNode.MustDecorate(dslshape.NodeSpecificReferencePredicateRelation, consumed.Value)
   393  	return specificNode
   394  }
   395  
   396  func (p *sourceParser) consumeTypePath() (string, bool) {
   397  	var segments []string
   398  
   399  	for {
   400  		segment, ok := p.consumeIdentifier()
   401  		if !ok {
   402  			return "", false
   403  		}
   404  
   405  		segments = append(segments, segment)
   406  
   407  		_, ok = p.tryConsume(lexer.TokenTypeDiv)
   408  		if !ok {
   409  			break
   410  		}
   411  	}
   412  
   413  	return strings.Join(segments, "/"), true
   414  }
   415  
   416  // consumePermission consumes a permission.
   417  // ```permission foo = bar + baz```
   418  func (p *sourceParser) consumePermission() AstNode {
   419  	permNode := p.startNode(dslshape.NodeTypePermission)
   420  	defer p.mustFinishNode()
   421  
   422  	// permission ...
   423  	p.consumeKeyword("permission")
   424  	permissionName, ok := p.consumeIdentifier()
   425  	if !ok {
   426  		return permNode
   427  	}
   428  
   429  	permNode.MustDecorate(dslshape.NodePredicateName, permissionName)
   430  
   431  	// =
   432  	_, ok = p.consume(lexer.TokenTypeEquals)
   433  	if !ok {
   434  		return permNode
   435  	}
   436  
   437  	permNode.Connect(dslshape.NodePermissionPredicateComputeExpression, p.consumeComputeExpression())
   438  	return permNode
   439  }
   440  
   441  // ComputeExpressionOperators defines the binary operators in precedence order.
   442  var ComputeExpressionOperators = []binaryOpDefinition{
   443  	{lexer.TokenTypeMinus, dslshape.NodeTypeExclusionExpression},
   444  	{lexer.TokenTypeAnd, dslshape.NodeTypeIntersectExpression},
   445  	{lexer.TokenTypePlus, dslshape.NodeTypeUnionExpression},
   446  }
   447  
   448  // consumeComputeExpression consumes an expression for computing a permission.
   449  func (p *sourceParser) consumeComputeExpression() AstNode {
   450  	// Compute expressions consist of a set of binary operators, so build a tree with proper
   451  	// precedence.
   452  	binaryParser := p.buildBinaryOperatorExpressionFnTree(ComputeExpressionOperators)
   453  	found, ok := binaryParser()
   454  	if !ok {
   455  		return p.createErrorNodef("Expected compute expression for permission")
   456  	}
   457  	return found
   458  }
   459  
   460  // tryConsumeComputeExpression attempts to consume a nested compute expression.
   461  func (p *sourceParser) tryConsumeComputeExpression(subTryExprFn tryParserFn, binaryTokenType lexer.TokenType, nodeType dslshape.NodeType) (AstNode, bool) {
   462  	rightNodeBuilder := func(leftNode AstNode, operatorToken lexer.Lexeme) (AstNode, bool) {
   463  		rightNode, ok := subTryExprFn()
   464  		if !ok {
   465  			return nil, false
   466  		}
   467  
   468  		// Create the expression node representing the binary expression.
   469  		exprNode := p.createNode(nodeType)
   470  		exprNode.Connect(dslshape.NodeExpressionPredicateLeftExpr, leftNode)
   471  		exprNode.Connect(dslshape.NodeExpressionPredicateRightExpr, rightNode)
   472  		return exprNode, true
   473  	}
   474  	return p.performLeftRecursiveParsing(subTryExprFn, rightNodeBuilder, nil, binaryTokenType)
   475  }
   476  
   477  // tryConsumeArrowExpression attempts to consume an arrow expression.
   478  // ```foo->bar->baz->meh```
   479  func (p *sourceParser) tryConsumeArrowExpression() (AstNode, bool) {
   480  	rightNodeBuilder := func(leftNode AstNode, operatorToken lexer.Lexeme) (AstNode, bool) {
   481  		rightNode, ok := p.tryConsumeBaseExpression()
   482  		if !ok {
   483  			return nil, false
   484  		}
   485  
   486  		// Create the expression node representing the binary expression.
   487  		exprNode := p.createNode(dslshape.NodeTypeArrowExpression)
   488  		exprNode.Connect(dslshape.NodeExpressionPredicateLeftExpr, leftNode)
   489  		exprNode.Connect(dslshape.NodeExpressionPredicateRightExpr, rightNode)
   490  		return exprNode, true
   491  	}
   492  	return p.performLeftRecursiveParsing(p.tryConsumeIdentifierLiteral, rightNodeBuilder, nil, lexer.TokenTypeRightArrow)
   493  }
   494  
   495  // tryConsumeBaseExpression attempts to consume base compute expressions (identifiers, parenthesis).
   496  // ```(foo + bar)```
   497  // ```(foo)```
   498  // ```foo```
   499  // ```nil```
   500  func (p *sourceParser) tryConsumeBaseExpression() (AstNode, bool) {
   501  	switch {
   502  	// Nested expression.
   503  	case p.isToken(lexer.TokenTypeLeftParen):
   504  		comments := p.currentToken.comments
   505  
   506  		p.consume(lexer.TokenTypeLeftParen)
   507  		exprNode := p.consumeComputeExpression()
   508  		p.consume(lexer.TokenTypeRightParen)
   509  
   510  		// Attach any comments found to the consumed expression.
   511  		p.decorateComments(exprNode, comments)
   512  
   513  		return exprNode, true
   514  
   515  	// Nil expression.
   516  	case p.isKeyword("nil"):
   517  		return p.tryConsumeNilExpression()
   518  
   519  	// Identifier.
   520  	case p.isToken(lexer.TokenTypeIdentifier):
   521  		return p.tryConsumeIdentifierLiteral()
   522  	}
   523  
   524  	return nil, false
   525  }
   526  
   527  // tryConsumeIdentifierLiteral attempts to consume an identifier as a literal
   528  // expression.
   529  //
   530  // ```foo```
   531  func (p *sourceParser) tryConsumeIdentifierLiteral() (AstNode, bool) {
   532  	if !p.isToken(lexer.TokenTypeIdentifier) {
   533  		return nil, false
   534  	}
   535  
   536  	identNode := p.startNode(dslshape.NodeTypeIdentifier)
   537  	defer p.mustFinishNode()
   538  
   539  	identifier, _ := p.consumeIdentifier()
   540  	identNode.MustDecorate(dslshape.NodeIdentiferPredicateValue, identifier)
   541  	return identNode, true
   542  }
   543  
   544  func (p *sourceParser) tryConsumeNilExpression() (AstNode, bool) {
   545  	if !p.isKeyword("nil") {
   546  		return nil, false
   547  	}
   548  
   549  	node := p.startNode(dslshape.NodeTypeNilExpression)
   550  	p.consumeKeyword("nil")
   551  	defer p.mustFinishNode()
   552  	return node, true
   553  }