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
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/// Represents errors that can occur when adding an aspect manually.
96#[derive(Debug, Error)]
97pub enum AspectAddingError {
98    /// Indicades that an aspect with this name has already been added.
99    #[error("An aspect of this name already exists")]
100    AlreadyExists,
101
102    /// Indicates an invalid character was encountered.
103    #[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/// Represents errors that can occur when parsing a single aspect default expression.
114#[derive(Debug, Error)]
115pub enum AspectExpressionError {
116    /// Error while adding a default aspect from the aspects file.
117    #[error("Error adding default aspect: {0}")]
118    AddingAspect(#[source] AspectAddingError),
119    /// An invalid aspect default was found in the aspects file.
120    #[error("Invalid aspect default: {0}")]
121    InvalidAspectDefault(Box<str>),
122}
123
124/// Represents errors that can occur when parsing conditions.
125#[derive(Copy, Clone, Debug, Error)]
126pub enum ConditionParsingError {
127    /// Indicates an invalid character was encountered.
128    #[error("Invalid character '{0}' for condition names")]
129    InvalidCharacter(char),
130
131    /// Indicates an invalid condition format.
132    #[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/// Represents the kinds of errors that can occur when parsing a line.
143#[derive(Debug, Error)]
144pub enum ErrorKind {
145    /// Indicates an error occurred while parsing the line.
146    #[error("Input error while parsing line")]
147    LineParsing,
148
149    /// Indicates an error occurred while parsing an expression.
150    #[error("Parsing expression failed: {0}")]
151    ExpressionParsing(ParseError<ConditionParsingError>),
152
153    /// Indicates conflicting conditions were encountered.
154    #[error("Encountered conflicting conditions: {0}")]
155    ConflictingCondition(InvalidChangeError),
156
157    /// Indicates an invalid character was encountered while parsing the event name.
158    #[error("Invalid character '{0}' in event name")]
159    InvalidCharacterInEventName(char),
160
161    /// Error while adding an aspect default from an expression.
162    #[error("{0}")]
163    AddingAspectExpression(#[source] AspectExpressionError),
164
165    /// Indicates a subheader was encountered without a corresponding header.
166    #[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/// Represents errors that can occur during parsing.
196#[derive(Debug, Error)]
197#[error("Line {line}: {kind}")]
198pub struct Error {
199    /// The line the error occured on.
200    line: usize,
201    /// The error kind.
202    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/// A multilinear info containing the mapped aspect and event names.
250#[derive(Default)]
251pub struct NamedMultilinearInfo {
252    /// The parsed `MultilinearInfo` instance.
253    pub info: MultilinearInfo,
254    /// A map associating events with their names.
255    pub events: IndexMap<Event, Vec<Str>>,
256    /// A map associating aspects with their names and the names of the aspect.
257    pub aspects: AspectMap,
258}
259
260/// A parser for multilinear system definitions, supporting incremental parsing
261/// across multiple files or input streams.
262#[derive(Default)]
263pub struct MultilinearParser(NamedMultilinearInfo);
264
265impl MultilinearParser {
266    /// Adds a new aspect and sets a default value.
267    ///
268    /// # Errors
269    ///
270    /// Returns [`AspectAddingError`] if the aspect already exists or if the names aren't valid.
271    #[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    /// Adds a new aspect as a default value from an expression in the form of `aspect: name`.
283    ///
284    /// # Errors
285    ///
286    /// Returns [`AspectExpressionError`] if the format doesn't match, if the aspect already exists, or if the names aren't valid.
287    #[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    /// Parses additional multilinear data from the given reader.
295    ///
296    /// # Arguments
297    ///
298    /// - `reader` - The input source to parse from
299    /// - `namespace` - Initial header context/path for events (e.g., `vec!["Main Story".into()]`)
300    ///
301    /// # Example
302    ///
303    /// ```no_run
304    /// use std::fs::File;
305    /// use multilinear_parser::MultilinearParser;
306    ///
307    /// let mut parser = MultilinearParser::default();
308    /// parser.parse(File::open("chapter1.mld").unwrap(), &[]).unwrap();
309    /// parser.parse(File::open("chapter2.mld").unwrap(), &[]).unwrap();
310    /// ```
311    ///
312    /// # Errors
313    ///
314    /// Returns [`Error`] if the input can't be read or its content doesn't parse as a valid multilinear definition.
315    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    /// Consumes the parser and returns the fully parsed data.
447    ///
448    /// After calling this, the parser can no longer be used.
449    #[must_use]
450    pub fn into_info(self) -> NamedMultilinearInfo {
451        self.0
452    }
453}
454
455/// Parses a complete multilinear system from a single reader.
456///
457/// This is a convenience wrapper for single-file parsing. For multi-file parsing,
458/// use [`MultilinearParser`] directly.
459///
460/// # Example
461///
462/// ```no_run
463/// use std::fs::File;
464/// use multilinear_parser::parse_multilinear;
465///
466/// let story = parse_multilinear(File::open("story.mld").unwrap()).unwrap();
467/// ```
468///
469/// # Errors
470///
471/// Returns [`Error`] if the input can't be read or its content doesn't parse as a valid multilinear definition.
472pub 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};