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 channel 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
25use header_parsing::parse_header;
26use logical_expressions::{LogicalExpression, ParseError};
27use thiserror::Error;
28
29use multilinear::{Channel, Condition, Event, EventInfo, InvalidChangeError, MultilinearInfo};
30
31use std::{
32    io::{BufRead, BufReader, Read},
33    marker::PhantomData,
34};
35
36/// Stores index based maps.
37pub struct IndexMap<I: Into<usize> + From<usize>, V> {
38    entries: Vec<V>,
39    index: PhantomData<I>,
40}
41
42impl<I: Into<usize> + From<usize>, V> Default for IndexMap<I, V> {
43    fn default() -> Self {
44        Self {
45            entries: Vec::new(),
46            index: PhantomData,
47        }
48    }
49}
50
51impl<I: Into<usize> + From<usize>, V> IndexMap<I, V> {
52    fn insert(&mut self, index: I, value: V) {
53        let i = index.into();
54        assert_eq!(i, self.entries.len(), "Wrong insertion order");
55        self.entries.push(value);
56    }
57}
58
59/// An iterator over indices and values of an index map.
60pub struct IntoIter<I: Into<usize> + From<usize>, V> {
61    data: std::iter::Enumerate<std::vec::IntoIter<V>>,
62    index: PhantomData<I>,
63}
64
65impl<I: Into<usize> + From<usize>, V> Iterator for IntoIter<I, V> {
66    type Item = (I, V);
67
68    fn next(&mut self) -> Option<(I, V)> {
69        let (i, value) = self.data.next()?;
70        Some((i.into(), value))
71    }
72}
73
74impl<I: Into<usize> + From<usize>, V> IntoIterator for IndexMap<I, V> {
75    type Item = (I, V);
76    type IntoIter = IntoIter<I, V>;
77
78    fn into_iter(self) -> IntoIter<I, V> {
79        IntoIter {
80            data: self.entries.into_iter().enumerate(),
81            index: PhantomData,
82        }
83    }
84}
85
86#[derive(Copy, Clone, Debug)]
87struct ValueCheckingError(char);
88
89type Str = Box<str>;
90
91fn check_name(name: &str) -> Result<(), ValueCheckingError> {
92    if let Some(c) = name
93        .chars()
94        .find(|&c| !c.is_alphanumeric() && !"_- ".contains(c))
95    {
96        Err(ValueCheckingError(c))
97    } else {
98        Ok(())
99    }
100}
101
102fn valid_name(name: &str) -> Result<&str, ValueCheckingError> {
103    let name = name.trim();
104    check_name(name)?;
105    Ok(name)
106}
107
108fn value_index(value_names: &mut Vec<Str>, name: &str) -> Result<usize, ValueCheckingError> {
109    let name = valid_name(name)?;
110
111    if let Some(index) = value_names.iter().position(|x| x.as_ref() == name) {
112        return Ok(index);
113    }
114
115    let index = value_names.len();
116    value_names.push(name.into());
117    Ok(index)
118}
119
120fn channel_info<'a>(
121    channels: &'a mut IndexMap<Channel, (Str, Vec<Str>)>,
122    name: &str,
123    info: &mut MultilinearInfo,
124) -> Result<(Channel, &'a mut Vec<Str>), ValueCheckingError> {
125    let name = valid_name(name)?;
126
127    if let Some(i) = channels
128        .entries
129        .iter()
130        .position(|(checked_name, _)| checked_name.as_ref() == name)
131    {
132        return Ok((Channel(i), &mut channels.entries[i].1));
133    }
134
135    let channel = info.add_channel();
136    channels.insert(channel, (name.into(), Vec::new()));
137
138    let (_, value_names) = channels.entries.last_mut().unwrap();
139
140    let _ = value_index(value_names, "");
141
142    Ok((channel, value_names))
143}
144
145/// Represents errors that can occur when parsing conditions.
146#[derive(Copy, Clone, Debug, Error)]
147pub enum ConditionParsingError {
148    /// Indicates an invalid character was encountered.
149    #[error("Invalid character '{0}' for condition names")]
150    InvalidCharacter(char),
151
152    /// Indicates an invalid condition format.
153    #[error("Invalid condition format")]
154    InvalidCondition,
155}
156
157impl From<ValueCheckingError> for ConditionParsingError {
158    fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
159        Self::InvalidCharacter(c)
160    }
161}
162
163/// Represents the kinds of errors that can occur when parsing a line.
164#[derive(Copy, Clone, Debug, Error)]
165pub enum ErrorKind {
166    /// Indicates an error occurred while parsing the line.
167    #[error("Input error while parsing line")]
168    LineParsing,
169
170    /// Indicates an error occurred while parsing an expression.
171    #[error("Parsing expression failed: {0}")]
172    ExpressionParsing(ParseError<ConditionParsingError>),
173
174    /// Indicates conflicting conditions were encountered.
175    #[error("Encountered conflicting conditions: {0}")]
176    ConflictingCondition(InvalidChangeError),
177
178    /// Indicates an invalid character was encountered while parsing the event name.
179    #[error("Invalid character '{0}' in event name")]
180    InvalidCharacterInEventName(char),
181
182    /// Indicates no event was specified.
183    #[error("No event has been specified")]
184    NoEvent,
185
186    /// Indicates a subheader was encountered without a corresponding header.
187    #[error("Subheader without matching header")]
188    SubheaderWithoutHeader,
189}
190
191trait ErrorLine {
192    type Output;
193
194    fn line(self, line: usize) -> Self::Output;
195}
196
197impl ErrorLine for ErrorKind {
198    type Output = Error;
199
200    fn line(self, line: usize) -> Error {
201        Error { line, kind: self }
202    }
203}
204
205impl<T> ErrorLine for Result<T, ErrorKind> {
206    type Output = Result<T, Error>;
207
208    fn line(self, line: usize) -> Result<T, Error> {
209        match self {
210            Ok(value) => Ok(value),
211            Err(err) => Err(err.line(line)),
212        }
213    }
214}
215
216/// Represents errors that can occur during parsing.
217#[derive(Debug, Error)]
218#[error("Line {line}: {kind}")]
219pub struct Error {
220    /// The line the error occured on.
221    line: usize,
222    /// The error kind.
223    kind: ErrorKind,
224}
225
226/// A multilinear info containing the mapped channel and event names.
227#[derive(Default)]
228pub struct NamedMultilinearInfo {
229    /// The parsed `MultilinearInfo` instance.
230    pub info: MultilinearInfo,
231    /// A map associating events with their names.
232    pub events: IndexMap<Event, Vec<Str>>,
233    /// A map associating channels with their names and the names of the channel.
234    pub channels: IndexMap<Channel, (Str, Vec<Str>)>,
235}
236
237/// A parser for multilinear system definitions, supporting incremental parsing
238/// across multiple files or input streams.
239#[derive(Default)]
240pub struct MultilinearParser(NamedMultilinearInfo);
241
242impl MultilinearParser {
243    /// Parses additional multilinear data from the given reader.
244    ///
245    /// # Arguments
246    ///
247    /// * `reader` - The input source to parse from
248    /// * `namespace` - Initial header context/path for events (e.g., `vec!["Main Story".into()]`)
249    ///
250    /// # Example
251    ///
252    /// ```no_run
253    /// use std::fs::File;
254    /// use multilinear_parser::MultilinearParser;
255    ///
256    /// let mut parser = MultilinearParser::default();
257    /// parser.parse(File::open("chapter1.mld").unwrap(), Vec::new()).unwrap();
258    /// parser.parse(File::open("chapter2.mld").unwrap(), Vec::new()).unwrap();
259    /// ```
260    pub fn parse<R: Read>(&mut self, reader: R, mut namespace: Vec<Str>) -> Result<(), Error> {
261        let NamedMultilinearInfo {
262            info,
263            events,
264            channels,
265        } = &mut self.0;
266
267        let mut condition_groups = Vec::new();
268        let mut condition_lines = Vec::new();
269
270        let mut last_header_line = 0;
271
272        for (line_number, line) in BufReader::new(reader).lines().enumerate() {
273            let Ok(line) = line else {
274                return Err(ErrorKind::LineParsing.line(line_number));
275            };
276
277            if line.trim().is_empty() {
278                if !condition_lines.is_empty() {
279                    condition_groups.push(LogicalExpression::and(condition_lines));
280                    condition_lines = Vec::new();
281                }
282                continue;
283            }
284
285            if let Some(success) = parse_header(&mut namespace, &line) {
286                let Ok(changes) = success else {
287                    return Err(ErrorKind::SubheaderWithoutHeader.line(line_number));
288                };
289
290                if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
291                    return Err(ErrorKind::InvalidCharacterInEventName(c)).line(line_number);
292                }
293
294                if !condition_lines.is_empty() {
295                    condition_groups.push(LogicalExpression::and(condition_lines));
296                    condition_lines = Vec::new();
297                }
298
299                if !condition_groups.is_empty() {
300                    let mut event = EventInfo::new();
301                    for conditions in LogicalExpression::or(condition_groups).expand() {
302                        if let Err(err) = event.add_change(&conditions) {
303                            return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
304                        }
305                    }
306
307                    let event = info.add_event(event);
308                    events.insert(event, changes.path.clone());
309
310                    condition_groups = Vec::new();
311                }
312
313                last_header_line = line_number + 1;
314
315                changes.apply();
316
317                continue;
318            }
319
320            if namespace.is_empty() {
321                return Err(ErrorKind::NoEvent.line(line_number));
322            }
323
324            let parse_conditions = |condition: &str| {
325                let Some((channel, change)) = condition.split_once(':') else {
326                    return Err(ConditionParsingError::InvalidCondition);
327                };
328
329                let (channel, value_names) = channel_info(channels, channel.trim(), info)?;
330                Ok(if let Some((from, to)) = change.split_once('>') {
331                    let from = value_index(value_names, from)?;
332                    let to = value_index(value_names, to)?;
333                    Condition::change(channel, from, to)
334                } else {
335                    let change = value_index(value_names, change)?;
336                    Condition::new(channel, change)
337                })
338            };
339
340            let conditions = LogicalExpression::parse_with(&line, parse_conditions);
341
342            let conditions = match conditions {
343                Ok(conditions) => conditions,
344                Err(err) => return Err(ErrorKind::ExpressionParsing(err).line(line_number)),
345            };
346
347            condition_lines.push(conditions);
348        }
349
350        if !condition_lines.is_empty() {
351            condition_groups.push(LogicalExpression::and(condition_lines));
352        }
353
354        if !condition_groups.is_empty() {
355            let mut event = EventInfo::new();
356            for conditions in LogicalExpression::or(condition_groups).expand() {
357                if let Err(err) = event.add_change(&conditions) {
358                    return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
359                }
360            }
361
362            let event = info.add_event(event);
363            events.insert(event, namespace);
364        }
365
366        Ok(())
367    }
368
369    /// Consumes the parser and returns the fully parsed data.
370    ///
371    /// After calling this, the parser can no longer be used.
372    pub fn into_info(self) -> NamedMultilinearInfo {
373        self.0
374    }
375}
376
377/// Parses a complete multilinear system from a single reader.
378///
379/// This is a convenience wrapper for single-file parsing. For multi-file parsing,
380/// use [`MultilinearParser`] directly.
381///
382/// # Example
383///
384/// ```no_run
385/// use std::fs::File;
386/// use multilinear_parser::parse_multilinear;
387///
388/// let story = parse_multilinear(File::open("story.mld").unwrap()).unwrap();
389/// ```
390pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
391    let mut result = MultilinearParser::default();
392    result.parse(reader, Vec::new())?;
393    Ok(result.0)
394}