Skip to main content

rhai/api/
formatting.rs

1//! Module that provide formatting services to the [`Engine`].
2use crate::packages::iter_basic::{BitRange, CharsStream, StepRange};
3use crate::parser::{ParseResult, ParseState};
4use crate::tokenizer::TokenStream;
5use crate::{
6    Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError,
7    SmartString, ERR,
8};
9use std::any::type_name;
10#[cfg(feature = "no_std")]
11use std::prelude::v1::*;
12
13/// Map the name of a standard type into a friendly form.
14#[inline]
15#[must_use]
16pub fn map_std_type_name(name: &str, shorthands: bool) -> &str {
17    let name = name.trim();
18
19    match name {
20        "INT" => return type_name::<crate::INT>(),
21        #[cfg(not(feature = "no_float"))]
22        "FLOAT" => return type_name::<crate::FLOAT>(),
23        _ => (),
24    }
25
26    if name == type_name::<String>() {
27        return if shorthands { "string" } else { "String" };
28    }
29    if name == type_name::<ImmutableString>() || name == "ImmutableString" {
30        return if shorthands {
31            "string"
32        } else {
33            "ImmutableString"
34        };
35    }
36    if name == type_name::<&str>() {
37        return if shorthands { "string" } else { "&str" };
38    }
39    #[cfg(feature = "decimal")]
40    if name == type_name::<rust_decimal::Decimal>() {
41        return if shorthands { "decimal" } else { "Decimal" };
42    }
43    if name == type_name::<FnPtr>() || name == "FnPtr" {
44        return if shorthands { "Fn" } else { "FnPtr" };
45    }
46    #[cfg(not(feature = "no_index"))]
47    if name == type_name::<crate::Array>() || name == "Array" {
48        return if shorthands { "array" } else { "Array" };
49    }
50    #[cfg(not(feature = "no_index"))]
51    if name == type_name::<crate::Blob>() || name == "Blob" {
52        return if shorthands { "blob" } else { "Blob" };
53    }
54    #[cfg(not(feature = "no_object"))]
55    if name == type_name::<crate::Map>() || name == "Map" {
56        return if shorthands { "map" } else { "Map" };
57    }
58    #[cfg(not(feature = "no_time"))]
59    if name == type_name::<crate::Instant>() || name == "Instant" {
60        return if shorthands { "timestamp" } else { "Instant" };
61    }
62    if name == type_name::<ExclusiveRange>() || name == "ExclusiveRange" {
63        return if shorthands {
64            "range"
65        } else if cfg!(feature = "only_i32") {
66            "Range<i32>"
67        } else {
68            "Range<i64>"
69        };
70    }
71    if name == type_name::<InclusiveRange>() || name == "InclusiveRange" {
72        return if shorthands {
73            "range="
74        } else if cfg!(feature = "only_i32") {
75            "RangeInclusive<i32>"
76        } else {
77            "RangeInclusive<i64>"
78        };
79    }
80    if name == type_name::<BitRange>() {
81        return if shorthands { "range" } else { "BitRange" };
82    }
83    if name == type_name::<CharsStream>() {
84        return if shorthands { "range" } else { "CharStream" };
85    }
86
87    let step_range_name = type_name::<StepRange<u8>>();
88    let step_range_name = &step_range_name[..step_range_name.len() - 3];
89
90    if name.starts_with(step_range_name) && name.ends_with('>') {
91        return if shorthands {
92            "range"
93        } else {
94            let step_range_name = step_range_name.split("::").last().unwrap();
95            &step_range_name[..step_range_name.len() - 1]
96        };
97    }
98
99    #[cfg(not(feature = "no_float"))]
100    if name == type_name::<crate::packages::iter_basic::StepRange<crate::FLOAT>>() {
101        return if shorthands {
102            "range"
103        } else {
104            "StepFloatRange"
105        };
106    }
107    #[cfg(feature = "decimal")]
108    if name == type_name::<crate::packages::iter_basic::StepRange<rust_decimal::Decimal>>() {
109        return if shorthands {
110            "range"
111        } else {
112            "StepDecimalRange"
113        };
114    }
115
116    name.strip_prefix("rhai::")
117        .map_or(name, |s| map_std_type_name(s, shorthands))
118}
119
120/// Format a Rust parameter type to be display-friendly.
121///
122/// * `rhai::` prefix is cleared.
123/// * `()` is cleared.
124/// * `&mut` is cleared.
125/// * `INT` and `FLOAT` are expanded.
126/// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`][crate::RhaiResultOf] are expanded.
127#[cfg(feature = "metadata")]
128pub fn format_param_type_for_display(typ: &str, is_return_type: bool) -> std::borrow::Cow<'_, str> {
129    const RESULT_TYPE: &str = "Result<";
130    const ERROR_TYPE: &str = ",Box<EvalAltResult>>";
131    const RHAI_RESULT_TYPE: &str = "RhaiResult";
132    const RHAI_RESULT_TYPE_EXPAND: &str = "Result<Dynamic, Box<EvalAltResult>>";
133    const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<";
134    const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box<EvalAltResult>>";
135    const RHAI_RANGE: &str = "ExclusiveRange";
136    const RHAI_RANGE_TYPE: &str = "Range<";
137    const RHAI_RANGE_EXPAND: &str = "Range<{}>";
138    const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange";
139    const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<";
140    const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>";
141
142    let typ = typ.trim();
143
144    if let Some(x) = typ.strip_prefix("rhai::") {
145        return format_param_type_for_display(x, is_return_type);
146    } else if let Some(x) = typ.strip_prefix("&mut ") {
147        let r = format_param_type_for_display(x, false);
148        return if r == x {
149            typ.into()
150        } else {
151            format!("&mut {r}").into()
152        };
153    } else if typ.contains(' ') {
154        let typ = typ.replace(' ', "");
155        let r = format_param_type_for_display(&typ, is_return_type);
156        return r.into_owned().into();
157    }
158
159    match typ {
160        "" | "()" if is_return_type => "".into(),
161        "INT" => std::any::type_name::<crate::INT>().into(),
162        #[cfg(not(feature = "no_float"))]
163        "FLOAT" => std::any::type_name::<crate::FLOAT>().into(),
164        RHAI_RANGE => RHAI_RANGE_EXPAND
165            .replace("{}", std::any::type_name::<crate::INT>())
166            .into(),
167        RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND
168            .replace("{}", std::any::type_name::<crate::INT>())
169            .into(),
170        RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(),
171        ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => {
172            let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1];
173            RHAI_RANGE_EXPAND
174                .replace("{}", format_param_type_for_display(inner, false).trim())
175                .into()
176        }
177        ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => {
178            let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1];
179            RHAI_INCLUSIVE_RANGE_EXPAND
180                .replace("{}", format_param_type_for_display(inner, false).trim())
181                .into()
182        }
183        ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => {
184            let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1];
185            RHAI_RESULT_OF_TYPE_EXPAND
186                .replace("{}", format_param_type_for_display(inner, false).trim())
187                .into()
188        }
189        ty if ty.starts_with(RESULT_TYPE) && ty.ends_with(ERROR_TYPE) => {
190            let inner = &ty[RESULT_TYPE.len()..ty.len() - ERROR_TYPE.len()];
191            RHAI_RESULT_OF_TYPE_EXPAND
192                .replace("{}", format_param_type_for_display(inner, false).trim())
193                .into()
194        }
195        ty => ty.into(),
196    }
197}
198
199impl Engine {
200    /// Pretty-print a type name.
201    ///
202    /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
203    /// the type name provided for the registration will be used.
204    #[inline]
205    #[must_use]
206    pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
207        self.global_modules
208            .iter()
209            .find_map(|m| m.get_custom_type_display_by_name(name))
210            .or_else(|| {
211                #[cfg(not(feature = "no_module"))]
212                return self
213                    .global_sub_modules
214                    .values()
215                    .find_map(|m| m.get_custom_type_display_by_name(name));
216
217                #[cfg(feature = "no_module")]
218                return None;
219            })
220            .unwrap_or_else(|| map_std_type_name(name, true))
221    }
222
223    /// Format a Rust parameter type.
224    ///
225    /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
226    /// the type name provided for the registration will be used.
227    ///
228    /// This method properly handles type names beginning with `&mut`.
229    #[cfg(feature = "metadata")]
230    #[inline]
231    #[must_use]
232    pub(crate) fn format_param_type<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> {
233        if let Some(x) = name.strip_prefix("&mut ") {
234            return match self.format_param_type(x) {
235                r if r == x => name.into(),
236                r => format!("&mut {r}").into(),
237            };
238        }
239
240        self.map_type_name(name).into()
241    }
242
243    /// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.
244    #[cold]
245    #[inline(never)]
246    #[must_use]
247    pub(crate) fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> RhaiError {
248        ERR::ErrorMismatchDataType(self.map_type_name(type_name::<T>()).into(), typ.into(), pos)
249            .into()
250    }
251
252    /// Compact a script to eliminate insignificant whitespaces and comments.
253    ///
254    /// This is useful to prepare a script for further compressing.
255    ///
256    /// The output script is semantically identical to the input script, except smaller in size.
257    ///
258    /// Unlike other uglifiers and minifiers, this method does not rename variables nor perform any
259    /// optimization on the input script.
260    #[inline]
261    pub fn compact_script(&self, script: impl AsRef<str>) -> ParseResult<String> {
262        let scripts = [script];
263        let (mut stream, tc) = self.lex(&scripts);
264
265        tc.borrow_mut().compressed = Some(String::new());
266        stream.state.last_token = Some(SmartString::new_const());
267
268        let input = &mut TokenStream::new(stream);
269        let lib = &mut <_>::default();
270        let state = ParseState::new(None, input, tc.clone(), lib);
271
272        let mut _ast = self.parse(
273            state,
274            #[cfg(not(feature = "no_optimize"))]
275            crate::OptimizationLevel::None,
276        )?;
277
278        let guard = tc.borrow();
279        Ok(guard.compressed.as_ref().unwrap().into())
280    }
281}