1#![deny(missing_docs)]
2
3pub use index_map::IndexMap;
26
27use header_parsing::parse_header;
28use logical_expressions::{LogicalExpression, ParseError};
29use thiserror::Error;
30
31use multilinear::{Aspect, Change, Event, InvalidChangeError, MultilinearInfo};
32
33use std::io::{BufRead, BufReader, Read};
34
35mod index_map;
36
37#[derive(Copy, Clone, Debug)]
38struct ValueCheckingError(char);
39
40type Str = Box<str>;
41
42fn check_name(name: &str) -> Result<(), ValueCheckingError> {
43 if let Some(c) = name
44 .chars()
45 .find(|&c| !c.is_alphanumeric() && !"_- ".contains(c))
46 {
47 Err(ValueCheckingError(c))
48 } else {
49 Ok(())
50 }
51}
52
53fn valid_name(name: &str) -> Result<&str, ValueCheckingError> {
54 let name = name.trim();
55 check_name(name)?;
56 Ok(name)
57}
58
59fn value_index(value_names: &mut Vec<Str>, name: &str) -> Result<usize, ValueCheckingError> {
60 let name = valid_name(name)?;
61
62 if let Some(index) = value_names.iter().position(|x| x.as_ref() == name) {
63 return Ok(index);
64 }
65
66 let index = value_names.len();
67 value_names.push(name.into());
68 Ok(index)
69}
70
71#[expect(clippy::indexing_slicing)]
72fn aspect_info<'a>(
73 aspects: &'a mut AspectMap,
74 name: &str,
75 info: &mut MultilinearInfo,
76) -> Result<(Aspect, &'a mut Vec<Str>), ValueCheckingError> {
77 let name = valid_name(name)?;
78
79 let existing = aspects
80 .entries
81 .iter()
82 .position(|(checked_name, _)| checked_name.as_ref() == name);
83
84 let index = if let Some(index) = existing {
85 index
86 } else {
87 let aspect = info.add_aspect();
88 aspects.insert(aspect, (name.into(), vec!["".into()]));
89 aspect.0
90 };
91
92 Ok((Aspect(index), &mut aspects.entries[index].1))
93}
94
95#[derive(Debug, Error)]
97pub enum AspectAddingError {
98 #[error("An aspect of this name already exists")]
100 AlreadyExists,
101
102 #[error("Invalid character '{0}' for condition names")]
104 InvalidCharacter(char),
105}
106
107impl From<ValueCheckingError> for AspectAddingError {
108 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
109 Self::InvalidCharacter(c)
110 }
111}
112
113#[derive(Debug, Error)]
115pub enum AspectExpressionError {
116 #[error("Error adding default aspect: {0}")]
118 AddingAspect(#[source] AspectAddingError),
119 #[error("Invalid aspect default: {0}")]
121 InvalidAspectDefault(Box<str>),
122}
123
124#[derive(Copy, Clone, Debug, Error)]
126pub enum ConditionParsingError {
127 #[error("Invalid character '{0}' for condition names")]
129 InvalidCharacter(char),
130
131 #[error("Invalid condition format")]
133 InvalidCondition,
134}
135
136impl From<ValueCheckingError> for ConditionParsingError {
137 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
138 Self::InvalidCharacter(c)
139 }
140}
141
142#[derive(Debug, Error)]
144pub enum ErrorKind {
145 #[error("Input error while parsing line")]
147 LineParsing,
148
149 #[error("Parsing expression failed: {0}")]
151 ExpressionParsing(ParseError<ConditionParsingError>),
152
153 #[error("Encountered conflicting conditions: {0}")]
155 ConflictingCondition(InvalidChangeError),
156
157 #[error("Invalid character '{0}' in event name")]
159 InvalidCharacterInEventName(char),
160
161 #[error("{0}")]
163 AddingAspectExpression(#[source] AspectExpressionError),
164
165 #[error("Subheader without matching header")]
167 SubheaderWithoutHeader,
168}
169
170trait ErrorLine {
171 type Output;
172
173 fn line(self, line: usize) -> Self::Output;
174}
175
176impl ErrorLine for ErrorKind {
177 type Output = Error;
178
179 fn line(self, line: usize) -> Error {
180 Error { line, kind: self }
181 }
182}
183
184impl<T> ErrorLine for Result<T, ErrorKind> {
185 type Output = Result<T, Error>;
186
187 fn line(self, line: usize) -> Result<T, Error> {
188 match self {
189 Ok(value) => Ok(value),
190 Err(err) => Err(err.line(line)),
191 }
192 }
193}
194
195#[derive(Debug, Error)]
197#[error("Line {line}: {kind}")]
198pub struct Error {
199 line: usize,
201 kind: ErrorKind,
203}
204
205type AspectMap = IndexMap<Aspect, (Str, Vec<Str>)>;
206
207fn add_new_aspect(
208 info: &mut MultilinearInfo,
209 aspects: &mut AspectMap,
210 aspect_name: &str,
211 default_name: &str,
212) -> Result<Aspect, AspectAddingError> {
213 let aspect_name = valid_name(aspect_name)?;
214 let default_name = valid_name(default_name)?;
215
216 if aspects
217 .entries
218 .iter()
219 .any(|(checked_name, _)| checked_name.as_ref() == aspect_name)
220 {
221 return Err(AspectAddingError::AlreadyExists);
222 }
223
224 let aspect = info.add_aspect();
225 aspects.insert(aspect, (aspect_name.into(), vec![default_name.into()]));
226
227 Ok(aspect)
228}
229
230fn add_aspect_expression(
231 info: &mut MultilinearInfo,
232 aspects: &mut AspectMap,
233 line: &str,
234) -> Result<(), AspectExpressionError> {
235 let line = line.trim();
236 if line.is_empty() {
237 return Ok(());
238 }
239 let Some((aspect, default_value)) = line.split_once(':') else {
240 return Err(AspectExpressionError::InvalidAspectDefault(line.into()));
241 };
242 if let Err(err) = add_new_aspect(info, aspects, aspect, default_value) {
243 return Err(AspectExpressionError::AddingAspect(err));
244 }
245
246 Ok(())
247}
248
249#[derive(Default)]
251pub struct NamedMultilinearInfo {
252 pub info: MultilinearInfo,
254 pub events: IndexMap<Event, Vec<Str>>,
256 pub aspects: AspectMap,
258}
259
260#[derive(Default)]
263pub struct MultilinearParser(NamedMultilinearInfo);
264
265impl MultilinearParser {
266 #[inline]
272 pub fn add_new_aspect(
273 &mut self,
274 aspect_name: &str,
275 default_name: &str,
276 ) -> Result<Aspect, AspectAddingError> {
277 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
278
279 add_new_aspect(info, aspects, aspect_name, default_name)
280 }
281
282 #[inline]
288 pub fn add_aspect_expression(&mut self, line: &str) -> Result<(), AspectExpressionError> {
289 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
290
291 add_aspect_expression(info, aspects, line)
292 }
293
294 pub fn parse<R: Read>(&mut self, reader: R, parent_namespace: &[Str]) -> Result<(), Error> {
316 let mut child_namespace = Vec::new();
317
318 let NamedMultilinearInfo {
319 info,
320 events,
321 aspects,
322 } = &mut self.0;
323
324 let mut condition_groups = Vec::new();
325 let mut condition_lines = Vec::new();
326
327 let mut last_header_line = 0;
328
329 for (line_number, line) in BufReader::new(reader).lines().enumerate() {
330 let line_number = line_number + 1;
331 let Ok(line) = line else {
332 return Err(ErrorKind::LineParsing.line(line_number));
333 };
334
335 if line.trim().is_empty() {
336 if !condition_lines.is_empty() {
337 condition_groups.push(LogicalExpression::and(condition_lines));
338 condition_lines = Vec::new();
339 }
340 continue;
341 }
342
343 if let Some(success) = parse_header(&mut child_namespace, &line) {
344 let Ok(changes) = success else {
345 return Err(ErrorKind::SubheaderWithoutHeader.line(line_number));
346 };
347
348 if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
349 return Err(ErrorKind::InvalidCharacterInEventName(c)).line(line_number);
350 }
351
352 if !condition_lines.is_empty() {
353 condition_groups.push(LogicalExpression::and(condition_lines));
354 condition_lines = Vec::new();
355 }
356
357 let namespace_parent =
358 condition_groups.is_empty() && changes.level() == changes.path.len();
359
360 if last_header_line > 0 && !namespace_parent {
361 let mut event_edit = info.add_event();
362 for conditions in LogicalExpression::or(condition_groups).expand() {
363 if let Err(err) = event_edit.add_change(&conditions) {
364 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
365 }
366 }
367
368 let mut namespace = parent_namespace.to_vec();
369 namespace.extend(changes.path.clone());
370 events.insert(event_edit.event(), namespace);
371
372 condition_groups = Vec::new();
373 }
374
375 last_header_line = line_number + 1;
376
377 changes.apply();
378
379 continue;
380 }
381
382 if parent_namespace.is_empty() && child_namespace.is_empty() {
383 if let Err(err) = add_aspect_expression(info, aspects, &line) {
384 return Err(ErrorKind::AddingAspectExpression(err).line(line_number));
385 }
386 continue;
387 }
388
389 let line: &str = line.split_once('#').map_or(&line, |(line, _comment)| line);
390
391 let parse_expression = |condition: &str| {
392 let Some((aspect, changes)) = condition.split_once(':') else {
393 return Err(ConditionParsingError::InvalidCondition);
394 };
395
396 let (aspect, value_names) = aspect_info(aspects, aspect.trim(), info)?;
397 Ok(LogicalExpression::or(
398 changes
399 .split(';')
400 .map(|change| -> Result<_, ValueCheckingError> {
401 Ok(LogicalExpression::Condition(
402 if let Some((from, to)) = change.split_once('>') {
403 let from = value_index(value_names, from)?;
404 let to = value_index(value_names, to)?;
405 Change::transition(aspect, from, to)
406 } else {
407 let change = value_index(value_names, change)?;
408 Change::condition(aspect, change)
409 },
410 ))
411 })
412 .collect::<Result<_, _>>()?,
413 ))
414 };
415
416 let conditions = LogicalExpression::parse_with_expression(line, parse_expression);
417
418 let conditions = match conditions {
419 Ok(conditions) => conditions,
420 Err(err) => return Err(ErrorKind::ExpressionParsing(err).line(line_number)),
421 };
422
423 condition_lines.push(conditions);
424 }
425
426 if !condition_lines.is_empty() {
427 condition_groups.push(LogicalExpression::and(condition_lines));
428 }
429
430 if last_header_line > 0 {
431 let mut event_edit = info.add_event();
432 for conditions in LogicalExpression::or(condition_groups).expand() {
433 if let Err(err) = event_edit.add_change(&conditions) {
434 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
435 }
436 }
437
438 let mut namespace = parent_namespace.to_vec();
439 namespace.extend(child_namespace);
440 events.insert(event_edit.event(), namespace);
441 }
442
443 Ok(())
444 }
445
446 #[must_use]
450 pub fn into_info(self) -> NamedMultilinearInfo {
451 self.0
452 }
453}
454
455pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
473 let mut result = MultilinearParser::default();
474 result.parse(reader, &[])?;
475 Ok(result.0)
476}
477
478mod extended;
479
480pub use extended::{
481 AspectError, AspectErrorKind, DirectoryOrFileError, DirectoryOrFileErrorKind, ExtendedError,
482 parse_multilinear_extended,
483};