cel_eval/
lib.rs

1#[cfg(not(target_arch = "wasm32"))]
2uniffi::include_scaffolding!("cel");
3mod ast;
4mod models;
5
6use crate::ast::{ASTExecutionContext, JSONExpression};
7use crate::models::PassableValue::Function;
8use crate::models::{ExecutionContext, PassableMap, PassableValue};
9use crate::models::PassableValue::PMap;
10use crate::ExecutableType::{CompiledProgram, AST};
11use async_trait::async_trait;
12use cel_interpreter::extractors::This;
13use cel_interpreter::objects::{Key, Map, TryIntoValue};
14use cel_interpreter::{Context, ExecutionError, Expression, FunctionContext, Program, Value};
15use std::collections::HashMap;
16use std::error::Error;
17use std::fmt;
18use std::fmt::Debug;
19use std::ops::Deref;
20use std::sync::{Arc, mpsc, Mutex};
21use std::thread::spawn;
22use cel_parser::parse;
23
24#[cfg(target_arch = "wasm32")]
25use wasm_bindgen_futures::spawn_local;
26#[cfg(not(target_arch = "wasm32"))]
27use futures_lite::future::block_on;
28use uniffi::deps::log::__private_api::log;
29
30
31/**
32 * Host context trait that defines the methods that the host context should implement,
33 * i.e. iOS or Android calling code. This trait is used to resolve dynamic properties in the
34 * CEL expression during evaluation, such as `computed.daysSinceEvent("event_name")` or similar.
35 * Note: Since WASM async support in the browser is still not fully mature, we're using the
36 * target_arch cfg to define the trait methods differently for WASM and non-WASM targets.
37 */
38#[cfg(target_arch = "wasm32")]
39pub trait HostContext: Send + Sync {
40    fn computed_property(&self, name: String, args: String) -> String;
41
42    fn device_property(&self, name: String, args: String) -> String;
43}
44
45#[cfg(not(target_arch = "wasm32"))]
46#[async_trait]
47pub trait HostContext: Send + Sync {
48    async fn computed_property(&self, name: String, args: String) -> String;
49
50    async fn device_property(&self, name: String, args: String) -> String;
51}
52
53/**
54 * Evaluate a CEL expression with the given AST
55 * @param ast The AST Execution Context, serialized as JSON. This defines the AST, the variables, and the platform properties.
56 * @param host The host context to use for resolving properties
57 * @return The result of the evaluation, either "true" or "false"
58 */
59pub fn evaluate_ast_with_context(definition: String, host: Arc<dyn HostContext>) -> String {
60    let data: Result<ASTExecutionContext,_> = serde_json::from_str(definition.as_str());
61    let data = match data {
62        Ok(data) => data,
63        Err(_) => {
64            let e : Result<_, String> = Err::<ASTExecutionContext,String>("Invalid execution context JSON".to_string());
65            return serde_json::to_string(&e).unwrap()
66        }
67    };
68    let host = host.clone();
69    let res = execute_with(
70        AST(data.expression.into()),
71        data.variables,
72        data.computed,
73        data.device,
74        host,
75    ).map(|val| val.to_passable())
76        .map_err(|err| err.to_string());
77    serde_json::to_string(&res).unwrap()
78}
79
80/**
81 * Evaluate a CEL expression with the given AST without any context
82 * @param ast The AST of the expression, serialized as JSON. This AST should contain already resolved dynamic variables.
83 * @return The result of the evaluation, either "true" or "false"
84 */
85pub fn evaluate_ast(ast: String) -> String {
86    let data: Result<JSONExpression,_> = serde_json::from_str(ast.as_str());
87    let data : JSONExpression = match data {
88        Ok(data) => data,
89        Err(_) => {
90            let e : Result<_, String> = Err::<JSONExpression,String>("Invalid definition for AST Execution".to_string());
91            return serde_json::to_string(&e).unwrap()
92        }
93    };
94    let ctx = Context::default();
95    let res = ctx.resolve(&data.into())
96        .map(|val| DisplayableValue(val.clone()).to_passable())
97        .map_err(|err| DisplayableError(err).to_string());
98    serde_json::to_string(&res).unwrap()
99}
100
101/**
102 * Evaluate a CEL expression with the given definition by compiling it first.
103 * @param definition The definition of the expression, serialized as JSON. This defines the expression, the variables, and the platform properties.
104 * @param host The host context to use for resolving properties
105 * @return The result of the evaluation, either "true" or "false"
106 */
107
108pub fn evaluate_with_context(definition: String, host: Arc<dyn HostContext>) -> String {
109    let data: Result<ExecutionContext,_> = serde_json::from_str(definition.as_str());
110    let data: ExecutionContext = match data {
111        Ok(data) => data,
112        Err(_) => {
113            let e : Result<ExecutionContext, String> = Err("Invalid execution context JSON".to_string());
114            return serde_json::to_string(&e).unwrap()
115        }
116    };
117    let compiled = Program::compile(data.expression.as_str())
118        .map(|program| CompiledProgram(program));
119    let result = match compiled {
120        Ok(compiled) => {
121            execute_with(
122                compiled,
123                data.variables,
124                data.computed,
125                data.device,
126                host,
127            ).map(|val| val.to_passable())
128                .map_err(|err| err.to_string())
129
130        }
131        Err(e) =>
132            Err("Failed to compile expression".to_string())
133    };
134    serde_json::to_string(&result).unwrap()
135}
136
137/**
138 * Transforms a given CEL expression into a CEL AST, serialized as JSON.
139 * @param expression The CEL expression to parse
140 * @return The AST of the expression, serialized as JSON
141 */
142pub fn parse_to_ast(expression: String) -> String {
143    let ast: Result<JSONExpression, _> = parse(expression.as_str()).map(|expr| expr.into());
144    let ast = ast
145        .map_err(|err| err.to_string());
146    serde_json::to_string(&ast.unwrap()).unwrap()
147}
148
149/**
150Type of expression to be executed, either a compiled program or an AST.
151 */
152enum ExecutableType {
153    AST(Expression),
154    CompiledProgram(Program),
155}
156
157/**
158 * Execute a CEL expression, either compiled or pure AST; with the given context.
159 * @param executable The executable type, either an AST or a compiled program
160 * @param variables The variables to use in the expression
161 * @param platform The platform properties or functions to use in the expression
162 * @param host The host context to use for resolving properties
163 */
164fn execute_with(
165    executable: ExecutableType,
166    variables: PassableMap,
167    computed: Option<HashMap<String, Vec<PassableValue>>>,
168    device: Option<HashMap<String, Vec<PassableValue>>>,
169    host: Arc<dyn HostContext + 'static>,
170) -> Result<DisplayableValue, DisplayableError> {
171    let host = host.clone();
172    let host = Arc::new(Mutex::new(host));
173    let mut ctx = Context::default();
174    // Isolate device to re-bind later
175    let device_map = variables.clone();
176    let device_map = device_map.map.get("device").clone().unwrap_or(&PMap(HashMap::new())).clone();
177
178    // Add predefined variables locally to the context
179    variables
180        .map
181        .iter()
182        .for_each(|it| {
183            let _ = ctx.add_variable(it.0.as_str(), it.1.to_cel());
184        });
185    // Add maybe function
186    ctx.add_function("maybe", maybe);
187
188    // This function is used to extract the value of a property from the host context
189    // As UniFFi doesn't support recursive enums yet, we have to pass it in as a
190    // JSON serialized string of a PassableValue from Host and deserialize it here
191
192    enum PropType {
193        Computed,
194        Device,
195    }
196    #[cfg(not(target_arch = "wasm32"))]
197    fn prop_for(
198        prop_type: PropType,
199        name: Arc<String>,
200        args: Option<Vec<PassableValue>>,
201        ctx: &Arc<dyn HostContext>,
202    ) -> Result<PassableValue, String> {
203        // Get computed property
204        let val = futures_lite::future::block_on(async move {
205            let ctx = ctx.clone();
206            let args = if let Some(args) = args {
207                serde_json::to_string(&args)
208            } else {
209                serde_json::to_string::<Vec<PassableValue>>(&vec![])
210            };
211            match args {
212                Ok(args) => {
213                    match prop_type {
214                        PropType::Computed => Ok(ctx.computed_property(
215                            name.clone().to_string(),
216                            args,
217                        ).await),
218                        PropType::Device => Ok(ctx.device_property(
219                            name.clone().to_string(),
220                            args,
221                        ).await),
222                    }
223                }
224                Err(e) => {
225                    Err(ExecutionError::UndeclaredReference(name).to_string())
226                }
227            }
228        });
229        // Deserialize the value
230        let passable: Result<PassableValue, String> =
231            val.map(|val| serde_json::from_str(val.as_str()).unwrap_or(PassableValue::Null))
232                .map_err(|err| err.to_string());
233
234        passable
235    }
236
237    #[cfg(target_arch = "wasm32")]
238    fn prop_for(
239        prop_type: PropType,
240        name: Arc<String>,
241        args: Option<Vec<PassableValue>>,
242        ctx: &Arc<dyn HostContext>,
243    ) -> Option<PassableValue> {
244        let ctx = ctx.clone();
245
246        let val = match prop_type {
247            PropType::Computed => ctx.computed_property(
248                name.clone().to_string(),
249                serde_json::to_string(&args).expect("Failed to serialize args for computed property"),
250            ),
251            PropType::Device => ctx.device_property(
252                name.clone().to_string(),
253                serde_json::to_string(&args).expect("Failed to serialize args for computed property"),
254            ),
255        };
256        // Deserialize the value
257        let passable: Option<PassableValue> = serde_json::from_str(val.as_str()).unwrap_or(Some(PassableValue::Null));
258
259        passable
260    }
261
262    let computed = computed.unwrap_or(HashMap::new()).clone();
263
264    // Create computed properties as a map of keys and function names
265    let computed_host_properties: HashMap<Key, Value> = computed
266        .iter()
267        .map(|it| {
268            let args = it.1.clone();
269            let args = if args.is_empty() {
270                None
271            } else {
272                Some(Box::new(PassableValue::List(args)))
273            };
274            let name = it.0.clone();
275            (
276                Key::String(Arc::new(name.clone())),
277                Function(name, args).to_cel(),
278            )
279        })
280        .collect();
281
282    let device = device.unwrap_or(HashMap::new()).clone();
283
284
285    // From defined properties the device properties
286    let total_device_properties = if let PMap(map) = device_map {
287        map
288    } else {
289        HashMap::new()
290    };
291
292    // Create device properties as a map of keys and function names
293    let device_host_properties: HashMap<Key, Value> = device
294        .iter()
295        .map(|it| {
296            let args = it.1.clone();
297            let args = if args.is_empty() {
298                None
299            } else {
300                Some(Box::new(PassableValue::List(args)))
301            };
302            let name = it.0.clone();
303            (
304                Key::String(Arc::new(name.clone())),
305                Function(name, args).to_cel(),
306            )
307        })
308        .chain(total_device_properties.iter().map(|(k, v)| (Key::String(Arc::new(k.clone())), v.to_cel().clone())))
309        .collect();
310
311    // Add the map to the `computed` object
312    let _ = ctx.add_variable(
313        "computed",
314        Value::Map(Map {
315            map: Arc::new(computed_host_properties),
316        }),
317    );
318
319    // Add the map to the `device` object
320    let _ = ctx.add_variable(
321        "device",
322        Value::Map(Map {
323            map: Arc::new(device_host_properties),
324        }),
325    );
326
327
328    let binding = device.clone();
329    // Combine the device and computed properties
330    let host_properties = binding
331        .iter()
332        .chain(computed.iter())
333        .map(|(k, v)| (k.clone(), v.clone()))
334        .into_iter();
335
336    let mut device_properties_clone = device.clone().clone();
337    // Add those functions to the context
338    for it in host_properties {
339        let mut value = device_properties_clone.clone();
340        let key = it.0.clone();
341        let host_clone = Arc::clone(&host); // Clone the Arc to pass into the closure
342        let key_str = key.clone(); // Clone key for usage in the closure
343        ctx.add_function(
344            key_str.as_str(),
345            move |ftx: &FunctionContext| -> Result<Value, ExecutionError> {
346                let device = value.clone();
347                let fx = ftx.clone();
348                let name = fx.name.clone(); // Move the name into the closure
349                let args = fx.args.clone(); // Clone the arguments
350                let host = host_clone.lock(); // Lock the host for safe access
351                match host {
352                    Ok(host) => {
353                        prop_for(
354                            if device.contains_key(&it.0)
355                            { PropType::Device } else { PropType::Computed },
356                            name.clone(),
357                            Some(
358                                args.iter()
359                                    .map(|expression| {
360                                        DisplayableValue(ftx.ptx.resolve(expression).unwrap()).to_passable()
361                                    })
362                                    .collect(),
363                            ),
364                            &*host,
365                        )
366                            .map_or(Err(ExecutionError::UndeclaredReference(name)), |v| {
367                                Ok(v.to_cel())
368                            })
369                    }
370                    Err(e) => {
371                        let e = e.to_string();
372                        let name = name.clone().to_string();
373                        let error = ExecutionError::FunctionError { function: name, message: e };
374                        Err(error)
375                    }
376                }
377            },
378        );
379    }
380
381    let val = match executable {
382        AST(ast) => &ctx.resolve(&ast),
383        CompiledProgram(program) => &program.execute(&ctx),
384    };
385
386    val.clone().map(|val| DisplayableValue(val.clone()))
387        .map_err(|err| DisplayableError(err))
388}
389
390pub fn maybe(
391    ftx: &FunctionContext,
392    This(_this): This<Value>,
393    left: Expression,
394    right: Expression,
395) -> Result<Value, ExecutionError> {
396    return ftx.ptx.resolve(&left).or_else(|_| ftx.ptx.resolve(&right));
397}
398
399// Wrappers around CEL values used so that we can create extensions on them
400pub struct DisplayableValue(Value);
401
402pub struct DisplayableError(ExecutionError);
403
404impl fmt::Display for DisplayableValue {
405    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406        match &self.0 {
407            Value::Int(i) => write!(f, "{}", i),
408            Value::Float(x) => write!(f, "{}", x),
409            Value::String(s) => write!(f, "{}", s),
410            // Add more variants as needed
411            Value::UInt(i) => write!(f, "{}", i),
412            Value::Bytes(_) => {
413                write!(f, "{}", "bytes go here")
414            }
415            Value::Bool(b) => write!(f, "{}", b),
416            Value::Duration(d) => write!(f, "{}", d),
417            Value::Timestamp(t) => write!(f, "{}", t),
418            Value::Null => write!(f, "{}", "null"),
419            Value::Function(name, _) => write!(f, "{}", name),
420            Value::Map(map) => {
421                let res: HashMap<String, String> = map
422                    .map
423                    .iter()
424                    .map(|(k, v)| {
425                        let key = DisplayableValue(k.try_into_value().unwrap().clone()).to_string();
426                        let value = DisplayableValue(v.clone()).to_string().replace("\\", "");
427                        (key, value)
428                    })
429                    .collect();
430                let map = serde_json::to_string(&res).unwrap();
431                write!(f, "{}", map)
432            }
433            Value::List(list) => write!(
434                f,
435                "{}",
436                list.iter()
437                    .map(|v| {
438                        let key = DisplayableValue(v.clone());
439                        return key.to_string();
440                    })
441                    .collect::<Vec<_>>()
442                    .join(",\n ")
443            ),
444        }
445    }
446}
447
448impl fmt::Display for DisplayableError {
449    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
450        write!(f, "{}", self.0.to_string().as_str())
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    struct TestContext {
459        map: HashMap<String, String>,
460    }
461
462    #[async_trait]
463    impl HostContext for TestContext {
464        async fn computed_property(&self, name: String, args: String) -> String {
465            self.map.get(&name).unwrap().to_string()
466        }
467
468        async fn device_property(&self, name: String, args: String) -> String {
469            self.map.get(&name).unwrap().to_string()
470        }
471    }
472
473    #[tokio::test]
474    async fn test_variables() {
475        let ctx = Arc::new(TestContext {
476            map: HashMap::new(),
477        });
478        let res = evaluate_with_context(
479            r#"
480        {
481            "variables": {
482             "map" : {
483                    "foo": {"type": "int", "value": 100}
484            }},
485            "expression": "foo == 100"
486        }
487
488        "#
489                .to_string(),
490            ctx,
491        );
492        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
493    }
494
495    #[tokio::test]
496    async fn test_execution_with_ctx() {
497        let ctx = Arc::new(TestContext {
498            map: HashMap::new(),
499        });
500        let res = evaluate_with_context(
501            r#"
502        {
503            "variables": {
504             "map" : {
505                    "foo": {"type": "int", "value": 100},
506                    "bar": {"type": "int", "value": 42}
507            }},
508            "expression": "foo + bar == 142"
509        }
510
511        "#
512                .to_string(),
513            ctx,
514        );
515        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
516    }
517
518    #[test]
519    fn test_unknown_function_with_arg_fails_with_undeclared_ref() {
520        let ctx = Arc::new(TestContext {
521            map: HashMap::new(),
522        });
523
524        let res = evaluate_with_context(
525            r#"
526        {
527            "variables": {
528             "map" : {
529                    "foo": {"type": "int", "value": 100}
530            }},
531            "expression": "test_custom_func(foo) == 101"
532        }
533
534        "#
535                .to_string(),
536            ctx,
537        );
538        assert_eq!(res, "{\"Err\":\"Undeclared reference to 'test_custom_func'\"}");
539    }
540
541    #[test]
542    fn test_list_contains() {
543        let ctx = Arc::new(TestContext {
544            map: HashMap::new(),
545        });
546        let res = evaluate_with_context(
547            r#"
548        {
549            "variables": {
550                 "map" : {
551                    "numbers": {
552                        "type" : "list",
553                        "value" : [
554                            {"type": "int", "value": 1},
555                            {"type": "int", "value": 2},
556                            {"type": "int", "value": 3}
557                             ]
558                       }
559                 }
560            },
561            "expression": "numbers.contains(2)"
562        }
563
564        "#
565                .to_string(),
566            ctx,
567        );
568        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
569    }
570
571    #[tokio::test]
572    async fn test_execution_with_map() {
573        let ctx = Arc::new(TestContext {
574            map: HashMap::new(),
575        });
576        let res = evaluate_with_context(
577            r#"
578        {
579                    "variables": {
580                        "map": {
581                            "user": {
582                                "type": "map",
583                                "value": {
584                                    "should_display": {
585                                        "type": "bool",
586                                        "value": true
587                                    },
588                                    "some_value": {
589                                        "type": "uint",
590                                        "value": 13
591                                    }
592                                }
593                            }
594                        }
595                    },
596                    "expression": "user.should_display == true && user.some_value > 12"
597       }
598
599        "#
600                .to_string(),
601            ctx,
602        );
603        println!("{}", res.clone());
604        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
605    }
606
607    #[tokio::test]
608    async fn test_execution_with_failure() {
609        let ctx = Arc::new(TestContext {
610            map: HashMap::new(),
611        });
612        let res = evaluate_with_context(
613            r#"
614        {
615                    "variables": {
616                        "map": {
617                            "user": {
618                                "type": "map",
619                                "value": {
620                                    "some_value": {
621                                        "type": "uint",
622                                        "value": 13
623                                    }
624                                }
625                            }
626                        }
627                    },
628                    "expression": "user.should_display == true && user.some_value > 12"
629       }
630
631        "#
632                .to_string(),
633            ctx,
634        );
635        println!("{}", res.clone());
636        assert_eq!(res, "{\"Err\":\"No such key: should_display\"}");
637    }
638
639    #[tokio::test]
640    async fn test_execution_with_null() {
641        let ctx = Arc::new(TestContext {
642            map: HashMap::new(),
643        });
644        let res = evaluate_with_context(
645            r#"
646        {
647                    "variables": {
648                        "map": {
649                            "user": {
650                                "type": "map",
651                                "value": {
652                                    "some_value": {
653                                        "type": "Null",
654                                        "value": null
655                                    }
656                                }
657                            }
658                        }
659                    },
660                    "expression": "user.should_display == true && user.some_value > 12"
661       }
662
663        "#
664                .to_string(),
665            ctx,
666        );
667        println!("{}", res.clone());
668        assert_eq!(res, "{\"Err\":\"No such key: should_display\"}");
669    }
670    #[tokio::test]
671    async fn test_execution_with_platform_computed_reference() {
672        let days_since = PassableValue::UInt(7);
673        let days_since = serde_json::to_string(&days_since).unwrap();
674        let ctx = Arc::new(TestContext {
675            map: [("minutesSince".to_string(), days_since)]
676                .iter()
677                .cloned()
678                .collect(),
679        });
680        let res = evaluate_with_context(
681            r#"
682    {
683        "variables": {
684            "map": {}
685        },
686        "expression": "device.minutesSince('app_launch') == computed.minutesSince('app_install')",
687        "computed": {
688            "daysSince": [
689                {
690                    "type": "string",
691                    "value": "event_name"
692                }
693            ],
694            "minutesSince": [
695                {
696                    "type": "string",
697                    "value": "event_name"
698                }
699            ],
700            "hoursSince": [
701                {
702                    "type": "string",
703                    "value": "event_name"
704                }
705            ],
706            "monthsSince": [
707                {
708                    "type": "string",
709                    "value": "event_name"
710                }
711            ]
712        },
713        "device": {
714            "daysSince": [
715                {
716                    "type": "string",
717                    "value": "event_name"
718                }
719            ],
720            "minutesSince": [
721                {
722                    "type": "string",
723                    "value": "event_name"
724                }
725            ],
726            "hoursSince": [
727                {
728                    "type": "string",
729                    "value": "event_name"
730                }
731            ],
732            "monthsSince": [
733                {
734                    "type": "string",
735                    "value": "event_name"
736                }
737            ]
738        }
739    }"#.to_string(),
740            ctx,
741        );
742        println!("{}", res.clone());
743        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
744    }
745
746    #[tokio::test]
747    async fn test_execution_with_platform_device_function_and_property() {
748        let days_since = PassableValue::UInt(7);
749        let days_since = serde_json::to_string(&days_since).unwrap();
750        let ctx = Arc::new(TestContext {
751            map: [("minutesSince".to_string(), days_since)]
752                .iter()
753                .cloned()
754                .collect(),
755        });
756        let res = evaluate_with_context(
757            r#"
758    {
759        "variables": {
760            "map": {
761                "device": {
762                    "type": "map",
763                    "value": {
764                        "trial_days": {
765                            "type": "uint",
766                            "value": 7
767                        }
768                    }
769                }
770            }
771        },
772        "expression": "computed.minutesSince('app_launch') == device.trial_days",
773        "computed": {
774            "daysSince": [
775                {
776                    "type": "string",
777                    "value": "event_name"
778                }
779            ],
780            "minutesSince": [
781                {
782                    "type": "string",
783                    "value": "event_name"
784                }
785            ],
786            "hoursSince": [
787                {
788                    "type": "string",
789                    "value": "event_name"
790                }
791            ],
792            "monthsSince": [
793                {
794                    "type": "string",
795                    "value": "event_name"
796                }
797            ]
798        },
799        "device": {
800            "daysSince": [
801                {
802                    "type": "string",
803                    "value": "event_name"
804                }
805            ],
806            "minutesSince": [
807                {
808                    "type": "string",
809                    "value": "event_name"
810                }
811            ],
812            "hoursSince": [
813                {
814                    "type": "string",
815                    "value": "event_name"
816                }
817            ],
818            "monthsSince": [
819                {
820                    "type": "string",
821                    "value": "event_name"
822                }
823            ]
824        }
825    }"#.to_string(),
826            ctx,
827        );
828        println!("{}", res.clone());
829        assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
830    }
831
832
833    #[test]
834    fn test_parse_to_ast() {
835        let expression = "device.daysSince(app_install) == 3";
836        let ast_json = parse_to_ast(expression.to_string());
837        println!("\nSerialized AST:");
838        println!("{}", ast_json);
839        // Deserialize back to JSONExpression
840        let deserialized_json_expr: JSONExpression = serde_json::from_str(&ast_json).unwrap();
841
842        // Convert back to original Expression
843        let deserialized_expr: Expression = deserialized_json_expr.into();
844
845        println!("\nDeserialized Expression:");
846        println!("{:?}", deserialized_expr);
847
848        let parsed_expression = parse(expression).unwrap();
849        assert_eq!(parsed_expression, deserialized_expr);
850        println!("\nOriginal and deserialized expressions are equal!");
851    }
852}