Skip to main content

open_cypher/
parser.rs

1//! Strict and recovering parser entry points.
2
3use std::fmt;
4
5use lalrpop_util::ParseError;
6
7use crate::ast::{
8    BinaryOperator, Clause, ClauseKind, ErrorNode, Expr, ExprKind, Identifier, IntegerLiteral,
9    IntegerRadix, IsPredicate, LabelExpression, ListComprehension, Literal, LiteralKind,
10    MapProjection, MapProjectionItem, MatchClause, Name, Node, Parameter, ParameterName,
11    PathFactor, PathFactorKind, Pattern, PatternComprehension, Program, QualifiedName, Quantifier,
12    Query, QueryKind, QueryStatement, QuoteStyle, RegularQuery, RelationshipDirection,
13    RelationshipPattern, SingleQuery, SingleQueryKind, StatementKind, StringLiteral, UnionBranch,
14    UnionOperator,
15};
16use crate::diagnostic::{Diagnostic, DiagnosticCode, ParseErrors};
17use crate::lexer;
18use crate::span::Span;
19use crate::token::{Keyword, Token, TokenKind};
20
21#[path = "generated/cypher.rs"]
22#[allow(clippy::all)]
23mod generated;
24
25const MAX_DIAGNOSTICS: usize = 32;
26
27/// A parsed program and its complete, trivia-preserving token stream.
28#[derive(Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct ParsedProgram {
31    /// The owned syntax tree.
32    pub program: Program,
33    /// Every token, including whitespace and comments.
34    pub tokens: Vec<Token>,
35}
36
37/// The result of a recovering parse.
38#[derive(Clone, Debug, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct ParseOutcome<T> {
41    /// The recovered value, when the selected entry point can construct one.
42    ///
43    /// [`parse_recovering`] currently always returns a `ParsedProgram`: valid
44    /// input produces the parsed tree, while invalid input produces a
45    /// whole-input error statement.
46    pub value: Option<T>,
47    /// Lexer and parser diagnostics in deterministic source order.
48    pub diagnostics: Vec<Diagnostic>,
49}
50
51/// Parses one complete source input, rejecting lexical or syntactic errors.
52pub fn parse(source: &str) -> Result<ParsedProgram, ParseErrors> {
53    let outcome = parse_recovering(source);
54    if outcome.diagnostics.iter().any(Diagnostic::is_error) {
55        return Err(ParseErrors::new(outcome.diagnostics));
56    }
57
58    outcome.value.ok_or_else(|| {
59        ParseErrors::new(vec![Diagnostic::error(
60            DiagnosticCode::Internal,
61            "the parser did not produce a program",
62            Span::empty(source.len()),
63        )])
64    })
65}
66
67/// Parses while retaining diagnostics and always constructing a program root.
68///
69/// Recovery is currently whole-input recovery. A syntax failure yields one
70/// error statement rather than locally recovered clause or expression nodes.
71#[must_use]
72pub fn parse_recovering(source: &str) -> ParseOutcome<ParsedProgram> {
73    let lexed = lexer::lex(source);
74    let mut diagnostics = lexed.diagnostics.clone();
75    let significant = parser_tokens(&lexed.tokens);
76
77    let parsed = parse_program_with_contextual_names(source, &significant);
78    let program = match parsed {
79        Ok(program) => Some(program),
80        Err(error) => {
81            diagnostics.push(parse_diagnostic(source, error));
82            None
83        }
84    };
85
86    if program.is_none() || !diagnostics.is_empty() {
87        collect_delimiter_diagnostics(source, &lexed.tokens, &mut diagnostics);
88    }
89
90    normalize_diagnostics(source.len(), &mut diagnostics);
91
92    let program = program.unwrap_or_else(|| recovered_program(source, &lexed.tokens));
93    ParseOutcome {
94        value: Some(ParsedProgram {
95            program,
96            tokens: lexed.tokens,
97        }),
98        diagnostics,
99    }
100}
101
102type SpannedParserToken = (usize, ParserToken, usize);
103
104fn parse_program_with_contextual_names(
105    source: &str,
106    tokens: &[SpannedParserToken],
107) -> Result<Program, LalrpopError> {
108    parse_tokens_with_contextual_names(tokens, |contextual| parse_program(source, contextual))
109}
110
111fn parse_tokens_with_contextual_names<T>(
112    tokens: &[SpannedParserToken],
113    mut parse_tokens: impl FnMut(&[SpannedParserToken]) -> Result<T, LalrpopError>,
114) -> Result<T, LalrpopError> {
115    let mut contextual = tokens.to_vec();
116    let mut resolved = vec![false; tokens.len()];
117    for index in 0..tokens.len() {
118        let follows_name_marker = index
119            .checked_sub(1)
120            .and_then(|previous| tokens.get(previous))
121            .is_some_and(|token| matches!(token.1, ParserToken::As | ParserToken::Dot));
122        if is_aliased_bare_expression(tokens, index) && tokens[index].1.is_keyword_literal() {
123            // Preserve the native literal interpretation and keep later retry
124            // errors from reclassifying this already-valid projection item.
125            resolved[index] = true;
126            continue;
127        }
128        if tokens[index].1.is_contextual_name_candidate()
129            && ((follows_name_marker
130                && !(tokens[index].1 == ParserToken::As && tokens[index - 1].1 == ParserToken::As))
131                || is_solo_projection_keyword(tokens, index)
132                || is_aliased_bare_expression(tokens, index))
133        {
134            resolved[index] = true;
135            contextual[index].1 = ParserToken::Identifier;
136        }
137    }
138
139    let initial_error = match parse_tokens(&contextual) {
140        Ok(value) => return Ok(value),
141        Err(error) => error,
142    };
143
144    let mut best_error = initial_error.clone();
145    let mut error = initial_error;
146
147    // The generated state machines already reinterpret a keyword when its
148    // native action is an immediate error and the identifier action is valid.
149    // A smaller set of constructs has delayed ambiguity: the keyword shifts as
150    // syntax, then fails at a later token or EOF (for example a bare COUNT).
151    // Reclassify the nearest remaining candidate and retry those cases.
152    // Each pass commits one lexical-context signature (the keyword and its two
153    // neighboring tokens), so repeated non-reserved words in the same name
154    // position are handled together without conflating distinct token kinds.
155    // Valid queries can use an arbitrary number of non-reserved names, and
156    // malformed input is bounded by the finite set of signatures instead of
157    // an exponential search through keyword subsets.
158    loop {
159        if error_location(&error) >= error_location(&best_error) {
160            best_error = error.clone();
161        }
162
163        let location = error_location(&error);
164        let candidate = tokens
165            .iter()
166            .enumerate()
167            .filter(|(index, (_, token, _))| {
168                token.is_contextual_name_candidate() && !resolved[*index]
169            })
170            .map(|(index, (start, _, end))| {
171                let distance = if location < *start {
172                    *start - location
173                } else {
174                    location.saturating_sub(*end)
175                };
176                let exact = (*start..*end).contains(&location);
177                let followed_by_as = tokens
178                    .get(index + 1)
179                    .is_some_and(|token| token.1 == ParserToken::As);
180                let priority = if exact && (tokens[index].1 != ParserToken::As || followed_by_as) {
181                    0
182                } else if followed_by_as && tokens[index].1 != ParserToken::As {
183                    1
184                } else if exact {
185                    2
186                } else {
187                    3
188                };
189                (priority, distance, index)
190            })
191            .min();
192        let Some((_, _, index)) = candidate else {
193            break;
194        };
195
196        let signature = contextual_name_signature(tokens, index);
197        for peer in 0..tokens.len() {
198            if !resolved[peer]
199                && tokens[peer].1.is_contextual_name_candidate()
200                && contextual_name_signature(tokens, peer) == signature
201            {
202                resolved[peer] = true;
203                contextual[peer].1 = ParserToken::Identifier;
204            }
205        }
206        match parse_tokens(&contextual) {
207            Ok(value) => return Ok(value),
208            Err(next_error)
209                if error_location(&next_error) > location
210                    || matches!(error, ParseError::User { .. }) =>
211            {
212                error = next_error;
213            }
214            Err(_) => break,
215        }
216    }
217
218    Err(best_error)
219}
220
221fn is_aliased_bare_expression(tokens: &[SpannedParserToken], index: usize) -> bool {
222    // A one-token projection item followed by AS cannot be one of the keyword
223    // constructs with delayed ambiguity (CASE, COUNT, EXISTS, and so on).
224    // Resolve every such item in one scan. The caller separately preserves the
225    // established interpretation of the six keyword-backed literal spellings.
226    index
227        .checked_sub(1)
228        .and_then(|previous| tokens.get(previous))
229        .is_some_and(|token| {
230            matches!(
231                token.1,
232                ParserToken::Return | ParserToken::With | ParserToken::Yield | ParserToken::Comma
233            )
234        })
235        && tokens
236            .get(index + 1)
237            .is_some_and(|token| token.1 == ParserToken::As)
238}
239
240fn is_solo_projection_keyword(tokens: &[SpannedParserToken], index: usize) -> bool {
241    if !matches!(tokens[index].1, ParserToken::SetAll | ParserToken::Distinct)
242        || !index
243            .checked_sub(1)
244            .and_then(|previous| tokens.get(previous))
245            .is_some_and(|token| matches!(token.1, ParserToken::Return | ParserToken::With))
246    {
247        return false;
248    }
249
250    tokens.get(index + 1).is_none_or(|token| {
251        matches!(
252            token.1,
253            ParserToken::As
254                | ParserToken::Comma
255                | ParserToken::Order
256                | ParserToken::Offset
257                | ParserToken::Skip
258                | ParserToken::Limit
259                | ParserToken::Where
260                | ParserToken::Union
261                | ParserToken::Semicolon
262        ) || clause_starts(token.1)
263    })
264}
265
266fn contextual_name_signature(
267    tokens: &[SpannedParserToken],
268    index: usize,
269) -> (Option<ParserToken>, ParserToken, Option<ParserToken>) {
270    (
271        index
272            .checked_sub(1)
273            .and_then(|previous| tokens.get(previous))
274            .map(|token| token.1),
275        tokens[index].1,
276        tokens.get(index + 1).map(|token| token.1),
277    )
278}
279
280fn parse_program(source: &str, tokens: &[SpannedParserToken]) -> Result<Program, LalrpopError> {
281    if tokens.is_empty() {
282        return Ok(Program::new(Vec::new(), Span::empty(0)));
283    }
284
285    let (query_tokens, terminator) = match tokens.last() {
286        Some((start, ParserToken::Semicolon, end)) => {
287            (&tokens[..tokens.len() - 1], Some(Span::new(*start, *end)))
288        }
289        _ => (tokens, None),
290    };
291
292    if query_tokens.is_empty() {
293        return Err(ParseError::UnrecognizedEof {
294            location: terminator.map_or(0, |span| span.start),
295            expected: vec!["query clause".to_owned()],
296        });
297    }
298
299    let (head, unions) = match parse_regular_query(source, query_tokens) {
300        Ok(query) => query,
301        Err(regular_error)
302            if !top_level_indices(query_tokens, |token| token == ParserToken::Union).is_empty() =>
303        {
304            return Err(regular_error);
305        }
306        Err(regular_error) => {
307            let input = query_tokens.iter().copied().map(Ok);
308            let clause = match generated::StandaloneCallRootParser::new().parse(source, input) {
309                Ok(clause) => clause,
310                Err(_) => return Err(regular_error),
311            };
312            let span = clause.span;
313            (
314                Node::new(
315                    SingleQueryKind {
316                        clauses: vec![clause],
317                    },
318                    span,
319                ),
320                Vec::new(),
321            )
322        }
323    };
324    let query_span = Span::new(
325        query_tokens.first().expect("non-empty query").0,
326        query_tokens.last().expect("non-empty query").2,
327    );
328    let query = Node::new(
329        QueryKind::Regular(RegularQuery { head, unions }),
330        query_span,
331    );
332    let statement_span = terminator.map_or(query_span, |span| query_span.cover(span));
333    let statement = Node::new(
334        StatementKind::Query(QueryStatement {
335            mode: None,
336            query,
337            terminator,
338        }),
339        statement_span,
340    );
341    Ok(Program::new(vec![statement], statement_span))
342}
343
344fn parse_regular_query(
345    source: &str,
346    tokens: &[SpannedParserToken],
347) -> Result<(SingleQuery, Vec<UnionBranch>), LalrpopError> {
348    let unsplit_error = match parse_single_query(source, tokens) {
349        Ok(query) => return Ok((query, Vec::new())),
350        Err(error) => error,
351    };
352
353    for union_index in top_level_indices(tokens, |token| token == ParserToken::Union) {
354        let Ok(head) = parse_single_query(source, &tokens[..union_index]) else {
355            continue;
356        };
357
358        let mut right_start = union_index + 1;
359        let operator = match tokens.get(right_start).map(|token| token.1) {
360            Some(ParserToken::All) => {
361                right_start += 1;
362                UnionOperator::All
363            }
364            Some(ParserToken::Distinct) => {
365                right_start += 1;
366                UnionOperator::Distinct
367            }
368            _ => UnionOperator::Default,
369        };
370        if right_start >= tokens.len() {
371            continue;
372        }
373
374        let Ok((right, mut tail)) = parse_regular_query(source, &tokens[right_start..]) else {
375            continue;
376        };
377        let operator_end = tokens[right_start - 1].2;
378        let operator = Node::new(operator, Span::new(tokens[union_index].0, operator_end));
379        let mut unions = Vec::with_capacity(1 + tail.len());
380        unions.push(UnionBranch {
381            operator,
382            query: right,
383        });
384        unions.append(&mut tail);
385        return Ok((head, unions));
386    }
387
388    Err(unsplit_error)
389}
390
391fn parse_single_query(
392    source: &str,
393    tokens: &[SpannedParserToken],
394) -> Result<SingleQuery, LalrpopError> {
395    if tokens.is_empty() {
396        return Err(ParseError::UnrecognizedEof {
397            location: 0,
398            expected: vec!["query clause".to_owned()],
399        });
400    }
401
402    let mut clauses: Vec<Clause> = Vec::new();
403    let mut cursor = 0;
404    while cursor < tokens.len() {
405        let mut candidate_ends = top_level_indices(&tokens[cursor + 1..], clause_starts)
406            .into_iter()
407            .map(|index| cursor + 1 + index)
408            .collect::<Vec<_>>();
409        candidate_ends.push(tokens.len());
410        candidate_ends.sort_unstable();
411        candidate_ends.dedup();
412
413        let mut best_error = None;
414        let mut parsed = None;
415        for end in candidate_ends {
416            let input = tokens[cursor..end].iter().copied().map(Ok);
417            match generated::ClauseRootParser::new().parse(source, input) {
418                Ok(clause) => {
419                    if matches!(&clause.kind, ClauseKind::Return(_)) && end < tokens.len() {
420                        return Err(ParseError::ExtraToken { token: tokens[end] });
421                    }
422                    parsed = Some((clause, end));
423                    break;
424                }
425                Err(error) => retain_farthest_error(&mut best_error, error),
426            }
427        }
428
429        let Some((clause, end)) = parsed else {
430            return Err(best_error.unwrap_or(ParseError::UnrecognizedEof {
431                location: tokens[cursor].0,
432                expected: vec!["query clause".to_owned()],
433            }));
434        };
435        clauses.push(clause);
436        cursor = end;
437    }
438
439    let span = Span::new(
440        tokens.first().expect("non-empty query").0,
441        tokens.last().unwrap().2,
442    );
443    Ok(Node::new(SingleQueryKind { clauses }, span))
444}
445
446fn retain_farthest_error(slot: &mut Option<LalrpopError>, candidate: LalrpopError) {
447    let candidate_location = error_location(&candidate);
448    if slot
449        .as_ref()
450        .is_none_or(|current| candidate_location >= error_location(current))
451    {
452        *slot = Some(candidate);
453    }
454}
455
456fn error_location(error: &LalrpopError) -> usize {
457    match error {
458        ParseError::InvalidToken { location } | ParseError::UnrecognizedEof { location, .. } => {
459            *location
460        }
461        ParseError::UnrecognizedToken { token, .. } | ParseError::ExtraToken { token } => token.0,
462        ParseError::User { .. } => 0,
463    }
464}
465
466fn top_level_indices(
467    tokens: &[SpannedParserToken],
468    predicate: impl Fn(ParserToken) -> bool,
469) -> Vec<usize> {
470    let mut depth = 0usize;
471    let mut indices = Vec::new();
472    for (index, (_, token, _)) in tokens.iter().copied().enumerate() {
473        if depth == 0 && predicate(token) {
474            indices.push(index);
475        }
476        match token {
477            ParserToken::LeftParen | ParserToken::LeftBracket | ParserToken::LeftBrace => {
478                depth = depth.saturating_add(1);
479            }
480            ParserToken::RightParen | ParserToken::RightBracket | ParserToken::RightBrace => {
481                depth = depth.saturating_sub(1);
482            }
483            _ => {}
484        }
485    }
486    indices
487}
488
489fn clause_starts(token: ParserToken) -> bool {
490    matches!(
491        token,
492        ParserToken::Match
493            | ParserToken::Optional
494            | ParserToken::Unwind
495            | ParserToken::With
496            | ParserToken::Return
497            | ParserToken::Create
498            | ParserToken::Merge
499            | ParserToken::Set
500            | ParserToken::Remove
501            | ParserToken::Delete
502            | ParserToken::Detach
503            | ParserToken::Call
504    )
505}
506
507fn normalize_diagnostics(source_len: usize, diagnostics: &mut Vec<Diagnostic>) {
508    diagnostics.sort_by(|left, right| {
509        left.primary_span
510            .start
511            .cmp(&right.primary_span.start)
512            .then(left.primary_span.end.cmp(&right.primary_span.end))
513            .then(left.code.cmp(&right.code))
514            .then(left.message.cmp(&right.message))
515    });
516    diagnostics.dedup_by(|left, right| {
517        left.code == right.code
518            && left.primary_span == right.primary_span
519            && left.message == right.message
520    });
521
522    if diagnostics.len() > MAX_DIAGNOSTICS {
523        diagnostics.truncate(MAX_DIAGNOSTICS - 1);
524        diagnostics.push(Diagnostic::error(
525            DiagnosticCode::TooManyErrors,
526            "additional parser diagnostics were suppressed",
527            Span::empty(source_len),
528        ));
529    }
530}
531
532fn recovered_program(source: &str, tokens: &[Token]) -> Program {
533    let span = significant_span(tokens).unwrap_or_else(|| Span::empty(source.len()));
534    let statements = if span.is_empty() {
535        Vec::new()
536    } else {
537        vec![Node::new(StatementKind::Error(ErrorNode), span)]
538    };
539    Program::new(statements, span)
540}
541
542fn significant_span(tokens: &[Token]) -> Option<Span> {
543    let mut significant = tokens.iter().filter(|token| !token.is_trivia());
544    let first = significant.next()?;
545    let mut span = first.span;
546    for token in significant {
547        span = span.cover(token.span);
548    }
549    Some(span)
550}
551
552fn collect_delimiter_diagnostics(
553    source: &str,
554    tokens: &[Token],
555    diagnostics: &mut Vec<Diagnostic>,
556) {
557    let mut stack: Vec<(TokenKind, Span)> = Vec::new();
558
559    for token in tokens.iter().filter(|token| !token.is_trivia()) {
560        match token.kind {
561            TokenKind::LeftParen | TokenKind::LeftBracket | TokenKind::LeftBrace => {
562                stack.push((token.kind, token.span));
563            }
564            TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => {
565                let expected_open = match token.kind {
566                    TokenKind::RightParen => TokenKind::LeftParen,
567                    TokenKind::RightBracket => TokenKind::LeftBracket,
568                    TokenKind::RightBrace => TokenKind::LeftBrace,
569                    _ => unreachable!(),
570                };
571                match stack.pop() {
572                    Some((open, _)) if open == expected_open => {}
573                    Some((open, open_span)) => diagnostics.push(
574                        Diagnostic::error(
575                            DiagnosticCode::MismatchedDelimiter,
576                            format!(
577                                "mismatched delimiter: {} does not close {}",
578                                token.kind, open
579                            ),
580                            token.span,
581                        )
582                        .with_label(open_span, "this delimiter was opened here"),
583                    ),
584                    None => diagnostics.push(Diagnostic::error(
585                        DiagnosticCode::MismatchedDelimiter,
586                        format!("unexpected closing delimiter {}", token.kind),
587                        token.span,
588                    )),
589                }
590            }
591            _ => {}
592        }
593    }
594
595    for (open, span) in stack {
596        diagnostics.push(
597            Diagnostic::error(
598                DiagnosticCode::UnclosedDelimiter,
599                format!("unclosed delimiter {open}"),
600                span,
601            )
602            .with_help(format!(
603                "add the matching delimiter before byte {}",
604                source.len()
605            )),
606        );
607    }
608}
609
610type LalrpopError = ParseError<usize, ParserToken, ()>;
611
612fn parse_diagnostic(source: &str, error: LalrpopError) -> Diagnostic {
613    match error {
614        ParseError::InvalidToken { location } => Diagnostic::error(
615            DiagnosticCode::InvalidToken,
616            "invalid token",
617            Span::empty(location.min(source.len())),
618        ),
619        ParseError::UnrecognizedEof { location, expected } => {
620            let expected = normalized_expected(expected);
621            let message = if expected.is_empty() {
622                "unexpected end of input".to_owned()
623            } else {
624                format!("unexpected end of input; expected {expected}")
625            };
626            Diagnostic::error(
627                DiagnosticCode::UnexpectedEof,
628                message,
629                Span::empty(location.min(source.len())),
630            )
631        }
632        ParseError::UnrecognizedToken {
633            token: (start, token, end),
634            expected,
635        } => {
636            let expected = normalized_expected(expected);
637            let message = if expected.is_empty() {
638                format!("unexpected token {token}")
639            } else {
640                format!("unexpected token {token}; expected {expected}")
641            };
642            Diagnostic::error(
643                DiagnosticCode::UnexpectedToken,
644                message,
645                Span::new(start, end),
646            )
647        }
648        ParseError::ExtraToken {
649            token: (start, token, end),
650        } => Diagnostic::error(
651            DiagnosticCode::ExtraToken,
652            format!("extra token {token} after the end of the statement"),
653            Span::new(start, end),
654        ),
655        ParseError::User { .. } => Diagnostic::error(
656            DiagnosticCode::UnexpectedToken,
657            "input does not satisfy the selected syntax production",
658            Span::empty(source.len()),
659        ),
660    }
661}
662
663fn normalized_expected(mut expected: Vec<String>) -> String {
664    for item in &mut expected {
665        *item = item.trim_matches('"').to_owned();
666    }
667    expected.sort();
668    expected.dedup();
669    match expected.as_slice() {
670        [] => String::new(),
671        [only] => format!("`{only}`"),
672        [head @ .., last] => {
673            let head = head
674                .iter()
675                .map(|item| format!("`{item}`"))
676                .collect::<Vec<_>>()
677                .join(", ");
678            format!("{head}, or `{last}`")
679        }
680    }
681}
682
683fn parser_tokens(tokens: &[Token]) -> Vec<SpannedParserToken> {
684    let significant = tokens
685        .iter()
686        .filter(|token| !token.is_trivia())
687        .collect::<Vec<_>>();
688    let mut result = Vec::with_capacity(significant.len());
689    let mut index = 0;
690    while index < significant.len() {
691        if let Some(end) = escaped_parameter_end(&significant, index) {
692            result.push((
693                significant[index].span.start,
694                ParserToken::Parameter,
695                significant[end].span.end,
696            ));
697            index = end + 1;
698            continue;
699        }
700        if let Some(end) = pattern_comprehension_end(&significant, index) {
701            result.push((
702                significant[index].span.start,
703                ParserToken::PatternComprehension,
704                significant[end].span.end,
705            ));
706            index = end + 1;
707            continue;
708        }
709        if significant[index].kind == TokenKind::LeftParen
710            && is_pattern_expression_context(&significant, index)
711            && let Some(end) = pattern_expression_end(&significant, index)
712        {
713            result.push((
714                significant[index].span.start,
715                ParserToken::PatternExpression,
716                significant[end].span.end,
717            ));
718            index = end + 1;
719            continue;
720        }
721        if is_relationship_label_start(&significant, index)
722            && let Some(end) = relationship_label_end(&significant, index)
723        {
724            result.push((
725                significant[index].span.start,
726                ParserToken::RelationshipLabelExpression,
727                significant[end].span.end,
728            ));
729            index = end + 1;
730            continue;
731        }
732        if is_label_predicate_context(&significant, index)
733            && let Some(end) = label_predicate_end(&significant, index)
734        {
735            result.push((
736                significant[index].span.start,
737                ParserToken::LabelPredicate,
738                significant[end].span.end,
739            ));
740            index = end + 1;
741            continue;
742        }
743        if !is_procedure_name_start(&significant, index)
744            && let Some(end) = qualified_function_name_end(&significant, index)
745        {
746            result.push((
747                significant[index].span.start,
748                ParserToken::QualifiedFunctionName,
749                significant[end].span.end,
750            ));
751            index = end + 1;
752            continue;
753        }
754        let token = significant[index];
755        let mut parser_token = ParserToken::from(token.kind);
756        if parser_token == ParserToken::All && is_explicit_set_all(&significant, index) {
757            parser_token = ParserToken::SetAll;
758        }
759        result.push((token.span.start, parser_token, token.span.end));
760        index += 1;
761    }
762    result
763}
764
765fn escaped_parameter_end(tokens: &[&Token], start: usize) -> Option<usize> {
766    (tokens.get(start)?.kind == TokenKind::Dollar
767        && tokens.get(start + 1).is_some_and(|token| {
768            token.kind == TokenKind::EscapedIdentifier && tokens[start].span.end == token.span.start
769        }))
770    .then_some(start + 1)
771}
772
773fn qualified_function_name_end(tokens: &[&Token], start: usize) -> Option<usize> {
774    if !is_symbolic_name_kind(tokens.get(start)?.kind) {
775        return None;
776    }
777
778    let mut end = start;
779    let mut components = 1usize;
780    while tokens
781        .get(end + 1)
782        .is_some_and(|token| token.kind == TokenKind::Dot)
783        && tokens
784            .get(end + 2)
785            .is_some_and(|token| is_symbolic_name_kind(token.kind))
786    {
787        end += 2;
788        components += 1;
789    }
790
791    (components > 1
792        && tokens
793            .get(end + 1)
794            .is_some_and(|token| token.kind == TokenKind::LeftParen))
795    .then_some(end)
796}
797
798fn is_procedure_name_start(tokens: &[&Token], start: usize) -> bool {
799    let mut first_component = start;
800    while first_component >= 2
801        && tokens[first_component - 1].kind == TokenKind::Dot
802        && is_symbolic_name_kind(tokens[first_component - 2].kind)
803    {
804        first_component -= 2;
805    }
806
807    first_component
808        .checked_sub(1)
809        .and_then(|index| tokens.get(index))
810        .is_some_and(|token| token.kind == TokenKind::Keyword(Keyword::Call))
811}
812
813fn is_symbolic_name_kind(kind: TokenKind) -> bool {
814    matches!(
815        kind,
816        TokenKind::Identifier | TokenKind::EscapedIdentifier | TokenKind::Keyword(_)
817    )
818}
819
820fn is_explicit_set_all(tokens: &[&Token], index: usize) -> bool {
821    if tokens
822        .get(index + 1)
823        .is_some_and(|token| token.kind == TokenKind::LeftParen)
824    {
825        return false;
826    }
827
828    let Some(previous) = index.checked_sub(1).and_then(|index| tokens.get(index)) else {
829        return false;
830    };
831    if matches!(
832        previous.kind,
833        TokenKind::Keyword(Keyword::Return | Keyword::With)
834    ) {
835        return true;
836    }
837    if previous.kind != TokenKind::LeftParen {
838        return false;
839    }
840
841    index
842        .checked_sub(2)
843        .and_then(|index| tokens.get(index))
844        .is_some_and(|token| {
845            matches!(
846                ParserToken::from(token.kind),
847                ParserToken::Identifier
848                    | ParserToken::EscapedIdentifier
849                    | ParserToken::Count
850                    | ParserToken::Match
851                    | ParserToken::Path
852                    | ParserToken::Return
853            )
854        })
855}
856
857fn pattern_comprehension_end(tokens: &[&Token], start: usize) -> Option<usize> {
858    if tokens.get(start)?.kind != TokenKind::LeftBracket {
859        return None;
860    }
861
862    let mut brackets = 0usize;
863    let mut parentheses = 0usize;
864    let mut braces = 0usize;
865    let mut pipe_at = None;
866    let mut arrow = false;
867    let mut top_level_nodes = 0usize;
868    let mut top_level_minuses = 0usize;
869    for (index, token) in tokens.iter().enumerate().skip(start) {
870        match token.kind {
871            TokenKind::LeftBracket => brackets += 1,
872            TokenKind::RightBracket => {
873                if brackets == 1 && parentheses == 0 && braces == 0 {
874                    let has_projection = pipe_at.is_some_and(|pipe| index > pipe + 1);
875                    let undirected_relationship = top_level_nodes >= 2 && top_level_minuses >= 2;
876                    return (has_projection && (arrow || undirected_relationship)).then_some(index);
877                }
878                brackets = brackets.saturating_sub(1);
879            }
880            TokenKind::LeftParen => {
881                if pipe_at.is_none() && brackets == 1 && parentheses == 0 && braces == 0 {
882                    top_level_nodes += 1;
883                }
884                parentheses += 1;
885            }
886            TokenKind::RightParen => parentheses = parentheses.saturating_sub(1),
887            TokenKind::LeftBrace => braces += 1,
888            TokenKind::RightBrace => braces = braces.saturating_sub(1),
889            TokenKind::Pipe if brackets == 1 && parentheses == 0 && braces == 0 => {
890                pipe_at = Some(index)
891            }
892            TokenKind::LeftArrow | TokenKind::RightArrow if pipe_at.is_none() => arrow = true,
893            TokenKind::Minus
894                if pipe_at.is_none() && brackets == 1 && parentheses == 0 && braces == 0 =>
895            {
896                top_level_minuses += 1;
897            }
898            _ => {}
899        }
900    }
901    None
902}
903
904fn is_pattern_expression_context(tokens: &[&Token], start: usize) -> bool {
905    if start >= 2
906        && tokens[start - 1].kind == TokenKind::LeftParen
907        && matches!(
908            tokens[start - 2].kind,
909            TokenKind::Keyword(Keyword::ShortestPath | Keyword::AllShortestPaths)
910        )
911    {
912        return false;
913    }
914    if start >= 2
915        && tokens[start - 1].kind == TokenKind::LeftBrace
916        && matches!(
917            tokens[start - 2].kind,
918            TokenKind::Keyword(Keyword::Exists | Keyword::Count | Keyword::Collect)
919        )
920    {
921        return false;
922    }
923    if is_in_map_entry_value(tokens, start) {
924        return true;
925    }
926
927    // Like is_label_predicate_context, skip markers inside completed
928    // bracketed/braced constructs (a relationship-type `|` in `[:A|B]` is not
929    // an expression context), while a comprehension's projection `|` in the
930    // construct that still encloses the candidate remains visible.
931    let mut nested_brackets = 0usize;
932    let mut nested_braces = 0usize;
933    for (index, token) in tokens[..start].iter().enumerate().rev() {
934        match token.kind {
935            TokenKind::RightBracket => {
936                nested_brackets = nested_brackets.saturating_add(1);
937                continue;
938            }
939            TokenKind::LeftBracket if nested_brackets != 0 => {
940                nested_brackets = nested_brackets.saturating_sub(1);
941                continue;
942            }
943            TokenKind::RightBrace => {
944                nested_braces = nested_braces.saturating_add(1);
945                continue;
946            }
947            TokenKind::LeftBrace if nested_braces != 0 => {
948                nested_braces = nested_braces.saturating_sub(1);
949                continue;
950            }
951            _ if nested_brackets != 0 || nested_braces != 0 => continue,
952            _ => {}
953        }
954        if tokens
955            .get(index + 1)
956            .is_some_and(|next| next.kind == TokenKind::Equal)
957        {
958            continue;
959        }
960        match token.kind {
961            TokenKind::Pipe => return true,
962            TokenKind::Keyword(
963                Keyword::Where
964                | Keyword::Return
965                | Keyword::With
966                | Keyword::Unwind
967                | Keyword::Set
968                | Keyword::Delete
969                | Keyword::Then
970                | Keyword::Else
971                | Keyword::When
972                | Keyword::Case,
973            ) => return true,
974            TokenKind::Keyword(
975                Keyword::Match | Keyword::Create | Keyword::Merge | Keyword::Call | Keyword::Remove,
976            ) => return false,
977            _ => {}
978        }
979    }
980    false
981}
982
983fn pattern_expression_end(tokens: &[&Token], start: usize) -> Option<usize> {
984    if tokens.get(start)?.kind != TokenKind::LeftParen {
985        return None;
986    }
987
988    let mut node_end =
989        matching_delimiter(tokens, start, TokenKind::LeftParen, TokenKind::RightParen)?;
990    let mut relationships = 0usize;
991    while let Some(after_relationship) = relationship_syntax_end(tokens, node_end + 1) {
992        let mut next_node = after_relationship;
993        if matches!(
994            tokens.get(next_node).map(|token| token.kind),
995            Some(TokenKind::Star | TokenKind::Plus | TokenKind::Question)
996        ) {
997            next_node += 1;
998        } else if tokens
999            .get(next_node)
1000            .is_some_and(|token| token.kind == TokenKind::LeftBrace)
1001        {
1002            next_node = matching_delimiter(
1003                tokens,
1004                next_node,
1005                TokenKind::LeftBrace,
1006                TokenKind::RightBrace,
1007            )? + 1;
1008        }
1009        if !tokens
1010            .get(next_node)
1011            .is_some_and(|token| token.kind == TokenKind::LeftParen)
1012        {
1013            break;
1014        }
1015        node_end = matching_delimiter(
1016            tokens,
1017            next_node,
1018            TokenKind::LeftParen,
1019            TokenKind::RightParen,
1020        )?;
1021        relationships += 1;
1022    }
1023
1024    (relationships > 0).then_some(node_end)
1025}
1026
1027fn relationship_syntax_end(tokens: &[&Token], start: usize) -> Option<usize> {
1028    let mut cursor = match (
1029        tokens.get(start).map(|token| token.kind),
1030        tokens.get(start + 1).map(|token| token.kind),
1031    ) {
1032        (Some(TokenKind::Minus | TokenKind::LeftArrow), _) => start + 1,
1033        (Some(TokenKind::Less), Some(TokenKind::Minus)) => start + 2,
1034        _ => return None,
1035    };
1036    if tokens
1037        .get(cursor)
1038        .is_some_and(|token| token.kind == TokenKind::LeftBracket)
1039    {
1040        cursor = matching_delimiter(
1041            tokens,
1042            cursor,
1043            TokenKind::LeftBracket,
1044            TokenKind::RightBracket,
1045        )? + 1;
1046    }
1047    match (
1048        tokens.get(cursor).map(|token| token.kind),
1049        tokens.get(cursor + 1).map(|token| token.kind),
1050    ) {
1051        (Some(TokenKind::Minus), Some(TokenKind::Greater)) => Some(cursor + 2),
1052        (Some(TokenKind::Minus | TokenKind::RightArrow), _) => Some(cursor + 1),
1053        _ => None,
1054    }
1055}
1056
1057fn matching_delimiter(
1058    tokens: &[&Token],
1059    start: usize,
1060    open: TokenKind,
1061    close: TokenKind,
1062) -> Option<usize> {
1063    if tokens.get(start)?.kind != open {
1064        return None;
1065    }
1066    let mut depth = 0usize;
1067    for (index, token) in tokens.iter().enumerate().skip(start) {
1068        if token.kind == open {
1069            depth += 1;
1070        } else if token.kind == close {
1071            depth = depth.saturating_sub(1);
1072            if depth == 0 {
1073                return Some(index);
1074            }
1075        }
1076    }
1077    None
1078}
1079
1080fn is_relationship_label_start(tokens: &[&Token], start: usize) -> bool {
1081    if tokens
1082        .get(start)
1083        .is_none_or(|token| token.kind != TokenKind::Colon)
1084    {
1085        return false;
1086    }
1087    match start.checked_sub(1).and_then(|index| tokens.get(index)) {
1088        Some(token) if token.kind == TokenKind::LeftBracket => true,
1089        Some(token) if is_symbolic_name_kind(token.kind) => {
1090            start >= 2 && tokens[start - 2].kind == TokenKind::LeftBracket
1091        }
1092        _ => false,
1093    }
1094}
1095
1096fn relationship_label_end(tokens: &[&Token], start: usize) -> Option<usize> {
1097    let end = label_predicate_end(tokens, start)?;
1098    if !(start + 1..=end).any(|index| tokens[index].kind == TokenKind::Colon) {
1099        return Some(end);
1100    }
1101
1102    let mut cursor = start + 1;
1103    loop {
1104        if cursor > end || !is_symbolic_name_kind(tokens[cursor].kind) {
1105            return None;
1106        }
1107        cursor += 1;
1108        if cursor > end {
1109            return Some(end);
1110        }
1111        if tokens[cursor].kind != TokenKind::Pipe
1112            || tokens
1113                .get(cursor + 1)
1114                .is_none_or(|token| token.kind != TokenKind::Colon)
1115        {
1116            return None;
1117        }
1118        cursor += 2;
1119    }
1120}
1121
1122fn is_label_predicate_context(tokens: &[&Token], start: usize) -> bool {
1123    if !matches!(
1124        tokens.get(start).map(|token| token.kind),
1125        Some(TokenKind::Colon | TokenKind::Keyword(Keyword::Is))
1126    ) {
1127        return false;
1128    }
1129    if tokens[start].kind == TokenKind::Keyword(Keyword::Is)
1130        && tokens
1131            .get(start + 1)
1132            .is_some_and(|token| token.kind == TokenKind::Keyword(Keyword::As))
1133        && tokens
1134            .get(start + 2)
1135            .is_some_and(|token| is_symbolic_name_kind(token.kind))
1136    {
1137        return false;
1138    }
1139    if tokens[start].kind == TokenKind::Colon
1140        && start >= 2
1141        && is_symbolic_name_kind(tokens[start - 1].kind)
1142        && matches!(tokens[start - 2].kind, TokenKind::LeftBrace)
1143    {
1144        return false;
1145    }
1146    if is_in_map_entry_value(tokens, start) {
1147        return true;
1148    }
1149    if tokens[start].kind == TokenKind::Colon
1150        && start >= 2
1151        && is_symbolic_name_kind(tokens[start - 1].kind)
1152        && tokens[start - 2].kind == TokenKind::Comma
1153        && is_inside_braces(tokens, start)
1154    {
1155        return false;
1156    }
1157
1158    // Ignore context markers inside a completed bracketed/braced construct.
1159    // In particular, a relationship-type `|` in `[:A|B]` must not make a
1160    // later node label look like an expression predicate. Markers in the
1161    // construct that currently encloses the candidate still have zero
1162    // relative depth and remain visible (for example a comprehension `|`).
1163    let mut nested_brackets = 0usize;
1164    let mut nested_braces = 0usize;
1165    for (index, token) in tokens[..start].iter().enumerate().rev() {
1166        match token.kind {
1167            TokenKind::RightBracket => {
1168                nested_brackets = nested_brackets.saturating_add(1);
1169                continue;
1170            }
1171            TokenKind::LeftBracket if nested_brackets != 0 => {
1172                nested_brackets = nested_brackets.saturating_sub(1);
1173                continue;
1174            }
1175            TokenKind::RightBrace => {
1176                nested_braces = nested_braces.saturating_add(1);
1177                continue;
1178            }
1179            TokenKind::LeftBrace if nested_braces != 0 => {
1180                nested_braces = nested_braces.saturating_sub(1);
1181                continue;
1182            }
1183            _ if nested_brackets != 0 || nested_braces != 0 => continue,
1184            _ => {}
1185        }
1186        if tokens
1187            .get(index + 1)
1188            .is_some_and(|next| next.kind == TokenKind::Equal)
1189        {
1190            continue;
1191        }
1192        match token.kind {
1193            TokenKind::Pipe => return true,
1194            TokenKind::Keyword(
1195                Keyword::Where
1196                | Keyword::Return
1197                | Keyword::With
1198                | Keyword::Unwind
1199                | Keyword::Delete
1200                | Keyword::Then
1201                | Keyword::Else
1202                | Keyword::When
1203                | Keyword::Case,
1204            ) => return true,
1205            TokenKind::Keyword(
1206                Keyword::Match
1207                | Keyword::Create
1208                | Keyword::Merge
1209                | Keyword::Call
1210                | Keyword::Set
1211                | Keyword::Remove,
1212            ) => return false,
1213            _ => {}
1214        }
1215    }
1216    false
1217}
1218
1219fn is_inside_braces(tokens: &[&Token], start: usize) -> bool {
1220    let mut nested = 0usize;
1221    for token in tokens[..start].iter().rev() {
1222        match token.kind {
1223            TokenKind::RightBrace => nested += 1,
1224            TokenKind::LeftBrace if nested == 0 => return true,
1225            TokenKind::LeftBrace => nested = nested.saturating_sub(1),
1226            _ => {}
1227        }
1228    }
1229    false
1230}
1231
1232fn is_in_map_entry_value(tokens: &[&Token], start: usize) -> bool {
1233    let mut open_braces = Vec::new();
1234    for (index, token) in tokens.iter().enumerate().take(start) {
1235        match token.kind {
1236            TokenKind::LeftBrace => open_braces.push(index),
1237            TokenKind::RightBrace => {
1238                let _ = open_braces.pop();
1239            }
1240            _ => {}
1241        }
1242    }
1243    let Some(open) = open_braces.last().copied() else {
1244        return false;
1245    };
1246
1247    let mut depth = 0usize;
1248    let mut after_separator = false;
1249    for token in &tokens[open + 1..start] {
1250        match token.kind {
1251            TokenKind::LeftParen | TokenKind::LeftBracket | TokenKind::LeftBrace => {
1252                depth += 1;
1253            }
1254            TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => {
1255                depth = depth.saturating_sub(1);
1256            }
1257            TokenKind::Comma if depth == 0 => after_separator = false,
1258            TokenKind::Colon if depth == 0 => after_separator = true,
1259            _ => {}
1260        }
1261    }
1262    after_separator
1263}
1264
1265fn label_predicate_end(tokens: &[&Token], start: usize) -> Option<usize> {
1266    let expression_start = match tokens.get(start)?.kind {
1267        TokenKind::Colon => start + 1,
1268        TokenKind::Keyword(Keyword::Is) => {
1269            if (matches!(
1270                tokens.get(start + 1).map(|token| token.kind),
1271                Some(TokenKind::Keyword(Keyword::Null))
1272            ) && !matches!(
1273                tokens.get(start + 2).map(|token| token.kind),
1274                Some(TokenKind::Pipe | TokenKind::Ampersand)
1275            )) || matches!(
1276                (
1277                    tokens.get(start + 1).map(|token| token.kind),
1278                    tokens.get(start + 2).map(|token| token.kind),
1279                ),
1280                (
1281                    Some(TokenKind::Keyword(Keyword::Not)),
1282                    Some(TokenKind::Keyword(Keyword::Null)),
1283                )
1284            ) {
1285                return None;
1286            }
1287            start + 1
1288        }
1289        _ => return None,
1290    };
1291    let end = label_or_end(tokens, expression_start)?;
1292    Some(end.saturating_sub(1))
1293}
1294
1295fn label_or_end(tokens: &[&Token], start: usize) -> Option<usize> {
1296    let mut cursor = label_and_end(tokens, start)?;
1297    while tokens
1298        .get(cursor)
1299        .is_some_and(|token| token.kind == TokenKind::Pipe)
1300    {
1301        cursor += 1;
1302        if tokens
1303            .get(cursor)
1304            .is_some_and(|token| token.kind == TokenKind::Colon)
1305        {
1306            cursor += 1;
1307        }
1308        cursor = label_and_end(tokens, cursor)?;
1309    }
1310    Some(cursor)
1311}
1312
1313fn label_and_end(tokens: &[&Token], start: usize) -> Option<usize> {
1314    let mut cursor = label_primary_end(tokens, start)?;
1315    while matches!(
1316        tokens.get(cursor).map(|token| token.kind),
1317        Some(TokenKind::Ampersand | TokenKind::Colon)
1318    ) {
1319        cursor = label_primary_end(tokens, cursor + 1)?;
1320    }
1321    Some(cursor)
1322}
1323
1324fn label_primary_end(tokens: &[&Token], start: usize) -> Option<usize> {
1325    let mut cursor = start;
1326    if tokens
1327        .get(cursor)
1328        .is_some_and(|token| token.kind == TokenKind::Bang)
1329    {
1330        cursor += 1;
1331    }
1332    match tokens.get(cursor)?.kind {
1333        TokenKind::Percent => Some(cursor + 1),
1334        kind if is_symbolic_name_kind(kind) => Some(cursor + 1),
1335        TokenKind::LeftParen => {
1336            let end = label_or_end(tokens, cursor + 1)?;
1337            tokens
1338                .get(end)
1339                .is_some_and(|token| token.kind == TokenKind::RightParen)
1340                .then_some(end + 1)
1341        }
1342        _ => None,
1343    }
1344}
1345
1346#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1347pub(crate) enum ParserToken {
1348    Identifier,
1349    EscapedIdentifier,
1350    Parameter,
1351    Integer,
1352    HexInteger,
1353    OctalInteger,
1354    Float,
1355    String,
1356    PatternComprehension,
1357    PatternExpression,
1358    LabelPredicate,
1359    RelationshipLabelExpression,
1360    QualifiedFunctionName,
1361    All,
1362    SetAll,
1363    AllShortestPaths,
1364    And,
1365    Any,
1366    As,
1367    Asc,
1368    Ascending,
1369    By,
1370    Call,
1371    Case,
1372    Contains,
1373    Collect,
1374    Count,
1375    Create,
1376    Delete,
1377    Desc,
1378    Descending,
1379    Detach,
1380    Distinct,
1381    Else,
1382    End,
1383    Ends,
1384    Exists,
1385    False,
1386    Group,
1387    Groups,
1388    In,
1389    Inf,
1390    Infinity,
1391    Is,
1392    Limit,
1393    Match,
1394    Merge,
1395    Nan,
1396    None,
1397    Not,
1398    Null,
1399    Offset,
1400    On,
1401    Optional,
1402    Or,
1403    Order,
1404    Path,
1405    Paths,
1406    Reduce,
1407    Remove,
1408    Return,
1409    Set,
1410    Shortest,
1411    ShortestPath,
1412    Simple,
1413    Single,
1414    Skip,
1415    Starts,
1416    Then,
1417    Trail,
1418    Trim,
1419    True,
1420    Union,
1421    Unwind,
1422    Walk,
1423    When,
1424    Where,
1425    With,
1426    Xor,
1427    Yield,
1428    Acyclic,
1429    LeftParen,
1430    RightParen,
1431    LeftBracket,
1432    RightBracket,
1433    LeftBrace,
1434    RightBrace,
1435    Comma,
1436    Dot,
1437    DotDot,
1438    Colon,
1439    DoubleColon,
1440    Semicolon,
1441    Pipe,
1442    DoublePipe,
1443    Ampersand,
1444    Question,
1445    Dollar,
1446    Plus,
1447    Minus,
1448    Star,
1449    Slash,
1450    Percent,
1451    Caret,
1452    Bang,
1453    Equal,
1454    NotEqual,
1455    Less,
1456    LessEqual,
1457    Greater,
1458    GreaterEqual,
1459    PlusEqual,
1460    FatArrow,
1461    RegexMatch,
1462    LeftArrow,
1463    RightArrow,
1464    Invalid,
1465}
1466
1467impl fmt::Display for ParserToken {
1468    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1469        write!(formatter, "`{}`", self.name())
1470    }
1471}
1472
1473impl ParserToken {
1474    const fn is_keyword_literal(self) -> bool {
1475        matches!(
1476            self,
1477            Self::False | Self::Inf | Self::Infinity | Self::Nan | Self::Null | Self::True
1478        )
1479    }
1480
1481    const fn is_contextual_name_candidate(self) -> bool {
1482        !matches!(
1483            self,
1484            Self::Identifier
1485                | Self::EscapedIdentifier
1486                | Self::Parameter
1487                | Self::Integer
1488                | Self::HexInteger
1489                | Self::OctalInteger
1490                | Self::Float
1491                | Self::String
1492                | Self::PatternComprehension
1493                | Self::PatternExpression
1494                | Self::LabelPredicate
1495                | Self::RelationshipLabelExpression
1496                | Self::QualifiedFunctionName
1497                | Self::LeftParen
1498                | Self::RightParen
1499                | Self::LeftBracket
1500                | Self::RightBracket
1501                | Self::LeftBrace
1502                | Self::RightBrace
1503                | Self::Comma
1504                | Self::Dot
1505                | Self::DotDot
1506                | Self::Colon
1507                | Self::DoubleColon
1508                | Self::Semicolon
1509                | Self::Pipe
1510                | Self::DoublePipe
1511                | Self::Ampersand
1512                | Self::Question
1513                | Self::Dollar
1514                | Self::Plus
1515                | Self::Minus
1516                | Self::Star
1517                | Self::Slash
1518                | Self::Percent
1519                | Self::Caret
1520                | Self::Bang
1521                | Self::Equal
1522                | Self::NotEqual
1523                | Self::Less
1524                | Self::LessEqual
1525                | Self::Greater
1526                | Self::GreaterEqual
1527                | Self::PlusEqual
1528                | Self::FatArrow
1529                | Self::RegexMatch
1530                | Self::LeftArrow
1531                | Self::RightArrow
1532                | Self::Invalid
1533        )
1534    }
1535
1536    const fn name(self) -> &'static str {
1537        match self {
1538            Self::Identifier => "identifier",
1539            Self::EscapedIdentifier => "escaped identifier",
1540            Self::Parameter => "parameter",
1541            Self::Integer => "integer",
1542            Self::HexInteger => "hex integer",
1543            Self::OctalInteger => "octal integer",
1544            Self::Float => "float",
1545            Self::String => "string",
1546            Self::PatternComprehension => "pattern comprehension",
1547            Self::PatternExpression => "pattern expression",
1548            Self::LabelPredicate => "label predicate",
1549            Self::RelationshipLabelExpression => "relationship label expression",
1550            Self::QualifiedFunctionName => "qualified function name",
1551            Self::All => "ALL",
1552            Self::SetAll => "ALL",
1553            Self::AllShortestPaths => "ALLSHORTESTPATHS",
1554            Self::And => "AND",
1555            Self::Any => "ANY",
1556            Self::As => "AS",
1557            Self::Asc => "ASC",
1558            Self::Ascending => "ASCENDING",
1559            Self::By => "BY",
1560            Self::Call => "CALL",
1561            Self::Case => "CASE",
1562            Self::Contains => "CONTAINS",
1563            Self::Collect => "COLLECT",
1564            Self::Count => "COUNT",
1565            Self::Create => "CREATE",
1566            Self::Delete => "DELETE",
1567            Self::Desc => "DESC",
1568            Self::Descending => "DESCENDING",
1569            Self::Detach => "DETACH",
1570            Self::Distinct => "DISTINCT",
1571            Self::Else => "ELSE",
1572            Self::End => "END",
1573            Self::Ends => "ENDS",
1574            Self::Exists => "EXISTS",
1575            Self::False => "FALSE",
1576            Self::Group => "GROUP",
1577            Self::Groups => "GROUPS",
1578            Self::In => "IN",
1579            Self::Inf => "INF",
1580            Self::Infinity => "INFINITY",
1581            Self::Is => "IS",
1582            Self::Limit => "LIMIT",
1583            Self::Match => "MATCH",
1584            Self::Merge => "MERGE",
1585            Self::Nan => "NAN",
1586            Self::None => "NONE",
1587            Self::Not => "NOT",
1588            Self::Null => "NULL",
1589            Self::Offset => "OFFSET",
1590            Self::On => "ON",
1591            Self::Optional => "OPTIONAL",
1592            Self::Or => "OR",
1593            Self::Order => "ORDER",
1594            Self::Path => "PATH",
1595            Self::Paths => "PATHS",
1596            Self::Reduce => "REDUCE",
1597            Self::Remove => "REMOVE",
1598            Self::Return => "RETURN",
1599            Self::Set => "SET",
1600            Self::Shortest => "SHORTEST",
1601            Self::ShortestPath => "SHORTESTPATH",
1602            Self::Simple => "SIMPLE",
1603            Self::Single => "SINGLE",
1604            Self::Skip => "SKIP",
1605            Self::Starts => "STARTS",
1606            Self::Then => "THEN",
1607            Self::Trail => "TRAIL",
1608            Self::Trim => "TRIM",
1609            Self::True => "TRUE",
1610            Self::Union => "UNION",
1611            Self::Unwind => "UNWIND",
1612            Self::Walk => "WALK",
1613            Self::When => "WHEN",
1614            Self::Where => "WHERE",
1615            Self::With => "WITH",
1616            Self::Xor => "XOR",
1617            Self::Yield => "YIELD",
1618            Self::Acyclic => "ACYCLIC",
1619            Self::LeftParen => "(",
1620            Self::RightParen => ")",
1621            Self::LeftBracket => "[",
1622            Self::RightBracket => "]",
1623            Self::LeftBrace => "{",
1624            Self::RightBrace => "}",
1625            Self::Comma => ",",
1626            Self::Dot => ".",
1627            Self::DotDot => "..",
1628            Self::Colon => ":",
1629            Self::DoubleColon => "::",
1630            Self::Semicolon => ";",
1631            Self::Pipe => "|",
1632            Self::DoublePipe => "||",
1633            Self::Ampersand => "&",
1634            Self::Question => "?",
1635            Self::Dollar => "$",
1636            Self::Plus => "+",
1637            Self::Minus => "-",
1638            Self::Star => "*",
1639            Self::Slash => "/",
1640            Self::Percent => "%",
1641            Self::Caret => "^",
1642            Self::Bang => "!",
1643            Self::Equal => "=",
1644            Self::NotEqual => "<>",
1645            Self::Less => "<",
1646            Self::LessEqual => "<=",
1647            Self::Greater => ">",
1648            Self::GreaterEqual => ">=",
1649            Self::PlusEqual => "+=",
1650            Self::FatArrow => "=>",
1651            Self::RegexMatch => "=~",
1652            Self::LeftArrow => "<-",
1653            Self::RightArrow => "->",
1654            Self::Invalid => "invalid token",
1655        }
1656    }
1657}
1658
1659impl From<TokenKind> for ParserToken {
1660    fn from(kind: TokenKind) -> Self {
1661        match kind {
1662            TokenKind::Keyword(keyword) => keyword_token(keyword),
1663            TokenKind::Identifier => Self::Identifier,
1664            TokenKind::EscapedIdentifier => Self::EscapedIdentifier,
1665            TokenKind::Parameter => Self::Parameter,
1666            TokenKind::Integer => Self::Integer,
1667            TokenKind::HexInteger => Self::HexInteger,
1668            TokenKind::OctalInteger => Self::OctalInteger,
1669            TokenKind::Float => Self::Float,
1670            TokenKind::String => Self::String,
1671            TokenKind::LeftParen => Self::LeftParen,
1672            TokenKind::RightParen => Self::RightParen,
1673            TokenKind::LeftBracket => Self::LeftBracket,
1674            TokenKind::RightBracket => Self::RightBracket,
1675            TokenKind::LeftBrace => Self::LeftBrace,
1676            TokenKind::RightBrace => Self::RightBrace,
1677            TokenKind::Comma => Self::Comma,
1678            TokenKind::Dot => Self::Dot,
1679            TokenKind::DotDot => Self::DotDot,
1680            TokenKind::Colon => Self::Colon,
1681            TokenKind::DoubleColon => Self::DoubleColon,
1682            TokenKind::Semicolon => Self::Semicolon,
1683            TokenKind::Pipe => Self::Pipe,
1684            TokenKind::DoublePipe => Self::DoublePipe,
1685            TokenKind::Ampersand => Self::Ampersand,
1686            TokenKind::Question => Self::Question,
1687            TokenKind::Dollar => Self::Dollar,
1688            TokenKind::Plus => Self::Plus,
1689            TokenKind::Minus => Self::Minus,
1690            TokenKind::Star => Self::Star,
1691            TokenKind::Slash => Self::Slash,
1692            TokenKind::Percent => Self::Percent,
1693            TokenKind::Caret => Self::Caret,
1694            TokenKind::Bang => Self::Bang,
1695            TokenKind::Equal => Self::Equal,
1696            TokenKind::NotEqual => Self::NotEqual,
1697            TokenKind::Less => Self::Less,
1698            TokenKind::LessEqual => Self::LessEqual,
1699            TokenKind::Greater => Self::Greater,
1700            TokenKind::GreaterEqual => Self::GreaterEqual,
1701            TokenKind::PlusEqual => Self::PlusEqual,
1702            TokenKind::FatArrow => Self::FatArrow,
1703            TokenKind::RegexMatch => Self::RegexMatch,
1704            TokenKind::LeftArrow => Self::LeftArrow,
1705            TokenKind::RightArrow => Self::RightArrow,
1706            TokenKind::Invalid => Self::Invalid,
1707            TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment => {
1708                unreachable!("trivia is filtered before parser token conversion")
1709            }
1710        }
1711    }
1712}
1713
1714fn keyword_token(keyword: Keyword) -> ParserToken {
1715    match keyword {
1716        Keyword::All => ParserToken::All,
1717        Keyword::AllShortestPaths => ParserToken::AllShortestPaths,
1718        Keyword::And => ParserToken::And,
1719        Keyword::Any => ParserToken::Any,
1720        Keyword::As => ParserToken::As,
1721        Keyword::Asc => ParserToken::Asc,
1722        Keyword::Ascending => ParserToken::Ascending,
1723        Keyword::By => ParserToken::By,
1724        Keyword::Call => ParserToken::Call,
1725        Keyword::Case => ParserToken::Case,
1726        Keyword::Contains => ParserToken::Contains,
1727        Keyword::Collect => ParserToken::Collect,
1728        Keyword::Count => ParserToken::Count,
1729        Keyword::Create => ParserToken::Create,
1730        Keyword::Delete => ParserToken::Delete,
1731        Keyword::Desc => ParserToken::Desc,
1732        Keyword::Descending => ParserToken::Descending,
1733        Keyword::Detach => ParserToken::Detach,
1734        Keyword::Distinct => ParserToken::Distinct,
1735        Keyword::Else => ParserToken::Else,
1736        Keyword::End => ParserToken::End,
1737        Keyword::Ends => ParserToken::Ends,
1738        Keyword::Exists => ParserToken::Exists,
1739        Keyword::False => ParserToken::False,
1740        Keyword::Group => ParserToken::Group,
1741        Keyword::Groups => ParserToken::Groups,
1742        Keyword::In => ParserToken::In,
1743        Keyword::Inf => ParserToken::Inf,
1744        Keyword::Infinity => ParserToken::Infinity,
1745        Keyword::Is => ParserToken::Is,
1746        Keyword::Limit => ParserToken::Limit,
1747        Keyword::Match => ParserToken::Match,
1748        Keyword::Merge => ParserToken::Merge,
1749        Keyword::Nan => ParserToken::Nan,
1750        Keyword::None => ParserToken::None,
1751        Keyword::Not => ParserToken::Not,
1752        Keyword::Null => ParserToken::Null,
1753        Keyword::Offset => ParserToken::Offset,
1754        Keyword::On => ParserToken::On,
1755        Keyword::Optional => ParserToken::Optional,
1756        Keyword::Or => ParserToken::Or,
1757        Keyword::Order => ParserToken::Order,
1758        Keyword::Path => ParserToken::Path,
1759        Keyword::Paths => ParserToken::Paths,
1760        Keyword::Reduce => ParserToken::Reduce,
1761        Keyword::Remove => ParserToken::Remove,
1762        Keyword::Return => ParserToken::Return,
1763        Keyword::Set => ParserToken::Set,
1764        Keyword::Shortest => ParserToken::Shortest,
1765        Keyword::ShortestPath => ParserToken::ShortestPath,
1766        Keyword::Simple => ParserToken::Simple,
1767        Keyword::Single => ParserToken::Single,
1768        Keyword::Skip => ParserToken::Skip,
1769        Keyword::Starts => ParserToken::Starts,
1770        Keyword::Then => ParserToken::Then,
1771        Keyword::Trail => ParserToken::Trail,
1772        Keyword::Trim => ParserToken::Trim,
1773        Keyword::True => ParserToken::True,
1774        Keyword::Union => ParserToken::Union,
1775        Keyword::Unwind => ParserToken::Unwind,
1776        Keyword::Walk => ParserToken::Walk,
1777        Keyword::When => ParserToken::When,
1778        Keyword::Where => ParserToken::Where,
1779        Keyword::With => ParserToken::With,
1780        Keyword::Xor => ParserToken::Xor,
1781        Keyword::Yield => ParserToken::Yield,
1782        Keyword::Acyclic => ParserToken::Acyclic,
1783    }
1784}
1785
1786pub(crate) fn name(source: &str, start: usize, end: usize, escaped: bool) -> Name {
1787    let span = Span::new(start, end);
1788    let raw = &source[start..end];
1789    let text = if escaped {
1790        decode_delimited(raw, '`')
1791    } else {
1792        raw.to_owned()
1793    };
1794    Node::new(Identifier::new(text, escaped), span)
1795}
1796
1797pub(crate) fn qualified_function_name(source: &str, start: usize, end: usize) -> QualifiedName {
1798    let fragment = source.get(start..end).unwrap_or_default();
1799    let lexed = lexer::lex(fragment);
1800    let parts = lexed
1801        .tokens
1802        .iter()
1803        .filter(|token| !token.is_trivia() && token.kind != TokenKind::Dot)
1804        .map(|token| {
1805            let part_start = start + token.span.start;
1806            let part_end = start + token.span.end;
1807            name(
1808                source,
1809                part_start,
1810                part_end,
1811                token.kind == TokenKind::EscapedIdentifier,
1812            )
1813        })
1814        .collect();
1815    QualifiedName {
1816        parts,
1817        span: Span::new(start, end),
1818    }
1819}
1820
1821pub(crate) fn parameter(source: &str, start: usize, end: usize) -> Parameter {
1822    let raw = &source[start + 1..end];
1823    let name = if raw.starts_with('`') && raw.ends_with('`') {
1824        ParameterName::Named(decode_delimited(raw, '`'))
1825    } else if raw.bytes().all(|byte| byte.is_ascii_digit()) {
1826        ParameterName::Positional(raw.to_owned())
1827    } else {
1828        ParameterName::Named(raw.to_owned())
1829    };
1830    Parameter { name }
1831}
1832
1833pub(crate) fn string_literal(source: &str, start: usize, end: usize) -> StringLiteral {
1834    let raw = &source[start..end];
1835    let delimiter = raw.chars().next().unwrap_or('\'');
1836    let quote = if delimiter == '\'' {
1837        QuoteStyle::Single
1838    } else {
1839        QuoteStyle::Double
1840    };
1841    StringLiteral {
1842        value: decode_delimited(raw, delimiter),
1843        quote,
1844    }
1845}
1846
1847fn decode_delimited(raw: &str, delimiter: char) -> String {
1848    let delimiter_len = delimiter.len_utf8();
1849    let inner = raw
1850        .get(delimiter_len..raw.len().saturating_sub(delimiter_len))
1851        .unwrap_or_default();
1852    let mut decoded = String::with_capacity(inner.len());
1853    let mut characters = inner.chars().peekable();
1854    while let Some(character) = characters.next() {
1855        if character == delimiter && characters.peek() == Some(&delimiter) {
1856            let _ = characters.next();
1857            decoded.push(delimiter);
1858            continue;
1859        }
1860        if character != '\\' {
1861            decoded.push(character);
1862            continue;
1863        }
1864        let Some(escaped) = characters.next() else {
1865            decoded.push('\\');
1866            break;
1867        };
1868        match escaped {
1869            't' => decoded.push('\t'),
1870            'b' => decoded.push('\u{0008}'),
1871            'n' => decoded.push('\n'),
1872            'r' => decoded.push('\r'),
1873            'f' => decoded.push('\u{000c}'),
1874            '\\' => decoded.push('\\'),
1875            '\'' => decoded.push('\''),
1876            '"' => decoded.push('"'),
1877            '`' => decoded.push('`'),
1878            'u' | 'U' => {
1879                let digits = if escaped == 'u' { 4 } else { 6 };
1880                let mut scalar = 0u32;
1881                let mut complete = true;
1882                for _ in 0..digits {
1883                    match characters.next().and_then(|digit| digit.to_digit(16)) {
1884                        Some(digit) => scalar = scalar * 16 + digit,
1885                        None => {
1886                            complete = false;
1887                            break;
1888                        }
1889                    }
1890                }
1891                if complete && let Some(character) = char::from_u32(scalar) {
1892                    decoded.push(character);
1893                }
1894            }
1895            other => {
1896                // Invalid escapes are rejected by the lexer, so preserving an
1897                // unexpected spelling here is only a defensive fallback.
1898                decoded.push('\\');
1899                decoded.push(other);
1900            }
1901        }
1902    }
1903    decoded
1904}
1905
1906pub(crate) fn integer_literal(
1907    source: &str,
1908    start: usize,
1909    end: usize,
1910    radix: IntegerRadix,
1911) -> Literal {
1912    let span = Span::new(start, end);
1913    Node::new(
1914        LiteralKind::Integer(IntegerLiteral {
1915            text: source[start..end].to_owned(),
1916            radix,
1917        }),
1918        span,
1919    )
1920}
1921
1922pub(crate) fn list_singleton_or_comprehension(start: usize, end: usize, expression: Expr) -> Expr {
1923    let span = Span::new(start, end);
1924    match split_comprehension_source(expression) {
1925        ComprehensionSource::Comprehension { variable, list } => Node::new(
1926            ExprKind::ListComprehension(ListComprehension {
1927                variable,
1928                list: Box::new(list),
1929                predicate: None,
1930                projection: None,
1931            }),
1932            span,
1933        ),
1934        ComprehensionSource::Expression(expression) => {
1935            Node::new(ExprKind::List(vec![expression]), span)
1936        }
1937    }
1938}
1939
1940pub(crate) fn list_comprehension_expr(
1941    start: usize,
1942    end: usize,
1943    source_expression: Expr,
1944    predicate: Option<Expr>,
1945    projection: Option<Expr>,
1946) -> Result<Expr, LalrpopError> {
1947    let ComprehensionSource::Comprehension { variable, list } =
1948        split_comprehension_source(source_expression)
1949    else {
1950        return Err(user_parse_error());
1951    };
1952    Ok(Node::new(
1953        ExprKind::ListComprehension(ListComprehension {
1954            variable,
1955            list: Box::new(list),
1956            predicate: predicate.map(Box::new),
1957            projection: projection.map(Box::new),
1958        }),
1959        Span::new(start, end),
1960    ))
1961}
1962
1963enum ComprehensionSource {
1964    Comprehension { variable: Name, list: Expr },
1965    Expression(Expr),
1966}
1967
1968fn split_comprehension_source(expression: Expr) -> ComprehensionSource {
1969    let is_source = matches!(
1970        &expression.kind,
1971        ExprKind::Binary { left, operator, .. }
1972            if operator.kind == BinaryOperator::In
1973                && matches!(&left.kind, ExprKind::Variable(_))
1974    );
1975    if !is_source {
1976        return ComprehensionSource::Expression(expression);
1977    }
1978
1979    let ExprKind::Binary {
1980        left,
1981        operator: _,
1982        right,
1983    } = expression.kind
1984    else {
1985        unreachable!("the source shape was checked above")
1986    };
1987    let ExprKind::Variable(variable) = left.kind else {
1988        unreachable!("the source variable shape was checked above")
1989    };
1990    ComprehensionSource::Comprehension {
1991        variable,
1992        list: *right,
1993    }
1994}
1995
1996pub(crate) fn pattern_comprehension(
1997    source: &str,
1998    start: usize,
1999    end: usize,
2000) -> Result<Expr, LalrpopError> {
2001    let Some(fragment) = source.get(start..end) else {
2002        return Err(user_parse_error());
2003    };
2004    let lexed = lexer::lex(fragment);
2005    if !lexed.diagnostics.is_empty() {
2006        return Err(user_parse_error());
2007    }
2008    // A `|` at the comprehension's top level may be either the projection
2009    // separator or part of a label disjunction (for example `b:X|Y`). Try
2010    // each candidate as the separator, left to right; reserving it keeps the
2011    // label scanner from consuming it, and the first split that yields a
2012    // valid comprehension wins.
2013    for pipe_index in top_level_pipe_candidates(&lexed.tokens) {
2014        let tokens = nested_parser_tokens(&lexed.tokens, start, Some(pipe_index));
2015        if let Ok(expression) = parse_comprehension_tokens(source, start, end, &tokens) {
2016            return Ok(expression);
2017        }
2018    }
2019    Err(user_parse_error())
2020}
2021
2022fn top_level_pipe_candidates(tokens: &[Token]) -> Vec<usize> {
2023    let mut candidates = Vec::new();
2024    let mut brackets = 0usize;
2025    let mut parentheses = 0usize;
2026    let mut braces = 0usize;
2027    for (index, token) in tokens.iter().enumerate() {
2028        if token.is_trivia() {
2029            continue;
2030        }
2031        match token.kind {
2032            TokenKind::LeftBracket => brackets += 1,
2033            TokenKind::RightBracket => brackets = brackets.saturating_sub(1),
2034            TokenKind::LeftParen => parentheses += 1,
2035            TokenKind::RightParen => parentheses = parentheses.saturating_sub(1),
2036            TokenKind::LeftBrace => braces += 1,
2037            TokenKind::RightBrace => braces = braces.saturating_sub(1),
2038            TokenKind::Pipe if brackets == 1 && parentheses == 0 && braces == 0 => {
2039                candidates.push(index);
2040            }
2041            _ => {}
2042        }
2043    }
2044    candidates
2045}
2046
2047fn parse_comprehension_tokens(
2048    source: &str,
2049    start: usize,
2050    end: usize,
2051    tokens: &[SpannedParserToken],
2052) -> Result<Expr, LalrpopError> {
2053    let outer_start = tokens
2054        .iter()
2055        .position(|token| token.0 == start && token.1 == ParserToken::LeftBracket)
2056        .ok_or_else(user_parse_error)?;
2057    let outer_end = tokens
2058        .iter()
2059        .rposition(|token| token.2 == end && token.1 == ParserToken::RightBracket)
2060        .ok_or_else(user_parse_error)?;
2061
2062    let pipe = find_top_level_token(tokens, outer_start + 1, outer_end, ParserToken::Pipe)
2063        .ok_or_else(user_parse_error)?;
2064
2065    let mut pattern_start = outer_start + 1;
2066    let binding = if pattern_start + 1 < pipe
2067        && tokens[pattern_start + 1].1 == ParserToken::Equal
2068        && (is_name_token(tokens[pattern_start].1)
2069            || tokens[pattern_start].1.is_contextual_name_candidate())
2070    {
2071        let token = tokens[pattern_start];
2072        pattern_start += 2;
2073        Some(name(
2074            source,
2075            token.0,
2076            token.2,
2077            token.1 == ParserToken::EscapedIdentifier,
2078        ))
2079    } else {
2080        None
2081    };
2082
2083    let where_index = find_top_level_token(tokens, pattern_start, pipe, ParserToken::Where);
2084    let pattern_end = where_index.unwrap_or(pipe);
2085    if pattern_start >= pattern_end || pipe + 1 >= outer_end {
2086        return Err(user_parse_error());
2087    }
2088    let pattern = parse_pattern_fragment(source, &tokens[pattern_start..pattern_end])?;
2089    let predicate = match where_index {
2090        Some(index) if index + 1 < pipe => Some(Box::new(parse_expr_fragment(
2091            source,
2092            &tokens[index + 1..pipe],
2093        )?)),
2094        Some(_) => return Err(user_parse_error()),
2095        None => None,
2096    };
2097    let projection = parse_expr_fragment(source, &tokens[pipe + 1..outer_end])?;
2098
2099    Ok(Node::new(
2100        ExprKind::PatternComprehension(PatternComprehension {
2101            binding,
2102            pattern,
2103            predicate,
2104            projection: Box::new(projection),
2105        }),
2106        Span::new(start, end),
2107    ))
2108}
2109
2110pub(crate) fn pattern_expression(
2111    source: &str,
2112    start: usize,
2113    end: usize,
2114) -> Result<Expr, LalrpopError> {
2115    let Some(fragment) = source.get(start..end) else {
2116        return Err(user_parse_error());
2117    };
2118    let lexed = lexer::lex(fragment);
2119    if !lexed.diagnostics.is_empty() {
2120        return Err(user_parse_error());
2121    }
2122    let tokens = nested_parser_tokens(&lexed.tokens, start, None);
2123    let pattern = parse_pattern_fragment(source, &tokens)?;
2124    Ok(Node::new(ExprKind::Pattern(pattern), Span::new(start, end)))
2125}
2126
2127fn parse_pattern_fragment(
2128    source: &str,
2129    tokens: &[SpannedParserToken],
2130) -> Result<Pattern, LalrpopError> {
2131    parse_tokens_with_contextual_names(tokens, |contextual| {
2132        generated::PatternFragmentParser::new().parse(source, contextual.iter().copied().map(Ok))
2133    })
2134}
2135
2136fn parse_expr_fragment(source: &str, tokens: &[SpannedParserToken]) -> Result<Expr, LalrpopError> {
2137    parse_tokens_with_contextual_names(tokens, |contextual| {
2138        generated::ExprFragmentParser::new().parse(source, contextual.iter().copied().map(Ok))
2139    })
2140}
2141
2142pub(crate) fn label_comparison_suffix(
2143    source: &str,
2144    start: usize,
2145    end: usize,
2146) -> Result<ComparisonSuffix, LalrpopError> {
2147    Ok(ComparisonSuffix::Is {
2148        negated: false,
2149        predicate: IsPredicate::Label(parse_label_expression(source, start, end, true)?),
2150        end,
2151    })
2152}
2153
2154pub(crate) fn relationship_label_expression(
2155    source: &str,
2156    start: usize,
2157    end: usize,
2158) -> Result<LabelExpression, LalrpopError> {
2159    parse_label_expression(source, start, end, true)
2160}
2161
2162fn parse_label_expression(
2163    source: &str,
2164    start: usize,
2165    end: usize,
2166    normalize_legacy_relationship: bool,
2167) -> Result<LabelExpression, LalrpopError> {
2168    let Some(fragment) = source.get(start..end) else {
2169        return Err(user_parse_error());
2170    };
2171    let lexed = lexer::lex(fragment);
2172    if !lexed.diagnostics.is_empty() {
2173        return Err(user_parse_error());
2174    }
2175    let mut tokens = lexed
2176        .tokens
2177        .iter()
2178        .filter(|token| !token.is_trivia())
2179        .map(|token| {
2180            (
2181                start + token.span.start,
2182                ParserToken::from(token.kind),
2183                start + token.span.end,
2184            )
2185        })
2186        .collect::<Vec<_>>();
2187    if normalize_legacy_relationship && !legacy_label_tokens_are_consistent(&tokens) {
2188        return Err(user_parse_error());
2189    }
2190    let mut normalized = Vec::with_capacity(tokens.len());
2191    for (index, mut token) in tokens.drain(..).enumerate() {
2192        if normalize_legacy_relationship
2193            && token.1 == ParserToken::Colon
2194            && normalized
2195                .last()
2196                .is_some_and(|previous: &SpannedParserToken| previous.1 == ParserToken::Pipe)
2197        {
2198            continue;
2199        }
2200        if index > 0 && token.1.is_contextual_name_candidate() {
2201            token.1 = ParserToken::Identifier;
2202        }
2203        normalized.push(token);
2204    }
2205    generated::LabelPredicateFragmentParser::new().parse(source, normalized.into_iter().map(Ok))
2206}
2207
2208fn legacy_label_tokens_are_consistent(tokens: &[SpannedParserToken]) -> bool {
2209    let has_internal_colon = tokens
2210        .iter()
2211        .enumerate()
2212        .skip(1)
2213        .any(|(_, token)| token.1 == ParserToken::Colon);
2214    if !has_internal_colon {
2215        return true;
2216    }
2217    if tokens
2218        .first()
2219        .is_none_or(|token| token.1 != ParserToken::Colon)
2220    {
2221        return false;
2222    }
2223
2224    let mut cursor = 1usize;
2225    if tokens
2226        .get(cursor)
2227        .is_none_or(|token| !is_name_token(token.1) && !token.1.is_contextual_name_candidate())
2228    {
2229        return false;
2230    }
2231    cursor += 1;
2232    let pipe_separated = tokens
2233        .get(cursor)
2234        .is_some_and(|token| token.1 == ParserToken::Pipe);
2235    while cursor < tokens.len() {
2236        if pipe_separated {
2237            if tokens
2238                .get(cursor)
2239                .is_none_or(|token| token.1 != ParserToken::Pipe)
2240                || tokens
2241                    .get(cursor + 1)
2242                    .is_none_or(|token| token.1 != ParserToken::Colon)
2243            {
2244                return false;
2245            }
2246            cursor += 2;
2247        } else {
2248            if tokens
2249                .get(cursor)
2250                .is_none_or(|token| token.1 != ParserToken::Colon)
2251            {
2252                return false;
2253            }
2254            cursor += 1;
2255        }
2256        if tokens
2257            .get(cursor)
2258            .is_none_or(|token| !is_name_token(token.1) && !token.1.is_contextual_name_candidate())
2259        {
2260            return false;
2261        }
2262        cursor += 1;
2263    }
2264    true
2265}
2266
2267fn user_parse_error() -> LalrpopError {
2268    ParseError::User { error: () }
2269}
2270
2271/// `reserved_pipe` is a raw index into `tokens` naming a `|` that must come
2272/// through as a plain `Pipe` token (a pattern comprehension's projection
2273/// separator): forward scans starting before it see a truncated stream so
2274/// they cannot consume it, while context checks keep the full view.
2275fn nested_parser_tokens(
2276    tokens: &[Token],
2277    base: usize,
2278    reserved_pipe: Option<usize>,
2279) -> Vec<SpannedParserToken> {
2280    let significant = tokens
2281        .iter()
2282        .filter(|token| !token.is_trivia())
2283        .collect::<Vec<_>>();
2284    let boundary = reserved_pipe.map(|raw| {
2285        tokens[..raw]
2286            .iter()
2287            .filter(|token| !token.is_trivia())
2288            .count()
2289    });
2290    let mut result = Vec::with_capacity(significant.len());
2291    let mut index = 0;
2292    while index < significant.len() {
2293        let scan: &[&Token] = match boundary {
2294            Some(boundary) if index < boundary => &significant[..boundary],
2295            _ => &significant,
2296        };
2297        if let Some(end) = escaped_parameter_end(scan, index) {
2298            result.push((
2299                base + significant[index].span.start,
2300                ParserToken::Parameter,
2301                base + significant[end].span.end,
2302            ));
2303            index = end + 1;
2304            continue;
2305        }
2306        if index != 0 {
2307            if let Some(end) = pattern_comprehension_end(scan, index) {
2308                result.push((
2309                    base + significant[index].span.start,
2310                    ParserToken::PatternComprehension,
2311                    base + significant[end].span.end,
2312                ));
2313                index = end + 1;
2314                continue;
2315            }
2316            if significant[index].kind == TokenKind::LeftParen
2317                && is_pattern_expression_context(&significant, index)
2318                && let Some(end) = pattern_expression_end(scan, index)
2319            {
2320                result.push((
2321                    base + significant[index].span.start,
2322                    ParserToken::PatternExpression,
2323                    base + significant[end].span.end,
2324                ));
2325                index = end + 1;
2326                continue;
2327            }
2328            if is_relationship_label_start(&significant, index)
2329                && let Some(end) = relationship_label_end(scan, index)
2330            {
2331                result.push((
2332                    base + significant[index].span.start,
2333                    ParserToken::RelationshipLabelExpression,
2334                    base + significant[end].span.end,
2335                ));
2336                index = end + 1;
2337                continue;
2338            }
2339            if is_label_predicate_context(&significant, index)
2340                && let Some(end) = label_predicate_end(scan, index)
2341            {
2342                result.push((
2343                    base + significant[index].span.start,
2344                    ParserToken::LabelPredicate,
2345                    base + significant[end].span.end,
2346                ));
2347                index = end + 1;
2348                continue;
2349            }
2350        }
2351
2352        if !is_procedure_name_start(&significant, index)
2353            && let Some(end) = qualified_function_name_end(scan, index)
2354        {
2355            result.push((
2356                base + significant[index].span.start,
2357                ParserToken::QualifiedFunctionName,
2358                base + significant[end].span.end,
2359            ));
2360            index = end + 1;
2361            continue;
2362        }
2363
2364        let token = significant[index];
2365        let mut parser_token = ParserToken::from(token.kind);
2366        if parser_token == ParserToken::All && is_explicit_set_all(&significant, index) {
2367            parser_token = ParserToken::SetAll;
2368        }
2369        result.push((base + token.span.start, parser_token, base + token.span.end));
2370        index += 1;
2371    }
2372    result
2373}
2374
2375fn find_top_level_token(
2376    tokens: &[SpannedParserToken],
2377    start: usize,
2378    end: usize,
2379    needle: ParserToken,
2380) -> Option<usize> {
2381    let mut parentheses = 0usize;
2382    let mut brackets = 0usize;
2383    let mut braces = 0usize;
2384    for (index, (_, token, _)) in tokens.iter().copied().enumerate().take(end).skip(start) {
2385        if parentheses == 0 && brackets == 0 && braces == 0 && token == needle {
2386            return Some(index);
2387        }
2388        match token {
2389            ParserToken::LeftParen => parentheses += 1,
2390            ParserToken::RightParen => parentheses = parentheses.saturating_sub(1),
2391            ParserToken::LeftBracket => brackets += 1,
2392            ParserToken::RightBracket => brackets = brackets.saturating_sub(1),
2393            ParserToken::LeftBrace => braces += 1,
2394            ParserToken::RightBrace => braces = braces.saturating_sub(1),
2395            _ => {}
2396        }
2397    }
2398    None
2399}
2400
2401fn is_name_token(token: ParserToken) -> bool {
2402    matches!(
2403        token,
2404        ParserToken::Identifier
2405            | ParserToken::EscapedIdentifier
2406            | ParserToken::Match
2407            | ParserToken::Path
2408            | ParserToken::Return
2409    )
2410}
2411
2412pub(crate) fn query_from_pattern(pattern: Pattern, where_clause: Option<Expr>) -> Query {
2413    let span = pattern.span;
2414    let clause = Node::new(
2415        ClauseKind::Match(MatchClause {
2416            optional: false,
2417            mode: None,
2418            pattern,
2419            hints: Vec::new(),
2420            where_clause,
2421        }),
2422        span,
2423    );
2424    let head = Node::new(
2425        SingleQueryKind {
2426            clauses: vec![clause],
2427        },
2428        span,
2429    );
2430    Node::new(
2431        QueryKind::Regular(RegularQuery {
2432            head,
2433            unions: Vec::new(),
2434        }),
2435        span,
2436    )
2437}
2438
2439pub(crate) type RelationshipDetail = (
2440    Option<Name>,
2441    Option<crate::ast::LabelExpression>,
2442    Option<Quantifier>,
2443    Option<crate::ast::Expr>,
2444    Option<crate::ast::Expr>,
2445);
2446
2447pub(crate) fn relationship_factor(
2448    start: usize,
2449    end: usize,
2450    direction: RelationshipDirection,
2451    detail: Option<RelationshipDetail>,
2452    outer_quantifier: Option<Quantifier>,
2453) -> PathFactor {
2454    let (variable, labels, inner_quantifier, properties, where_clause) =
2455        detail.unwrap_or((None, None, None, None, None));
2456
2457    Node::new(
2458        PathFactorKind::Relationship(RelationshipPattern {
2459            direction: Node::new(direction, Span::new(start, end)),
2460            variable,
2461            labels,
2462            legacy_quantifier: inner_quantifier,
2463            graph_quantifier: outer_quantifier,
2464            properties: properties.map(Box::new),
2465            where_clause: where_clause.map(Box::new),
2466        }),
2467        Span::new(start, end),
2468    )
2469}
2470
2471pub(crate) fn fold_binary(mut expression: Expr, tail: Vec<(Node<BinaryOperator>, Expr)>) -> Expr {
2472    for (operator, right) in tail {
2473        let span = expression.span.cover(right.span);
2474        expression = Node::new(
2475            ExprKind::Binary {
2476                left: Box::new(expression),
2477                operator,
2478                right: Box::new(right),
2479            },
2480            span,
2481        );
2482    }
2483    expression
2484}
2485
2486pub(crate) enum ComparisonSuffix {
2487    Binary(Node<BinaryOperator>, Expr),
2488    Is {
2489        negated: bool,
2490        predicate: IsPredicate,
2491        end: usize,
2492    },
2493}
2494
2495pub(crate) fn fold_comparison(mut expression: Expr, tail: Vec<ComparisonSuffix>) -> Expr {
2496    for suffix in tail {
2497        match suffix {
2498            ComparisonSuffix::Binary(operator, right) => {
2499                let span = expression.span.cover(right.span);
2500                expression = Node::new(
2501                    ExprKind::Binary {
2502                        left: Box::new(expression),
2503                        operator,
2504                        right: Box::new(right),
2505                    },
2506                    span,
2507                );
2508            }
2509            ComparisonSuffix::Is {
2510                negated,
2511                predicate,
2512                end,
2513            } => {
2514                let span = Span::new(expression.span.start, end);
2515                expression = Node::new(
2516                    ExprKind::Is {
2517                        expression: Box::new(expression),
2518                        negated,
2519                        predicate,
2520                    },
2521                    span,
2522                );
2523            }
2524        }
2525    }
2526    expression
2527}
2528
2529pub(crate) enum PostfixSuffix {
2530    Property(Name),
2531    MapProjection {
2532        items: Vec<MapProjectionItem>,
2533        end: usize,
2534    },
2535    Index {
2536        index: Expr,
2537        end: usize,
2538    },
2539    Slice {
2540        lower: Option<Expr>,
2541        upper: Option<Expr>,
2542        end: usize,
2543    },
2544}
2545
2546pub(crate) fn fold_postfix(mut expression: Expr, suffixes: Vec<PostfixSuffix>) -> Expr {
2547    for suffix in suffixes {
2548        expression = match suffix {
2549            PostfixSuffix::Property(key) => {
2550                let span = expression.span.cover(key.span);
2551                Node::new(
2552                    ExprKind::Property {
2553                        expression: Box::new(expression),
2554                        key,
2555                    },
2556                    span,
2557                )
2558            }
2559            PostfixSuffix::MapProjection { items, end } => {
2560                let span = Span::new(expression.span.start, end);
2561                Node::new(
2562                    ExprKind::MapProjection(MapProjection {
2563                        base: Box::new(expression),
2564                        items,
2565                    }),
2566                    span,
2567                )
2568            }
2569            PostfixSuffix::Index { index, end } => {
2570                let span = Span::new(expression.span.start, end);
2571                Node::new(
2572                    ExprKind::Index {
2573                        expression: Box::new(expression),
2574                        index: Box::new(index),
2575                    },
2576                    span,
2577                )
2578            }
2579            PostfixSuffix::Slice { lower, upper, end } => {
2580                let span = Span::new(expression.span.start, end);
2581                Node::new(
2582                    ExprKind::Slice {
2583                        expression: Box::new(expression),
2584                        lower: lower.map(Box::new),
2585                        upper: upper.map(Box::new),
2586                    },
2587                    span,
2588                )
2589            }
2590        };
2591    }
2592    expression
2593}