ladfile_builder/
lib.rs

1//! Parsing definitions for the LAD (Language Agnostic Decleration) file format.
2pub mod plugin;
3
4use bevy::{ecs::world::World, log, platform::collections::HashSet};
5use bevy_mod_scripting_core::{
6    bindings::{
7        function::{
8            namespace::Namespace,
9            script_function::{
10                DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext,
11            },
12        },
13        MarkAsCore, MarkAsGenerated, MarkAsSignificant, ReflectReference,
14    },
15    docgen::{
16        info::FunctionInfo,
17        typed_through::{ThroughTypeInfo, TypedWrapperKind, UntypedWrapperKind},
18        TypedThrough,
19    },
20    match_by_type,
21};
22use bevy_reflect::{NamedField, TypeInfo, TypeRegistry, Typed, UnnamedField};
23use ladfile::*;
24use std::{
25    any::TypeId,
26    borrow::Cow,
27    cmp::{max, min},
28    collections::HashMap,
29    ffi::OsString,
30    path::PathBuf,
31};
32
33/// We can assume that the types here will be either primitives
34/// or reflect types, as the rest will be covered by typed wrappers
35/// so just check
36fn primitive_from_type_id(type_id: TypeId) -> Option<LadBMSPrimitiveKind> {
37    match_by_type!(match type_id {
38        i: bool => return Some(LadBMSPrimitiveKind::Bool),
39        i: isize => return Some(LadBMSPrimitiveKind::Isize),
40        i: i8 => return Some(LadBMSPrimitiveKind::I8),
41        i: i16 => return Some(LadBMSPrimitiveKind::I16),
42        i: i32 => return Some(LadBMSPrimitiveKind::I32),
43        i: i64 => return Some(LadBMSPrimitiveKind::I64),
44        i: i128 => return Some(LadBMSPrimitiveKind::I128),
45        i: usize => return Some(LadBMSPrimitiveKind::Usize),
46        i: u8 => return Some(LadBMSPrimitiveKind::U8),
47        i: u16 => return Some(LadBMSPrimitiveKind::U16),
48        i: u32 => return Some(LadBMSPrimitiveKind::U32),
49        i: u64 => return Some(LadBMSPrimitiveKind::U64),
50        i: u128 => return Some(LadBMSPrimitiveKind::U128),
51        i: f32 => return Some(LadBMSPrimitiveKind::F32),
52        i: f64 => return Some(LadBMSPrimitiveKind::F64),
53        i: char => return Some(LadBMSPrimitiveKind::Char),
54        i: &'static str => return Some(LadBMSPrimitiveKind::Str),
55        i: str => return Some(LadBMSPrimitiveKind::Str),
56        i: String => return Some(LadBMSPrimitiveKind::String),
57        i: OsString => return Some(LadBMSPrimitiveKind::OsString),
58        i: PathBuf => return Some(LadBMSPrimitiveKind::PathBuf),
59        i: FunctionCallContext => return Some(LadBMSPrimitiveKind::FunctionCallContext),
60        i: DynamicScriptFunction => return Some(LadBMSPrimitiveKind::DynamicFunction),
61        i: DynamicScriptFunctionMut => return Some(LadBMSPrimitiveKind::DynamicFunctionMut),
62        i: ReflectReference => return Some(LadBMSPrimitiveKind::ReflectReference)
63    });
64    None
65}
66
67/// A builder for constructing LAD files.
68/// This should be your preferred way of constructing LAD files.
69pub struct LadFileBuilder<'t> {
70    file: LadFile,
71    type_id_mapping: HashMap<TypeId, LadTypeId>,
72    type_registry: &'t TypeRegistry,
73    sorted: bool,
74    exclude_types_involving_unregistered_types: bool,
75}
76
77impl<'t> LadFileBuilder<'t> {
78    /// Create a new LAD file builder without default primitives.
79    pub fn new_empty(type_registry: &'t TypeRegistry) -> Self {
80        Self {
81            file: LadFile::new(),
82            type_id_mapping: HashMap::new(),
83            type_registry,
84            sorted: false,
85            exclude_types_involving_unregistered_types: false,
86        }
87    }
88
89    /// Create a new LAD file builder loaded with primitives.
90    pub fn new(type_registry: &'t TypeRegistry) -> Self {
91        let mut builder = Self::new_empty(type_registry);
92
93        builder
94            .add_bms_primitive::<bool>("A boolean value")
95            .add_bms_primitive::<isize>("A signed pointer-sized integer")
96            .add_bms_primitive::<i8>("A signed 8-bit integer")
97            .add_bms_primitive::<i16>("A signed 16-bit integer")
98            .add_bms_primitive::<i32>("A signed 32-bit integer")
99            .add_bms_primitive::<i64>("A signed 64-bit integer")
100            .add_bms_primitive::<i128>("A signed 128-bit integer")
101            .add_bms_primitive::<usize>("An unsigned pointer-sized integer")
102            .add_bms_primitive::<u8>("An unsigned 8-bit integer")
103            .add_bms_primitive::<u16>("An unsigned 16-bit integer")
104            .add_bms_primitive::<u32>("An unsigned 32-bit integer")
105            .add_bms_primitive::<u64>("An unsigned 64-bit integer")
106            .add_bms_primitive::<u128>("An unsigned 128-bit integer")
107            .add_bms_primitive::<f32>("A 32-bit floating point number")
108            .add_bms_primitive::<f64>("A 64-bit floating point number")
109            .add_bms_primitive::<char>("An 8-bit character")
110            .add_bms_primitive::<&'static str>("A string slice")
111            .add_bms_primitive::<String>("A heap allocated string")
112            .add_bms_primitive::<OsString>("A heap allocated OS string")
113            .add_bms_primitive::<PathBuf>("A heap allocated file path")
114            .add_bms_primitive::<FunctionCallContext>("Function call context, if accepted by a function, means the function can access the world in arbitrary ways.")
115            .add_bms_primitive::<DynamicScriptFunction>("A callable dynamic function")
116            .add_bms_primitive::<DynamicScriptFunctionMut>("A stateful and callable dynamic function")
117            .add_bms_primitive::<ReflectReference>("A reference to a reflectable type");
118
119        builder
120    }
121
122    /// Set whether types involving unregistered types should be excluded.
123    /// I.e. `HashMap<T, V>` with T or V not being registered will be excluded.
124    pub fn set_exclude_including_unregistered(&mut self, exclude: bool) -> &mut Self {
125        self.exclude_types_involving_unregistered_types = exclude;
126        self
127    }
128
129    /// Set whether the LAD file should be sorted at build time.
130    pub fn set_sorted(&mut self, sorted: bool) -> &mut Self {
131        self.sorted = sorted;
132        self
133    }
134
135    /// Add a BMS primitive to the LAD file.
136    /// Will do nothing if the type is not a BMS primitive.
137    pub fn add_bms_primitive<T: 'static>(
138        &mut self,
139        docs: impl Into<Cow<'static, str>>,
140    ) -> &mut Self {
141        let type_id = self.lad_id_from_type_id(TypeId::of::<T>());
142        let kind = match primitive_from_type_id(TypeId::of::<T>()) {
143            Some(primitive) => primitive,
144            None => return self,
145        };
146        self.file.primitives.insert(
147            type_id,
148            LadBMSPrimitiveType {
149                kind,
150                documentation: docs.into(),
151            },
152        );
153        self
154    }
155
156    /// Mark a type as generated.
157    pub fn mark_generated(&mut self, type_id: TypeId) -> &mut Self {
158        let type_id = self.lad_id_from_type_id(type_id);
159        if let Some(t) = self.file.types.get_mut(&type_id) {
160            t.generated = true;
161        }
162        self
163    }
164
165    /// Set the insignificance value of a type.
166    pub fn set_insignificance(&mut self, type_id: TypeId, importance: usize) -> &mut Self {
167        let type_id = self.lad_id_from_type_id(type_id);
168        if let Some(t) = self.file.types.get_mut(&type_id) {
169            t.insignificance = importance;
170        }
171        self
172    }
173
174    /// Add a global instance to the LAD file.
175    ///
176    /// Requires the type to be registered via [`Self::add_type`] or [`Self::add_type_info`] first to provide rich type information.
177    ///
178    /// If `is_static` is true, the instance will be treated as a static instance
179    /// and hence not support method call syntax or method calls (i.e. only functions without a self parameter can be called on them).
180    pub fn add_instance<T: 'static + TypedThrough>(
181        &mut self,
182        key: impl Into<Cow<'static, str>>,
183        is_static: bool,
184    ) -> &mut Self {
185        let type_info = T::through_type_info();
186        let type_kind = self.lad_type_kind_from_through_type(&type_info);
187        self.file.globals.insert(
188            key.into(),
189            LadInstance {
190                type_kind,
191                is_static,
192            },
193        );
194        self
195    }
196
197    /// An untyped version of [`Self::add_instance`].
198    ///
199    /// Adds a global instance to the LAD file.
200    pub fn add_instance_dynamic(
201        &mut self,
202        key: impl Into<Cow<'static, str>>,
203        is_static: bool,
204        through_type: ThroughTypeInfo,
205    ) -> &mut Self {
206        let type_kind = self.lad_type_kind_from_through_type(&through_type);
207        self.file.globals.insert(
208            key.into(),
209            LadInstance {
210                type_kind,
211                is_static,
212            },
213        );
214        self
215    }
216
217    /// Adds a global instance to the LAD file with a custom lad type kind.
218    pub fn add_instance_manually(
219        &mut self,
220        key: impl Into<Cow<'static, str>>,
221        is_static: bool,
222        type_kind: LadTypeKind,
223    ) -> &mut Self {
224        self.file.globals.insert(
225            key.into(),
226            LadInstance {
227                type_kind,
228                is_static,
229            },
230        );
231        self
232    }
233
234    /// Adds a type which does not implement reflect to the list of types.
235    pub fn add_nonreflect_type<T: 'static>(
236        &mut self,
237        crate_: Option<&str>,
238        documentation: &str,
239    ) -> &mut Self {
240        let path = std::any::type_name::<T>().to_string();
241        let identifier = path
242            .split("::")
243            .last()
244            .map(|o| o.to_owned())
245            .unwrap_or_else(|| path.clone());
246
247        let lad_type_id = self.lad_id_from_type_id(std::any::TypeId::of::<T>());
248        self.file.types.insert(
249            lad_type_id,
250            LadType {
251                identifier,
252                crate_: crate_.map(|s| s.to_owned()),
253                path,
254                generics: vec![],
255                documentation: Some(documentation.trim().to_owned()),
256                associated_functions: vec![],
257                layout: LadTypeLayout::Opaque,
258                generated: false,
259                insignificance: default_importance(),
260            },
261        );
262        self
263    }
264
265    /// Add a type definition to the LAD file.
266    ///
267    /// Equivalent to calling [`Self::add_type_info`] with `T::type_info()`.
268    pub fn add_type<T: Typed>(&mut self) -> &mut Self {
269        self.add_type_info(T::type_info());
270        self
271    }
272
273    /// Add a type definition to the LAD file.
274    /// Will overwrite any existing type definitions with the same type id.
275    pub fn add_type_info(&mut self, type_info: &TypeInfo) -> &mut Self {
276        let registration = self.type_registry.get(type_info.type_id());
277
278        let mut insignificance = default_importance();
279        let mut generated = false;
280        if let Some(registration) = registration {
281            if registration.contains::<MarkAsGenerated>() {
282                generated = true;
283            }
284            if registration.contains::<MarkAsCore>() {
285                insignificance = default_importance() / 2;
286            }
287            if registration.contains::<MarkAsSignificant>() {
288                insignificance = default_importance() / 4;
289            }
290        }
291
292        let type_id = self.lad_id_from_type_id(type_info.type_id());
293        let lad_type = LadType {
294            identifier: type_info
295                .type_path_table()
296                .ident()
297                .unwrap_or_default()
298                .to_string(),
299            generics: type_info
300                .generics()
301                .iter()
302                .map(|param| LadGeneric {
303                    type_id: self.lad_id_from_type_id(param.type_id()),
304                    name: param.name().to_string(),
305                })
306                .collect(),
307            documentation: type_info.docs().map(|s| s.to_string()),
308            associated_functions: Vec::new(),
309            crate_: type_info
310                .type_path_table()
311                .crate_name()
312                .map(|s| s.to_owned()),
313            path: type_info.type_path_table().path().to_owned(),
314            layout: self.lad_layout_from_type_info(type_info),
315            generated,
316            insignificance,
317        };
318        self.file.types.insert(type_id, lad_type);
319        self
320    }
321
322    /// Adds all nested types within the given `ThroughTypeInfo`.
323    pub fn add_through_type_info(&mut self, type_info: &ThroughTypeInfo) -> &mut Self {
324        match type_info {
325            ThroughTypeInfo::UntypedWrapper { through_type, .. } => {
326                self.add_type_info(through_type);
327            }
328            ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
329                TypedWrapperKind::Union(ti) => {
330                    for ti in ti {
331                        self.add_through_type_info(ti);
332                    }
333                }
334                TypedWrapperKind::Vec(ti) => {
335                    self.add_through_type_info(ti);
336                }
337                TypedWrapperKind::HashMap(til, tir) => {
338                    self.add_through_type_info(til);
339                    self.add_through_type_info(tir);
340                }
341                TypedWrapperKind::Array(ti, _) => {
342                    self.add_through_type_info(ti);
343                }
344                TypedWrapperKind::Option(ti) => {
345                    self.add_through_type_info(ti);
346                }
347                TypedWrapperKind::InteropResult(ti) => {
348                    self.add_through_type_info(ti);
349                }
350                TypedWrapperKind::Tuple(ti) => {
351                    for ti in ti {
352                        self.add_through_type_info(ti);
353                    }
354                }
355            },
356            ThroughTypeInfo::TypeInfo(type_info) => {
357                self.add_type_info(type_info);
358            }
359        }
360
361        self
362    }
363
364    /// Add a function definition to the LAD file.
365    /// Will overwrite any existing function definitions with the same function id.
366    ///
367    /// Parses argument and return specific docstrings as per: https://github.com/rust-lang/rust/issues/57525
368    ///
369    /// i.e. looks for blocks like:
370    /// ```rust,ignore
371    /// /// Arguments:
372    /// ///  * `arg_name`: docstring1
373    /// ///  * `arg_name2`: docstring2
374    /// ///
375    /// /// Returns:
376    /// ///  * `return_name`: return docstring
377    /// ```
378    ///
379    /// And then removes them from the original block, instead putting it in each argument / return docstring
380    pub fn add_function_info(&mut self, function_info: &FunctionInfo) -> &mut Self {
381        let default_docstring = Cow::Owned("".into());
382        let (main_docstring, arg_docstrings, return_docstring) =
383            Self::split_docstring(function_info.docs.as_ref().unwrap_or(&default_docstring));
384
385        let function_id = self.lad_function_id_from_info(function_info);
386        let lad_function = LadFunction {
387            identifier: function_info.name.clone(),
388            arguments: function_info
389                .arg_info
390                .clone()
391                .into_iter()
392                .map(|arg| {
393                    let kind = match &arg.type_info {
394                        Some(through_type) => self.lad_type_kind_from_through_type(through_type),
395                        None => LadTypeKind::Unknown(self.lad_id_from_type_id(arg.type_id)),
396                    };
397                    LadArgument {
398                        kind,
399                        documentation: arg_docstrings.iter().find_map(|(name, doc)| {
400                            (Some(name.as_str()) == arg.name.as_deref())
401                                .then_some(Cow::Owned(doc.clone()))
402                        }),
403                        name: arg.name,
404                    }
405                })
406                .collect(),
407            return_type: LadArgument {
408                name: return_docstring.as_ref().cloned().map(|(n, _)| n.into()),
409                documentation: return_docstring.map(|(_, v)| v.into()),
410                kind: function_info
411                    .return_info
412                    .type_info
413                    .clone()
414                    .map(|info| self.lad_type_kind_from_through_type(&info))
415                    .unwrap_or_else(|| {
416                        LadTypeKind::Unknown(
417                            self.lad_id_from_type_id(function_info.return_info.type_id),
418                        )
419                    }),
420            },
421            documentation: (!main_docstring.is_empty()).then_some(main_docstring.into()),
422            namespace: match function_info.namespace {
423                Namespace::Global => LadFunctionNamespace::Global,
424                Namespace::OnType(type_id) => {
425                    LadFunctionNamespace::Type(self.lad_id_from_type_id(type_id))
426                }
427            },
428        };
429        self.file.functions.insert(function_id, lad_function);
430        self
431    }
432
433    /// Set the markdown description of the LAD file.
434    pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
435        self.file.description = Some(description.into());
436        self
437    }
438
439    fn has_unknowns(&self, type_id: TypeId) -> bool {
440        if primitive_from_type_id(type_id).is_some() {
441            return false;
442        }
443
444        let type_info = match self.type_registry.get_type_info(type_id) {
445            Some(info) => info,
446            None => return true,
447        };
448        let inner_type_ids: Vec<_> = match type_info {
449            TypeInfo::Struct(struct_info) => {
450                struct_info.generics().iter().map(|g| g.type_id()).collect()
451            }
452            TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info
453                .generics()
454                .iter()
455                .map(|g| g.type_id())
456                .collect(),
457            TypeInfo::Tuple(tuple_info) => {
458                tuple_info.generics().iter().map(|g| g.type_id()).collect()
459            }
460            TypeInfo::List(list_info) => vec![list_info.item_ty().id()],
461            TypeInfo::Array(array_info) => vec![array_info.item_ty().id()],
462            TypeInfo::Map(map_info) => vec![map_info.key_ty().id(), map_info.value_ty().id()],
463            TypeInfo::Set(set_info) => vec![set_info.value_ty().id()],
464            TypeInfo::Enum(enum_info) => enum_info.generics().iter().map(|g| g.type_id()).collect(),
465            TypeInfo::Opaque(_) => vec![],
466        };
467
468        inner_type_ids.iter().any(|id| self.has_unknowns(*id))
469    }
470
471    /// Build the finalized and optimized LAD file.
472    pub fn build(&mut self) -> LadFile {
473        let mut file = std::mem::replace(&mut self.file, LadFile::new());
474
475        if self.exclude_types_involving_unregistered_types {
476            let mut to_remove = HashSet::new();
477            for reg in self.type_registry.iter() {
478                let type_id = reg.type_id();
479                if self.has_unknowns(type_id) {
480                    to_remove.insert(self.lad_id_from_type_id(type_id));
481                }
482            }
483
484            // remove those type ids
485            file.types.retain(|id, _| !to_remove.contains(id));
486        }
487
488        // associate functions on type namespaces with their types
489        for (function_id, function) in file.functions.iter() {
490            match &function.namespace {
491                LadFunctionNamespace::Type(type_id) => {
492                    if let Some(t) = file.types.get_mut(type_id) {
493                        t.associated_functions.push(function_id.clone());
494                    } else {
495                        log::warn!(
496                            "Function {} is on type {}, but the type is not registered in the LAD file.",
497                            function_id,
498                            type_id
499                        );
500                    }
501                }
502                LadFunctionNamespace::Global => {}
503            }
504        }
505
506        if self.sorted {
507            file.types.sort_by(|ak, av, bk, bv| {
508                let complexity_a: usize = av
509                    .path
510                    .char_indices()
511                    .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
512                    .sum();
513                let complexity_b = bv
514                    .path
515                    .char_indices()
516                    .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
517                    .sum();
518
519                let has_functions_a = !av.associated_functions.is_empty();
520                let has_functions_b = !bv.associated_functions.is_empty();
521
522                let ordered_by_name = ak.cmp(bk);
523                let ordered_by_generics_complexity = complexity_a.cmp(&complexity_b);
524                let ordered_by_generated = av.generated.cmp(&bv.generated);
525                let ordered_by_having_functions = has_functions_b.cmp(&has_functions_a);
526                let ordered_by_significance = av.insignificance.cmp(&bv.insignificance);
527
528                ordered_by_significance
529                    .then(ordered_by_having_functions)
530                    .then(ordered_by_generics_complexity)
531                    .then(ordered_by_name)
532                    .then(ordered_by_generated)
533            });
534
535            file.functions.sort_keys();
536            file.primitives.sort_keys();
537        }
538
539        file
540    }
541
542    /// Checks if a line is one of:
543    /// - `# key:`
544    /// - `key:`
545    /// - `key`
546    /// - `## key`
547    ///
548    /// Or similar patterns
549    fn is_docstring_delimeter(key: &str, line: &str) -> bool {
550        line.trim()
551            .trim_start_matches("#")
552            .trim_end_matches(":")
553            .trim()
554            .eq_ignore_ascii_case(key)
555    }
556
557    /// Parses lines of the pattern:
558    /// * `arg` : val
559    ///
560    /// returning (arg,val) without markup
561    fn parse_arg_docstring(line: &str) -> Option<(&str, &str)> {
562        let regex =
563            regex::Regex::new(r#"\s*\*\s*`(?<arg>[^`]+)`\s*[:-]\s*(?<val>.+[^\s]).*$"#).ok()?;
564        let captures = regex.captures(line)?;
565        let arg = captures.name("arg")?;
566        let val = captures.name("val")?;
567
568        Some((arg.as_str(), val.as_str()))
569    }
570
571    /// Splits the docstring, into the following:
572    /// - The main docstring
573    /// - The argument docstrings
574    /// - The return docstring
575    ///
576    /// While removing any prefixes
577    fn split_docstring(
578        docstring: &str,
579    ) -> (String, Vec<(String, String)>, Option<(String, String)>) {
580        // find a line containing only `Arguments:` ignoring spaces and markdown headings
581        let lines = docstring.lines().collect::<Vec<_>>();
582
583        // this must exist for us to parse any of the areas
584        let argument_line_idx = match lines
585            .iter()
586            .enumerate()
587            .find_map(|(idx, l)| Self::is_docstring_delimeter("arguments", l).then_some(idx))
588        {
589            Some(a) => a,
590            None => return (docstring.to_owned(), vec![], None),
591        };
592
593        // this can, not exist, if arguments area does
594        let return_line_idx = lines.iter().enumerate().find_map(|(idx, l)| {
595            (Self::is_docstring_delimeter("returns", l)
596                || Self::is_docstring_delimeter("return", l))
597            .then_some(idx)
598        });
599
600        let return_area_idx = return_line_idx.unwrap_or(usize::MAX);
601        let return_area_first = argument_line_idx > return_area_idx;
602        let argument_range = match return_area_first {
603            true => argument_line_idx..lines.len(),
604            false => argument_line_idx..return_area_idx,
605        };
606        let return_range = match return_area_first {
607            true => return_area_idx..argument_line_idx,
608            false => return_area_idx..lines.len(),
609        };
610        let non_main_area =
611            min(return_area_idx, argument_line_idx)..max(return_area_idx, argument_line_idx);
612
613        let parsed_lines = lines
614            .iter()
615            .enumerate()
616            .map(|(i, l)| {
617                match Self::parse_arg_docstring(l) {
618                    Some(parsed) => {
619                        // figure out if it's in the argument, return or neither of the areas
620                        // if return area doesn't exist assign everything to arguments
621                        let in_argument_range = argument_range.contains(&i);
622                        let in_return_range = return_range.contains(&i);
623                        (l, Some((in_argument_range, in_return_range, parsed)))
624                    }
625                    None => (l, None),
626                }
627            })
628            .collect::<Vec<_>>();
629
630        // collect all argument docstrings, and the first return docstring, removing those lines from the docstring (and the argument/return headers)
631        // any other ones leave alone
632        let main_docstring = parsed_lines
633            .iter()
634            .enumerate()
635            .filter_map(|(i, (l, parsed))| {
636                ((!non_main_area.contains(&i) || !l.trim().is_empty())
637                    && (i != return_area_idx && i != argument_line_idx)
638                    && (parsed.is_none() || parsed.is_some_and(|(a, b, _)| !a && !b)))
639                .then_some((**l).to_owned())
640            })
641            .collect::<Vec<_>>();
642
643        let arg_docstrings = parsed_lines
644            .iter()
645            .filter_map(|(_l, parsed)| {
646                parsed.and_then(|(is_arg, is_return, (a, b))| {
647                    (is_arg && !is_return).then_some((a.to_owned(), b.to_owned()))
648                })
649            })
650            .collect();
651
652        let return_docstring = parsed_lines.iter().find_map(|(_l, parsed)| {
653            parsed.and_then(|(is_arg, is_return, (a, b))| {
654                (!is_arg && is_return).then_some((a.to_owned(), b.to_owned()))
655            })
656        });
657
658        (main_docstring.join("\n"), arg_docstrings, return_docstring)
659    }
660
661    fn variant_identifier_for_non_enum(type_info: &TypeInfo) -> Cow<'static, str> {
662        type_info
663            .type_path_table()
664            .ident()
665            .unwrap_or_else(|| type_info.type_path_table().path())
666            .into()
667    }
668
669    fn struct_variant_from_named_fields<'a, I: Iterator<Item = &'a NamedField>>(
670        &mut self,
671        name: Cow<'static, str>,
672        fields: I,
673    ) -> LadVariant {
674        LadVariant::Struct {
675            name,
676            fields: fields
677                .map(|field| LadNamedField {
678                    name: field.name().to_string(),
679                    type_: self.lad_id_from_type_id(field.type_id()),
680                })
681                .collect(),
682        }
683    }
684
685    fn tuple_struct_variant_from_fields<'a, I: Iterator<Item = &'a UnnamedField>>(
686        &mut self,
687        name: Cow<'static, str>,
688        fields: I,
689    ) -> LadVariant {
690        LadVariant::TupleStruct {
691            name,
692            fields: fields
693                .map(|field| LadField {
694                    type_: self.lad_id_from_type_id(field.type_id()),
695                })
696                .collect(),
697        }
698    }
699
700    fn lad_layout_from_type_info(&mut self, type_info: &TypeInfo) -> LadTypeLayout {
701        match type_info {
702            TypeInfo::Struct(struct_info) => {
703                let fields = (0..struct_info.field_len()).filter_map(|i| struct_info.field_at(i));
704
705                LadTypeLayout::MonoVariant(self.struct_variant_from_named_fields(
706                    Self::variant_identifier_for_non_enum(type_info),
707                    fields,
708                ))
709            }
710            TypeInfo::TupleStruct(tuple_struct_info) => {
711                let fields = (0..tuple_struct_info.field_len())
712                    .filter_map(|i| tuple_struct_info.field_at(i));
713
714                LadTypeLayout::MonoVariant(self.tuple_struct_variant_from_fields(
715                    Self::variant_identifier_for_non_enum(type_info),
716                    fields,
717                ))
718            }
719            TypeInfo::Enum(enum_info) => {
720                let mut variants = Vec::new();
721                for i in 0..enum_info.variant_len() {
722                    if let Some(variant) = enum_info.variant_at(i) {
723                        let variant_name = variant.name();
724                        let variant = match variant {
725                            bevy_reflect::VariantInfo::Struct(struct_variant_info) => {
726                                let fields = (0..struct_variant_info.field_len())
727                                    .filter_map(|i| struct_variant_info.field_at(i));
728
729                                self.struct_variant_from_named_fields(variant_name.into(), fields)
730                            }
731                            bevy_reflect::VariantInfo::Tuple(tuple_variant_info) => {
732                                let fields = (0..tuple_variant_info.field_len())
733                                    .filter_map(|i| tuple_variant_info.field_at(i));
734
735                                self.tuple_struct_variant_from_fields(variant_name.into(), fields)
736                            }
737                            bevy_reflect::VariantInfo::Unit(_) => LadVariant::Unit {
738                                name: variant_name.into(),
739                            },
740                        };
741                        variants.push(variant);
742                    }
743                }
744                LadTypeLayout::Enum(variants)
745            }
746            _ => LadTypeLayout::Opaque,
747        }
748    }
749
750    fn lad_id_from_type_id(&mut self, type_id: TypeId) -> LadTypeId {
751        // a special exception
752        if type_id == std::any::TypeId::of::<World>() {
753            return LadTypeId::new_string_id("World".into());
754        }
755
756        if let Some(lad_id) = self.type_id_mapping.get(&type_id) {
757            return lad_id.clone();
758        }
759
760        let new_id = match primitive_from_type_id(type_id) {
761            Some(primitive) => primitive.lad_type_id(),
762            None => {
763                if let Some(info) = self.type_registry.get_type_info(type_id) {
764                    LadTypeId::new_string_id(info.type_path_table().path().into())
765                } else {
766                    LadTypeId::new_string_id(format!("{type_id:?}").into())
767                }
768            }
769        };
770
771        self.type_id_mapping.insert(type_id, new_id.clone());
772        new_id
773    }
774
775    fn lad_function_id_from_info(&mut self, function_info: &FunctionInfo) -> LadFunctionId {
776        let namespace_string = match function_info.namespace {
777            bevy_mod_scripting_core::bindings::function::namespace::Namespace::Global => {
778                "".to_string()
779            }
780            bevy_mod_scripting_core::bindings::function::namespace::Namespace::OnType(type_id) => {
781                self.lad_id_from_type_id(type_id).to_string()
782            }
783        };
784
785        LadFunctionId::new_string_id(format!("{}::{}", namespace_string, function_info.name))
786    }
787
788    fn lad_type_kind_from_through_type(&mut self, through_type: &ThroughTypeInfo) -> LadTypeKind {
789        match through_type {
790            ThroughTypeInfo::UntypedWrapper {
791                through_type,
792                wrapper_kind,
793                ..
794            } => match wrapper_kind {
795                UntypedWrapperKind::Ref => {
796                    LadTypeKind::Ref(self.lad_id_from_type_id(through_type.type_id()))
797                }
798                UntypedWrapperKind::Mut => {
799                    LadTypeKind::Mut(self.lad_id_from_type_id(through_type.type_id()))
800                }
801                UntypedWrapperKind::Val => {
802                    LadTypeKind::Val(self.lad_id_from_type_id(through_type.type_id()))
803                }
804            },
805            ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
806                TypedWrapperKind::Vec(through_type_info) => LadTypeKind::Vec(Box::new(
807                    self.lad_type_kind_from_through_type(through_type_info),
808                )),
809                TypedWrapperKind::HashMap(through_type_info, through_type_info1) => {
810                    LadTypeKind::HashMap(
811                        Box::new(self.lad_type_kind_from_through_type(through_type_info)),
812                        Box::new(self.lad_type_kind_from_through_type(through_type_info1)),
813                    )
814                }
815                TypedWrapperKind::Array(through_type_info, size) => LadTypeKind::Array(
816                    Box::new(self.lad_type_kind_from_through_type(through_type_info)),
817                    *size,
818                ),
819                TypedWrapperKind::Option(through_type_info) => LadTypeKind::Option(Box::new(
820                    self.lad_type_kind_from_through_type(through_type_info),
821                )),
822                TypedWrapperKind::InteropResult(through_type_info) => LadTypeKind::InteropResult(
823                    Box::new(self.lad_type_kind_from_through_type(through_type_info)),
824                ),
825                TypedWrapperKind::Tuple(through_type_infos) => LadTypeKind::Tuple(
826                    through_type_infos
827                        .iter()
828                        .map(|through_type_info| {
829                            self.lad_type_kind_from_through_type(through_type_info)
830                        })
831                        .collect(),
832                ),
833                TypedWrapperKind::Union(through_type_infos) => LadTypeKind::Union(
834                    through_type_infos
835                        .iter()
836                        .map(|through_type_info| {
837                            self.lad_type_kind_from_through_type(through_type_info)
838                        })
839                        .collect(),
840                ),
841            },
842            ThroughTypeInfo::TypeInfo(type_info) => {
843                match primitive_from_type_id(type_info.type_id()) {
844                    Some(primitive) => LadTypeKind::Primitive(primitive),
845                    None => LadTypeKind::Unknown(self.lad_id_from_type_id(type_info.type_id())),
846                }
847            }
848        }
849    }
850}
851
852#[cfg(test)]
853mod test {
854    use std::collections::HashMap;
855
856    use bevy_mod_scripting_core::{
857        bindings::{
858            function::{
859                from::Ref,
860                namespace::{GlobalNamespace, IntoNamespace},
861            },
862            Union, Val,
863        },
864        docgen::info::GetFunctionInfo,
865    };
866    use bevy_reflect::Reflect;
867
868    use super::*;
869
870    /// normalize line endings etc..
871    fn normalize_file(file: &mut String) {
872        *file = file.replace("\r\n", "\n");
873    }
874
875    #[test]
876    fn test_empty_lad_file_serializes_correctly() {
877        let lad_file = LadFile::new();
878        let serialized = serialize_lad_file(&lad_file, false).unwrap();
879        let deserialized = parse_lad_file(&serialized).unwrap();
880        assert_eq!(lad_file, deserialized);
881        assert_eq!(deserialized.version, ladfile::LAD_VERSION);
882    }
883
884    #[test]
885    fn parse_docstrings_is_resistant_to_whitespace() {
886        pretty_assertions::assert_eq!(
887            LadFileBuilder::parse_arg_docstring("* `arg` : doc"),
888            Some(("arg", "doc"))
889        );
890        pretty_assertions::assert_eq!(
891            LadFileBuilder::parse_arg_docstring("  * `arg` - doc"),
892            Some(("arg", "doc"))
893        );
894        pretty_assertions::assert_eq!(
895            LadFileBuilder::parse_arg_docstring("   *   `arg`   :    doc     "),
896            Some(("arg", "doc"))
897        );
898    }
899
900    #[test]
901    fn docstring_delimeter_detection_is_flexible() {
902        assert!(LadFileBuilder::is_docstring_delimeter(
903            "arguments",
904            "arguments"
905        ));
906        assert!(LadFileBuilder::is_docstring_delimeter(
907            "arguments",
908            "Arguments:"
909        ));
910        assert!(LadFileBuilder::is_docstring_delimeter(
911            "arguments",
912            "## Arguments"
913        ));
914        assert!(LadFileBuilder::is_docstring_delimeter(
915            "arguments",
916            "## Arguments:"
917        ));
918        assert!(LadFileBuilder::is_docstring_delimeter(
919            "arguments",
920            "Arguments"
921        ));
922    }
923
924    /// Helper function to assert that splitting the docstring produces the expected output.
925    fn assert_docstring_split(
926        input: &str,
927        expected_main: &str,
928        expected_args: &[(&str, &str)],
929        expected_return: Option<(&str, &str)>,
930        test_name: &str,
931    ) {
932        let (main, args, ret) = LadFileBuilder::split_docstring(input);
933
934        pretty_assertions::assert_eq!(
935            main,
936            expected_main,
937            "main docstring was incorrect - {}",
938            test_name
939        );
940
941        let expected_args: Vec<(String, String)> = expected_args
942            .iter()
943            .map(|(a, b)| (a.to_string(), b.to_string()))
944            .collect();
945        pretty_assertions::assert_eq!(
946            args,
947            expected_args,
948            "argument docstring was incorrect - {}",
949            test_name
950        );
951
952        let expected_ret = expected_return.map(|(a, b)| (a.to_string(), b.to_string()));
953        pretty_assertions::assert_eq!(
954            ret,
955            expected_ret,
956            "return docstring was incorrect - {}",
957            test_name
958        );
959    }
960
961    #[test]
962    fn docstrings_parse_correctly_from_various_formats() {
963        assert_docstring_split(
964            r#"
965                ## Hello
966                Arguments: 
967                    * `arg1` - some docs
968                    * `arg2` : some more docs
969                # Returns
970                    * `return` : return docs
971            "#
972            .trim(),
973            "## Hello",
974            &[("arg1", "some docs"), ("arg2", "some more docs")],
975            Some(("return", "return docs")),
976            "normal docstring",
977        );
978        assert_docstring_split(
979            r#"
980                Arguments: 
981                    * `arg1` - some docs
982                    * `arg2` : some more docs
983                Returns
984                    * `return` : return docs
985            "#
986            .trim(),
987            "",
988            &[("arg1", "some docs"), ("arg2", "some more docs")],
989            Some(("return", "return docs")),
990            "empty main docstring",
991        );
992        assert_docstring_split(
993            r#"
994                Arguments: 
995                    * `arg1` - some docs
996                    * `arg2` : some more docs
997            "#
998            .trim(),
999            "",
1000            &[("arg1", "some docs"), ("arg2", "some more docs")],
1001            None,
1002            "no return docstring",
1003        );
1004        assert_docstring_split(
1005            r#"
1006                Returns
1007                    * `return` : return docs
1008            "#
1009            .trim(),
1010            r#"
1011                Returns
1012                    * `return` : return docs
1013            "#
1014            .trim(),
1015            &[],
1016            None,
1017            "no argument docstring",
1018        );
1019        assert_docstring_split(
1020            r#"
1021                ## Hello
1022            "#
1023            .trim(),
1024            "## Hello",
1025            &[],
1026            None,
1027            "no argument or return docstring",
1028        );
1029        // return first
1030        assert_docstring_split(
1031            r#"
1032                Returns
1033                    * `return` : return docs
1034                Arguments: 
1035                    * `arg1` - some docs
1036                    * `arg2` : some more docs
1037            "#
1038            .trim(),
1039            "",
1040            &[("arg1", "some docs"), ("arg2", "some more docs")],
1041            Some(("return", "return docs")),
1042            "return first",
1043        );
1044        // whitespace in between
1045        assert_docstring_split(
1046            r#"
1047                ## Hello
1048
1049
1050                Arguments: 
1051                    * `arg1` - some docs
1052                    * `arg2` : some more docs
1053
1054                Returns
1055                    * `return` : return docs
1056            "#
1057            .trim(),
1058            "## Hello\n\n",
1059            &[("arg1", "some docs"), ("arg2", "some more docs")],
1060            Some(("return", "return docs")),
1061            "whitespace in between",
1062        );
1063    }
1064
1065    /// Set to true to put output into test_assets.
1066    const BLESS_TEST_FILE: bool = false;
1067
1068    #[test]
1069    fn test_serializes_as_expected() {
1070        let mut type_registry = TypeRegistry::default();
1071
1072        #[derive(Reflect)]
1073        /// I am a struct
1074        struct StructType<T> {
1075            /// hello from field
1076            field: usize,
1077            /// hello from field 2
1078            field2: T,
1079        }
1080
1081        impl TypedThrough for StructType<usize> {
1082            fn through_type_info() -> ThroughTypeInfo {
1083                ThroughTypeInfo::TypeInfo(Self::type_info())
1084            }
1085        }
1086
1087        #[derive(Reflect)]
1088        /// I am a unit test type
1089        struct UnitType;
1090
1091        impl TypedThrough for UnitType {
1092            fn through_type_info() -> ThroughTypeInfo {
1093                ThroughTypeInfo::TypeInfo(Self::type_info())
1094            }
1095        }
1096
1097        #[derive(Reflect)]
1098        /// I am a tuple test type
1099        struct TupleStructType(pub usize, #[doc = "hello"] pub String);
1100
1101        #[derive(Reflect)]
1102        enum EnumType {
1103            /// hello from variant
1104            Unit,
1105            /// hello from variant 2
1106            Struct {
1107                /// hello from field
1108                field: usize,
1109            },
1110            /// hello from variant 3
1111            TupleStruct(usize, #[doc = "asd"] String),
1112        }
1113
1114        type_registry.register::<StructType<usize>>();
1115        type_registry.register::<UnitType>();
1116        type_registry.register::<TupleStructType>();
1117        type_registry.register::<EnumType>();
1118
1119        let function = |_: ReflectReference, _: usize| 2usize;
1120        let function_info = function
1121            .get_function_info("hello_world".into(), StructType::<usize>::into_namespace())
1122            .with_docs("hello docs");
1123
1124        let function_with_complex_args =
1125            |_: ReflectReference, _: (usize, String), _: Option<Vec<Ref<EnumType>>>| 2usize;
1126        let function_with_complex_args_info = function_with_complex_args
1127            .get_function_info("hello_world".into(), StructType::<usize>::into_namespace())
1128            .with_arg_names(&["ref_", "tuple", "option_vec_ref_wrapper"])
1129            .with_docs(
1130                "Arguments: ".to_owned()
1131                    + "\n"
1132                    + " * `ref_`: I am some docs for argument 1"
1133                    + "\n"
1134                    + " * `tuple`: I am some docs for argument 2"
1135                    + "\n"
1136                    + " * `option_vec_ref_wrapper`: I am some docs for argument 3"
1137                    + "\n"
1138                    + "Returns: "
1139                    + "\n"
1140                    + " * `return`: I am some docs for the return type, I provide a name for the return value too",
1141            );
1142
1143        let global_function = |_: usize| 2usize;
1144        let global_function_info = global_function
1145            .get_function_info("hello_world".into(), GlobalNamespace::into_namespace())
1146            .with_arg_names(&["arg1"]);
1147
1148        let mut lad_file = LadFileBuilder::new(&type_registry)
1149            .set_description("## Hello gentlemen\n I am  markdown file.\n - hello\n - world")
1150            .set_sorted(true)
1151            .add_function_info(&function_info)
1152            .add_function_info(&global_function_info)
1153            .add_function_info(&function_with_complex_args_info)
1154            .add_type::<StructType<usize>>()
1155            .add_type::<UnitType>()
1156            .add_type::<TupleStructType>()
1157            .add_type_info(EnumType::type_info())
1158            .add_instance::<Val<StructType<usize>>>("my_static_instance", true)
1159            .add_instance::<Vec<Val<UnitType>>>("my_non_static_instance", false)
1160            .add_instance::<HashMap<String, Union<String, String>>>("map", false)
1161            .build();
1162
1163        // normalize the version so we don't have to update it every time
1164        lad_file.version = "{{version}}".into();
1165        let mut serialized = serialize_lad_file(&lad_file, true).unwrap();
1166
1167        normalize_file(&mut serialized);
1168
1169        if BLESS_TEST_FILE {
1170            let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
1171            let path_to_test_assets = std::path::Path::new(&manifest_dir)
1172                .join("..")
1173                .join("ladfile")
1174                .join("test_assets");
1175
1176            println!("Blessing test file at {path_to_test_assets:?}");
1177            std::fs::write(path_to_test_assets.join("test.lad.json"), &serialized).unwrap();
1178            panic!("Blessed test file, please rerun the test");
1179        }
1180
1181        let mut expected = ladfile::EXAMPLE_LADFILE.to_string();
1182        normalize_file(&mut expected);
1183
1184        pretty_assertions::assert_eq!(serialized.trim(), expected.trim(),);
1185    }
1186
1187    #[test]
1188    fn test_asset_deserializes_correctly() {
1189        let asset = ladfile::EXAMPLE_LADFILE.to_string();
1190        let deserialized = parse_lad_file(&asset).unwrap();
1191        assert_eq!(deserialized.version, "{{version}}");
1192    }
1193
1194    #[test]
1195    fn test_nested_unregistered_generic_is_removed() {
1196        let mut type_registry = TypeRegistry::default();
1197
1198        #[derive(Reflect)]
1199        #[reflect(no_field_bounds, from_reflect = false)]
1200        struct StructType<T> {
1201            #[reflect(ignore)]
1202            phantom: std::marker::PhantomData<T>,
1203        }
1204
1205        #[derive(Reflect)]
1206        struct Blah;
1207
1208        type_registry.register::<StructType<Blah>>();
1209
1210        let lad_file = LadFileBuilder::new_empty(&type_registry)
1211            .set_sorted(true)
1212            .set_exclude_including_unregistered(true)
1213            .add_type::<StructType<Blah>>()
1214            .build();
1215
1216        assert_eq!(lad_file.types.len(), 0);
1217    }
1218}