Skip to main content

graphforge_core/
parse_error.rs

1//! Structured parse error type for the GraphForge Cypher parser.
2
3use crate::{Span, UnsupportedCypherFeature};
4use serde::{Deserialize, Serialize};
5
6/// The kind of parse error.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[non_exhaustive]
9pub enum ParseErrorKind {
10    /// Valid grammar for a recognized construct whose execution is unavailable.
11    UnsupportedFeature(UnsupportedCypherFeature),
12    /// The lexer encountered a byte sequence it could not tokenize.
13    UnexpectedChar,
14    /// The parser encountered a token that does not fit the grammar at this
15    /// position.
16    UnexpectedToken {
17        /// What was found.
18        found: String,
19        /// What the parser expected (human-readable).
20        expected: Vec<String>,
21    },
22    /// A string literal was opened but never closed.
23    UnterminatedString,
24    /// A block comment was opened with `/*` but never closed with `*/`.
25    UnterminatedBlockComment,
26    /// An integer or float literal could not be parsed.
27    InvalidNumericLiteral,
28    /// A `$` parameter prefix was not followed by a valid name or index.
29    InvalidParameter,
30    /// A query ended before it was syntactically complete.
31    UnexpectedEof {
32        /// What the parser expected at end of input.
33        expected: Vec<String>,
34    },
35}
36
37/// A structured parse error produced by `graphforge-cypher`.
38///
39/// `ParseError` is the error type in the `Result` returned by
40/// `graphforge_cypher::parse`. The differential test harness uses the `span` and
41/// `kind` fields to assert that both parsers flag the same source location.
42/// A structured parse error produced by `graphforge-cypher`.
43///
44/// `ParseError` is the error type in the `Result` returned by
45/// `graphforge_cypher::parse`. The differential test harness uses the `span` and
46/// `kind` fields to assert that both parsers flag the same source location.
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct ParseError {
49    /// Structured classification of the failure.
50    pub kind: ParseErrorKind,
51    /// Source location of the offending token or character.
52    pub span: Span,
53    /// Human-readable explanation (may include context beyond `kind`).
54    pub message: String,
55}
56
57impl std::fmt::Display for ParseError {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        let kind_str = match &self.kind {
60            ParseErrorKind::UnsupportedFeature(feature) => {
61                format!("{} is not supported", feature.description())
62            }
63            ParseErrorKind::UnexpectedChar => "unexpected character".to_owned(),
64            ParseErrorKind::UnexpectedToken { found, .. } => {
65                format!("unexpected token '{found}'")
66            }
67            ParseErrorKind::UnterminatedString => "unterminated string".to_owned(),
68            ParseErrorKind::UnterminatedBlockComment => "unterminated block comment".to_owned(),
69            ParseErrorKind::InvalidNumericLiteral => "invalid numeric literal".to_owned(),
70            ParseErrorKind::InvalidParameter => "invalid parameter".to_owned(),
71            ParseErrorKind::UnexpectedEof { .. } => "unexpected end of input".to_owned(),
72        };
73        write!(f, "{kind_str} at {}", self.span)
74    }
75}
76
77impl std::error::Error for ParseError {}
78
79impl ParseError {
80    /// Create a new `ParseError`.
81    #[must_use]
82    pub fn new(kind: ParseErrorKind, span: Span, message: impl Into<String>) -> Self {
83        Self {
84            kind,
85            span,
86            message: message.into(),
87        }
88    }
89}