Skip to main content

rhai_trace/
lib.rs

1//! `rhai_trace` is a small Rust library which provides better error and span support 
2//! for [Rhai](https://rhai.rs), the embeddable scripting language.
3//!
4//! # Example
5//!
6//! ```rust
7//! use rhai_trace::{SpanTracer, BetterError};
8//! use rhai::Engine;
9//!
10//! fn main() -> Result<(), Box<dyn std::error::Error>> {
11//!     // Example Rhai code
12//!     let code = r#"
13//!         let a = 42;
14//!         let b = a + 1;
15//!         fn multiply(x, y) { x * y }
16//!         let c = multiply("a", 7);  // <-- will trigger runtime error
17//!     "#;
18//!
19//!     // Initialize the span tracer
20//!     let tracer = SpanTracer::new();
21//!
22//!     // Extract spans from the code
23//!     let spans = tracer.extract_from(code)?;
24//!
25//!     println!("Extracted spans:");
26//!     for span in &spans {
27//!         for span in &spans {
28//!             println!("{}..{}: '{}'",
29//!                 span.start(),
30//!                 span.end(),
31//!                 &code[span.start()..span.end()]
32//!             );
33//!         }
34//!     }
35//!
36//!     // Attempt to execute the code with Rhai engine
37//!     let engine = Engine::new();
38//!     match engine.eval::<rhai::Dynamic>(code) {
39//!         Ok(result) => println!("Execution result: {:?}", result),
40//!         Err(e) => {
41//!             // Improve the error using our library
42//!             if let Ok(better) = BetterError::improve_eval_error(&e, code, &engine, None) {
43//!                 // This returns a [`BetterError`] structure.
44//!                 //
45//!                 // It includes all the information you need to print a
46//!                 // pretty error! It provides all the information needed to display
47//!                 // diagnostics (message, hint, note, etc.) and can be used with any
48//!                 // diagnostic or pretty-printing crate, even those that 
49//!                 // don't natively support spans.
50//!                 //
51//!                 // A full example showing this integration is provided below. 
52//                  // See the repository link at the end of this page for a full working example.
53//!             } else {
54//!                 eprintln!("Original Error: {:?}", e);
55//!             }
56//!         }
57//!     }
58//!
59//!     Ok(())
60//! }
61//! ```
62//!
63//!
64//! For a complete working example that integrates `rhai_trace` with the [`ariadne`](https://docs.rs/ariadne) crate for pretty error reporting, check out the example folder:
65//! [GitHub Example](https://github.com/Byson94/rhai_trace/tree/main/example)
66
67pub mod error;
68pub mod span;
69pub mod tracer;
70
71// == Rexporting ==//
72pub use error::BetterError;
73pub use span::Span;
74pub use tracer::SpanTracer;
75
76#[cfg(test)]
77mod test {
78    use super::*;
79    use rhai::{Engine, Dynamic};
80
81    #[test]
82    fn test_span_extraction() {
83        let code = r#"
84            let a = 42;
85            let b = a + 1;
86            fn add(x, y) { x + y }
87            let z = a/0;
88            let c = add(a, b);
89        "#;
90
91        let tracer = SpanTracer::new();
92        let spans_result = tracer.extract_from(code);
93
94        match spans_result {
95            Ok(_) => {}
96            Err(ref err) => {
97                if let Some(parse_err) = err.downcast_ref::<rhai::ParseError>() {
98                    match BetterError::improve_parse_error(&parse_err, code) {
99                        Ok(better_error) => println!("Better error: {:?}", better_error),
100                        Err(e) => eprintln!("Failed to improve parse error: {:?}", e),
101                    }
102
103                    return;
104                } else {
105                    eprintln!("Other error: {:?}", err);
106                    return;
107                }
108            }
109        }
110
111        let spans = spans_result.unwrap();
112        assert!(!spans.is_empty(), "There should be some spans extracted");
113
114        //  Check that all spans have valid start/end
115        for span in &spans {
116            assert!(span.start() <= span.end(), "Span start should be <= end");
117            assert!(
118                span.end() <= code.len(),
119                "Span end should not exceed code length"
120            );
121
122            // Check that span content is not empty
123            let snippet = &code[span.start()..span.end()];
124            assert!(
125                !snippet.trim().is_empty(),
126                "Span content should not be empty"
127            );
128        }
129
130        // Debug output
131        for span in &spans {
132            println!("Span: {:?} -> '{}'", span, &code[span.start()..span.end()]);
133        }
134    }
135
136    #[test]
137    fn test_better_error() {
138        let code = r#"
139fn multiply(x, y) { x * y }
140
141multiply("a", 2);
142
143return "test complete"
144        "#;
145
146        let engine = Engine::new();
147        let mut scope = rhai::Scope::new();
148        let _ = engine.eval_with_scope::<Dynamic>(&mut scope, code).map_err(|e| {
149            eprintln!(
150                "Better Error: {:#?}",
151                BetterError::improve_eval_error(&e, code, &engine, None)
152            );
153        });
154    }
155}