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}