1use std::collections::BTreeMap;
2
3use crate::parser::lexer::{LexError, Span};
4
5#[derive(Debug, Clone, PartialEq)]
9pub enum Attribute {
10 Integer(i64),
11 Real(f64),
12 String(String),
16 Enum(String),
18 Binary(String),
20 EntityRef(u64),
22 Unset,
24 Derived,
26 List(Vec<Attribute>),
28 Typed {
30 type_name: String,
31 value: Box<Attribute>,
32 },
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct RawEntityPart {
38 pub name: String,
40 pub attributes: Vec<Attribute>,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum RawEntity {
46 Simple {
47 id: u64,
48 name: String,
50 attributes: Vec<Attribute>,
51 span: Span,
53 },
54 Complex {
55 id: u64,
56 parts: Vec<RawEntityPart>,
57 span: Span,
59 },
60}
61
62impl RawEntity {
63 #[must_use]
65 pub fn id(&self) -> u64 {
66 match self {
67 Self::Simple { id, .. } | Self::Complex { id, .. } => *id,
68 }
69 }
70
71 #[must_use]
73 pub fn span(&self) -> Span {
74 match self {
75 Self::Simple { span, .. } | Self::Complex { span, .. } => *span,
76 }
77 }
78}
79
80#[derive(Debug)]
82pub struct Graph {
83 pub schema: super::schema::SchemaId,
86 pub header: Vec<RawEntity>,
88 pub entities: BTreeMap<u64, RawEntity>,
91 pub external_references: BTreeMap<u64, String>,
95 pub anchors: Vec<(String, u64)>,
98 pub warnings: Vec<ParseWarning>,
103}
104
105#[derive(Debug, Clone, PartialEq)]
113pub enum ParseWarning {
114 MissingHeaderSemicolon {
117 entity_name: String,
118 span: super::lexer::Span,
119 },
120 EmptyAttribute { span: super::lexer::Span },
123 Ed3SectionDiscarded {
127 section: String,
128 span: super::lexer::Span,
129 },
130}
131
132impl Graph {
133 #[must_use]
135 pub fn get(&self, id: u64) -> Option<&RawEntity> {
136 self.entities.get(&id)
137 }
138}
139
140#[derive(Debug, Clone, PartialEq)]
145pub enum Error {
146 Lex(LexError),
148 UnexpectedEof { expected: &'static str },
150 UnexpectedToken {
152 expected: &'static str,
153 found: super::lexer::TokenKind,
154 span: Span,
155 },
156 DuplicateEntityId { id: u64, span: Span },
158 MissingHeaderEntity { name: &'static str },
160 MalformedFileSchema { span: Span },
162 InvalidAttributePosition { span: Span, detail: &'static str },
164 NestingTooDeep { span: Span },
169}
170
171impl From<LexError> for Error {
172 fn from(err: LexError) -> Self {
173 Self::Lex(err)
174 }
175}
176
177impl std::fmt::Display for Error {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 match self {
180 Self::Lex(inner) => inner.fmt(f),
181 Self::UnexpectedEof { expected } => {
182 write!(
183 f,
184 "parse error: unexpected end of file, expected {expected}"
185 )
186 }
187 Self::UnexpectedToken {
188 expected,
189 found,
190 span,
191 } => write!(
192 f,
193 "parse error at line {}, column {}: expected {expected}, found {found:?}",
194 span.line, span.column,
195 ),
196 Self::DuplicateEntityId { id, span } => write!(
197 f,
198 "parse error at line {}, column {}: duplicate entity #{id}",
199 span.line, span.column,
200 ),
201 Self::MissingHeaderEntity { name } => {
202 write!(f, "parse error: missing required HEADER entity {name}")
203 }
204 Self::MalformedFileSchema { span } => write!(
205 f,
206 "parse error at line {}, column {}: malformed FILE_SCHEMA",
207 span.line, span.column,
208 ),
209 Self::InvalidAttributePosition { span, detail } => write!(
210 f,
211 "parse error at line {}, column {}: {detail}",
212 span.line, span.column,
213 ),
214 Self::NestingTooDeep { span } => write!(
215 f,
216 "parse error at line {}, column {}: parameter nesting too deep",
217 span.line, span.column,
218 ),
219 }
220 }
221}
222
223impl std::error::Error for Error {
224 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
225 match self {
226 Self::Lex(inner) => Some(inner),
227 _ => None,
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn raw_entity_id_simple() {
238 let e = RawEntity::Simple {
239 id: 42,
240 name: "TEST".into(),
241 attributes: vec![],
242 span: Span {
243 start: 0,
244 end: 3,
245 line: 1,
246 column: 1,
247 },
248 };
249 assert_eq!(e.id(), 42);
250 }
251
252 #[test]
253 fn raw_entity_id_complex() {
254 let e = RawEntity::Complex {
255 id: 99,
256 parts: vec![],
257 span: Span {
258 start: 0,
259 end: 3,
260 line: 1,
261 column: 1,
262 },
263 };
264 assert_eq!(e.id(), 99);
265 }
266
267 #[test]
268 fn raw_entity_span_accessor() {
269 let span = Span {
270 start: 10,
271 end: 20,
272 line: 3,
273 column: 5,
274 };
275 let e = RawEntity::Simple {
276 id: 1,
277 name: "A".into(),
278 attributes: vec![],
279 span,
280 };
281 assert_eq!(e.span(), span);
282 }
283
284 #[test]
285 fn parse_error_from_lex_error() {
286 let lex_err = LexError {
287 kind: crate::parser::lexer::LexErrorKind::UnexpectedCharacter,
288 span: Span {
289 start: 0,
290 end: 1,
291 line: 1,
292 column: 1,
293 },
294 snippet: "@".into(),
295 };
296 let parse_err = Error::from(lex_err.clone());
297 assert_eq!(parse_err, Error::Lex(lex_err));
298 }
299
300 #[test]
301 fn parse_error_display_delegates_for_lex() {
302 let lex_err = LexError {
303 kind: crate::parser::lexer::LexErrorKind::UnexpectedCharacter,
304 span: Span {
305 start: 0,
306 end: 1,
307 line: 1,
308 column: 1,
309 },
310 snippet: "@".into(),
311 };
312 let parse_err = Error::Lex(lex_err.clone());
313 assert_eq!(parse_err.to_string(), lex_err.to_string());
315 }
316
317 #[test]
318 fn parse_error_implements_std_error() {
319 fn assert_error<E: std::error::Error>(_: &E) {}
320 let err = Error::UnexpectedEof {
321 expected: "semicolon",
322 };
323 assert_error(&err);
324 }
325
326 #[test]
327 fn entity_graph_get_returns_entity() {
328 let span = Span {
329 start: 0,
330 end: 1,
331 line: 1,
332 column: 1,
333 };
334 let entity = RawEntity::Simple {
335 id: 1,
336 name: "X".into(),
337 attributes: vec![],
338 span,
339 };
340 let mut entities = BTreeMap::new();
341 entities.insert(1, entity.clone());
342 let graph = Graph {
343 schema: super::super::schema::SchemaId::default(),
344 header: vec![],
345 entities,
346 external_references: BTreeMap::new(),
347 anchors: vec![],
348 warnings: vec![],
349 };
350 assert_eq!(graph.get(1), Some(&entity));
351 assert_eq!(graph.get(999), None);
352 }
353}