Skip to main content

kernel/config/expression/
parse.rs

1//! Tokenizer and recursive-descent parser for [`Expression`].
2//!
3//! ```text
4//! expression := term (("+" | "-") term)*
5//! term       := unary (("*" | "/") unary)*
6//! unary      := "-" unary | primary
7//! primary    := column | number | "(" expression ")"
8//! column     := [A-Za-z_][A-Za-z0-9_]*      (lowercased, at most 63 characters)
9//! number     := [0-9]+ ("." [0-9]+)?
10//! ```
11//!
12//! Whitespace between tokens is ignored. Positions are 1-based characters.
13//! A whole input [`ColumnName`] accepts (trimmed, Unicode-lowercased) is that
14//! column before any tokenizing: it holds no operator, so it can't be one.
15//!
16//! ```
17//! use kernel::config::{Expression, ExpressionErrorKind};
18//!
19//! let err = Expression::parse("(quantity * 2").unwrap_err();
20//! assert_eq!(err.kind, ExpressionErrorKind::UnclosedParenthesis);
21//! assert_eq!(err.position, 1);
22//! ```
23
24use std::fmt;
25
26use super::{BinaryOp, Expression, NumberLiteral};
27use crate::common::ColumnName;
28
29/// How deep an expression may nest: parentheses and unary minus on the way
30/// down, and the height of the resulting tree. The canonical text of a tree
31/// nests no deeper than its height, so both caps agree on what renders.
32pub const MAX_DEPTH: usize = 64;
33
34/// A malformed expression: what went wrong and where.
35#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
36#[error("at position {position}: {kind}")]
37pub struct ExpressionError {
38    /// The 1-based character position the parser stopped at.
39    pub position: usize,
40    /// What went wrong.
41    pub kind: ExpressionErrorKind,
42}
43
44/// What made an expression malformed.
45#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
46pub enum ExpressionErrorKind {
47    #[error("the expression is empty")]
48    Empty,
49    #[error(
50        "unexpected character `{0}`: an expression holds column names, numbers, \
51         `+ - * /` and parentheses"
52    )]
53    UnexpectedCharacter(char),
54    #[error("a number needs a digit after its `.`")]
55    IncompleteNumber,
56    #[error("`{0}` is not a valid column name: at most 63 characters")]
57    InvalidColumn(String),
58    #[error("expected a column, a number, `-` or `(`, found {0}")]
59    ExpectedOperand(String),
60    #[error("expected an operator (`+ - * /`) or the end, found {0}")]
61    ExpectedOperator(String),
62    #[error("this `(` is never closed")]
63    UnclosedParenthesis,
64    #[error("this `)` has no matching `(`")]
65    UnmatchedParenthesis,
66    #[error("the expression nests deeper than {MAX_DEPTH} levels")]
67    TooDeep,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71enum Token {
72    Column(String),
73    Number(String),
74    Plus,
75    Minus,
76    Star,
77    Slash,
78    Open,
79    Close,
80    End,
81}
82
83impl fmt::Display for Token {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::Column(name) => write!(f, "column `{name}`"),
87            Self::Number(text) => write!(f, "number `{text}`"),
88            Self::Plus => f.write_str("`+`"),
89            Self::Minus => f.write_str("`-`"),
90            Self::Star => f.write_str("`*`"),
91            Self::Slash => f.write_str("`/`"),
92            Self::Open => f.write_str("`(`"),
93            Self::Close => f.write_str("`)`"),
94            Self::End => f.write_str("the end"),
95        }
96    }
97}
98
99fn error(position: usize, kind: ExpressionErrorKind) -> ExpressionError {
100    ExpressionError { position, kind }
101}
102
103/// Split `input` into tokens, each with its 1-based position, ending in
104/// [`Token::End`].
105fn tokenize(input: &str) -> Result<Vec<(Token, usize)>, ExpressionError> {
106    let mut tokens = Vec::new();
107    let mut chars = input.chars().zip(1..).peekable();
108    let mut last = 0;
109    while let Some((c, position)) = chars.next() {
110        last = position;
111        let token = match c {
112            c if c.is_whitespace() => continue,
113            '+' => Token::Plus,
114            '-' => Token::Minus,
115            '*' => Token::Star,
116            '/' => Token::Slash,
117            '(' => Token::Open,
118            ')' => Token::Close,
119            c if c.is_ascii_alphabetic() || c == '_' => {
120                let mut name = String::from(c.to_ascii_lowercase());
121                while let Some(&(next, at)) = chars.peek() {
122                    if !(next.is_ascii_alphanumeric() || next == '_') {
123                        break;
124                    }
125                    name.push(next.to_ascii_lowercase());
126                    last = at;
127                    chars.next();
128                }
129                Token::Column(name)
130            }
131            c if c.is_ascii_digit() => {
132                let mut text = String::from(c);
133                take_digits(&mut chars, &mut text, &mut last);
134                if let Some(&('.', dot)) = chars.peek() {
135                    text.push('.');
136                    last = dot;
137                    chars.next();
138                    let before = text.len();
139                    take_digits(&mut chars, &mut text, &mut last);
140                    if text.len() == before {
141                        return Err(error(dot, ExpressionErrorKind::IncompleteNumber));
142                    }
143                }
144                Token::Number(text)
145            }
146            other => {
147                return Err(error(
148                    position,
149                    ExpressionErrorKind::UnexpectedCharacter(other),
150                ));
151            }
152        };
153        tokens.push((token, position));
154    }
155    tokens.push((Token::End, last + 1));
156    Ok(tokens)
157}
158
159fn take_digits(
160    chars: &mut std::iter::Peekable<impl Iterator<Item = (char, usize)>>,
161    text: &mut String,
162    last: &mut usize,
163) {
164    while let Some(&(next, at)) = chars.peek() {
165        if !next.is_ascii_digit() {
166            break;
167        }
168        text.push(next);
169        *last = at;
170        chars.next();
171    }
172}
173
174/// A parsed subtree and its height (a leaf is 0).
175struct Node {
176    expression: Expression,
177    height: usize,
178}
179
180struct Parser {
181    tokens: Vec<(Token, usize)>,
182    next: usize,
183}
184
185pub(super) fn parse(input: &str) -> Result<Expression, ExpressionError> {
186    if let Ok(column) = ColumnName::try_new(input) {
187        return Ok(Expression::Column(column));
188    }
189    let mut parser = Parser {
190        tokens: tokenize(input)?,
191        next: 0,
192    };
193    if let (Token::End, _) = parser.peek() {
194        return Err(error(1, ExpressionErrorKind::Empty));
195    }
196    let node = parser.expression(0)?;
197    match parser.peek() {
198        (Token::End, _) => Ok(node.expression),
199        (Token::Close, position) => Err(error(position, ExpressionErrorKind::UnmatchedParenthesis)),
200        (token, position) => Err(error(
201            position,
202            ExpressionErrorKind::ExpectedOperator(token.to_string()),
203        )),
204    }
205}
206
207impl Parser {
208    fn peek(&self) -> (Token, usize) {
209        self.tokens
210            .get(self.next)
211            .or_else(|| self.tokens.last())
212            .cloned()
213            .unwrap_or((Token::End, 1))
214    }
215
216    fn advance(&mut self) {
217        self.next += 1;
218    }
219
220    fn expression(&mut self, depth: usize) -> Result<Node, ExpressionError> {
221        let mut left = self.term(depth)?;
222        loop {
223            let (op, position) = match self.peek() {
224                (Token::Plus, position) => (BinaryOp::Add, position),
225                (Token::Minus, position) => (BinaryOp::Subtract, position),
226                _ => return Ok(left),
227            };
228            self.advance();
229            let right = self.term(depth)?;
230            left = binary(op, left, right, position)?;
231        }
232    }
233
234    fn term(&mut self, depth: usize) -> Result<Node, ExpressionError> {
235        let mut left = self.unary(depth)?;
236        loop {
237            let (op, position) = match self.peek() {
238                (Token::Star, position) => (BinaryOp::Multiply, position),
239                (Token::Slash, position) => (BinaryOp::Divide, position),
240                _ => return Ok(left),
241            };
242            self.advance();
243            let right = self.unary(depth)?;
244            left = binary(op, left, right, position)?;
245        }
246    }
247
248    fn unary(&mut self, depth: usize) -> Result<Node, ExpressionError> {
249        let (Token::Minus, position) = self.peek() else {
250            return self.primary(depth);
251        };
252        let depth = deeper(depth, position)?;
253        self.advance();
254        let inner = self.unary(depth)?;
255        let height = taller(inner.height, position)?;
256        Ok(Node {
257            expression: Expression::Negate(Box::new(inner.expression)),
258            height,
259        })
260    }
261
262    fn primary(&mut self, depth: usize) -> Result<Node, ExpressionError> {
263        let (token, position) = self.peek();
264        let expression = match token {
265            Token::Column(name) => match ColumnName::try_new(&name) {
266                Ok(column) => Expression::Column(column),
267                Err(_) => {
268                    return Err(error(position, ExpressionErrorKind::InvalidColumn(name)));
269                }
270            },
271            Token::Number(text) => Expression::Number(NumberLiteral(text)),
272            Token::Open => {
273                let depth = deeper(depth, position)?;
274                self.advance();
275                let inner = self.expression(depth)?;
276                return match self.peek() {
277                    (Token::Close, _) => {
278                        self.advance();
279                        Ok(inner)
280                    }
281                    (Token::End, _) => {
282                        Err(error(position, ExpressionErrorKind::UnclosedParenthesis))
283                    }
284                    (token, at) => Err(error(
285                        at,
286                        ExpressionErrorKind::ExpectedOperator(token.to_string()),
287                    )),
288                };
289            }
290            other => {
291                return Err(error(
292                    position,
293                    ExpressionErrorKind::ExpectedOperand(other.to_string()),
294                ));
295            }
296        };
297        self.advance();
298        Ok(Node {
299            expression,
300            height: 0,
301        })
302    }
303}
304
305fn binary(op: BinaryOp, left: Node, right: Node, position: usize) -> Result<Node, ExpressionError> {
306    let height = taller(left.height.max(right.height), position)?;
307    Ok(Node {
308        expression: Expression::Binary {
309            op,
310            left: Box::new(left.expression),
311            right: Box::new(right.expression),
312        },
313        height,
314    })
315}
316
317fn deeper(depth: usize, position: usize) -> Result<usize, ExpressionError> {
318    if depth >= MAX_DEPTH {
319        return Err(error(position, ExpressionErrorKind::TooDeep));
320    }
321    Ok(depth + 1)
322}
323
324fn taller(height: usize, position: usize) -> Result<usize, ExpressionError> {
325    if height >= MAX_DEPTH {
326        return Err(error(position, ExpressionErrorKind::TooDeep));
327    }
328    Ok(height + 1)
329}