1pub 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
33fn 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
67pub 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 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 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 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 pub fn set_sorted(&mut self, sorted: bool) -> &mut Self {
131 self.sorted = sorted;
132 self
133 }
134
135 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 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 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 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 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 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 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 pub fn add_type<T: Typed>(&mut self) -> &mut Self {
269 self.add_type_info(T::type_info());
270 self
271 }
272
273 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 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 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 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 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 file.types.retain(|id, _| !to_remove.contains(id));
486 }
487
488 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 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 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 fn split_docstring(
578 docstring: &str,
579 ) -> (String, Vec<(String, String)>, Option<(String, String)>) {
580 let lines = docstring.lines().collect::<Vec<_>>();
582
583 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 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 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 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 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 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 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 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 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 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 struct StructType<T> {
1075 field: usize,
1077 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 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 struct TupleStructType(pub usize, #[doc = "hello"] pub String);
1100
1101 #[derive(Reflect)]
1102 enum EnumType {
1103 Unit,
1105 Struct {
1107 field: usize,
1109 },
1110 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 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}