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}