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    collections::HashMap,
33    io::{BufRead, BufReader, Read},
34};
35
36#[derive(Copy, Clone, Debug)]
37struct ValueCheckingError(char);
38
39type Str = Box<str>;
40
41fn check_name(name: &str) -> Result<(), ValueCheckingError> {
42    if let Some(c) = name
43        .chars()
44        .find(|&c| !c.is_alphanumeric() && !"_- ".contains(c))
45    {
46        Err(ValueCheckingError(c))
47    } else {
48        Ok(())
49    }
50}
51
52fn valid_name(name: &str) -> Result<&str, ValueCheckingError> {
53    let name = name.trim();
54    check_name(name)?;
55    Ok(name)
56}
57
58struct ValueMap {
59    map: HashMap<Str, usize>,
60}
61
62impl ValueMap {
63    fn into_array(self) -> Box<[Str]> {
64        let mut result = vec!["".into(); self.map.len()].into_boxed_slice();
65        for (name, index) in self.map {
66            result[index] = name;
67        }
68        result
69    }
70}
71
72impl ValueMap {
73    fn new() -> Self {
74        let mut result = Self {
75            map: HashMap::new(),
76        };
77        let _ = result.index("");
78        result
79    }
80
81    fn index(&mut self, name: &str) -> Result<usize, ValueCheckingError> {
82        let name = valid_name(name)?;
83
84        if !self.map.contains_key(name) {
85            let index = self.map.len();
86            self.map.insert(name.into(), index);
87        }
88
89        let name = self.map.get_mut(name).unwrap();
90        Ok(*name)
91    }
92}
93
94/// Represents errors that can occur when parsing conditions.
95#[derive(Copy, Clone, Debug, Error)]
96pub enum ConditionParsingError {
97    /// Indicates an invalid character was encountered.
98    #[error("Invalid character '{0}' for condition names")]
99    InvalidCharacter(char),
100
101    /// Indicates an invalid condition format.
102    #[error("Invalid condition format")]
103    InvalidCondition,
104}
105
106impl From<ValueCheckingError> for ConditionParsingError {
107    fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
108        Self::InvalidCharacter(c)
109    }
110}
111
112/// Represents the kinds of errors that can occur when parsing a line.
113#[derive(Copy, Clone, Debug, Error)]
114pub enum LineErrorKind {
115    /// Indicates an error occurred while parsing the line.
116    #[error("Input error while parsing line")]
117    LineParsing,
118
119    /// Indicates an error occurred while parsing an expression.
120    #[error("Parsing expression failed: {0}")]
121    ExpressionParsing(ParseError<ConditionParsingError>),
122
123    /// Indicates conflicting conditions were encountered.
124    #[error("Encountered conflicting conditions: {0}")]
125    ConflictingCondition(InvalidChangeError),
126
127    /// Indicates an invalid character was encountered while parsing the event name.
128    #[error("Invalid character '{0}' in event name")]
129    InvalidCharacterInEventName(char),
130
131    /// Indicates no event was specified.
132    #[error("No event has been specified")]
133    NoEvent,
134
135    /// Indicates a subheader was encountered without a corresponding header.
136    #[error("Subheader without matching header")]
137    SubheaderWithoutHeader,
138}
139
140trait ErrorLine {
141    type Output;
142
143    fn line(self, line: usize) -> Self::Output;
144}
145
146impl ErrorLine for LineErrorKind {
147    type Output = Error;
148
149    fn line(self, line: usize) -> Error {
150        Error::Line { line, kind: self }
151    }
152}
153
154impl<T> ErrorLine for Result<T, LineErrorKind> {
155    type Output = Result<T, Error>;
156
157    fn line(self, line: usize) -> Result<T, Error> {
158        match self {
159            Ok(value) => Ok(value),
160            Err(err) => Err(err.line(line)),
161        }
162    }
163}
164
165/// Represents errors that can occur during parsing.
166#[derive(Debug, Error)]
167pub enum Error {
168    /// Indicates an error occurred on a specific line.
169    #[error("Line {line}: {kind}")]
170    Line {
171        /// The line the error occured on.
172        line: usize,
173        /// The error kind.
174        kind: LineErrorKind,
175    },
176
177    /// Indicates conflicting conditions were encountered.
178    #[error("Conflicting conditions detected")]
179    ConflictingCondition,
180}
181
182struct ChannelMap {
183    map: HashMap<Str, (Channel, ValueMap)>,
184}
185
186impl ChannelMap {
187    fn new() -> Self {
188        Self {
189            map: HashMap::new(),
190        }
191    }
192
193    fn index(
194        &mut self,
195        name: &str,
196        info: &mut MultilinearInfo,
197    ) -> Result<(Channel, &mut ValueMap), ValueCheckingError> {
198        let name = valid_name(name)?;
199
200        if !self.map.contains_key(name) {
201            let channel = info.add_channel();
202            self.map.insert(name.into(), (channel, ValueMap::new()));
203        }
204
205        let (name, value) = self.map.get_mut(name).unwrap();
206        Ok((*name, value))
207    }
208}
209
210/// A multilinear info containing the mapped channel and event names.
211pub struct NamedMultilinearInfo {
212    /// The parsed `MultilinearInfo` instance.
213    pub info: MultilinearInfo,
214    /// A map associating events with their names.
215    pub events: HashMap<Event, Vec<Str>>,
216    /// A map associating channels with their names and the names of the channel.
217    pub channels: HashMap<Channel, (Str, Box<[Str]>)>,
218}
219
220/// Parses a multilinear system from a reader.
221///
222/// This function reads the multilinear system definition from the provided reader and builds a `NamedMultilinearInfo` instance.
223/// It returns the parsed `NamedMultilinearInfo` if successful, or an `Error` if parsing fails.
224pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
225    let mut info = MultilinearInfo::new();
226    let mut current_event_name = Vec::new();
227    let mut event_names = HashMap::new();
228    let mut channel_names = ChannelMap::new();
229
230    let mut condition_groups = Vec::new();
231    let mut condition_lines = Vec::new();
232
233    for (line_number, line) in BufReader::new(reader).lines().enumerate() {
234        let Ok(line) = line else {
235            return Err(LineErrorKind::LineParsing.line(line_number));
236        };
237
238        if line.trim().is_empty() {
239            if !condition_lines.is_empty() {
240                condition_groups.push(LogicalExpression::and(condition_lines));
241                condition_lines = Vec::new();
242            }
243            continue;
244        }
245
246        if let Some(success) = parse_header(&mut current_event_name, &line) {
247            let Ok(changes) = success else {
248                return Err(LineErrorKind::SubheaderWithoutHeader.line(line_number));
249            };
250
251            if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
252                return Err(LineErrorKind::InvalidCharacterInEventName(c)).line(line_number);
253            }
254
255            if !condition_lines.is_empty() {
256                condition_groups.push(LogicalExpression::and(condition_lines));
257                condition_lines = Vec::new();
258            }
259
260            if !condition_groups.is_empty() {
261                let mut event = EventInfo::new();
262                for conditions in LogicalExpression::or(condition_groups).expand() {
263                    if let Err(err) = event.add_change(&conditions) {
264                        return Err(LineErrorKind::ConflictingCondition(err).line(line_number));
265                    }
266                }
267
268                let event = info.add_event(event);
269                event_names.insert(event, changes.path.clone());
270
271                condition_groups = Vec::new();
272            }
273
274            changes.apply();
275
276            continue;
277        }
278
279        if current_event_name.is_empty() {
280            return Err(LineErrorKind::NoEvent.line(line_number));
281        };
282
283        let parse_conditions = |condition: &str| {
284            let Some((channel, change)) = condition.split_once(':') else {
285                return Err(ConditionParsingError::InvalidCondition);
286            };
287
288            let (channel, value_names) = channel_names.index(channel.trim(), &mut info)?;
289            Ok(if let Some((from, to)) = change.split_once('>') {
290                let from = value_names.index(from)?;
291                let to = value_names.index(to)?;
292                Condition::change(channel, from, to)
293            } else {
294                let change = value_names.index(change)?;
295                Condition::new(channel, change)
296            })
297        };
298
299        let conditions = LogicalExpression::parse_with(&line, parse_conditions);
300
301        let conditions = match conditions {
302            Ok(conditions) => conditions,
303            Err(err) => return Err(LineErrorKind::ExpressionParsing(err).line(line_number)),
304        };
305
306        condition_lines.push(conditions);
307    }
308
309    if !condition_lines.is_empty() {
310        condition_groups.push(LogicalExpression::and(condition_lines));
311    }
312
313    if !condition_groups.is_empty() {
314        let mut event = EventInfo::new();
315        for conditions in LogicalExpression::or(condition_groups).expand() {
316            if event.add_change(&conditions).is_err() {
317                return Err(Error::ConflictingCondition);
318            }
319        }
320
321        let event = info.add_event(event);
322        event_names.insert(event, current_event_name);
323    }
324
325    let channels = channel_names
326        .map
327        .into_iter()
328        .map(|(name, (channel, values))| (channel, (name, values.into_array())))
329        .collect();
330
331    Ok(NamedMultilinearInfo {
332        info,
333        events: event_names,
334        channels,
335    })
336}