Skip to main content

multilinear_parser/
lib.rs

1#![deny(missing_docs)]
2
3//! The `multilinear-parser` library provides functionality to parse a multilinear system from a text-based format.
4//! It allows you to define events, which rely on various aspect specific conditions or changes using a markdown inspired syntax.
5//!
6//! Example Event Syntax:
7//!
8//! ```text
9//! # Move to Livingroom
10//!
11//! place: bedroom > livingroom
12//!
13//! # Get Dressed
14//!
15//! place: bedroom
16//! clothes: pajamas > casual
17//! ```
18//!
19//! Supports logical combinations:
20//!
21//! ```text
22//! (clothes: pajamas | clothes: casual) & place: bedroom
23//! ```
24
25pub 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/// Represents errors that can occur when adding an aspect manually.
95#[derive(Debug, Error)]
96pub enum AspectAddingError {
97    /// Indicades that an aspect with this name has already been added.
98    #[error("An aspect of this name already exists")]
99    AlreadyExists,
100
101    /// Indicates an invalid character was encountered.
102    #[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/// Represents errors that can occur when parsing a single aspect default expression.
113#[derive(Debug, Error)]
114pub enum AspectExpressionError {
115    /// Error while adding a default aspect from the aspects file.
116    #[error("Error adding default aspect: {0}")]
117    AddingAspect(#[source] AspectAddingError),
118    /// An invalid aspect default was found in the aspects file.
119    #[error("Invalid aspect default: {0}")]
120    InvalidAspectDefault(Box<str>),
121}
122
123/// Represents errors that can occur when parsing conditions.
124#[derive(Copy, Clone, Debug, Error)]
125pub enum ConditionParsingError {
126    /// Indicates an invalid character was encountered.
127    #[error("Invalid character '{0}' for condition names")]
128    InvalidCharacter(char),
129
130    /// Indicates an invalid condition format.
131    #[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/// Represents the kinds of errors that can occur when parsing a line.
142#[derive(Debug, Error)]
143pub enum ErrorKind {
144    /// Indicates an error occurred while parsing the line.
145    #[error("Input error while parsing line")]
146    LineParsing,
147
148    /// Indicates an error occurred while parsing an expression.
149    #[error("Parsing expression failed: {0}")]
150    ExpressionParsing(ParseError<ConditionParsingError>),
151
152    /// Indicates conflicting conditions were encountered.
153    #[error("Encountered conflicting conditions: {0}")]
154    ConflictingCondition(InvalidChangeError),
155
156    /// Indicates an invalid character was encountered while parsing the event name.
157    #[error("Invalid character '{0}' in event name")]
158    InvalidCharacterInEventName(char),
159
160    /// Error while adding an aspect default from an expression.
161    #[error("{0}")]
162    AddingAspectExpression(#[source] AspectExpressionError),
163
164    /// Indicates a subheader was encountered without a corresponding header.
165    #[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/// Represents errors that can occur during parsing.
195#[derive(Debug, Error)]
196#[error("Line {line}: {kind}")]
197pub struct Error {
198    /// The line the error occured on.
199    line: usize,
200    /// The error kind.
201    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/// A multilinear info containing the mapped aspect and event names.
249#[derive(Default)]
250pub struct NamedMultilinearInfo {
251    /// The parsed `MultilinearInfo` instance.
252    pub info: MultilinearInfo,
253    /// A map associating events with their names.
254    pub events: IndexMap<Event, Vec<Str>>,
255    /// A map associating aspects with their names and the names of the aspect.
256    pub aspects: AspectMap,
257}
258
259/// A parser for multilinear system definitions, supporting incremental parsing
260/// across multiple files or input streams.
261#[derive(Default)]
262pub struct MultilinearParser(NamedMultilinearInfo);
263
264impl MultilinearParser {
265    /// Adds a new aspect and sets a default value.
266    ///
267    /// Fails if aspect already exists or if the names aren't valid.
268    #[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    /// Adds a new aspect as a default value from an expression in the form of `aspect: name`.
280    ///
281    /// Fails if the format doesn't match, if aspect already exists or if the names aren't valid.
282    #[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    /// Parses additional multilinear data from the given reader.
290    ///
291    /// # Arguments
292    ///
293    /// - `reader` - The input source to parse from
294    /// - `namespace` - Initial header context/path for events (e.g., `vec!["Main Story".into()]`)
295    ///
296    /// # Example
297    ///
298    /// ```no_run
299    /// use std::fs::File;
300    /// use multilinear_parser::MultilinearParser;
301    ///
302    /// let mut parser = MultilinearParser::default();
303    /// parser.parse(File::open("chapter1.mld").unwrap(), &[]).unwrap();
304    /// parser.parse(File::open("chapter2.mld").unwrap(), &[]).unwrap();
305    /// ```
306    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    /// Consumes the parser and returns the fully parsed data.
435    ///
436    /// After calling this, the parser can no longer be used.
437    pub fn into_info(self) -> NamedMultilinearInfo {
438        self.0
439    }
440}
441
442/// Parses a complete multilinear system from a single reader.
443///
444/// This is a convenience wrapper for single-file parsing. For multi-file parsing,
445/// use [`MultilinearParser`] directly.
446///
447/// # Example
448///
449/// ```no_run
450/// use std::fs::File;
451/// use multilinear_parser::parse_multilinear;
452///
453/// let story = parse_multilinear(File::open("story.mld").unwrap()).unwrap();
454/// ```
455pub 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};