#![deny(missing_docs)]
pub use index_map::IndexMap;
use header_parsing::parse_header;
use logical_expressions::{LogicalExpression, ParseError};
use thiserror::Error;
use multilinear::{Aspect, Change, Event, InvalidChangeError, MultilinearInfo};
use std::io::{BufRead, BufReader, Read};
mod index_map;
#[derive(Copy, Clone, Debug)]
struct ValueCheckingError(char);
type Str = Box<str>;
fn check_name(name: &str) -> Result<(), ValueCheckingError> {
if let Some(c) = name
.chars()
.find(|&c| !c.is_alphanumeric() && !"_- ".contains(c))
{
Err(ValueCheckingError(c))
} else {
Ok(())
}
}
fn valid_name(name: &str) -> Result<&str, ValueCheckingError> {
let name = name.trim();
check_name(name)?;
Ok(name)
}
fn value_index(value_names: &mut Vec<Str>, name: &str) -> Result<usize, ValueCheckingError> {
let name = valid_name(name)?;
if let Some(index) = value_names.iter().position(|x| x.as_ref() == name) {
return Ok(index);
}
let index = value_names.len();
value_names.push(name.into());
Ok(index)
}
fn aspect_info<'a>(
aspects: &'a mut AspectMap,
name: &str,
info: &mut MultilinearInfo,
) -> Result<(Aspect, &'a mut Vec<Str>), ValueCheckingError> {
let name = valid_name(name)?;
if let Some(i) = aspects
.entries
.iter()
.position(|(checked_name, _)| checked_name.as_ref() == name)
{
return Ok((Aspect(i), &mut aspects.entries[i].1));
}
let aspect = info.add_aspect();
aspects.insert(aspect, (name.into(), vec!["".into()]));
let (_, value_names) = aspects.entries.last_mut().unwrap();
Ok((aspect, value_names))
}
#[derive(Debug, Error)]
pub enum AspectAddingError {
#[error("An aspect of this name already exists")]
AlreadyExists,
#[error("Invalid character '{0}' for condition names")]
InvalidCharacter(char),
}
impl From<ValueCheckingError> for AspectAddingError {
fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
Self::InvalidCharacter(c)
}
}
#[derive(Debug, Error)]
pub enum AspectExpressionError {
#[error("Error adding default aspect: {0}")]
AddingAspect(#[source] AspectAddingError),
#[error("Invalid aspect default: {0}")]
InvalidAspectDefault(Box<str>),
}
#[derive(Copy, Clone, Debug, Error)]
pub enum ConditionParsingError {
#[error("Invalid character '{0}' for condition names")]
InvalidCharacter(char),
#[error("Invalid condition format")]
InvalidCondition,
}
impl From<ValueCheckingError> for ConditionParsingError {
fn from(ValueCheckingError(c): ValueCheckingError) -> Self {
Self::InvalidCharacter(c)
}
}
#[derive(Debug, Error)]
pub enum ErrorKind {
#[error("Input error while parsing line")]
LineParsing,
#[error("Parsing expression failed: {0}")]
ExpressionParsing(ParseError<ConditionParsingError>),
#[error("Encountered conflicting conditions: {0}")]
ConflictingCondition(InvalidChangeError),
#[error("Invalid character '{0}' in event name")]
InvalidCharacterInEventName(char),
#[error("{0}")]
AddingAspectExpression(#[source] AspectExpressionError),
#[error("Subheader without matching header")]
SubheaderWithoutHeader,
}
trait ErrorLine {
type Output;
fn line(self, line: usize) -> Self::Output;
}
impl ErrorLine for ErrorKind {
type Output = Error;
fn line(self, line: usize) -> Error {
Error { line, kind: self }
}
}
impl<T> ErrorLine for Result<T, ErrorKind> {
type Output = Result<T, Error>;
fn line(self, line: usize) -> Result<T, Error> {
match self {
Ok(value) => Ok(value),
Err(err) => Err(err.line(line)),
}
}
}
#[derive(Debug, Error)]
#[error("Line {line}: {kind}")]
pub struct Error {
line: usize,
kind: ErrorKind,
}
type AspectMap = IndexMap<Aspect, (Str, Vec<Str>)>;
fn add_new_aspect(
info: &mut MultilinearInfo,
aspects: &mut AspectMap,
aspect_name: &str,
default_name: &str,
) -> Result<Aspect, AspectAddingError> {
let aspect_name = valid_name(aspect_name)?;
let default_name = valid_name(default_name)?;
if aspects
.entries
.iter()
.any(|(checked_name, _)| checked_name.as_ref() == aspect_name)
{
return Err(AspectAddingError::AlreadyExists);
}
let aspect = info.add_aspect();
aspects.insert(aspect, (aspect_name.into(), vec![default_name.into()]));
Ok(aspect)
}
fn add_aspect_expression(
info: &mut MultilinearInfo,
aspects: &mut AspectMap,
line: &str,
) -> Result<(), AspectExpressionError> {
let line = line.trim();
if line.is_empty() {
return Ok(());
}
let Some((aspect, default_value)) = line.split_once(':') else {
return Err(AspectExpressionError::InvalidAspectDefault(line.into()));
};
if let Err(err) = add_new_aspect(info, aspects, aspect, default_value) {
return Err(AspectExpressionError::AddingAspect(err));
}
Ok(())
}
#[derive(Default)]
pub struct NamedMultilinearInfo {
pub info: MultilinearInfo,
pub events: IndexMap<Event, Vec<Str>>,
pub aspects: AspectMap,
}
#[derive(Default)]
pub struct MultilinearParser(NamedMultilinearInfo);
impl MultilinearParser {
#[inline]
pub fn add_new_aspect(
&mut self,
aspect_name: &str,
default_name: &str,
) -> Result<Aspect, AspectAddingError> {
let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
add_new_aspect(info, aspects, aspect_name, default_name)
}
#[inline]
pub fn add_aspect_expression(&mut self, line: &str) -> Result<(), AspectExpressionError> {
let NamedMultilinearInfo { info, aspects, .. } = &mut self.0;
add_aspect_expression(info, aspects, line)
}
pub fn parse<R: Read>(&mut self, reader: R, parent_namespace: &[Str]) -> Result<(), Error> {
let mut child_namespace = Vec::new();
let NamedMultilinearInfo {
info,
events,
aspects,
} = &mut self.0;
let mut condition_groups = Vec::new();
let mut condition_lines = Vec::new();
let mut last_header_line = 0;
for (line_number, line) in BufReader::new(reader).lines().enumerate() {
let line_number = line_number + 1;
let Ok(line) = line else {
return Err(ErrorKind::LineParsing.line(line_number));
};
if line.trim().is_empty() {
if !condition_lines.is_empty() {
condition_groups.push(LogicalExpression::and(condition_lines));
condition_lines = Vec::new();
}
continue;
}
if let Some(success) = parse_header(&mut child_namespace, &line) {
let Ok(changes) = success else {
return Err(ErrorKind::SubheaderWithoutHeader.line(line_number));
};
if let Err(ValueCheckingError(c)) = check_name(&changes.header) {
return Err(ErrorKind::InvalidCharacterInEventName(c)).line(line_number);
}
if !condition_lines.is_empty() {
condition_groups.push(LogicalExpression::and(condition_lines));
condition_lines = Vec::new();
}
if !condition_groups.is_empty() {
let mut event_edit = info.add_event();
for conditions in LogicalExpression::or(condition_groups).expand() {
if let Err(err) = event_edit.add_change(&conditions) {
return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
}
}
let mut namespace = parent_namespace.to_vec();
namespace.extend(changes.path.clone());
events.insert(event_edit.event(), namespace);
condition_groups = Vec::new();
}
last_header_line = line_number + 1;
changes.apply();
continue;
}
if parent_namespace.is_empty() && child_namespace.is_empty() {
if let Err(err) = add_aspect_expression(info, aspects, &line) {
return Err(ErrorKind::AddingAspectExpression(err).line(line_number));
}
continue;
}
let line: &str = line.split_once('#').map_or(&line, |(line, _comment)| line);
let parse_expression = |condition: &str| {
let Some((aspect, changes)) = condition.split_once(':') else {
return Err(ConditionParsingError::InvalidCondition);
};
let (aspect, value_names) = aspect_info(aspects, aspect.trim(), info)?;
Ok(LogicalExpression::or(
changes
.split(';')
.map(|change| -> Result<_, ValueCheckingError> {
Ok(LogicalExpression::Condition(
if let Some((from, to)) = change.split_once('>') {
let from = value_index(value_names, from)?;
let to = value_index(value_names, to)?;
Change::transition(aspect, from, to)
} else {
let change = value_index(value_names, change)?;
Change::condition(aspect, change)
},
))
})
.collect::<Result<_, _>>()?,
))
};
let conditions = LogicalExpression::parse_with_expression(line, parse_expression);
let conditions = match conditions {
Ok(conditions) => conditions,
Err(err) => return Err(ErrorKind::ExpressionParsing(err).line(line_number)),
};
condition_lines.push(conditions);
}
if !condition_lines.is_empty() {
condition_groups.push(LogicalExpression::and(condition_lines));
}
if !condition_groups.is_empty() {
let mut event_edit = info.add_event();
for conditions in LogicalExpression::or(condition_groups).expand() {
if let Err(err) = event_edit.add_change(&conditions) {
return Err(ErrorKind::ConflictingCondition(err).line(last_header_line));
}
}
let mut namespace = parent_namespace.to_vec();
namespace.extend(child_namespace);
events.insert(event_edit.event(), namespace);
}
Ok(())
}
pub fn into_info(self) -> NamedMultilinearInfo {
self.0
}
}
pub fn parse_multilinear<R: Read>(reader: R) -> Result<NamedMultilinearInfo, Error> {
let mut result = MultilinearParser::default();
result.parse(reader, &[])?;
Ok(result.0)
}
mod extended;
pub use extended::{
AspectError, AspectErrorKind, DirectoryOrFileError, DirectoryOrFileErrorKind, ExtendedError,
parse_multilinear_extended,
};