// Path-pattern parsing: node patterns with label expressions and inline // property maps, relationship patterns with GQL postfix quantifiers // ({m,n}, {m,}, {,n}, {m}, *, +) after the arrow -- the Cypher in-bracket // var-length spelling (*1..3) is rejected with a pointer to the GQL form.
package parser import ( ) // parsePattern is: node (rel node)*. func ( *parser) () (*ast.Pattern, error) { , := .parseNodePat() if != nil { return nil, } := &ast.Pattern{Start: *} for .peek().Kind == TokMinus || .peek().Kind == TokLt { , := .parseRelPat() if != nil { return nil, } , := .parseNodePat() if != nil { return nil, } .Hops = append(.Hops, ast.PatternHop{Rel: *, Node: *}) } return , nil } // parseNodePat is: '(' [var] [':' labelExpr] [propMap] ')'. func ( *parser) () (*ast.NodePat, error) { if , := .expect(TokLParen, "'(' starting a node pattern"); != nil { return nil, } := &ast.NodePat{} if := .peek(); .Kind == TokIdent { if isReserved(.Text) { return nil, errf(.Pos, "reserved word %q cannot be a node variable", .Text) } .Var = .Text .i++ } if .acceptTok(TokColon) { , := .parseLabelOr() if != nil { return nil, } if , := flattenConjunction(); { .Labels = } else { .LabelExpr = } } if .peek().Kind == TokLBrace { , , := .parsePropMap() if != nil { return nil, } .Props, .PropExprs = , } // Inline element predicate: (v:L WHERE expr) -- desugar conjoins it // onto the clause WHERE. if .peekKw("where") { .i++ , := .parseExpr() if != nil { return nil, } .Where = } if , := .expect(TokRParen, "')' closing a node pattern"); != nil { return nil, } return , nil } // parseLabelOr is the label-expression grammar: or over and over not over // (label | parens). Precedence: ! (tightest) > & (and ':') > |. func ( *parser) () (*ast.LabelExpr, error) { , := .parseLabelAnd() if != nil { return nil, } for .acceptTok(TokPipe) { , := .parseLabelAnd() if != nil { return nil, } = &ast.LabelExpr{Kind: ast.LabelOr, L: , R: } } return , nil } func ( *parser) () (*ast.LabelExpr, error) { , := .parseLabelNot() if != nil { return nil, } for .peek().Kind == TokAmp || .peek().Kind == TokColon { .i++ , := .parseLabelNot() if != nil { return nil, } = &ast.LabelExpr{Kind: ast.LabelAnd, L: , R: } } return , nil } func ( *parser) () (*ast.LabelExpr, error) { := 0 for .acceptTok(TokBang) { ++ } var *ast.LabelExpr if .acceptTok(TokPercent) { = &ast.LabelExpr{Kind: ast.LabelWild} for range { = &ast.LabelExpr{Kind: ast.LabelNot, L: } } return , nil } if .acceptTok(TokLParen) { , := .parseLabelOr() if != nil { return nil, } if , := .expect(TokRParen, "')' closing a label expression"); != nil { return nil, } = } else { , := .identName("a label") if != nil { return nil, } = &ast.LabelExpr{Kind: ast.LabelName, Name: } } for range { = &ast.LabelExpr{Kind: ast.LabelNot, L: } } return , nil } // flattenConjunction reports whether le is a plain conjunction of labels // and returns them in written order (the planner fast path); a tree with // any or/not stays a general label expression. func flattenConjunction( *ast.LabelExpr) ([]string, bool) { switch .Kind { case ast.LabelName: return []string{.Name}, true case ast.LabelAnd: , := (.L) , := (.R) if && { return append(, ...), true } } return nil, false } // parsePropMap is: '{' key ':' expr [, ...] '}'. A literal value keeps the // seek/filter fast path (Props); any other expression goes to PropExprs // for the desugar pass to lower into a WHERE equality. func ( *parser) () ([]ast.PropEntry, []ast.PropExprEntry, error) { .i++ // '{' var []ast.PropEntry var []ast.PropExprEntry for .peek().Kind != TokRBrace { , := .identName("a property key") if != nil { return nil, nil, } if , := .expect(TokColon, "':'"); != nil { return nil, nil, } , := .parseExpr() if != nil { return nil, nil, } if , := .(*ast.Lit); { = append(, ast.PropEntry{Key: , Val: .Value}) } else { = append(, ast.PropExprEntry{Key: , Val: }) } if !.acceptTok(TokComma) { break } } if , := .expect(TokRBrace, "'}' closing a property map"); != nil { return nil, nil, } return , , nil } // parseRelPat is one relationship step: -[detail]->, <-[detail]-, // -[detail]-, or the detail-free --, -->, <--; then an optional postfix // quantifier. func ( *parser) () (*ast.RelPat, error) { := &ast.RelPat{} := false if .acceptTok(TokLt) { = true } if , := .expect(TokMinus, "'-' in a relationship pattern"); != nil { return nil, } if .peek().Kind == TokLBracket { if := .parseRelDetail(); != nil { return nil, } } if , := .expect(TokMinus, "'-' closing a relationship pattern"); != nil { return nil, } := false if ! && .acceptTok(TokGt) { = true } switch { case : .Dir = ast.DirIn case : .Dir = ast.DirOut default: .Dir = ast.DirBoth } if := .parseQuantifier(); != nil { return nil, } return , nil } // parseRelDetail is: '[' [var] [':' type ('|' type)*] [propMap] ']'. The // Cypher in-bracket var-length (*1..3) is rejected in favor of the GQL // postfix quantifier. func ( *parser) ( *ast.RelPat) error { .i++ // '[' if := .peek(); .Kind == TokIdent { if isReserved(.Text) { return errf(.Pos, "reserved word %q cannot be a relationship variable", .Text) } .Var = .Text .i++ } if .acceptTok(TokColon) { for { , := .identName("a relationship type") if != nil { return } .Types = append(.Types, ) if !.acceptTok(TokPipe) { break } } } if .peek().Kind == TokStar { return errf(.peek().Pos, "in-bracket var-length (*m..n) is not GQL: quantify after the arrow, e.g. -[:T]->{1,3}") } if .peek().Kind == TokLBrace { , , := .parsePropMap() if != nil { return } .Props, .PropExprs = , } // Inline element predicate: [r:T WHERE expr] -- desugar conjoins it // onto the clause WHERE. if .peekKw("where") { .i++ , := .parseExpr() if != nil { return } .Where = } if , := .expect(TokRBracket, "']' closing a relationship pattern"); != nil { return } return nil } // parseQuantifier is the optional GQL postfix quantifier after the arrow: // {m,n} / {m,} / {,n} / {m} (exact) / * ({0,}) / + ({1,}). func ( *parser) ( *ast.RelPat) error { switch .peek().Kind { case TokQuestion: .i++ , := uint64(0), uint64(1) .Length = &ast.VarLength{Min: &, Max: &} case TokStar: .i++ .Length = &ast.VarLength{} case TokPlus: .i++ := uint64(1) .Length = &ast.VarLength{Min: &} case TokLBrace: // Only a quantifier follows an arrow with '{': digits and a comma. .i++ := &ast.VarLength{} if .peek().Kind == TokInt { , := .parseCount("quantifier") if != nil { return } .Min = & } if .acceptTok(TokComma) { if .peek().Kind == TokInt { , := .parseCount("quantifier") if != nil { return } .Max = & } } else { if .Min == nil { return errf(.peek().Pos, "empty quantifier: use {m,n}, {m,}, {,n}, or {m}") } .Max = .Min // {m} is exactly m } if , := .expect(TokRBrace, "'}' closing a quantifier"); != nil { return } .Length = } return nil }