aimx/expressions/
unary.rs

1//! Unary expression parsing for AIMX.
2//!
3//! Provides the AST and parser for unary operators (`!`, unary `+`/`-`, and
4//! `(Type)` casts) with right-to-left associativity. See
5//! [`parse_unary`] and [`Unary`].
6
7use crate::{
8    aim::{ContextLike, Typedef, WriterLike, Writer, parse_literal_type},
9    expressions::{ExpressionLike, Postfix, Primary, parse_postfix},
10    literals::Literal,
11    values::Value,
12};
13use anyhow::{Result, anyhow};
14use nom::{
15    IResult, Parser,
16    branch::alt,
17    character::complete::{char, multispace0},
18    combinator::map,
19    multi::many1, sequence::delimited,
20};
21use std::{
22    fmt,
23    sync::Arc,
24};
25
26/// Unary expression node.
27///
28/// Flattened AST for unary operators and primary expressions.
29#[derive(Debug, Clone, PartialEq)]
30pub enum Unary {
31    /// `!operand`
32    Not(Box<Unary>),
33    /// `+operand`
34    Positive(Box<Unary>),
35    /// `-operand`
36    Negative(Box<Unary>),
37    /// `(Bool)operand`
38    CastBool(Box<Unary>),
39    /// `(Date)operand`
40    CastDate(Box<Unary>),
41    /// `(Number)operand`
42    CastNumber(Box<Unary>),
43    /// `(Task)operand`
44    CastTask(Box<Unary>),
45    /// `(Text)operand`
46    CastText(Box<Unary>),
47    /// Primary or postfix expression.
48    Primary(Box<Primary>),
49}
50
51/// Parse a cast operator `(Type)` into a [`Typedef`].
52fn parse_cast_operator(input: &str) -> IResult<&str, Typedef> {
53    delimited(
54        char('('),
55        delimited(multispace0, parse_literal_type, multispace0),
56        char(')'),
57    ).parse(input)
58}
59
60/// Internal unary operator used while parsing.
61enum UnaryOp {
62    Not,
63    Positive,
64    Negative,
65    Cast(Typedef),
66}
67
68/// Parse a unary operator token.
69fn parse_unary_operator(input: &str) -> IResult<&str, UnaryOp> {
70    delimited(
71        multispace0,
72        alt((
73            map(char('!'), |_| UnaryOp::Not),
74            map(char('+'), |_| UnaryOp::Positive),
75            map(char('-'), |_| UnaryOp::Negative),
76            map(parse_cast_operator, |typedef| UnaryOp::Cast(typedef)),
77        )),
78        multispace0,
79    ).parse(input)
80}
81
82impl Unary {
83    /// Construct a [`Unary`] from a parsed unary operator and operand.
84    fn new(op: UnaryOp, unary: Unary) -> Self {
85        let boxed = Box::new(unary);
86        match op {
87            UnaryOp::Not => Unary::Not(boxed),
88            UnaryOp::Positive => Unary::Positive(boxed),
89            UnaryOp::Negative => Unary::Negative(boxed),
90            UnaryOp::Cast(typedef) => match typedef {
91                Typedef::Bool => Unary::CastBool(boxed),
92                Typedef::Date => Unary::CastDate(boxed),
93                Typedef::Number => Unary::CastNumber(boxed),
94                Typedef::Task => Unary::CastTask(boxed),
95                Typedef::Text => Unary::CastText(boxed),
96                _ => unreachable!(),
97            },
98        }
99    }
100
101    pub fn print(&self, writer: &mut Writer) {
102        match self {
103            Unary::Not(operand) => {
104                writer.write_unary_op("!", operand.as_ref());
105            }
106            Unary::Positive(operand) => {
107                writer.write_unary_op("+", operand.as_ref());
108            }
109            Unary::Negative(operand) => {
110                writer.write_unary_op("-", operand.as_ref());
111            }
112            Unary::CastBool(operand) => {
113                writer.write_str("(Bool)");
114                operand.print(writer);
115            }
116            Unary::CastDate(operand) => {
117                writer.write_str("(Date)");
118                operand.print(writer);
119            }
120            Unary::CastNumber(operand) => {
121                writer.write_str("(Number)");
122                operand.print(writer);
123            }
124            Unary::CastTask(operand) => {
125                writer.write_str("(Task)");
126                operand.print(writer);
127            }
128            Unary::CastText(operand) => {
129                writer.write_str("(Text)");
130                operand.print(writer);
131            }
132            Unary::Primary(primary) => primary.print(writer),
133        }
134    }
135}
136
137/// Parse a unary expression.
138///
139/// Supports chains of unary operators (right-to-left) applied to a postfix
140/// expression. Falls back to parsing a postfix-only expression.
141pub fn parse_unary(input: &str) -> IResult<&str, Unary> {
142    // Chain any unary operators
143    if let Ok((input, mut unary_chain)) = many1(parse_unary_operator).parse(input) {
144        // Get the primary operand
145        let (input, postfix) = parse_postfix(input)?;
146        // Convert to unary
147        let mut unary = match postfix {
148            Postfix::Primary(primary) => Unary::Primary(primary),
149            _ => Unary::Primary(Box::new(Primary::Postfix(postfix))),
150        };
151        // Iterate unary chain right to left
152        while let Some(op) = unary_chain.pop() {
153            unary = Unary::new(op, unary);
154        }
155        Ok((input, unary))
156    } else {
157        // Parse the primary
158        let (input, postfix) = parse_postfix(input)?;
159        let unary = match postfix {
160            Postfix::Primary(primary) => Unary::Primary(primary),
161            _ => Unary::Primary(Box::new(Primary::Postfix(postfix))),
162        };
163        Ok((input, unary))
164    }
165}
166
167impl ExpressionLike for Unary {
168    /// Evaluate the unary expression.
169    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
170        match self {
171            Unary::Not(operand) => {
172                let val = operand.evaluate(context)?;
173                if val.is_error() {
174                    return Ok(val);
175                }
176                let found = val.type_as_string();
177                match &*val.as_bool() {
178                    Value::Literal(Literal::Bool(b)) => Ok(Arc::new(Value::Literal(Literal::Bool(!b)))),
179                    _ => Err(anyhow!(
180                        "Expected Bool, found {}~{}",
181                        found,
182                        self.to_formula(),
183                    )),
184                }
185            }
186            Unary::Positive(operand) => {
187                let val = operand.evaluate(context)?;
188                if val.is_error() {
189                    return Ok(val);
190                }
191                let found = val.type_as_string();
192                let number = val.as_number()?;
193                match &*number {
194                    Value::Literal(Literal::Number(n)) => {
195                        Ok(Arc::new(Value::Literal(Literal::Number(*n))))
196                    }
197                    _ => Err(anyhow!(
198                        "Expected numeric operand, found {}~{}",
199                        found,
200                        self.to_formula(),
201                    )),
202                }
203            }
204            Unary::Negative(operand) => {
205                let val = operand.evaluate(context)?;
206                if val.is_error() {
207                    return Ok(val);
208                }
209                let found = val.type_as_string();
210                let number = val.as_number()?;
211                match &*number {
212                    Value::Literal(Literal::Number(n)) => {
213                        Ok(Arc::new(Value::Literal(Literal::Number(-n))))
214                    }
215                    _ => Err(anyhow!(
216                        "Expected numeric operand, found {}~{}",
217                        found,
218                        self.to_formula(),
219                    )),
220                }
221            }
222            Unary::CastBool(operand) => {
223                let val = operand.evaluate(context)?;
224                if val.is_error() {
225                    return Ok(val);
226                }
227                Ok(val.as_bool())
228            }
229            Unary::CastDate(operand) => {
230                let val = operand.evaluate(context)?;
231                if val.is_error() {
232                    return Ok(val);
233                }
234                val.as_date()
235            }
236            Unary::CastNumber(operand) => {
237                let val = operand.evaluate(context)?;
238                if val.is_error() {
239                    return Ok(val);
240                }
241                val.as_number()
242            }
243            Unary::CastTask(operand) => {
244                let val = operand.evaluate(context)?;
245                if val.is_error() {
246                    return Ok(val);
247                }
248                val.as_task()
249            }
250            Unary::CastText(operand) => {
251                let val = operand.evaluate(context)?;
252                if val.is_error() {
253                    return Ok(val);
254                }
255                val.as_text()
256            }
257            Unary::Primary(primary) => primary.evaluate(context),
258        }
259    }
260
261    /// Return the formula-string representation (round-trippable by the parser).
262    fn to_formula(&self) -> String {
263        let mut writer = Writer::formulizer();
264        self.print(&mut writer);
265        writer.finish()
266    }
267}
268
269impl WriterLike for Unary {
270    fn write(&self, writer: &mut Writer) {
271        self.print(writer);
272    }
273}
274
275impl fmt::Display for Unary {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(f, "{}", self.to_stringized())
278    }
279}