1pub mod plugin;
3
4use bevy_ecs::{
5 reflect::{ReflectComponent, ReflectResource},
6 world::World,
7};
8use bevy_log::warn;
9use bevy_mod_scripting_bindings::{
10 MarkAsCore, MarkAsGenerated, MarkAsSignificant, ReflectReference, ScriptValue,
11 docgen::{
12 TypedThrough,
13 info::FunctionInfo,
14 typed_through::{ThroughTypeInfo, TypedWrapperKind, UntypedWrapperKind},
15 },
16 function::{
17 namespace::Namespace,
18 script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext},
19 },
20 into_through_type_info,
21};
22pub use bevy_mod_scripting_bindings_domain::*; use bevy_platform::collections::{HashMap, HashSet};
24use bevy_reflect::{NamedField, TypeInfo, TypeRegistry, Typed, UnnamedField};
25use ladfile::*;
26use std::{
27 any::TypeId,
28 borrow::Cow,
29 cmp::{max, min},
30 ffi::OsString,
31 path::PathBuf,
32};
33
34fn primitive_from_type_id(type_id: TypeId) -> Option<ReflectionPrimitiveKind> {
38 Some(if type_id == TypeId::of::<bool>() {
39 ReflectionPrimitiveKind::Bool
40 } else if type_id == TypeId::of::<isize>() {
41 ReflectionPrimitiveKind::Isize
42 } else if type_id == TypeId::of::<i8>() {
43 ReflectionPrimitiveKind::I8
44 } else if type_id == TypeId::of::<i16>() {
45 ReflectionPrimitiveKind::I16
46 } else if type_id == TypeId::of::<i32>() {
47 ReflectionPrimitiveKind::I32
48 } else if type_id == TypeId::of::<i64>() {
49 ReflectionPrimitiveKind::I64
50 } else if type_id == TypeId::of::<i128>() {
51 ReflectionPrimitiveKind::I128
52 } else if type_id == TypeId::of::<usize>() {
53 ReflectionPrimitiveKind::Usize
54 } else if type_id == TypeId::of::<u8>() {
55 ReflectionPrimitiveKind::U8
56 } else if type_id == TypeId::of::<u16>() {
57 ReflectionPrimitiveKind::U16
58 } else if type_id == TypeId::of::<u32>() {
59 ReflectionPrimitiveKind::U32
60 } else if type_id == TypeId::of::<u64>() {
61 ReflectionPrimitiveKind::U64
62 } else if type_id == TypeId::of::<u128>() {
63 ReflectionPrimitiveKind::U128
64 } else if type_id == TypeId::of::<f32>() {
65 ReflectionPrimitiveKind::F32
66 } else if type_id == TypeId::of::<f64>() {
67 ReflectionPrimitiveKind::F64
68 } else if type_id == TypeId::of::<char>() {
69 ReflectionPrimitiveKind::Char
70 } else if type_id == TypeId::of::<&'static str>() {
71 ReflectionPrimitiveKind::Str
72 } else if type_id == TypeId::of::<String>() {
73 ReflectionPrimitiveKind::String
74 } else if type_id == TypeId::of::<OsString>() {
75 ReflectionPrimitiveKind::OsString
76 } else if type_id == TypeId::of::<PathBuf>() {
77 ReflectionPrimitiveKind::PathBuf
78 } else if type_id == TypeId::of::<FunctionCallContext>() {
79 ReflectionPrimitiveKind::FunctionCallContext
80 } else if type_id == TypeId::of::<DynamicScriptFunction>() {
81 ReflectionPrimitiveKind::DynamicFunction
82 } else if type_id == TypeId::of::<DynamicScriptFunctionMut>() {
83 ReflectionPrimitiveKind::DynamicFunctionMut
84 } else if type_id == TypeId::of::<ReflectReference>() {
85 ReflectionPrimitiveKind::ReflectReference
86 } else {
87 return None;
88 })
89}
90
91fn type_id_from_primitive(kind: &ReflectionPrimitiveKind) -> Option<TypeId> {
92 Some(match kind {
93 ReflectionPrimitiveKind::Bool => TypeId::of::<bool>(),
94 ReflectionPrimitiveKind::Isize => TypeId::of::<isize>(),
95 ReflectionPrimitiveKind::I8 => TypeId::of::<i8>(),
96 ReflectionPrimitiveKind::I16 => TypeId::of::<i16>(),
97 ReflectionPrimitiveKind::I32 => TypeId::of::<i32>(),
98 ReflectionPrimitiveKind::I64 => TypeId::of::<i64>(),
99 ReflectionPrimitiveKind::I128 => TypeId::of::<i128>(),
100 ReflectionPrimitiveKind::Usize => TypeId::of::<usize>(),
101 ReflectionPrimitiveKind::U8 => TypeId::of::<u8>(),
102 ReflectionPrimitiveKind::U16 => TypeId::of::<u16>(),
103 ReflectionPrimitiveKind::U32 => TypeId::of::<u32>(),
104 ReflectionPrimitiveKind::U64 => TypeId::of::<u64>(),
105 ReflectionPrimitiveKind::U128 => TypeId::of::<u128>(),
106 ReflectionPrimitiveKind::F32 => TypeId::of::<f32>(),
107 ReflectionPrimitiveKind::F64 => TypeId::of::<f64>(),
108 ReflectionPrimitiveKind::Char => TypeId::of::<char>(),
109 ReflectionPrimitiveKind::Str => TypeId::of::<&'static str>(),
110 ReflectionPrimitiveKind::String => TypeId::of::<String>(),
111 ReflectionPrimitiveKind::OsString => TypeId::of::<OsString>(),
112 ReflectionPrimitiveKind::PathBuf => TypeId::of::<PathBuf>(),
113 ReflectionPrimitiveKind::FunctionCallContext => TypeId::of::<FunctionCallContext>(),
114 ReflectionPrimitiveKind::DynamicFunction => TypeId::of::<DynamicScriptFunction>(),
115 ReflectionPrimitiveKind::DynamicFunctionMut => TypeId::of::<DynamicScriptFunctionMut>(),
116 ReflectionPrimitiveKind::ReflectReference => TypeId::of::<ReflectReference>(),
117 ReflectionPrimitiveKind::ScriptValue => TypeId::of::<ScriptValue>(),
118 ReflectionPrimitiveKind::External(_) => return None,
119 })
120}
121
122pub struct LadFileBuilder<'t> {
125 file: LadFile,
126 type_id_mapping: HashMap<TypeId, LadTypeId>,
127 type_registry: &'t TypeRegistry,
128 sorted: bool,
129 exclude_types_involving_unregistered_types: bool,
130}
131
132impl<'t> LadFileBuilder<'t> {
133 pub fn new_empty(type_registry: &'t TypeRegistry) -> Self {
135 Self {
136 file: LadFile::new(),
137 type_id_mapping: HashMap::new(),
138 type_registry,
139 sorted: false,
140 exclude_types_involving_unregistered_types: false,
141 }
142 }
143
144 pub fn new(type_registry: &'t TypeRegistry) -> Self {
146 use ReflectionPrimitiveKind::*;
147 let mut builder = Self::new_empty(type_registry);
148
149 builder
150 .add_bms_primitive(Bool,"A boolean value")
151 .add_bms_primitive(Isize, "A signed pointer-sized integer")
152 .add_bms_primitive(I8, "A signed 8-bit integer")
153 .add_bms_primitive(I16, "A signed 16-bit integer")
154 .add_bms_primitive(I32, "A signed 32-bit integer")
155 .add_bms_primitive(I64, "A signed 64-bit integer")
156 .add_bms_primitive(I128, "A signed 128-bit integer")
157 .add_bms_primitive(Usize, "An unsigned pointer-sized integer")
158 .add_bms_primitive(U8, "An unsigned 8-bit integer")
159 .add_bms_primitive(U16, "An unsigned 16-bit integer")
160 .add_bms_primitive(U32, "An unsigned 32-bit integer")
161 .add_bms_primitive(U64, "An unsigned 64-bit integer")
162 .add_bms_primitive(U128, "An unsigned 128-bit integer")
163 .add_bms_primitive(F32, "A 32-bit floating point number")
164 .add_bms_primitive(F64, "A 64-bit floating point number")
165 .add_bms_primitive(Char, "An 8-bit character")
166 .add_bms_primitive(Str, "A string slice")
167 .add_bms_primitive(String, "A heap allocated string")
168 .add_bms_primitive(OsString, "A heap allocated OS string")
169 .add_bms_primitive(PathBuf, "A heap allocated file path")
170 .add_bms_primitive(FunctionCallContext, "Function call context, if accepted by a function, means the function can access the world in arbitrary ways.")
171 .add_bms_primitive(DynamicFunction, "A callable dynamic function")
172 .add_bms_primitive(DynamicFunctionMut, "A stateful and callable dynamic function")
173 .add_bms_primitive(ScriptValue, "A value representing the union of all representable values")
174 .add_bms_primitive(ReflectReference, "A reference to a reflectable type");
175
176 builder
177 }
178
179 pub fn set_exclude_including_unregistered(&mut self, exclude: bool) -> &mut Self {
182 self.exclude_types_involving_unregistered_types = exclude;
183 self
184 }
185
186 pub fn set_sorted(&mut self, sorted: bool) -> &mut Self {
188 self.sorted = sorted;
189 self
190 }
191
192 pub fn add_bms_primitive(
195 &mut self,
196 primitive: ReflectionPrimitiveKind,
197 docs: impl Into<Cow<'static, str>>,
198 ) -> &mut Self {
199 let type_ident = primitive.to_string();
200 let lad_type_id = LadTypeId::new_string_id(type_ident.clone().into());
201 if let Some(type_id) = type_id_from_primitive(&primitive) {
202 self.type_id_mapping.insert(type_id, lad_type_id.clone());
203 }
204 self.file.types.insert(
205 lad_type_id,
206 LadTypeDefinition {
207 identifier: type_ident.clone(),
208 crate_: None,
209 path: type_ident,
210 generics: vec![],
211 documentation: Some(docs.into().to_string()),
212 associated_functions: vec![],
213 layout: LadTypeLayout::Opaque,
214 generated: false,
215 insignificance: default_importance(),
216 metadata: LadTypeMetadata {
217 is_component: false,
218 is_resource: false,
219 is_reflect: false,
220 mapped_to_primitive_kind: Some(primitive),
221 misc: Default::default(),
222 },
223 },
224 );
225 self
226 }
227
228 pub fn mark_generated(&mut self, type_id: TypeId) -> &mut Self {
230 let type_id = self.lad_id_from_type_id(type_id);
231 if let Some(t) = self.file.types.get_mut(&type_id) {
232 t.generated = true;
233 }
234 self
235 }
236
237 pub fn set_insignificance(&mut self, type_id: TypeId, importance: usize) -> &mut Self {
239 let type_id = self.lad_id_from_type_id(type_id);
240 if let Some(t) = self.file.types.get_mut(&type_id) {
241 t.insignificance = importance;
242 }
243 self
244 }
245
246 pub fn add_instance<T: 'static + TypedThrough>(
253 &mut self,
254 key: impl Into<Cow<'static, str>>,
255 is_static: bool,
256 ) -> &mut Self {
257 let type_info = T::through_type_info();
258 let type_kind = self.lad_type_kind_from_through_type(&type_info);
259 self.file.globals.insert(
260 key.into(),
261 LadInstance {
262 type_kind,
263 is_static,
264 },
265 );
266 self
267 }
268
269 pub fn add_instance_dynamic(
273 &mut self,
274 key: impl Into<Cow<'static, str>>,
275 is_static: bool,
276 through_type: ThroughTypeInfo,
277 ) -> &mut Self {
278 let type_kind = self.lad_type_kind_from_through_type(&through_type);
279 self.file.globals.insert(
280 key.into(),
281 LadInstance {
282 type_kind,
283 is_static,
284 },
285 );
286 self
287 }
288
289 pub fn add_instance_manually(
291 &mut self,
292 key: impl Into<Cow<'static, str>>,
293 is_static: bool,
294 type_kind: LadFieldOrVariableKind,
295 ) -> &mut Self {
296 self.file.globals.insert(
297 key.into(),
298 LadInstance {
299 type_kind,
300 is_static,
301 },
302 );
303 self
304 }
305
306 pub fn add_nonreflect_type<T: 'static>(
308 &mut self,
309 crate_: Option<&str>,
310 documentation: &str,
311 ) -> &mut Self {
312 let path = std::any::type_name::<T>().to_string();
313 let identifier = path
314 .split("::")
315 .last()
316 .map(|o| o.to_owned())
317 .unwrap_or_else(|| path.clone());
318
319 let lad_type_id = self.lad_id_from_type_id(std::any::TypeId::of::<T>());
320 self.file.types.insert(
321 lad_type_id,
322 LadTypeDefinition {
323 identifier,
324 crate_: crate_.map(|s| s.to_owned()),
325 path,
326 generics: vec![],
327 documentation: Some(documentation.trim().to_owned()),
328 associated_functions: vec![],
329 layout: LadTypeLayout::Opaque,
330 generated: false,
331 insignificance: default_importance(),
332 metadata: LadTypeMetadata {
333 is_component: false,
334 is_resource: false,
335 is_reflect: false,
336 mapped_to_primitive_kind: primitive_from_type_id(std::any::TypeId::of::<T>()),
337 misc: Default::default(),
338 },
339 },
340 );
341 self
342 }
343
344 pub fn add_type<T: Typed>(&mut self) -> &mut Self {
348 self.add_type_info(T::type_info());
349 self
350 }
351
352 pub fn add_type_info(&mut self, type_info: &TypeInfo) -> &mut Self {
355 let registration = self.type_registry.get(type_info.type_id());
356
357 let mut insignificance = default_importance();
358 let mut generated = false;
359 let mut is_component = false;
360 let mut is_resource = false;
361 let is_reflect = true;
362 if let Some(registration) = registration {
363 if registration.contains::<MarkAsGenerated>() {
364 generated = true;
365 }
366 if registration.contains::<MarkAsCore>() {
367 insignificance = default_importance() / 2;
368 }
369 if registration.contains::<MarkAsSignificant>() {
370 insignificance = default_importance() / 4;
371 }
372 if registration.contains::<ReflectResource>() {
373 is_resource = true
374 }
375 if registration.contains::<ReflectComponent>() {
376 is_component = true
377 }
378 }
379
380 let type_id = self.lad_id_from_type_id(type_info.type_id());
381 let lad_type = LadTypeDefinition {
382 identifier: type_info
383 .type_path_table()
384 .ident()
385 .unwrap_or_default()
386 .to_string(),
387 generics: type_info
388 .generics()
389 .iter()
390 .map(|param| LadGeneric {
391 type_id: self.lad_id_from_type_id(param.type_id()),
392 name: param.name().to_string(),
393 })
394 .collect(),
395 documentation: type_info.docs().map(|s| s.to_string()),
396 associated_functions: Vec::new(),
397 crate_: type_info
398 .type_path_table()
399 .crate_name()
400 .map(|s| s.to_owned()),
401 path: type_info.type_path_table().path().to_owned(),
402 layout: self.lad_layout_from_type_info(type_info),
403 generated,
404 insignificance,
405 metadata: LadTypeMetadata {
406 is_component,
407 is_resource,
408 is_reflect,
409 mapped_to_primitive_kind: primitive_from_type_id(type_info.type_id()),
410 misc: Default::default(),
411 },
412 };
413 self.file.types.insert(type_id, lad_type);
414 self
415 }
416
417 pub fn add_through_type_info(&mut self, type_info: &ThroughTypeInfo) -> &mut Self {
419 match type_info {
420 ThroughTypeInfo::UntypedWrapper { through_type, .. } => {
421 self.add_type_info(through_type);
422 }
423 ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
424 TypedWrapperKind::Union(ti) => {
425 for ti in ti {
426 self.add_through_type_info(ti);
427 }
428 }
429 TypedWrapperKind::Vec(ti) => {
430 self.add_through_type_info(ti);
431 }
432 TypedWrapperKind::HashMap(til, tir) => {
433 self.add_through_type_info(til);
434 self.add_through_type_info(tir);
435 }
436 TypedWrapperKind::HashSet(t) => {
437 self.add_through_type_info(t);
438 }
439 TypedWrapperKind::Array(ti, _) => {
440 self.add_through_type_info(ti);
441 }
442 TypedWrapperKind::Option(ti) => {
443 self.add_through_type_info(ti);
444 }
445 TypedWrapperKind::InteropResult(ti) => {
446 self.add_through_type_info(ti);
447 }
448 TypedWrapperKind::Tuple(ti) => {
449 for ti in ti {
450 self.add_through_type_info(ti);
451 }
452 }
453 },
454 ThroughTypeInfo::TypeInfo(type_info) => {
455 self.add_type_info(type_info);
456 }
457 ThroughTypeInfo::Primitive(_) => {}
458 }
459
460 self
461 }
462
463 pub fn add_function_info(&mut self, function_info: &FunctionInfo) -> &mut Self {
480 let default_docstring = Cow::Owned("".into());
481 let (main_docstring, arg_docstrings, return_docstring) =
482 Self::split_docstring(function_info.docs.as_ref().unwrap_or(&default_docstring));
483
484 let mut identifier = function_info.name.as_ref();
485 let mut overload_index = None;
486 if identifier.contains("-") {
487 let mut parts = identifier.split("-");
488 if let Some(less_overload) = parts.next() {
489 identifier = less_overload;
490 }
491 if let Some(number) = parts.next() {
492 overload_index = number.parse::<usize>().ok()
493 }
494 }
495
496 let function_id = self.lad_function_id_from_info(function_info);
497 let lad_function = LadFunction {
498 identifier: identifier.to_owned().into(),
499 overload_index,
500 arguments: function_info
501 .arg_info
502 .clone()
503 .into_iter()
504 .map(|arg| {
505 let kind = match &arg.type_info {
506 Some(through_type) => self.lad_type_kind_from_through_type(through_type),
507 None => {
508 LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(arg.type_id))
509 }
510 };
511 LadArgument {
512 kind,
513 documentation: arg_docstrings.iter().find_map(|(name, doc)| {
514 (Some(name.as_str()) == arg.name.as_deref())
515 .then_some(Cow::Owned(doc.clone()))
516 }),
517 name: arg.name,
518 }
519 })
520 .collect(),
521 return_type: LadArgument {
522 name: return_docstring.as_ref().cloned().map(|(n, _)| n.into()),
523 documentation: return_docstring.map(|(_, v)| v.into()),
524 kind: function_info
525 .return_info
526 .type_info
527 .clone()
528 .map(|info| self.lad_type_kind_from_through_type(&info))
529 .unwrap_or_else(|| {
530 LadFieldOrVariableKind::Unknown(
531 self.lad_id_from_type_id(function_info.return_info.type_id),
532 )
533 }),
534 },
535 documentation: (!main_docstring.is_empty()).then_some(main_docstring.into()),
536 namespace: match function_info.namespace {
537 Namespace::Global => LadFunctionNamespace::Global,
538 Namespace::OnType(type_id) => {
539 LadFunctionNamespace::Type(self.lad_id_from_type_id(type_id))
540 }
541 },
542 metadata: LadFunctionMetadata {
543 is_operator: ScriptOperatorNames::parse(identifier).is_some(),
544 misc: Default::default(),
545 },
546 };
547 self.file.functions.insert(function_id, lad_function);
548 self
549 }
550
551 pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
553 self.file.description = Some(description.into());
554 self
555 }
556
557 fn has_unknowns(&self, type_id: TypeId) -> bool {
558 if primitive_from_type_id(type_id).is_some() {
559 return false;
560 }
561
562 let type_info = match self.type_registry.get_type_info(type_id) {
563 Some(info) => info,
564 None => return true,
565 };
566 let inner_type_ids: Vec<_> = match type_info {
567 TypeInfo::Struct(struct_info) => {
568 struct_info.generics().iter().map(|g| g.type_id()).collect()
569 }
570 TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info
571 .generics()
572 .iter()
573 .map(|g| g.type_id())
574 .collect(),
575 TypeInfo::Tuple(tuple_info) => {
576 tuple_info.generics().iter().map(|g| g.type_id()).collect()
577 }
578 TypeInfo::List(list_info) => vec![list_info.item_ty().id()],
579 TypeInfo::Array(array_info) => vec![array_info.item_ty().id()],
580 TypeInfo::Map(map_info) => vec![map_info.key_ty().id(), map_info.value_ty().id()],
581 TypeInfo::Set(set_info) => vec![set_info.value_ty().id()],
582 TypeInfo::Enum(enum_info) => enum_info.generics().iter().map(|g| g.type_id()).collect(),
583 TypeInfo::Opaque(_) => vec![],
584 };
585
586 inner_type_ids.iter().any(|id| self.has_unknowns(*id))
587 }
588
589 pub fn build(&mut self) -> LadFile {
591 let mut file = std::mem::replace(&mut self.file, LadFile::new());
592
593 if self.exclude_types_involving_unregistered_types {
594 let mut to_remove = HashSet::new();
595 for reg in self.type_registry.iter() {
596 let type_id = reg.type_id();
597 if self.has_unknowns(type_id) {
598 to_remove.insert(self.lad_id_from_type_id(type_id));
599 }
600 }
601
602 file.types.retain(|id, _| !to_remove.contains(id));
604 }
605
606 for (function_id, function) in file.functions.iter() {
608 match &function.namespace {
609 LadFunctionNamespace::Type(type_id) => {
610 if let Some(t) = file.types.get_mut(type_id) {
611 t.associated_functions.push(function_id.clone());
612 } else {
613 warn!(
614 "Function {} is on type {}, but the type is not registered in the LAD file.",
615 function_id, type_id
616 );
617 }
618 }
619 LadFunctionNamespace::Global => {}
620 }
621 }
622
623 if self.sorted {
624 file.types.sort_by(|ak, av, bk, bv| {
625 let complexity_a: usize = av
626 .path
627 .char_indices()
628 .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
629 .sum();
630 let complexity_b = bv
631 .path
632 .char_indices()
633 .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1))
634 .sum();
635
636 let has_functions_a = !av.associated_functions.is_empty();
637 let has_functions_b = !bv.associated_functions.is_empty();
638
639 let ordered_by_name = ak.cmp(bk);
640 let ordered_by_generics_complexity = complexity_a.cmp(&complexity_b);
641 let ordered_by_generated = av.generated.cmp(&bv.generated);
642 let ordered_by_having_functions = has_functions_b.cmp(&has_functions_a);
643 let ordered_by_significance = av.insignificance.cmp(&bv.insignificance);
644
645 ordered_by_significance
646 .then(ordered_by_having_functions)
647 .then(ordered_by_generics_complexity)
648 .then(ordered_by_name)
649 .then(ordered_by_generated)
650 });
651
652 file.functions.sort_keys();
653 }
654
655 file
656 }
657
658 fn is_docstring_delimeter(key: &str, line: &str) -> bool {
666 line.trim()
667 .trim_start_matches("#")
668 .trim_end_matches(":")
669 .trim()
670 .eq_ignore_ascii_case(key)
671 }
672
673 fn parse_arg_docstring(line: &str) -> Option<(&str, &str)> {
678 let regex =
679 regex::Regex::new(r#"\s*\*\s*`(?<arg>[^`]+)`\s*[:-]\s*(?<val>.+[^\s]).*$"#).ok()?;
680 let captures = regex.captures(line)?;
681 let arg = captures.name("arg")?;
682 let val = captures.name("val")?;
683
684 Some((arg.as_str(), val.as_str()))
685 }
686
687 fn split_docstring(
694 docstring: &str,
695 ) -> (String, Vec<(String, String)>, Option<(String, String)>) {
696 let lines = docstring.lines().collect::<Vec<_>>();
698
699 let argument_line_idx = match lines
701 .iter()
702 .enumerate()
703 .find_map(|(idx, l)| Self::is_docstring_delimeter("arguments", l).then_some(idx))
704 {
705 Some(a) => a,
706 None => return (docstring.to_owned(), vec![], None),
707 };
708
709 let return_line_idx = lines.iter().enumerate().find_map(|(idx, l)| {
711 (Self::is_docstring_delimeter("returns", l)
712 || Self::is_docstring_delimeter("return", l))
713 .then_some(idx)
714 });
715
716 let return_area_idx = return_line_idx.unwrap_or(usize::MAX);
717 let return_area_first = argument_line_idx > return_area_idx;
718 let argument_range = match return_area_first {
719 true => argument_line_idx..lines.len(),
720 false => argument_line_idx..return_area_idx,
721 };
722 let return_range = match return_area_first {
723 true => return_area_idx..argument_line_idx,
724 false => return_area_idx..lines.len(),
725 };
726 let non_main_area =
727 min(return_area_idx, argument_line_idx)..max(return_area_idx, argument_line_idx);
728
729 let parsed_lines = lines
730 .iter()
731 .enumerate()
732 .map(|(i, l)| {
733 match Self::parse_arg_docstring(l) {
734 Some(parsed) => {
735 let in_argument_range = argument_range.contains(&i);
738 let in_return_range = return_range.contains(&i);
739 (l, Some((in_argument_range, in_return_range, parsed)))
740 }
741 None => (l, None),
742 }
743 })
744 .collect::<Vec<_>>();
745
746 let main_docstring = parsed_lines
749 .iter()
750 .enumerate()
751 .filter_map(|(i, (l, parsed))| {
752 ((!non_main_area.contains(&i) || !l.trim().is_empty())
753 && (i != return_area_idx && i != argument_line_idx)
754 && (parsed.is_none() || parsed.is_some_and(|(a, b, _)| !a && !b)))
755 .then_some((**l).to_owned())
756 })
757 .collect::<Vec<_>>();
758
759 let arg_docstrings = parsed_lines
760 .iter()
761 .filter_map(|(_l, parsed)| {
762 parsed.and_then(|(is_arg, is_return, (a, b))| {
763 (is_arg && !is_return).then_some((a.to_owned(), b.to_owned()))
764 })
765 })
766 .collect();
767
768 let return_docstring = parsed_lines.iter().find_map(|(_l, parsed)| {
769 parsed.and_then(|(is_arg, is_return, (a, b))| {
770 (!is_arg && is_return).then_some((a.to_owned(), b.to_owned()))
771 })
772 });
773
774 (main_docstring.join("\n"), arg_docstrings, return_docstring)
775 }
776
777 fn variant_identifier_for_non_enum(type_info: &TypeInfo) -> Cow<'static, str> {
778 type_info
779 .type_path_table()
780 .ident()
781 .unwrap_or_else(|| type_info.type_path_table().path())
782 .into()
783 }
784
785 fn struct_variant_from_named_fields<'a, I: Iterator<Item = &'a NamedField>>(
786 &mut self,
787 name: Cow<'static, str>,
788 fields: I,
789 ) -> LadVariant {
790 LadVariant::Struct {
791 name,
792 fields: fields
793 .map(|field| LadNamedField {
794 name: field.name().to_string(),
795 type_: self.lad_type_kind_from_type_id(field.type_id()),
796 })
797 .collect(),
798 }
799 }
800
801 fn tuple_struct_variant_from_fields<'a, I: Iterator<Item = &'a UnnamedField>>(
802 &mut self,
803 name: Cow<'static, str>,
804 fields: I,
805 ) -> LadVariant {
806 LadVariant::TupleStruct {
807 name,
808 fields: fields
809 .map(|field| LadField {
810 type_: self.lad_type_kind_from_type_id(field.type_id()),
811 })
812 .collect(),
813 }
814 }
815
816 fn lad_layout_from_type_info(&mut self, type_info: &TypeInfo) -> LadTypeLayout {
817 match type_info {
818 TypeInfo::Struct(struct_info) => {
819 let fields = (0..struct_info.field_len()).filter_map(|i| struct_info.field_at(i));
820
821 LadTypeLayout::MonoVariant(self.struct_variant_from_named_fields(
822 Self::variant_identifier_for_non_enum(type_info),
823 fields,
824 ))
825 }
826 TypeInfo::TupleStruct(tuple_struct_info) => {
827 let fields = (0..tuple_struct_info.field_len())
828 .filter_map(|i| tuple_struct_info.field_at(i));
829
830 LadTypeLayout::MonoVariant(self.tuple_struct_variant_from_fields(
831 Self::variant_identifier_for_non_enum(type_info),
832 fields,
833 ))
834 }
835 TypeInfo::Enum(enum_info) => {
836 let mut variants = Vec::new();
837 for i in 0..enum_info.variant_len() {
838 if let Some(variant) = enum_info.variant_at(i) {
839 let variant_name = variant.name();
840 let variant = match variant {
841 bevy_reflect::VariantInfo::Struct(struct_variant_info) => {
842 let fields = (0..struct_variant_info.field_len())
843 .filter_map(|i| struct_variant_info.field_at(i));
844
845 self.struct_variant_from_named_fields(variant_name.into(), fields)
846 }
847 bevy_reflect::VariantInfo::Tuple(tuple_variant_info) => {
848 let fields = (0..tuple_variant_info.field_len())
849 .filter_map(|i| tuple_variant_info.field_at(i));
850
851 self.tuple_struct_variant_from_fields(variant_name.into(), fields)
852 }
853 bevy_reflect::VariantInfo::Unit(_) => LadVariant::Unit {
854 name: variant_name.into(),
855 },
856 };
857 variants.push(variant);
858 }
859 }
860 LadTypeLayout::Enum(variants)
861 }
862 _ => LadTypeLayout::Opaque,
863 }
864 }
865
866 fn lad_type_kind_from_type_id(&mut self, type_id: TypeId) -> LadFieldOrVariableKind {
869 if let Some(type_info) = self.type_registry.get_type_info(type_id) {
870 let through_type_info = into_through_type_info(type_info);
871 self.lad_type_kind_from_through_type(&through_type_info)
872 } else {
873 LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(type_id))
874 }
875 }
876
877 fn lad_id_from_type_id(&mut self, type_id: TypeId) -> LadTypeId {
879 if type_id == std::any::TypeId::of::<World>() {
881 return LadTypeId::new_string_id("World".into());
882 }
883
884 if let Some(lad_id) = self.type_id_mapping.get(&type_id) {
885 return lad_id.clone();
886 }
887
888 let new_id = match primitive_from_type_id(type_id) {
889 Some(primitive) => LadTypeId::new_string_id(primitive.to_string().into()),
890 None => {
891 if let Some(info) = self.type_registry.get_type_info(type_id) {
892 LadTypeId::new_string_id(info.type_path_table().path().into())
893 } else {
894 LadTypeId::new_string_id(format!("{type_id:?}").into())
895 }
896 }
897 };
898
899 self.type_id_mapping.insert(type_id, new_id.clone());
900 new_id
901 }
902
903 fn lad_function_id_from_info(&mut self, function_info: &FunctionInfo) -> LadFunctionId {
904 let namespace_string = match function_info.namespace {
905 bevy_mod_scripting_bindings::function::namespace::Namespace::Global => "".to_string(),
906 bevy_mod_scripting_bindings::function::namespace::Namespace::OnType(type_id) => {
907 self.lad_id_from_type_id(type_id).to_string()
908 }
909 };
910
911 LadFunctionId::new_string_id(format!("{}::{}", namespace_string, function_info.name))
912 }
913
914 fn lad_type_kind_from_through_type(
915 &mut self,
916 through_type: &ThroughTypeInfo,
917 ) -> LadFieldOrVariableKind {
918 match through_type {
919 ThroughTypeInfo::UntypedWrapper {
920 through_type,
921 wrapper_kind,
922 ..
923 } => match wrapper_kind {
924 UntypedWrapperKind::Ref => {
925 LadFieldOrVariableKind::Ref(self.lad_id_from_type_id(through_type.type_id()))
926 }
927 UntypedWrapperKind::Mut => {
928 LadFieldOrVariableKind::Mut(self.lad_id_from_type_id(through_type.type_id()))
929 }
930 UntypedWrapperKind::Val => {
931 LadFieldOrVariableKind::Val(self.lad_id_from_type_id(through_type.type_id()))
932 }
933 },
934 ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind {
935 TypedWrapperKind::Vec(through_type_info) => LadFieldOrVariableKind::Vec(Box::new(
936 self.lad_type_kind_from_through_type(through_type_info),
937 )),
938 TypedWrapperKind::HashMap(through_type_info, through_type_info1) => {
939 LadFieldOrVariableKind::HashMap(
940 Box::new(self.lad_type_kind_from_through_type(through_type_info)),
941 Box::new(self.lad_type_kind_from_through_type(through_type_info1)),
942 )
943 }
944 TypedWrapperKind::HashSet(through_type_info) => LadFieldOrVariableKind::HashSet(
945 Box::new(self.lad_type_kind_from_through_type(through_type_info)),
946 ),
947 TypedWrapperKind::Array(through_type_info, size) => LadFieldOrVariableKind::Array(
948 Box::new(self.lad_type_kind_from_through_type(through_type_info)),
949 *size,
950 ),
951 TypedWrapperKind::Option(through_type_info) => LadFieldOrVariableKind::Option(
952 Box::new(self.lad_type_kind_from_through_type(through_type_info)),
953 ),
954 TypedWrapperKind::InteropResult(through_type_info) => {
955 LadFieldOrVariableKind::InteropResult(Box::new(
956 self.lad_type_kind_from_through_type(through_type_info),
957 ))
958 }
959 TypedWrapperKind::Tuple(through_type_infos) => LadFieldOrVariableKind::Tuple(
960 through_type_infos
961 .iter()
962 .map(|through_type_info| {
963 self.lad_type_kind_from_through_type(through_type_info)
964 })
965 .collect(),
966 ),
967 TypedWrapperKind::Union(through_type_infos) => LadFieldOrVariableKind::Union(
968 through_type_infos
969 .iter()
970 .map(|through_type_info| {
971 self.lad_type_kind_from_through_type(through_type_info)
972 })
973 .collect(),
974 ),
975 },
976 ThroughTypeInfo::TypeInfo(type_info) => {
977 match primitive_from_type_id(type_info.type_id()) {
978 Some(primitive) => LadFieldOrVariableKind::Primitive(primitive),
979 None => LadFieldOrVariableKind::Unknown(
980 self.lad_id_from_type_id(type_info.type_id()),
981 ),
982 }
983 }
984 ThroughTypeInfo::Primitive(reflection_primitive_kind) => {
985 LadFieldOrVariableKind::Primitive(reflection_primitive_kind.clone())
986 }
987 }
988 }
989}
990
991#[cfg(test)]
992mod test {
993 use std::collections::HashMap;
994
995 use bevy_mod_scripting_bindings::{
996 Union, Val,
997 docgen::info::GetFunctionInfo,
998 function::{
999 from::Ref,
1000 namespace::{GlobalNamespace, IntoNamespace},
1001 },
1002 };
1003 use bevy_reflect::Reflect;
1004
1005 use super::*;
1006
1007 fn normalize_file(file: &mut String) {
1009 *file = file.replace("\r\n", "\n");
1010 }
1011
1012 #[test]
1013 fn test_empty_lad_file_serializes_correctly() {
1014 let lad_file = LadFile::new();
1015 let serialized = serialize_lad_file(&lad_file, false).unwrap();
1016 let deserialized = parse_lad_file(&serialized).unwrap();
1017 assert_eq!(lad_file, deserialized);
1018 assert_eq!(deserialized.version, ladfile::LAD_VERSION);
1019 }
1020
1021 #[test]
1022 fn parse_docstrings_is_resistant_to_whitespace() {
1023 pretty_assertions::assert_eq!(
1024 LadFileBuilder::parse_arg_docstring("* `arg` : doc"),
1025 Some(("arg", "doc"))
1026 );
1027 pretty_assertions::assert_eq!(
1028 LadFileBuilder::parse_arg_docstring(" * `arg` - doc"),
1029 Some(("arg", "doc"))
1030 );
1031 pretty_assertions::assert_eq!(
1032 LadFileBuilder::parse_arg_docstring(" * `arg` : doc "),
1033 Some(("arg", "doc"))
1034 );
1035 }
1036
1037 #[test]
1038 fn docstring_delimeter_detection_is_flexible() {
1039 assert!(LadFileBuilder::is_docstring_delimeter(
1040 "arguments",
1041 "arguments"
1042 ));
1043 assert!(LadFileBuilder::is_docstring_delimeter(
1044 "arguments",
1045 "Arguments:"
1046 ));
1047 assert!(LadFileBuilder::is_docstring_delimeter(
1048 "arguments",
1049 "## Arguments"
1050 ));
1051 assert!(LadFileBuilder::is_docstring_delimeter(
1052 "arguments",
1053 "## Arguments:"
1054 ));
1055 assert!(LadFileBuilder::is_docstring_delimeter(
1056 "arguments",
1057 "Arguments"
1058 ));
1059 }
1060
1061 fn assert_docstring_split(
1063 input: &str,
1064 expected_main: &str,
1065 expected_args: &[(&str, &str)],
1066 expected_return: Option<(&str, &str)>,
1067 test_name: &str,
1068 ) {
1069 let (main, args, ret) = LadFileBuilder::split_docstring(input);
1070
1071 pretty_assertions::assert_eq!(
1072 main,
1073 expected_main,
1074 "main docstring was incorrect - {}",
1075 test_name
1076 );
1077
1078 let expected_args: Vec<(String, String)> = expected_args
1079 .iter()
1080 .map(|(a, b)| (a.to_string(), b.to_string()))
1081 .collect();
1082 pretty_assertions::assert_eq!(
1083 args,
1084 expected_args,
1085 "argument docstring was incorrect - {}",
1086 test_name
1087 );
1088
1089 let expected_ret = expected_return.map(|(a, b)| (a.to_string(), b.to_string()));
1090 pretty_assertions::assert_eq!(
1091 ret,
1092 expected_ret,
1093 "return docstring was incorrect - {}",
1094 test_name
1095 );
1096 }
1097
1098 #[test]
1099 fn docstrings_parse_correctly_from_various_formats() {
1100 assert_docstring_split(
1101 r#"
1102 ## Hello
1103 Arguments:
1104 * `arg1` - some docs
1105 * `arg2` : some more docs
1106 # Returns
1107 * `return` : return docs
1108 "#
1109 .trim(),
1110 "## Hello",
1111 &[("arg1", "some docs"), ("arg2", "some more docs")],
1112 Some(("return", "return docs")),
1113 "normal docstring",
1114 );
1115 assert_docstring_split(
1116 r#"
1117 Arguments:
1118 * `arg1` - some docs
1119 * `arg2` : some more docs
1120 Returns
1121 * `return` : return docs
1122 "#
1123 .trim(),
1124 "",
1125 &[("arg1", "some docs"), ("arg2", "some more docs")],
1126 Some(("return", "return docs")),
1127 "empty main docstring",
1128 );
1129 assert_docstring_split(
1130 r#"
1131 Arguments:
1132 * `arg1` - some docs
1133 * `arg2` : some more docs
1134 "#
1135 .trim(),
1136 "",
1137 &[("arg1", "some docs"), ("arg2", "some more docs")],
1138 None,
1139 "no return docstring",
1140 );
1141 assert_docstring_split(
1142 r#"
1143 Returns
1144 * `return` : return docs
1145 "#
1146 .trim(),
1147 r#"
1148 Returns
1149 * `return` : return docs
1150 "#
1151 .trim(),
1152 &[],
1153 None,
1154 "no argument docstring",
1155 );
1156 assert_docstring_split(
1157 r#"
1158 ## Hello
1159 "#
1160 .trim(),
1161 "## Hello",
1162 &[],
1163 None,
1164 "no argument or return docstring",
1165 );
1166 assert_docstring_split(
1168 r#"
1169 Returns
1170 * `return` : return docs
1171 Arguments:
1172 * `arg1` - some docs
1173 * `arg2` : some more docs
1174 "#
1175 .trim(),
1176 "",
1177 &[("arg1", "some docs"), ("arg2", "some more docs")],
1178 Some(("return", "return docs")),
1179 "return first",
1180 );
1181 assert_docstring_split(
1183 r#"
1184 ## Hello
1185
1186
1187 Arguments:
1188 * `arg1` - some docs
1189 * `arg2` : some more docs
1190
1191 Returns
1192 * `return` : return docs
1193 "#
1194 .trim(),
1195 "## Hello\n\n",
1196 &[("arg1", "some docs"), ("arg2", "some more docs")],
1197 Some(("return", "return docs")),
1198 "whitespace in between",
1199 );
1200 }
1201
1202 #[test]
1203 fn test_serializes_as_expected() {
1204 let mut type_registry = TypeRegistry::default();
1205
1206 #[derive(Reflect)]
1207 struct GenericStructType<T> {
1209 field: usize,
1211 field2: T,
1213 }
1214
1215 impl TypedThrough for GenericStructType<usize> {
1216 fn through_type_info() -> ThroughTypeInfo {
1217 ThroughTypeInfo::TypeInfo(Self::type_info())
1218 }
1219 }
1220
1221 #[derive(Reflect)]
1222 struct PlainStructType {
1224 int_field: usize,
1225 }
1226
1227 impl TypedThrough for PlainStructType {
1228 fn through_type_info() -> ThroughTypeInfo {
1229 ThroughTypeInfo::TypeInfo(Self::type_info())
1230 }
1231 }
1232
1233 #[derive(Reflect)]
1234 struct UnitType;
1236
1237 impl TypedThrough for UnitType {
1238 fn through_type_info() -> ThroughTypeInfo {
1239 ThroughTypeInfo::TypeInfo(Self::type_info())
1240 }
1241 }
1242
1243 #[derive(Reflect)]
1244 struct TupleStructType(pub usize, #[doc = "hello"] pub String);
1246
1247 #[derive(Reflect)]
1248 enum EnumType {
1249 Unit,
1251 Struct {
1253 field: usize,
1255 },
1256 TupleStruct(usize, #[doc = "asd"] String),
1258 }
1259
1260 type_registry.register::<GenericStructType<usize>>();
1261 type_registry.register::<UnitType>();
1262 type_registry.register::<TupleStructType>();
1263 type_registry.register::<EnumType>();
1264 type_registry.register::<PlainStructType>();
1265
1266 let plain_struct_function =
1267 |_: Ref<PlainStructType>, _: usize| PlainStructType { int_field: 2 };
1268 let plain_struct_function_info = plain_struct_function.get_function_info(
1269 "plain_struct_function".into(),
1270 PlainStructType::into_namespace(),
1271 );
1272
1273 let function = |_: ReflectReference, _: usize| 2usize;
1274 let function_info = function
1275 .get_function_info(
1276 "hello_world".into(),
1277 GenericStructType::<usize>::into_namespace(),
1278 )
1279 .with_docs("hello docs");
1280
1281 let function_with_complex_args =
1282 |_: ReflectReference, _: (usize, String), _: Option<Vec<Ref<EnumType>>>| 2usize;
1283 let function_with_complex_args_info = function_with_complex_args
1284 .get_function_info("hello_world".into(), GenericStructType::<usize>::into_namespace())
1285 .with_arg_names(&["ref_", "tuple", "option_vec_ref_wrapper"])
1286 .with_docs(
1287 "Arguments: ".to_owned()
1288 + "\n"
1289 + " * `ref_`: I am some docs for argument 1"
1290 + "\n"
1291 + " * `tuple`: I am some docs for argument 2"
1292 + "\n"
1293 + " * `option_vec_ref_wrapper`: I am some docs for argument 3"
1294 + "\n"
1295 + "Returns: "
1296 + "\n"
1297 + " * `return`: I am some docs for the return type, I provide a name for the return value too",
1298 );
1299
1300 let global_function = |_: usize| 2usize;
1301 let global_function_info = global_function
1302 .get_function_info("hello_world".into(), GlobalNamespace::into_namespace())
1303 .with_arg_names(&["arg1"]);
1304
1305 let mut lad_file = LadFileBuilder::new(&type_registry)
1306 .set_description("## Hello gentlemen\n I am markdown file.\n - hello\n - world")
1307 .set_sorted(true)
1308 .add_function_info(&plain_struct_function_info)
1309 .add_function_info(&function_info)
1310 .add_function_info(&global_function_info)
1311 .add_function_info(&function_with_complex_args_info)
1312 .add_type::<GenericStructType<usize>>()
1313 .add_type::<UnitType>()
1314 .add_type::<TupleStructType>()
1315 .add_type::<PlainStructType>()
1316 .add_type_info(EnumType::type_info())
1317 .add_instance::<Val<GenericStructType<usize>>>("my_static_instance", true)
1318 .add_instance::<Vec<Val<UnitType>>>("my_non_static_instance", false)
1319 .add_instance::<HashMap<String, Union<String, String>>>("map", false)
1320 .build();
1321
1322 lad_file.version = "{{version}}".into();
1324 let mut serialized = serialize_lad_file(&lad_file, true).unwrap();
1325
1326 normalize_file(&mut serialized);
1327
1328 if std::env::var("BLESS_MODE").is_ok() {
1329 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
1330 let path_to_test_assets = std::path::Path::new(&manifest_dir)
1331 .join("..")
1332 .join("ladfile")
1333 .join("test_assets");
1334
1335 println!("Blessing test file at {path_to_test_assets:?}");
1336 std::fs::write(path_to_test_assets.join("test.lad.json"), &serialized).unwrap();
1337 panic!("Blessed test file, please rerun the test");
1338 }
1339
1340 let mut expected = ladfile::EXAMPLE_LADFILE.to_string();
1341 normalize_file(&mut expected);
1342
1343 pretty_assertions::assert_eq!(serialized.trim(), expected.trim(),);
1344 }
1345
1346 #[test]
1347 fn test_asset_deserializes_correctly() {
1348 let asset = ladfile::EXAMPLE_LADFILE.to_string();
1349 let deserialized = parse_lad_file(&asset).unwrap();
1350 assert_eq!(deserialized.version, "{{version}}");
1351 }
1352
1353 #[test]
1354 fn test_nested_unregistered_generic_is_removed() {
1355 let mut type_registry = TypeRegistry::default();
1356
1357 #[derive(Reflect)]
1358 #[reflect(no_field_bounds, from_reflect = false)]
1359 struct StructType<T> {
1360 #[reflect(ignore)]
1361 phantom: std::marker::PhantomData<T>,
1362 }
1363
1364 #[derive(Reflect)]
1365 struct Blah;
1366
1367 type_registry.register::<StructType<Blah>>();
1368
1369 let lad_file = LadFileBuilder::new_empty(&type_registry)
1370 .set_sorted(true)
1371 .set_exclude_including_unregistered(true)
1372 .add_type::<StructType<Blah>>()
1373 .build();
1374
1375 assert_eq!(lad_file.types.len(), 0);
1376 }
1377}