use std::collections::BTreeMap;
use crate::parser::lexer::{LexError, Span};
#[derive(Debug, Clone, PartialEq)]
pub enum Attribute {
Integer(i64),
Real(f64),
String(String),
Enum(String),
Binary(String),
EntityRef(u64),
Unset,
Derived,
List(Vec<Attribute>),
Typed {
type_name: String,
value: Box<Attribute>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct RawEntityPart {
pub name: String,
pub attributes: Vec<Attribute>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RawEntity {
Simple {
id: u64,
name: String,
attributes: Vec<Attribute>,
span: Span,
},
Complex {
id: u64,
parts: Vec<RawEntityPart>,
span: Span,
},
}
impl RawEntity {
#[must_use]
pub fn id(&self) -> u64 {
match self {
Self::Simple { id, .. } | Self::Complex { id, .. } => *id,
}
}
#[must_use]
pub fn span(&self) -> Span {
match self {
Self::Simple { span, .. } | Self::Complex { span, .. } => *span,
}
}
}
#[derive(Debug)]
pub struct Graph {
pub schema: super::schema::SchemaId,
pub header: Vec<RawEntity>,
pub entities: BTreeMap<u64, RawEntity>,
pub external_references: BTreeMap<u64, String>,
pub anchors: Vec<(String, u64)>,
pub warnings: Vec<ParseWarning>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseWarning {
MissingHeaderSemicolon {
entity_name: String,
span: super::lexer::Span,
},
EmptyAttribute { span: super::lexer::Span },
Ed3SectionDiscarded {
section: String,
span: super::lexer::Span,
},
}
impl Graph {
#[must_use]
pub fn get(&self, id: u64) -> Option<&RawEntity> {
self.entities.get(&id)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
Lex(LexError),
UnexpectedEof { expected: &'static str },
UnexpectedToken {
expected: &'static str,
found: super::lexer::TokenKind,
span: Span,
},
DuplicateEntityId { id: u64, span: Span },
MissingHeaderEntity { name: &'static str },
MalformedFileSchema { span: Span },
InvalidAttributePosition { span: Span, detail: &'static str },
NestingTooDeep { span: Span },
}
impl From<LexError> for Error {
fn from(err: LexError) -> Self {
Self::Lex(err)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Lex(inner) => inner.fmt(f),
Self::UnexpectedEof { expected } => {
write!(
f,
"parse error: unexpected end of file, expected {expected}"
)
}
Self::UnexpectedToken {
expected,
found,
span,
} => write!(
f,
"parse error at line {}, column {}: expected {expected}, found {found:?}",
span.line, span.column,
),
Self::DuplicateEntityId { id, span } => write!(
f,
"parse error at line {}, column {}: duplicate entity #{id}",
span.line, span.column,
),
Self::MissingHeaderEntity { name } => {
write!(f, "parse error: missing required HEADER entity {name}")
}
Self::MalformedFileSchema { span } => write!(
f,
"parse error at line {}, column {}: malformed FILE_SCHEMA",
span.line, span.column,
),
Self::InvalidAttributePosition { span, detail } => write!(
f,
"parse error at line {}, column {}: {detail}",
span.line, span.column,
),
Self::NestingTooDeep { span } => write!(
f,
"parse error at line {}, column {}: parameter nesting too deep",
span.line, span.column,
),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Lex(inner) => Some(inner),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_entity_id_simple() {
let e = RawEntity::Simple {
id: 42,
name: "TEST".into(),
attributes: vec![],
span: Span {
start: 0,
end: 3,
line: 1,
column: 1,
},
};
assert_eq!(e.id(), 42);
}
#[test]
fn raw_entity_id_complex() {
let e = RawEntity::Complex {
id: 99,
parts: vec![],
span: Span {
start: 0,
end: 3,
line: 1,
column: 1,
},
};
assert_eq!(e.id(), 99);
}
#[test]
fn raw_entity_span_accessor() {
let span = Span {
start: 10,
end: 20,
line: 3,
column: 5,
};
let e = RawEntity::Simple {
id: 1,
name: "A".into(),
attributes: vec![],
span,
};
assert_eq!(e.span(), span);
}
#[test]
fn parse_error_from_lex_error() {
let lex_err = LexError {
kind: crate::parser::lexer::LexErrorKind::UnexpectedCharacter,
span: Span {
start: 0,
end: 1,
line: 1,
column: 1,
},
snippet: "@".into(),
};
let parse_err = Error::from(lex_err.clone());
assert_eq!(parse_err, Error::Lex(lex_err));
}
#[test]
fn parse_error_display_delegates_for_lex() {
let lex_err = LexError {
kind: crate::parser::lexer::LexErrorKind::UnexpectedCharacter,
span: Span {
start: 0,
end: 1,
line: 1,
column: 1,
},
snippet: "@".into(),
};
let parse_err = Error::Lex(lex_err.clone());
assert_eq!(parse_err.to_string(), lex_err.to_string());
}
#[test]
fn parse_error_implements_std_error() {
fn assert_error<E: std::error::Error>(_: &E) {}
let err = Error::UnexpectedEof {
expected: "semicolon",
};
assert_error(&err);
}
#[test]
fn entity_graph_get_returns_entity() {
let span = Span {
start: 0,
end: 1,
line: 1,
column: 1,
};
let entity = RawEntity::Simple {
id: 1,
name: "X".into(),
attributes: vec![],
span,
};
let mut entities = BTreeMap::new();
entities.insert(1, entity.clone());
let graph = Graph {
schema: super::super::schema::SchemaId::default(),
header: vec![],
entities,
external_references: BTreeMap::new(),
anchors: vec![],
warnings: vec![],
};
assert_eq!(graph.get(1), Some(&entity));
assert_eq!(graph.get(999), None);
}
}