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
71fn aspect_info<'a>(
72 aspects: &'a mut AspectMap,
73 name: &str,
74 info: &mut MultilinearInfo,
75) -> Result<(Aspect, &'a mut Vec<Str>), ValueCheckingError> {
76 let name = valid_name(name)?;
77
78 if let Some(i) = aspects
79 .entries
80 .iter()
81 .position(|(checked_name, _)| checked_name.as_ref() == name)
82 {
83 return Ok((Aspect(i), &mut aspects.entries[i].1));
84 }
85
86 let aspect = info.add_aspect();
87 aspects.insert(aspect, (name.into(), vec!["".into()]));
88
89 let Some((_, value_names)) = aspects.entries.last_mut() else {
90 unreachable!("entry was just inserted")
91 };
92
93 Ok((aspect, value_names))
94}
95
96#[derive(Debug, Error)]
98pub enum AspectAddingError {
99 #[error("An aspect of this name already exists")]
101 AlreadyExists,
102
103 #[error("Invalid character '{0}' for condition names")]
105 InvalidCharacter(char),
106}
107
108impl From<ValueCheckingError> for AspectAddingError {
109 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
110 Self::InvalidCharacter(c)
111 }
112}
113
114#[derive(Debug, Error)]
116pub enum AspectExpressionError {
117 #[error("Error adding default aspect: {0}")]
119 AddingAspect(#[source] AspectAddingError),
120 #[error("Invalid aspect default: {0}")]
122 InvalidAspectDefault(Box<str>),
123}
124
125#[derive(Copy, Clone, Debug, Error)]
127pub enum ConditionParsingError {
128 #[error("Invalid character '{0}' for condition names")]
130 InvalidCharacter(char),
131
132 #[error("Invalid condition format")]
134 InvalidCondition,
135}
136
137impl From<ValueCheckingError> for ConditionParsingError {
138 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
139 Self::InvalidCharacter(c)
140 }
141}
142
143#[derive(Debug, Error)]
145pub enum ErrorKind {
146 #[error("Input error while parsing line")]
148 LineParsing,
149
150 #[error("Parsing expression failed: {0}")]
152 ExpressionParsing(ParseError<ConditionParsingError>),
153
154 #[error("Encountered conflicting conditions: {0}")]
156 ConflictingCondition(InvalidChangeError),
157
158 #[error("Invalid character '{0}' in event name")]
160 InvalidCharacterInEventName(char),
161
162 #[error("{0}")]
164 AddingAspectExpression(#[source] AspectExpressionError),
165
166 #[error("Subheader without matching header")]
168 SubheaderWithoutHeader,
169}
170
171trait ErrorLine {
172 type Output;
173
174 fn line(self, line: usize) -> Self::Output;
175}
176
177impl ErrorLine for ErrorKind {
178 type Output = Error;
179
180 fn line(self, line: usize) -> Error {
181 Error { line, kind: self }
182 }
183}
184
185impl<T> ErrorLine for Result<T, ErrorKind> {
186 type Output = Result<T, Error>;
187
188 fn line(self, line: usize) -> Result<T, Error> {
189 match self {
190 Ok(value) => Ok(value),
191 Err(err) => Err(err.line(line)),
192 }
193 }
194}
195
196#[derive(Debug, Error)]
198#[error("Line {line}: {kind}")]
199pub struct Error {
200 line: usize,
202 kind: ErrorKind,
204}
205
206type AspectMap = IndexMap<Aspect, (Str, Vec<Str>)>;
207
208fn add_new_aspect(
209 info: &mut MultilinearInfo,
210 aspects: &mut AspectMap,
211 aspect_name: &str,
212 default_name: &str,
213) -> Result<Aspect, AspectAddingError> {
214 let aspect_name = valid_name(aspect_name)?;
215 let default_name = valid_name(default_name)?;
216
217 if aspects
218 .entries
219 .iter()
220 .any(|(checked_name, _)| checked_name.as_ref() == aspect_name)
221 {
222 return Err(AspectAddingError::AlreadyExists);
223 }
224
225 let aspect = info.add_aspect();
226 aspects.insert(aspect, (aspect_name.into(), vec![default_name.into()]));
227
228 Ok(aspect)
229}
230
231fn add_aspect_expression(
232 info: &mut MultilinearInfo,
233 aspects: &mut AspectMap,
234 line: &str,
235) -> Result<(), AspectExpressionError> {
236 let line = line.trim();
237 if line.is_empty() {
238 return Ok(());
239 }
240 let Some((aspect, default_value)) = line.split_once(':') else {
241 return Err(AspectExpressionError::InvalidAspectDefault(line.into()));
242 };
243 if let Err(err) = add_new_aspect(info, aspects, aspect, default_value) {
244 return Err(AspectExpressionError::AddingAspect(err));
245 }
246
247 Ok(())
248}
249
250#[derive(Default)]
252pub struct NamedMultilinearInfo {
253 pub info: MultilinearInfo,
255 pub events: IndexMap<Event, Vec<Str>>,
257 pub aspects: AspectMap,
259}
260
261#[derive(Default)]
264pub struct MultilinearParser(NamedMultilinearInfo);
265
266impl MultilinearParser {
267 #[inline]
273 pub fn add_new_aspect(
274 &mut self,
275 aspect_name: &str,
276 default_name: &str,
277 ) -> Result<Aspect, AspectAddingError> {
278 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
279
280 add_new_aspect(info, aspects, aspect_name, default_name)
281 }
282
283 #[inline]
289 pub fn add_aspect_expression(&mut self, line: &str) -> Result<(), AspectExpressionError> {
290 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
291
292 add_aspect_expression(info, aspects, line)
293 }
294
295 pub fn parse<R: Read>(&mut self, reader: R, parent_namespace: &[Str]) -> Result<(), Error> {
317 let mut child_namespace = Vec::new();
318
319 let NamedMultilinearInfo {
320 info,
321 events,
322 aspects,
323 } = &mut self.0;
324
325 let mut condition_groups = Vec::new();
326 let mut condition_lines = Vec::new();
327
328 let mut last_header_line = 0;
329
330 for (line_number, line) in BufReader::new(reader).lines().enumerate() {
331 let line_number = line_number + 1;
332 let Ok(line) = line else {
333 return Err(ErrorKind::LineParsing.line(line_number));
334 };
335
336 if line.trim().is_empty() {
337 if !condition_lines.is_empty() {
338 condition_groups.push(LogicalExpression::and(condition_lines));
339 condition_lines = Vec::new();
340 }
341 continue;
342 }
343
344 if let Some(success) = parse_header(&mut child_namespace, &line) {
345 let Ok(changes) = success else {
346 return Err(ErrorKind::SubheaderWithoutHeader.line(line_number));
347 };
348
349 if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
350 return Err(ErrorKind::InvalidCharacterInEventName(c)).line(line_number);
351 }
352
353 if !condition_lines.is_empty() {
354 condition_groups.push(LogicalExpression::and(condition_lines));
355 condition_lines = Vec::new();
356 }
357
358 if last_header_line > 0 {
359 let mut event_edit = info.add_event();
360 for conditions in LogicalExpression::or(condition_groups).expand() {
361 if let Err(err) = event_edit.add_change(&conditions) {
362 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
363 }
364 }
365
366 let mut namespace = parent_namespace.to_vec();
367 namespace.extend(changes.path.clone());
368 events.insert(event_edit.event(), namespace);
369
370 condition_groups = Vec::new();
371 }
372
373 last_header_line = line_number + 1;
374
375 changes.apply();
376
377 continue;
378 }
379
380 if parent_namespace.is_empty() && child_namespace.is_empty() {
381 if let Err(err) = add_aspect_expression(info, aspects, &line) {
382 return Err(ErrorKind::AddingAspectExpression(err).line(line_number));
383 }
384 continue;
385 }
386
387 let line: &str = line.split_once('#').map_or(&line, |(line, _comment)| line);
388
389 let parse_expression = |condition: &str| {
390 let Some((aspect, changes)) = condition.split_once(':') else {
391 return Err(ConditionParsingError::InvalidCondition);
392 };
393
394 let (aspect, value_names) = aspect_info(aspects, aspect.trim(), info)?;
395 Ok(LogicalExpression::or(
396 changes
397 .split(';')
398 .map(|change| -> Result<_, ValueCheckingError> {
399 Ok(LogicalExpression::Condition(
400 if let Some((from, to)) = change.split_once('>') {
401 let from = value_index(value_names, from)?;
402 let to = value_index(value_names, to)?;
403 Change::transition(aspect, from, to)
404 } else {
405 let change = value_index(value_names, change)?;
406 Change::condition(aspect, change)
407 },
408 ))
409 })
410 .collect::<Result<_, _>>()?,
411 ))
412 };
413
414 let conditions = LogicalExpression::parse_with_expression(line, parse_expression);
415
416 let conditions = match conditions {
417 Ok(conditions) => conditions,
418 Err(err) => return Err(ErrorKind::ExpressionParsing(err).line(line_number)),
419 };
420
421 condition_lines.push(conditions);
422 }
423
424 if !condition_lines.is_empty() {
425 condition_groups.push(LogicalExpression::and(condition_lines));
426 }
427
428 if last_header_line > 0 {
429 let mut event_edit = info.add_event();
430 for conditions in LogicalExpression::or(condition_groups).expand() {
431 if let Err(err) = event_edit.add_change(&conditions) {
432 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
433 }
434 }
435
436 let mut namespace = parent_namespace.to_vec();
437 namespace.extend(child_namespace);
438 events.insert(event_edit.event(), namespace);
439 }
440
441 Ok(())
442 }
443
444 #[must_use]
448 pub fn into_info(self) -> NamedMultilinearInfo {
449 self.0
450 }
451}
452
453pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
471 let mut result = MultilinearParser::default();
472 result.parse(reader, &[])?;
473 Ok(result.0)
474}
475
476mod extended;
477
478pub use extended::{
479 AspectError, AspectErrorKind, DirectoryOrFileError, DirectoryOrFileErrorKind, ExtendedError,
480 parse_multilinear_extended,
481};