--- /dev/null
+use std::{iter::Peekable, str::FromStr};
+
+use crate::spv::read::graph::Series;
+
+/// Operator precedence:
+///
+/// - `/` (highest)
+/// - `*`, `.` (middle)
+/// - `+` (lowest)
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Operator {
+ /// `+`
+ Blend,
+ /// `*`
+ Cross,
+ /// `/`
+ Nest,
+ /// `.`
+ Dot,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+enum Expression {
+ Unity,
+ Variable(String),
+ Operation {
+ operator: Operator,
+ operands: Box<[Expression; 2]>,
+ },
+}
+
+impl Expression {
+ fn new_variable(name: impl Into<String>) -> Self {
+ Self::Variable(name.into())
+ }
+ fn new_operation(operator: Operator, operand1: Expression, operand2: Expression) -> Self {
+ Self::Operation {
+ operator,
+ operands: Box::new([operand1, operand2]),
+ }
+ }
+}
+
+/// Error parsing an [Expression].
+#[derive(Clone, Debug, thiserror::Error, displaydoc::Display, PartialEq, Eq)]
+enum ExpressionError {
+ /// Empty expression.
+ EmptyString,
+ /// Expression was following by garbage.
+ TrailingGarbage,
+ /// Expression missing right parenthesis.
+ ExpectedRightParen,
+ /// Syntax error parsing expression.
+ SyntaxError,
+ /// Unexpected end of expression.
+ UnexpectedEof,
+}
+
+impl FromStr for Expression {
+ type Err = ExpressionError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ #[derive(Clone, Debug, PartialEq, Eq)]
+ enum Token {
+ Operator(Operator),
+ LeftParen,
+ RightParen,
+ Semicolon,
+ Unity,
+ Id(String),
+ }
+ impl Token {
+ fn from_delim(c: char) -> Option<Self> {
+ match c {
+ '+' => Some(Token::Operator(Operator::Blend)),
+ '*' => Some(Token::Operator(Operator::Cross)),
+ '/' => Some(Token::Operator(Operator::Nest)),
+ '.' => Some(Token::Operator(Operator::Dot)),
+ '(' => Some(Token::LeftParen),
+ ')' => Some(Token::RightParen),
+ ';' => Some(Token::Semicolon),
+ _ => None,
+ }
+ }
+ fn is_delim(c: char) -> bool {
+ Self::from_delim(c).is_some()
+ }
+ fn from_id(id: &str) -> Self {
+ if id == "1" {
+ Token::Unity
+ } else {
+ Token::Id(id.into())
+ }
+ }
+ }
+
+ struct Tokenizer<'a>(&'a str);
+
+ impl<'a> Iterator for Tokenizer<'a> {
+ type Item = Token;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut iter = self.0.chars();
+ if let Some(delim) = Token::from_delim(iter.next()?) {
+ self.0 = iter.as_str();
+ return Some(delim);
+ }
+ loop {
+ let (id, rest) = self.0.split_at(self.0.len() - iter.as_str().len());
+ if iter.next().is_none_or(Token::is_delim) {
+ self.0 = rest;
+ return Some(Token::from_id(id));
+ }
+ }
+ }
+ }
+
+ fn match_operator<F>(lexer: &mut Peekable<Tokenizer>, predicate: F) -> Option<Operator>
+ where
+ F: Fn(Operator) -> bool,
+ {
+ lexer.next_if_map(|token| {
+ if let Token::Operator(operator) = token
+ && predicate(operator)
+ {
+ Ok(operator)
+ } else {
+ Err(token)
+ }
+ })
+ }
+
+ fn parse_operator<P, I>(
+ lexer: &mut Peekable<Tokenizer>,
+ predicate: P,
+ inner: I,
+ ) -> Result<Expression, ExpressionError>
+ where
+ P: Fn(Operator) -> bool,
+ I: Fn(&mut Peekable<Tokenizer>) -> Result<Expression, ExpressionError>,
+ {
+ let mut retval = inner(lexer)?;
+ while let Some(operator) = match_operator(lexer, &predicate) {
+ let rhs = inner(lexer)?;
+ retval = Expression::new_operation(operator, retval, rhs);
+ }
+ Ok(retval)
+ }
+
+ fn blend<'a>(lexer: &mut Peekable<Tokenizer<'a>>) -> Result<Expression, ExpressionError> {
+ parse_operator(lexer, |op| op == Operator::Blend, cross_dot)
+ }
+
+ fn cross_dot<'a>(
+ lexer: &mut Peekable<Tokenizer<'a>>,
+ ) -> Result<Expression, ExpressionError> {
+ parse_operator(
+ lexer,
+ |op| op == Operator::Cross || op == Operator::Dot,
+ nest,
+ )
+ }
+
+ fn nest<'a>(lexer: &mut Peekable<Tokenizer<'a>>) -> Result<Expression, ExpressionError> {
+ parse_operator(lexer, |op| op == Operator::Nest, primary)
+ }
+
+ fn primary<'a>(lexer: &mut Peekable<Tokenizer<'a>>) -> Result<Expression, ExpressionError> {
+ match lexer.next() {
+ Some(Token::Unity) => Ok(Expression::Unity),
+ Some(Token::Id(id)) => Ok(Expression::Variable(id)),
+ Some(Token::LeftParen) => {
+ let retval = blend(lexer)?;
+ if lexer.next_if_eq(&Token::RightParen).is_none() {
+ return Err(ExpressionError::ExpectedRightParen);
+ }
+ Ok(retval)
+ }
+ Some(_) => Err(ExpressionError::SyntaxError),
+ None => Err(ExpressionError::UnexpectedEof),
+ }
+ }
+
+ let mut lexer = Tokenizer(s).peekable();
+ if lexer.peek().is_none() {
+ return Err(ExpressionError::EmptyString);
+ }
+ let expression = blend(&mut lexer)?;
+ if lexer.peek().is_some() {
+ return Err(ExpressionError::TrailingGarbage);
+ }
+ Ok(expression)
+ }
+}
+
+struct VarSet {
+ data: Vec<Series>,
+ //domain: Domain,
+}
+
+#[cfg(test)]
+mod tests {
+ use std::str::FromStr;
+
+ use crate::spv::read::graph::var_set::{Expression, ExpressionError, Operator};
+
+ #[test]
+ fn parse_expression() {
+ assert_eq!(Expression::from_str("1"), Ok(Expression::Unity));
+ assert_eq!(
+ Expression::from_str("abc"),
+ Ok(Expression::new_variable("abc"))
+ );
+ assert_eq!(
+ Expression::from_str("a+1"),
+ Ok(Expression::new_operation(
+ Operator::Blend,
+ Expression::new_variable("a"),
+ Expression::Unity
+ ))
+ );
+ assert_eq!(
+ Expression::from_str("abc"),
+ Ok(Expression::new_variable("abc"))
+ );
+ for (s, operator) in [
+ ("abc+def", Operator::Blend),
+ ("abc*def", Operator::Cross),
+ ("abc/def", Operator::Nest),
+ ("abc.def", Operator::Dot),
+ ] {
+ assert_eq!(
+ Expression::from_str(s),
+ Ok(Expression::new_operation(
+ operator,
+ Expression::new_variable("abc"),
+ Expression::new_variable("def")
+ ))
+ );
+ }
+
+ #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+ enum Side {
+ Left,
+ Right,
+ }
+ use Side::*;
+ for (s, operator1, operator2, higher) in [
+ // All the possible combinations of operators.
+ ("abc+def+ghi", Operator::Blend, Operator::Blend, Left),
+ ("abc*def+ghi", Operator::Cross, Operator::Blend, Left),
+ ("abc/def+ghi", Operator::Nest, Operator::Blend, Left),
+ ("abc.def+ghi", Operator::Dot, Operator::Blend, Left),
+ ("abc+def*ghi", Operator::Blend, Operator::Cross, Right),
+ ("abc*def*ghi", Operator::Cross, Operator::Cross, Left),
+ ("abc/def*ghi", Operator::Nest, Operator::Cross, Left),
+ ("abc.def*ghi", Operator::Dot, Operator::Cross, Left),
+ ("abc+def/ghi", Operator::Blend, Operator::Nest, Right),
+ ("abc*def/ghi", Operator::Cross, Operator::Nest, Right),
+ ("abc/def/ghi", Operator::Nest, Operator::Nest, Left),
+ ("abc.def/ghi", Operator::Dot, Operator::Nest, Right),
+ ("abc+def.ghi", Operator::Blend, Operator::Dot, Right),
+ ("abc*def.ghi", Operator::Cross, Operator::Dot, Left),
+ ("abc/def.ghi", Operator::Nest, Operator::Dot, Left),
+ ("abc.def.ghi", Operator::Dot, Operator::Dot, Left),
+ // Adding parentheses that match the default precedence.
+ ("(abc+def)+ghi", Operator::Blend, Operator::Blend, Left),
+ ("(abc*def)+ghi", Operator::Cross, Operator::Blend, Left),
+ ("(abc/def)+ghi", Operator::Nest, Operator::Blend, Left),
+ ("(abc.def)+ghi", Operator::Dot, Operator::Blend, Left),
+ ("abc+(def*ghi)", Operator::Blend, Operator::Cross, Right),
+ ("(abc*def)*ghi", Operator::Cross, Operator::Cross, Left),
+ ("(abc/def)*ghi", Operator::Nest, Operator::Cross, Left),
+ ("(abc.def)*ghi", Operator::Dot, Operator::Cross, Left),
+ ("abc+(def/ghi)", Operator::Blend, Operator::Nest, Right),
+ ("abc*(def/ghi)", Operator::Cross, Operator::Nest, Right),
+ ("(abc/def)/ghi", Operator::Nest, Operator::Nest, Left),
+ ("abc.(def/ghi)", Operator::Dot, Operator::Nest, Right),
+ ("abc+(def.ghi)", Operator::Blend, Operator::Dot, Right),
+ ("(abc*def).ghi", Operator::Cross, Operator::Dot, Left),
+ ("(abc/def).ghi", Operator::Nest, Operator::Dot, Left),
+ ("(abc.def).ghi", Operator::Dot, Operator::Dot, Left),
+ // Adding parentheses that reverse the default precedence.
+ ("abc+(def+ghi)", Operator::Blend, Operator::Blend, Right),
+ ("abc*(def+ghi)", Operator::Cross, Operator::Blend, Right),
+ ("abc/(def+ghi)", Operator::Nest, Operator::Blend, Right),
+ ("abc.(def+ghi)", Operator::Dot, Operator::Blend, Right),
+ ("(abc+def)*ghi", Operator::Blend, Operator::Cross, Left),
+ ("abc*(def*ghi)", Operator::Cross, Operator::Cross, Right),
+ ("abc/(def*ghi)", Operator::Nest, Operator::Cross, Right),
+ ("abc.(def*ghi)", Operator::Dot, Operator::Cross, Right),
+ ("(abc+def)/ghi", Operator::Blend, Operator::Nest, Left),
+ ("(abc*def)/ghi", Operator::Cross, Operator::Nest, Left),
+ ("abc/(def/ghi)", Operator::Nest, Operator::Nest, Right),
+ ("(abc.def)/ghi", Operator::Dot, Operator::Nest, Left),
+ ("(abc+def).ghi", Operator::Blend, Operator::Dot, Left),
+ ("abc*(def.ghi)", Operator::Cross, Operator::Dot, Right),
+ ("abc/(def.ghi)", Operator::Nest, Operator::Dot, Right),
+ ("abc.(def.ghi)", Operator::Dot, Operator::Dot, Right),
+ ] {
+ let operand1 = Expression::new_variable("abc");
+ let operand2 = Expression::new_variable("def");
+ let operand3 = Expression::new_variable("ghi");
+ let expected = match higher {
+ Left => Expression::new_operation(
+ operator2,
+ Expression::new_operation(operator1, operand1, operand2),
+ operand3,
+ ),
+ Right => Expression::new_operation(
+ operator1,
+ operand1,
+ Expression::new_operation(operator2, operand2, operand3),
+ ),
+ };
+ assert_eq!(
+ Expression::from_str(s),
+ Ok(expected),
+ "{s}, {operator1:?}, {operator2:?}, {higher:?}"
+ );
+ }
+
+ assert_eq!(Expression::from_str(""), Err(ExpressionError::EmptyString));
+ assert_eq!(
+ Expression::from_str("x)"),
+ Err(ExpressionError::TrailingGarbage)
+ );
+ assert_eq!(
+ Expression::from_str("(x"),
+ Err(ExpressionError::ExpectedRightParen)
+ );
+ assert_eq!(Expression::from_str("+"), Err(ExpressionError::SyntaxError));
+ assert_eq!(
+ Expression::from_str("x+"),
+ Err(ExpressionError::UnexpectedEof)
+ );
+ }
+}