From: Ben Pfaff Date: Thu, 10 Sep 2026 18:57:01 +0000 (-0700) Subject: add expression parser X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eeff9aff0cfec13caec9a6656cf9625ce14f4392;p=pspp add expression parser --- diff --git a/rust/pspp/README.md b/rust/pspp/README.md index 31f478c009..558c9fd8e8 100644 --- a/rust/pspp/README.md +++ b/rust/pspp/README.md @@ -14,7 +14,7 @@ This new version is very incomplete--in fact, only a subset of the To install this version of PSPP from source, first install Rust using the instructions at https://www.rust-lang.org/tools/install. PSPP -requires Rust 1.88 or later. Once you've done that, you can download +requires Rust 1.98 or later. Once you've done that, you can download PSPP source code from `crates.io` and build and install it with: ``` diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index dbdea52f6e..ed0411b7f9 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -75,6 +75,7 @@ mod frames; mod patterns; mod style; mod symbols; +mod var_set; #[derive(Clone, Debug, Default)] struct Map(HashMap, String>); diff --git a/rust/pspp/src/spv/read/graph/var_set.rs b/rust/pspp/src/spv/read/graph/var_set.rs new file mode 100644 index 0000000000..32f93339fb --- /dev/null +++ b/rust/pspp/src/spv/read/graph/var_set.rs @@ -0,0 +1,338 @@ +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) -> 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 { + #[derive(Clone, Debug, PartialEq, Eq)] + enum Token { + Operator(Operator), + LeftParen, + RightParen, + Semicolon, + Unity, + Id(String), + } + impl Token { + fn from_delim(c: char) -> Option { + 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 { + 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(lexer: &mut Peekable, predicate: F) -> Option + 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( + lexer: &mut Peekable, + predicate: P, + inner: I, + ) -> Result + where + P: Fn(Operator) -> bool, + I: Fn(&mut Peekable) -> Result, + { + 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>) -> Result { + parse_operator(lexer, |op| op == Operator::Blend, cross_dot) + } + + fn cross_dot<'a>( + lexer: &mut Peekable>, + ) -> Result { + parse_operator( + lexer, + |op| op == Operator::Cross || op == Operator::Dot, + nest, + ) + } + + fn nest<'a>(lexer: &mut Peekable>) -> Result { + parse_operator(lexer, |op| op == Operator::Nest, primary) + } + + fn primary<'a>(lexer: &mut Peekable>) -> Result { + 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, + //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) + ); + } +}