1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::fmt;
/// Parser errors for the Alopex SQL dialect.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParserError {
/// ALOPEX-P001: An unexpected token was encountered.
UnexpectedToken {
line: u64,
column: u64,
expected: String,
found: String,
},
/// ALOPEX-P002: A required token was missing.
ExpectedToken {
line: u64,
column: u64,
expected: String,
found: String,
},
/// ALOPEX-P003: String literal was not closed.
UnterminatedString { line: u64, column: u64 },
/// ALOPEX-P004: Number literal is malformed.
InvalidNumber {
line: u64,
column: u64,
value: String,
},
/// ALOPEX-P005: Vector literal is malformed.
InvalidVector { line: u64, column: u64 },
/// ALOPEX-P006: Parser exceeded maximum recursion depth.
RecursionLimitExceeded { depth: usize },
/// ALOPEX-P007: The Nim FFI parser hit an internal invariant violation
/// (a Nim `Defect`, e.g. `IndexDefect`/`FieldDefect`) rather than a
/// normal syntax error. This indicates a parser bug, not bad input, and
/// must stay distinguishable from `UnexpectedToken` so operators do not
/// mistake it for user error. See `nim-sql-parser/src/alopex_sql_parser.nim`
/// `alopex_parse_sql`'s `except ... Defect` branch.
InternalParserDefect { message: String },
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedToken {
line,
column,
expected,
found,
} => write!(
f,
"error[ALOPEX-P001]: unexpected token at line {line}, column {column}: expected {expected}, found {found}"
),
Self::ExpectedToken {
line,
column,
expected,
found,
} => write!(
f,
"error[ALOPEX-P002]: expected {expected} but found {found} at line {line}, column {column}"
),
Self::UnterminatedString { line, column } => write!(
f,
"error[ALOPEX-P003]: unterminated string literal starting at line {line}, column {column}"
),
Self::InvalidNumber {
line,
column,
value,
} => write!(
f,
"error[ALOPEX-P004]: invalid number literal '{value}' at line {line}, column {column}"
),
Self::InvalidVector { line, column } => write!(
f,
"error[ALOPEX-P005]: invalid vector literal at line {line}, column {column}"
),
Self::RecursionLimitExceeded { depth } => write!(
f,
"error[ALOPEX-P006]: recursion limit exceeded (depth: {depth})"
),
Self::InternalParserDefect { message } => write!(
f,
"error[ALOPEX-P007]: internal parser defect (this is a parser bug, not invalid SQL): {message}"
),
}
}
}
impl std::error::Error for ParserError {}
/// Convenience result type for parser operations.
pub type Result<T> = std::result::Result<T, ParserError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal_parser_defect_is_labeled_with_its_own_error_code() {
// ALOPEX-P007 は ALOPEX-P001 (UnexpectedToken) と機械的に区別できな
// ければならない。運用者がパーサーのバグ (Nim FFI 境界の Defect) と
// ユーザーの SQL 誤りを混同しないための契約。
let defect = ParserError::InternalParserDefect {
message: "field 'children' is not accessible".to_string(),
};
let rendered = defect.to_string();
assert!(rendered.contains("ALOPEX-P007"));
assert!(rendered.contains("field 'children' is not accessible"));
let token = ParserError::UnexpectedToken {
line: 1,
column: 2,
expected: "expr".to_string(),
found: "EOF".to_string(),
};
assert!(token.to_string().contains("ALOPEX-P001"));
assert_ne!(rendered, token.to_string());
}
}