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}