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 (_, value_names) = aspects.entries.last_mut().unwrap();
90
91 Ok((aspect, value_names))
92}
93
94#[derive(Debug, Error)]
96pub enum AspectAddingError {
97 #[error("An aspect of this name already exists")]
99 AlreadyExists,
100
101 #[error("Invalid character '{0}' for condition names")]
103 InvalidCharacter(char),
104}
105
106impl From<ValueCheckingError> for AspectAddingError {
107 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
108 Self::InvalidCharacter(c)
109 }
110}
111
112#[derive(Debug, Error)]
114pub enum AspectExpressionError {
115 #[error("Error adding default aspect: {0}")]
117 AddingAspect(#[source] AspectAddingError),
118 #[error("Invalid aspect default: {0}")]
120 InvalidAspectDefault(Box<str>),
121}
122
123#[derive(Copy, Clone, Debug, Error)]
125pub enum ConditionParsingError {
126 #[error("Invalid character '{0}' for condition names")]
128 InvalidCharacter(char),
129
130 #[error("Invalid condition format")]
132 InvalidCondition,
133}
134
135impl From<ValueCheckingError> for ConditionParsingError {
136 fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
137 Self::InvalidCharacter(c)
138 }
139}
140
141#[derive(Debug, Error)]
143pub enum ErrorKind {
144 #[error("Input error while parsing line")]
146 LineParsing,
147
148 #[error("Parsing expression failed: {0}")]
150 ExpressionParsing(ParseError<ConditionParsingError>),
151
152 #[error("Encountered conflicting conditions: {0}")]
154 ConflictingCondition(InvalidChangeError),
155
156 #[error("Invalid character '{0}' in event name")]
158 InvalidCharacterInEventName(char),
159
160 #[error("{0}")]
162 AddingAspectExpression(#[source] AspectExpressionError),
163
164 #[error("Subheader without matching header")]
166 SubheaderWithoutHeader,
167}
168
169trait ErrorLine {
170 type Output;
171
172 fn line(self, line: usize) -> Self::Output;
173}
174
175impl ErrorLine for ErrorKind {
176 type Output = Error;
177
178 fn line(self, line: usize) -> Error {
179 Error { line, kind: self }
180 }
181}
182
183impl<T> ErrorLine for Result<T, ErrorKind> {
184 type Output = Result<T, Error>;
185
186 fn line(self, line: usize) -> Result<T, Error> {
187 match self {
188 Ok(value) => Ok(value),
189 Err(err) => Err(err.line(line)),
190 }
191 }
192}
193
194#[derive(Debug, Error)]
196#[error("Line {line}: {kind}")]
197pub struct Error {
198 line: usize,
200 kind: ErrorKind,
202}
203
204type AspectMap = IndexMap<Aspect, (Str, Vec<Str>)>;
205
206fn add_new_aspect(
207 info: &mut MultilinearInfo,
208 aspects: &mut AspectMap,
209 aspect_name: &str,
210 default_name: &str,
211) -> Result<Aspect, AspectAddingError> {
212 let aspect_name = valid_name(aspect_name)?;
213 let default_name = valid_name(default_name)?;
214
215 if aspects
216 .entries
217 .iter()
218 .any(|(checked_name, _)| checked_name.as_ref() == aspect_name)
219 {
220 return Err(AspectAddingError::AlreadyExists);
221 }
222
223 let aspect = info.add_aspect();
224 aspects.insert(aspect, (aspect_name.into(), vec![default_name.into()]));
225
226 Ok(aspect)
227}
228
229fn add_aspect_expression(
230 info: &mut MultilinearInfo,
231 aspects: &mut AspectMap,
232 line: &str,
233) -> Result<(), AspectExpressionError> {
234 let line = line.trim();
235 if line.is_empty() {
236 return Ok(());
237 }
238 let Some((aspect, default_value)) = line.split_once(':') else {
239 return Err(AspectExpressionError::InvalidAspectDefault(line.into()));
240 };
241 if let Err(err) = add_new_aspect(info, aspects, aspect, default_value) {
242 return Err(AspectExpressionError::AddingAspect(err));
243 }
244
245 Ok(())
246}
247
248#[derive(Default)]
250pub struct NamedMultilinearInfo {
251 pub info: MultilinearInfo,
253 pub events: IndexMap<Event, Vec<Str>>,
255 pub aspects: AspectMap,
257}
258
259#[derive(Default)]
262pub struct MultilinearParser(NamedMultilinearInfo);
263
264impl MultilinearParser {
265 #[inline]
269 pub fn add_new_aspect(
270 &mut self,
271 aspect_name: &str,
272 default_name: &str,
273 ) -> Result<Aspect, AspectAddingError> {
274 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
275
276 add_new_aspect(info, aspects, aspect_name, default_name)
277 }
278
279 #[inline]
283 pub fn add_aspect_expression(&mut self, line: &str) -> Result<(), AspectExpressionError> {
284 let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
285
286 add_aspect_expression(info, aspects, line)
287 }
288
289 pub fn parse<R: Read>(&mut self, reader: R, parent_namespace: &[Str]) -> Result<(), Error> {
307 let mut child_namespace = Vec::new();
308
309 let NamedMultilinearInfo {
310 info,
311 events,
312 aspects,
313 } = &mut self.0;
314
315 let mut condition_groups = Vec::new();
316 let mut condition_lines = Vec::new();
317
318 let mut last_header_line = 0;
319
320 for (line_number, line) in BufReader::new(reader).lines().enumerate() {
321 let line_number = line_number + 1;
322 let Ok(line) = line else {
323 return Err(ErrorKind::LineParsing.line(line_number));
324 };
325
326 if line.trim().is_empty() {
327 if !condition_lines.is_empty() {
328 condition_groups.push(LogicalExpression::and(condition_lines));
329 condition_lines = Vec::new();
330 }
331 continue;
332 }
333
334 if let Some(success) = parse_header(&mut child_namespace, &line) {
335 let Ok(changes) = success else {
336 return Err(ErrorKind::SubheaderWithoutHeader.line(line_number));
337 };
338
339 if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
340 return Err(ErrorKind::InvalidCharacterInEventName(c)).line(line_number);
341 }
342
343 if !condition_lines.is_empty() {
344 condition_groups.push(LogicalExpression::and(condition_lines));
345 condition_lines = Vec::new();
346 }
347
348 if !condition_groups.is_empty() {
349 let mut event_edit = info.add_event();
350 for conditions in LogicalExpression::or(condition_groups).expand() {
351 if let Err(err) = event_edit.add_change(&conditions) {
352 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
353 }
354 }
355
356 let mut namespace = parent_namespace.to_vec();
357 namespace.extend(changes.path.clone());
358 events.insert(event_edit.event(), namespace);
359
360 condition_groups = Vec::new();
361 }
362
363 last_header_line = line_number + 1;
364
365 changes.apply();
366
367 continue;
368 }
369
370 if parent_namespace.is_empty() && child_namespace.is_empty() {
371 if let Err(err) = add_aspect_expression(info, aspects, &line) {
372 return Err(ErrorKind::AddingAspectExpression(err).line(line_number));
373 }
374 continue;
375 }
376
377 let line: &str = line.split_once('#').map_or(&line, |(line, _comment)| line);
378
379 let parse_expression = |condition: &str| {
380 let Some((aspect, changes)) = condition.split_once(':') else {
381 return Err(ConditionParsingError::InvalidCondition);
382 };
383
384 let (aspect, value_names) = aspect_info(aspects, aspect.trim(), info)?;
385 Ok(LogicalExpression::or(
386 changes
387 .split(';')
388 .map(|change| -> Result<_, ValueCheckingError> {
389 Ok(LogicalExpression::Condition(
390 if let Some((from, to)) = change.split_once('>') {
391 let from = value_index(value_names, from)?;
392 let to = value_index(value_names, to)?;
393 Change::transition(aspect, from, to)
394 } else {
395 let change = value_index(value_names, change)?;
396 Change::condition(aspect, change)
397 },
398 ))
399 })
400 .collect::<Result<_, _>>()?,
401 ))
402 };
403
404 let conditions = LogicalExpression::parse_with_expression(line, parse_expression);
405
406 let conditions = match conditions {
407 Ok(conditions) => conditions,
408 Err(err) => return Err(ErrorKind::ExpressionParsing(err).line(line_number)),
409 };
410
411 condition_lines.push(conditions);
412 }
413
414 if !condition_lines.is_empty() {
415 condition_groups.push(LogicalExpression::and(condition_lines));
416 }
417
418 if !condition_groups.is_empty() {
419 let mut event_edit = info.add_event();
420 for conditions in LogicalExpression::or(condition_groups).expand() {
421 if let Err(err) = event_edit.add_change(&conditions) {
422 return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
423 }
424 }
425
426 let mut namespace = parent_namespace.to_vec();
427 namespace.extend(child_namespace);
428 events.insert(event_edit.event(), namespace);
429 }
430
431 Ok(())
432 }
433
434 pub fn into_info(self) -> NamedMultilinearInfo {
438 self.0
439 }
440}
441
442pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
456 let mut result = MultilinearParser::default();
457 result.parse(reader, &[])?;
458 Ok(result.0)
459}
460
461mod extended;
462
463pub use extended::{
464 AspectError, AspectErrorKind, DirectoryOrFileError, DirectoryOrFileErrorKind, ExtendedError,
465 parse_multilinear_extended,
466};