Skip to main content

hedl_test/
expression_parsing.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Expression utility functions.
19//!
20//! Helper functions for creating expressions in test fixtures.
21//!
22//! This module provides both panic-on-error and safe error-returning variants
23//! for expression parsing. The `expr()` function panics on invalid input (useful
24//! for concise test code), while `try_expr()` returns a `Result` for more
25//! explicit error handling.
26
27use hedl_core::lex::{parse_expression, LexError};
28use hedl_core::{Expression, Value};
29use std::fmt;
30
31/// Error type for expression parsing failures.
32///
33/// This enum captures different categories of expression parsing errors
34/// and provides descriptive error information for proper error handling.
35///
36/// # Examples
37///
38/// ```
39/// use hedl_test::{try_expr, ExprError};
40///
41/// // Handle invalid syntax
42/// match try_expr("invalid syntax!") {
43///     Err(ExprError::ParseFailed { source, input }) => {
44///         println!("Failed to parse: {}", input);
45///         println!("Reason: {}", source);
46///     }
47///     Ok(expr) => println!("Success"),
48///     _ => {}
49/// }
50///
51/// // Handle empty input
52/// match try_expr("") {
53///     Err(ExprError::EmptyInput) => {
54///         println!("Expression cannot be empty");
55///     }
56///     _ => {}
57/// }
58/// ```
59#[derive(Debug, Clone)]
60pub enum ExprError {
61    /// Expression string is empty.
62    EmptyInput,
63
64    /// Expression parsing failed with a lexical error.
65    ///
66    /// Contains the underlying `LexError` and the original input string
67    /// for context in error reporting.
68    ParseFailed {
69        /// The underlying lexical error from the parser.
70        source: LexError,
71        /// The original input string that failed to parse.
72        input: String,
73    },
74
75    /// Expression is missing (null or not provided).
76    ///
77    /// Used when an expression is expected but not found.
78    Missing,
79}
80
81impl fmt::Display for ExprError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            ExprError::EmptyInput => {
85                write!(f, "Expression cannot be empty")
86            }
87            ExprError::ParseFailed { source, input } => {
88                write!(f, "Failed to parse expression '{input}': {source}")
89            }
90            ExprError::Missing => {
91                write!(f, "Expression is missing or null")
92            }
93        }
94    }
95}
96
97impl std::error::Error for ExprError {}
98
99/// Helper to create an Expression from a valid expression string.
100///
101/// This function panics on invalid input, making it convenient for test code
102/// where a concise failure is preferred over explicit error handling.
103///
104/// For error handling, use [`try_expr()`] instead.
105///
106/// # Panics
107///
108/// Panics if the expression string cannot be parsed, with a message showing
109/// the invalid input.
110///
111/// # Example
112///
113/// ```
114/// use hedl_test::expr;
115///
116/// // Simple literals
117/// let e = expr("42");
118/// let e = expr("3.14");
119/// let e = expr("\"hello\"");
120/// let e = expr("true");
121///
122/// // Identifiers
123/// let e = expr("x");
124/// let e = expr("my_variable");
125///
126/// // Function calls
127/// let e = expr("now()");
128/// let e = expr("add(x, y)");
129/// let e = expr("max(1, 2, 3)");
130///
131/// // Field access
132/// let e = expr("user.name");
133/// let e = expr("data.values.first");
134/// ```
135#[must_use]
136pub fn expr(s: &str) -> Expression {
137    try_expr(s).unwrap_or_else(|e| panic!("Invalid test expression: {e}"))
138}
139
140/// Safe variant of `expr()` that returns a `Result` instead of panicking.
141///
142/// This function provides explicit error handling for expression parsing,
143/// returning `Ok(Expression)` on success or `Err(ExprError)` on failure.
144///
145/// Use this when you need to handle invalid expressions gracefully, such as
146/// when processing user input or testing error conditions.
147///
148/// # Errors
149///
150/// Returns `Err(ExprError::EmptyInput)` if the expression string is empty.
151///
152/// Returns `Err(ExprError::ParseFailed)` if the expression string contains
153/// invalid syntax or fails to parse for any reason.
154///
155/// # Examples
156///
157/// ## Basic usage
158///
159/// ```
160/// use hedl_test::try_expr;
161///
162/// // Success cases
163/// assert!(try_expr("42").is_ok());
164/// assert!(try_expr("foo()").is_ok());
165/// assert!(try_expr("x.y.z").is_ok());
166///
167/// // Failure cases
168/// assert!(try_expr("").is_err());
169/// assert!(try_expr("!@#$%").is_err());
170/// ```
171///
172/// ## Error handling
173///
174/// ```
175/// use hedl_test::{try_expr, ExprError};
176///
177/// match try_expr("invalid!!!") {
178///     Ok(expr) => {
179///         println!("Parsed: {:?}", expr);
180///     }
181///     Err(ExprError::EmptyInput) => {
182///         println!("Empty expressions are not allowed");
183///     }
184///     Err(ExprError::ParseFailed { source, input }) => {
185///         eprintln!("Failed to parse '{}': {}", input, source);
186///     }
187///     Err(ExprError::Missing) => {
188///         println!("Expression was missing");
189///     }
190/// }
191/// ```
192///
193/// ## Testing error conditions
194///
195/// ```
196/// use hedl_test::try_expr;
197///
198/// // You can test error handling without panics
199/// let inputs = vec!["", "!!!", ")(", "func("];
200/// for input in inputs {
201///     match try_expr(input) {
202///         Err(_) => println!("'{}' correctly failed", input),
203///         Ok(_) => panic!("'{}' should have failed", input),
204///     }
205/// }
206/// ```
207pub fn try_expr(s: &str) -> Result<Expression, ExprError> {
208    if s.is_empty() {
209        return Err(ExprError::EmptyInput);
210    }
211
212    parse_expression(s).map_err(|source| ExprError::ParseFailed {
213        source,
214        input: s.to_string(),
215    })
216}
217
218/// Helper to create a `Value::Expression` from a valid expression string.
219///
220/// This function panics on invalid input. Use [`try_expr_value()`] for
221/// error handling.
222///
223/// # Examples
224///
225/// ```
226/// use hedl_test::expr_value;
227/// use hedl_core::Value;
228///
229/// let v = expr_value("42");
230/// let v = expr_value("foo()");
231/// let v = expr_value("x.y");
232/// ```
233#[must_use]
234pub fn expr_value(s: &str) -> Value {
235    Value::Expression(Box::new(expr(s)))
236}
237
238/// Safe variant of `expr_value()` that returns a `Result` instead of panicking.
239///
240/// This function provides explicit error handling for creating expression values,
241/// returning `Ok(Value)` on success or `Err(ExprError)` on failure.
242///
243/// # Errors
244///
245/// Returns the same errors as [`try_expr()`].
246///
247/// # Examples
248///
249/// ```
250/// use hedl_test::{try_expr_value, ExprError};
251/// use hedl_core::Value;
252///
253/// match try_expr_value("my_func()") {
254///     Ok(Value::Expression(_)) => println!("Success"),
255///     Err(e) => println!("Failed: {}", e),
256///     _ => {}
257/// }
258/// ```
259pub fn try_expr_value(s: &str) -> Result<Value, ExprError> {
260    try_expr(s).map(|e| Value::Expression(Box::new(e)))
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    /// Helper trait for Expression to check literal type
268    trait ExpressionExt {
269        fn is_literal(&self) -> bool;
270    }
271
272    impl ExpressionExt for Expression {
273        fn is_literal(&self) -> bool {
274            matches!(self, Expression::Literal { .. })
275        }
276    }
277
278    #[test]
279    fn test_expr_literals() {
280        assert!(expr("42").is_literal());
281        assert!(expr("3.14").is_literal());
282        assert!(expr("\"hello\"").is_literal());
283        assert!(expr("true").is_literal());
284        assert!(expr("false").is_literal());
285    }
286
287    #[test]
288    fn test_expr_identifiers() {
289        match expr("x") {
290            Expression::Identifier { name, .. } => assert_eq!(name, "x"),
291            _ => panic!("Expected identifier"),
292        }
293
294        match expr("my_var") {
295            Expression::Identifier { name, .. } => assert_eq!(name, "my_var"),
296            _ => panic!("Expected identifier"),
297        }
298    }
299
300    #[test]
301    fn test_expr_function_calls() {
302        match expr("now()") {
303            Expression::Call { name, args, .. } => {
304                assert_eq!(name, "now");
305                assert_eq!(args.len(), 0);
306            }
307            _ => panic!("Expected call"),
308        }
309
310        match expr("add(x, y)") {
311            Expression::Call { name, args, .. } => {
312                assert_eq!(name, "add");
313                assert_eq!(args.len(), 2);
314            }
315            _ => panic!("Expected call with 2 args"),
316        }
317    }
318
319    #[test]
320    fn test_expr_field_access() {
321        match expr("user.name") {
322            Expression::Access { field, .. } => assert_eq!(field, "name"),
323            _ => panic!("Expected field access"),
324        }
325
326        match expr("obj.x.y") {
327            Expression::Access { field, .. } => assert_eq!(field, "y"),
328            _ => panic!("Expected nested field access"),
329        }
330    }
331
332    #[test]
333    fn test_try_expr_success() {
334        assert!(try_expr("42").is_ok());
335        assert!(try_expr("foo()").is_ok());
336        assert!(try_expr("x.y").is_ok());
337        assert!(try_expr("true").is_ok());
338    }
339
340    #[test]
341    fn test_try_expr_empty_input() {
342        match try_expr("") {
343            Err(ExprError::EmptyInput) => {}
344            other => panic!("Expected EmptyInput error, got: {other:?}"),
345        }
346    }
347
348    #[test]
349    fn test_try_expr_invalid_syntax() {
350        match try_expr("!!!") {
351            Err(ExprError::ParseFailed { input, .. }) => {
352                assert_eq!(input, "!!!");
353            }
354            other => panic!("Expected ParseFailed error, got: {other:?}"),
355        }
356
357        match try_expr(")(") {
358            Err(ExprError::ParseFailed { .. }) => {}
359            other => panic!("Expected ParseFailed error, got: {other:?}"),
360        }
361    }
362
363    #[test]
364    fn test_expr_value_success() {
365        let v = expr_value("42");
366        match v {
367            Value::Expression(_) => {}
368            _ => panic!("Expected expression value"),
369        }
370    }
371
372    #[test]
373    fn test_try_expr_value_success() {
374        let result = try_expr_value("my_func()");
375        assert!(result.is_ok());
376        match result.unwrap() {
377            Value::Expression(_) => {}
378            _ => panic!("Expected expression value"),
379        }
380    }
381
382    #[test]
383    fn test_try_expr_value_empty() {
384        match try_expr_value("") {
385            Err(ExprError::EmptyInput) => {}
386            other => panic!("Expected EmptyInput error, got: {other:?}"),
387        }
388    }
389
390    #[test]
391    fn test_expr_error_display() {
392        let err = ExprError::EmptyInput;
393        assert_eq!(err.to_string(), "Expression cannot be empty");
394
395        let err = ExprError::Missing;
396        assert_eq!(err.to_string(), "Expression is missing or null");
397
398        let err = ExprError::ParseFailed {
399            source: hedl_core::lex::LexError::InvalidToken {
400                message: "test error".to_string(),
401                pos: hedl_core::lex::SourcePos::new(1, 1),
402            },
403            input: "bad input".to_string(),
404        };
405        assert!(err.to_string().contains("bad input"));
406        assert!(err.to_string().contains("test error"));
407    }
408
409    #[test]
410    fn test_expr_is_expression_trait() {
411        // Verify that Expression has necessary trait implementations
412        let e = expr("42");
413        let _ = format!("{e:?}"); // Debug
414        let e2 = expr("42");
415        let _ = e == e2; // PartialEq
416        let _ = e.clone(); // Clone
417    }
418
419    #[test]
420    #[should_panic(expected = "Invalid test expression")]
421    fn test_expr_panics_on_invalid() {
422        let _ = expr("!!!");
423    }
424
425    #[test]
426    #[should_panic(expected = "Invalid test expression")]
427    fn test_expr_panics_on_empty() {
428        let _ = expr("");
429    }
430
431    #[test]
432    fn test_try_expr_preserves_input_in_error() {
433        let input = "invalid stuff!!!";
434        match try_expr(input) {
435            Err(ExprError::ParseFailed {
436                input: err_input, ..
437            }) => {
438                assert_eq!(err_input, input);
439            }
440            _ => panic!("Expected ParseFailed with preserved input"),
441        }
442    }
443}