Skip to main content

expr/
parser.rs

1use crate::ast::node::Node;
2use crate::ast::program::Program;
3use crate::eval::Environment;
4use crate::functions::ExprCall;
5use crate::{ContextProvider, Error, Result, Value};
6use crate::{ExprPest, Rule};
7use pest::Parser as PestParser;
8use pest::iterators::Pairs;
9use std::fmt;
10use std::fmt::{Debug, Formatter};
11
12/// Parse an expr program to be run later
13pub fn compile(code: &str) -> Result<Program> {
14    #[cfg(debug_assertions)]
15    pest::set_error_detail(true);
16    let pairs = ExprPest::parse(Rule::full, code).map_err(|e| Error::PestError(Box::new(e)))?;
17    validate_numeric_literals(pairs.clone())?;
18    Ok(pairs.into())
19}
20
21fn validate_numeric_literals(pairs: Pairs<'_, Rule>) -> Result<()> {
22    for pair in pairs {
23        match pair.as_rule() {
24            Rule::int => {
25                Value::parse_integer(pair.as_str()).map_err(|error| {
26                    Error::ParseError(format!("invalid integer literal {}: {error}", pair.as_str()))
27                })?;
28            }
29            Rule::decimal => {
30                let value = Value::parse_float(pair.as_str()).map_err(|error| {
31                    Error::ParseError(format!("invalid float literal {}: {error}", pair.as_str()))
32                })?;
33                if !value.is_finite() {
34                    return Err(Error::ParseError(format!(
35                        "float literal is out of range: {}",
36                        pair.as_str()
37                    )));
38                }
39            }
40            _ => {}
41        }
42        validate_numeric_literals(pair.into_inner())?;
43    }
44    Ok(())
45}
46
47#[cfg(test)]
48mod literal_tests {
49    use super::compile;
50
51    #[test]
52    fn rejects_malformed_integer_separators() {
53        for code in ["1__0", "1_", "0x_2A_", "0b1__0"] {
54            assert!(compile(code).is_err(), "{code} should be rejected");
55        }
56    }
57
58    #[test]
59    fn rejects_integer_overflow_without_panicking() {
60        assert!(compile("0x10000000000000000").is_err());
61        assert!(compile("9223372036854775808").is_err());
62    }
63
64    #[test]
65    fn accepts_scientific_float_literals() {
66        for code in ["1e3", "1.2e-4", ".5e+2", "1_000.5_0e-2"] {
67            assert!(compile(code).is_ok(), "{code} should be accepted");
68        }
69    }
70
71    #[test]
72    fn rejects_malformed_or_overflowing_float_literals() {
73        for code in ["1e", "1e+", "1e_2", "1e9999"] {
74            assert!(compile(code).is_err(), "{code} should be rejected");
75        }
76    }
77
78    #[test]
79    fn rejects_raw_newlines_in_interpreted_literals() {
80        for code in ["\"a\nb\"", "'a\rb'", "b\"a\nb\"", "b'a\rb'"] {
81            assert!(compile(code).is_err(), "{code:?} should be rejected");
82        }
83        assert!(compile("`a\nb`").is_ok());
84    }
85
86    #[test]
87    fn conditional_keywords_require_identifier_boundaries() {
88        assert!(compile("if true { 1 } else { 2 }").is_ok());
89        assert!(compile("ifx { 1 } else { 2 }").is_err());
90        assert!(compile("if true { 1 } elseif { 2 }").is_err());
91    }
92}
93
94/// Main struct for parsing and evaluating expr programs
95///
96/// Example:
97///
98/// ```
99/// use expr::{Context, Parser};
100/// let ctx = Context::from_iter([("foo", 1), ("bar", 2)]);
101/// let p = Parser::new();
102/// assert_eq!(p.eval("foo + bar", &ctx).unwrap().to_string(), "3");
103/// ```
104#[deprecated(note = "Use `compile()` and `Environment` instead")]
105#[derive(Default)]
106pub struct Parser<'a> {
107    env: Environment<'a>,
108}
109
110#[allow(deprecated)]
111impl Debug for Parser<'_> {
112    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
113        f.debug_struct("ExprParser").finish()
114    }
115}
116
117#[allow(deprecated)]
118impl<'a> Parser<'a> {
119    /// Create a new parser with the default environment
120    pub fn new() -> Self {
121        Parser {
122            env: Environment::new(),
123        }
124    }
125
126    /// Add a function for expr programs to call
127    ///
128    /// Example:
129    /// ```
130    /// use std::collections::HashMap;
131    /// use expr::{Context, Parser, Value};
132    ///
133    /// let mut p = Parser::new();
134    /// let ctx = Context::default();
135    /// p.add_function("add", |c| {
136    ///   let mut sum = 0;
137    ///     for arg in c.args {
138    ///       if let Value::Integer(n) = arg {
139    ///         sum += n;
140    ///        } else {
141    ///          panic!("Invalid argument: {arg:?}");
142    ///        }
143    ///     }
144    ///   Ok(sum.into())
145    /// });
146    /// assert_eq!(p.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
147    /// ```
148    pub fn add_function<F>(&mut self, name: &str, f: F)
149    where
150        F: Fn(ExprCall) -> Result<Value> + 'a + Sync + Send,
151    {
152        self.env.add_function(name, Box::new(f));
153    }
154
155    /// Parse an expr program to be run later
156    pub fn compile(&self, code: &str) -> Result<Program> {
157        compile(code)
158    }
159
160    /// Run a compiled expr program
161    pub fn run(&self, program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
162        self.env.run(program, ctx)
163    }
164
165    /// Compile and run an expr program in one step
166    ///
167    /// Example:
168    /// ```
169    /// use std::collections::HashMap;
170    /// use expr::{Context, Parser};
171    /// let p = Parser::default();
172    /// let ctx = Context::default();
173    /// assert_eq!(p.eval("1 + 2", &ctx).unwrap().to_string(), "3");
174    /// ```
175    pub fn eval(&self, code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
176        self.env.eval(code, ctx)
177    }
178
179    pub fn eval_expr(&self, ctx: &dyn ContextProvider, node: &Node) -> Result<Value> {
180        self.env.eval_expr(ctx, node)
181    }
182}