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