diff --git a/.gitignore b/.gitignore index b8efa21749..339ec1b6e4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ assets/scripts/tlconfig.lua /assets/**/*.lad.json /docs/src/ladfiles/*.lad.json +/assets/**/bindings.lua \ No newline at end of file diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000000..389e940799 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "workspace.library": [ + "assets/definitions" + ], + "runtime.version": "Lua 5.4", + "hint.enable": false, + "diagnostics.workspaceDelay": 30000 +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d217aa9d16..10e246aeb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,10 +44,9 @@ default = [ "bevy_core_pipeline_bindings", ] -lua = [ - "bevy_mod_scripting_lua", - "bevy_mod_scripting_functions/lua_bindings", -] ## lua + +# lua +lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"] # one of these must be selected lua51 = ["bevy_mod_scripting_lua/lua51", "lua"] lua52 = ["bevy_mod_scripting_lua/lua52", "lua"] @@ -56,6 +55,10 @@ lua54 = ["bevy_mod_scripting_lua/lua54", "lua"] luajit = ["bevy_mod_scripting_lua/luajit", "lua"] luajit52 = ["bevy_mod_scripting_lua/luajit52", "lua"] luau = ["bevy_mod_scripting_lua/luau", "lua"] +lua_language_server_files = [ + "ladfile_builder", + "ladfile_builder/lua_language_server_files", +] # bindings core_functions = ["bevy_mod_scripting_functions/core_functions"] @@ -111,8 +114,10 @@ bevy_mod_scripting_functions = { workspace = true } bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } +bevy_mod_scripting_bindings_domain = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_script = { workspace = true } +ladfile_builder = { workspace = true, optional = true } [workspace.dependencies] # local crates @@ -127,8 +132,10 @@ bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", ver bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.16.0", default-features = false } bevy_mod_scripting_asset = { path = "crates/bevy_mod_scripting_asset", version = "0.16.0", default-features = false } bevy_mod_scripting_bindings = { path = "crates/bevy_mod_scripting_bindings", version = "0.16.0", default-features = false } +bevy_mod_scripting_bindings_domain = { path = "crates/bevy_mod_scripting_bindings_domain", version = "0.16.0", default-features = false } bevy_mod_scripting_display = { path = "crates/bevy_mod_scripting_display", version = "0.16.0", default-features = false } bevy_mod_scripting_script = { path = "crates/bevy_mod_scripting_script", version = "0.16.0", default-features = false } +lua_language_server_lad_backend = { path = "crates/lad_backends/lua_language_server_lad_backend", version = "0.16.0", default-features = false } # bevy @@ -215,7 +222,7 @@ pretty_assertions = { version = "1.4", default-features = false, features = [ "std", ] } manifest-dir-macros = { version = "0.1.18", default-features = false } -assert_cmd = { version = "2.0", default-features = false } +assert_cmd = { version = "2.1", default-features = false } tokio = { version = "1", default-features = false } bevy_console = { version = "0.14", default-features = false } tracing-tracy = { version = "0.11", default-features = false } @@ -260,6 +267,7 @@ members = [ "crates/testing_crates/script_integration_test_harness", "crates/bevy_mod_scripting_derive", "crates/ladfile", + "crates/lad_backends/lua_language_server_lad_backend", "crates/lad_backends/mdbook_lad_preprocessor", "crates/ladfile_builder", "crates/bevy_system_reflection", @@ -268,6 +276,7 @@ members = [ "crates/bevy_mod_scripting_bindings", "crates/bevy_mod_scripting_display", "crates/bevy_mod_scripting_script", + "crates/bevy_mod_scripting_bindings_domain", ] resolver = "2" exclude = ["codegen", "crates/macro_tests", "xtask"] diff --git a/assets/scripts/game_of_life.lua b/assets/scripts/game_of_life.lua index 85d6aa254e..c217f6b821 100644 --- a/assets/scripts/game_of_life.lua +++ b/assets/scripts/game_of_life.lua @@ -5,33 +5,35 @@ info("Lua: The game_of_life.lua script just got loaded") math.randomseed(os.time()) -function fetch_life_state() - -- find the first entity with life state - local i,v = next(world.query():component(LifeState):build()) +function fetch_life_state() + -- find the first entity with life state + local i, v = next(world.query():component(LifeState):build()) return v:components()[1] -end +end function on_script_loaded() info("Lua: Hello! I am initiating the game of life simulation state with randomness!") info("Lua: Click on the screen to set cells alive after running the `gol start` command") - + local life_state = fetch_life_state() + local cells = life_state.cells - + -- set some cells alive - for _=1,1000 do + for _ = 1, 1000 do local index = math.random(#cells) cells[index] = 255 end -end +end -function on_click(x,y) +function on_click(x, y) -- get the settings info("Lua: Clicked at x: " .. x .. " y: " .. y) local life_state = fetch_life_state() local cells = life_state.cells local settings = world.get_resource(Settings) + local dimensions = settings.physical_grid_dimensions local screen = settings.display_grid_dimensions @@ -51,18 +53,18 @@ function on_click(x,y) -- toggle a bunch of cells around if they exist local cell_offsets = { - {0,0}, - {1,0}, - {0,1}, - {1,1}, - {-1,0}, - {0,-1}, - {-1,-1}, - {1,-1}, - {-1,1} + { 0, 0 }, + { 1, 0 }, + { 0, 1 }, + { 1, 1 }, + { -1, 0 }, + { 0, -1 }, + { -1, -1 }, + { 1, -1 }, + { -1, 1 } } - for _,offset in pairs(cell_offsets) do + for _, offset in pairs(cell_offsets) do local offset_x = offset[1] local offset_y = offset[2] local new_index = index + offset_x + offset_y * dimension_x @@ -81,10 +83,10 @@ function on_update() -- primitives are passed by value to lua, keep a hold of old state but turn 255's into 1's local prev_state = {} - for v in pairs(cells) do - prev_state[#prev_state+1] = (not(v == 0)) and 1 or 0 + for v in pairs(cells) do + prev_state[#prev_state + 1] = (not (v == 0)) and 1 or 0 end - for i=1,(dimension_x * dimension_y) do + for i = 1, (dimension_x * dimension_y) do -- wrap around the north and south edges local north = prev_state[i - dimension_x] or prev_state[i + dimension_x * (dimension_y - 1)] local south = prev_state[i + dimension_x] or prev_state[i - dimension_x * (dimension_y - 1)] @@ -95,13 +97,13 @@ function on_update() local northwest = prev_state[i - dimension_x - 1] or 0 local southwest = prev_state[i + dimension_x - 1] or 0 - local neighbours = north + south + east + west + local neighbours = north + south + east + west + northeast + southeast + northwest + southwest - + -- was dead and got 3 neighbours now if prev_state[i] == 0 and neighbours == 3 then cells[i] = 255 - -- was alive and should die now + -- was alive and should die now elseif prev_state[i] == 1 and ((neighbours < 2) or (neighbours > 3)) then cells[i] = 0 end @@ -114,7 +116,7 @@ function on_script_unloaded() -- set state to 0's local life_state = fetch_life_state() local cells = life_state.cells - for i=1,#cells do + for i = 1, #cells do cells[i] = 0 end -end \ No newline at end of file +end diff --git a/crates/bevy_mod_scripting_bindings/Cargo.toml b/crates/bevy_mod_scripting_bindings/Cargo.toml index a94123c391..59d7901bf9 100644 --- a/crates/bevy_mod_scripting_bindings/Cargo.toml +++ b/crates/bevy_mod_scripting_bindings/Cargo.toml @@ -17,6 +17,7 @@ bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_script = { workspace = true } +bevy_mod_scripting_bindings_domain = { workspace = true } bevy_system_reflection = { workspace = true } bevy_diagnostic = { workspace = true } bevy_ecs = { workspace = true } @@ -30,6 +31,7 @@ itertools = { workspace = true } profiling = { workspace = true } bevy_asset = { workspace = true } variadics_please = { workspace = true } +serde = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/crates/bevy_mod_scripting_bindings/src/docgen/info.rs b/crates/bevy_mod_scripting_bindings/src/docgen/info.rs index b9afd5115c..069e0a22cf 100644 --- a/crates/bevy_mod_scripting_bindings/src/docgen/info.rs +++ b/crates/bevy_mod_scripting_bindings/src/docgen/info.rs @@ -229,6 +229,8 @@ variadics_please::all_tuples!(impl_documentable, 0, 13, T); #[cfg(test)] mod test { + use bevy_mod_scripting_bindings_domain::ReflectionPrimitiveKind; + use crate::{ docgen::typed_through::UntypedWrapperKind, function::from::{Mut, Ref, Val}, @@ -252,15 +254,15 @@ mod test { assert_eq!(info.arg_info[1].type_id, TypeId::of::()); match info.arg_info[0].type_info.as_ref().unwrap() { - ThroughTypeInfo::TypeInfo(type_info) => { - assert_eq!(type_info.type_id(), TypeId::of::()); + ThroughTypeInfo::Primitive(prim) => { + assert_eq!(*prim, ReflectionPrimitiveKind::I32); } _ => panic!("Expected TypeInfo"), } match info.arg_info[1].type_info.as_ref().unwrap() { - ThroughTypeInfo::TypeInfo(type_info) => { - assert_eq!(type_info.type_id(), TypeId::of::()); + ThroughTypeInfo::Primitive(prim) => { + assert_eq!(*prim, ReflectionPrimitiveKind::F32); } _ => panic!("Expected TypeInfo"), } diff --git a/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs b/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs index c5537ae803..f9eb5e86d1 100644 --- a/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs +++ b/crates/bevy_mod_scripting_bindings/src/docgen/typed_through.rs @@ -1,11 +1,7 @@ //! Defines a set of traits which destruture [`bevy_reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types //! which normally can't implement [`bevy_reflect::Typed`] to be used in a reflection context. -use std::{ffi::OsString, path::PathBuf}; - -use bevy_mod_scripting_derive::DebugWithTypeInfo; -use bevy_platform::collections::HashMap; -use bevy_reflect::{TypeInfo, Typed}; +use std::{any::TypeId, ffi::OsString, path::PathBuf}; use crate::{ ReflectReference, @@ -16,6 +12,10 @@ use crate::{ script_value::ScriptValue, }; use crate::{error::InteropError, reflection_extensions::TypeInfoExtensions}; +use bevy_mod_scripting_bindings_domain::ReflectionPrimitiveKind; +use bevy_mod_scripting_derive::DebugWithTypeInfo; +use bevy_platform::collections::HashMap; +use bevy_reflect::{TypeInfo, Typed}; /// All Through types follow one rule: /// - A through type can not contain a nested through type. It must always contain a fully typed inner type. @@ -40,6 +40,8 @@ pub enum ThroughTypeInfo { TypedWrapper(TypedWrapperKind), /// an actual type info TypeInfo(&'static TypeInfo), + /// A primitive type, which mostly speaks for itself + Primitive(ReflectionPrimitiveKind), } #[derive(Clone, PartialEq, Eq, DebugWithTypeInfo)] @@ -64,6 +66,8 @@ pub enum TypedWrapperKind { Vec(Box), /// Wraps a `HashMap` of a through typed type. HashMap(Box, Box), + /// Wraps a `HashSet` of a through typed type. + HashSet(Box), /// Wraps a `Result` of a through typed type. Array(Box, usize), /// Wraps an `Option` of a through typed type. @@ -128,6 +132,8 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple( tuple_types, ))); + } else if let Some(primitive) = as_reflect_primitive(type_info) { + return Some(ThroughTypeInfo::Primitive(primitive)); } None })(); @@ -138,6 +144,62 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { }) } +/// Returns the primitive kind if the given type info corresponds to one +pub fn as_reflect_primitive(type_info: &'static TypeInfo) -> Option { + let type_id = type_info.type_id(); + Some(if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Bool + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Isize + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I8 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I16 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I128 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Usize + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U8 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U16 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U128 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::F32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::F64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Char + } else if type_id == TypeId::of::<&'static str>() || type_id == TypeId::of::() { + ReflectionPrimitiveKind::Str + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::String + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::OsString + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::PathBuf + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::FunctionCallContext + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::DynamicFunction + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::DynamicFunctionMut + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::ReflectReference + } else { + return None; + }) +} + /// A trait for types that can be converted to a [`ThroughTypeInfo`]. pub trait TypedThrough { /// Get the [`ThroughTypeInfo`] for the type. @@ -225,11 +287,11 @@ impl TypedThrough for Option { } macro_rules! impl_through_typed { - ($($ty:ty),*) => { + ($($ty:ty => $ident:ident),*) => { $( impl $crate::docgen::typed_through::TypedThrough for $ty { fn through_type_info() -> $crate::docgen::typed_through::ThroughTypeInfo { - $crate::docgen::typed_through::ThroughTypeInfo::TypeInfo(<$ty as bevy_reflect::Typed>::type_info()) + $crate::docgen::typed_through::ThroughTypeInfo::Primitive(ReflectionPrimitiveKind::$ident) } } )* @@ -237,31 +299,31 @@ macro_rules! impl_through_typed { } impl_through_typed!( - FunctionCallContext, - ReflectReference, - DynamicScriptFunctionMut, - DynamicScriptFunction, - ScriptValue, - bool, - i8, - i16, - i32, - i64, - i128, - u8, - u16, - u32, - u64, - u128, - f32, - f64, - usize, - isize, - String, - PathBuf, - OsString, - char, - &'static str + FunctionCallContext => FunctionCallContext, + ReflectReference => ReflectReference, + DynamicScriptFunctionMut => DynamicFunctionMut, + DynamicScriptFunction => DynamicFunction, + ScriptValue => ScriptValue, + bool => Bool, + i8 => I8, + i16 => I16, + i32 => I32, + i64 => I64, + i128 => I128, + u8 => U8, + u16 => U16, + u32 => U32, + u64 => U64, + u128 => U128, + f32 => F32, + f64 => F64, + usize => Usize, + isize => Isize, + String => String, + PathBuf => PathBuf, + OsString => OsString, + char => Char, + &'static str => Str ); macro_rules! impl_through_typed_tuple { @@ -280,80 +342,54 @@ variadics_please::all_tuples!(impl_through_typed_tuple, 0, 13, T); mod test { use super::*; - fn assert_type_info_is_through() { + fn assert_type_info_is_primitive(kind: ReflectionPrimitiveKind) { let type_info = T::type_info(); let through_type_info = T::through_type_info(); - - match through_type_info { - ThroughTypeInfo::TypeInfo(info) => { - assert_eq!(info.type_id(), type_info.type_id()); - assert_eq!(info.type_path(), type_info.type_path()); - } - _ => panic!("Expected ThroughTypeInfo::TypeInfo"), - } - } - - fn assert_dynamic_through_type_is_val_info() { - let type_info = T::type_info(); - let through_type_info = into_through_type_info(type_info); - - match through_type_info { - ThroughTypeInfo::UntypedWrapper { - through_type, - wrapper_kind, - } => { - assert_eq!(wrapper_kind, UntypedWrapperKind::Val); - assert_eq!(through_type.type_id(), type_info.type_id()); - assert_eq!(through_type.type_path(), type_info.type_path()); + let dynamic_through_type_info = into_through_type_info(type_info); + + for (test, info) in [ + ("static", through_type_info), + ("dynamic", dynamic_through_type_info), + ] { + match info { + ThroughTypeInfo::Primitive(prim) => { + assert_eq!( + prim, + kind, + "expected {} to have primitive {test} type info: {kind}", + std::any::type_name::() + ) + } + _ => panic!("Expected ThroughTypeInfo::TypeInfo"), } - _ => panic!("Expected ThroughTypeInfo::TypeInfo"), } } #[test] fn test_typed_through_primitives() { - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::(); - assert_dynamic_through_type_is_val_info::(); - assert_type_info_is_through::<&'static str>(); - assert_dynamic_through_type_is_val_info::<&'static str>(); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::Bool); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::I8); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::I16); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::I32); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::I64); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::I128); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::U8); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::U16); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::U32); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::U64); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::U128); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::F32); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::F64); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::Usize); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::Isize); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::String); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::PathBuf); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::OsString); + assert_type_info_is_primitive::(ReflectionPrimitiveKind::Char); + assert_type_info_is_primitive::( + ReflectionPrimitiveKind::ReflectReference, + ); + assert_type_info_is_primitive::<&'static str>(ReflectionPrimitiveKind::Str); } #[test] diff --git a/crates/bevy_mod_scripting_bindings_domain/Cargo.toml b/crates/bevy_mod_scripting_bindings_domain/Cargo.toml new file mode 100644 index 0000000000..a2a2f668eb --- /dev/null +++ b/crates/bevy_mod_scripting_bindings_domain/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "bevy_mod_scripting_bindings_domain" +description = "Definitions of shared interfaces from the bevy_mod_scripting_bindings crate." +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +readme.workspace = true + +[dependencies] +serde = { workspace = true } +bevy_mod_scripting_display = { workspace = true } +bevy_mod_scripting_derive = { workspace = true } + +[lints] +workspace = true diff --git a/crates/bevy_mod_scripting_bindings_domain/src/lib.rs b/crates/bevy_mod_scripting_bindings_domain/src/lib.rs new file mode 100644 index 0000000000..560467b832 --- /dev/null +++ b/crates/bevy_mod_scripting_bindings_domain/src/lib.rs @@ -0,0 +1,7 @@ +//! Operators are special functions which de-sugar to nicer syntactic constructs, for example `a + b` in rust de-sugars to `a.add(b)` +//! This module contains abstractions for describing such operators across many languages. + +mod operators; +mod primitive; +pub use operators::*; +pub use primitive::*; diff --git a/crates/bevy_mod_scripting_bindings_domain/src/operators.rs b/crates/bevy_mod_scripting_bindings_domain/src/operators.rs new file mode 100644 index 0000000000..df29bd7d94 --- /dev/null +++ b/crates/bevy_mod_scripting_bindings_domain/src/operators.rs @@ -0,0 +1,75 @@ +//! Describes the operators supported by BMS. +//! +//! These operators are implemented by each language plugin, +//! by searching for appropriate functions in the registry. + +/// Main operator enum +pub enum ScriptOperatorNames { + /// a + b + Addition, + /// a - b + Subtraction, + /// a * b + Multiplication, + /// a / b + Division, + /// a % b + Remainder, + /// -a + Negation, + /// a ^ 2 or a ** b + Exponentiation, + /// a == b + Equality, + /// a < b + LessThanComparison, + /// len(a) + Length, + /// for a in b.iter() + Iteration, + /// print(a) + DisplayPrint, + /// debug(a) + DebugPrint, +} + +impl ScriptOperatorNames { + /// Returns the function names to dispatch these operators to + pub const fn script_function_name(self) -> &'static str { + match self { + ScriptOperatorNames::Addition => "add", + ScriptOperatorNames::Subtraction => "sub", + ScriptOperatorNames::Multiplication => "mul", + ScriptOperatorNames::Division => "div", + ScriptOperatorNames::Remainder => "rem", + ScriptOperatorNames::Negation => "neg", + ScriptOperatorNames::Exponentiation => "pow", + ScriptOperatorNames::Equality => "eq", + ScriptOperatorNames::LessThanComparison => "lt", + ScriptOperatorNames::Length => "len", + ScriptOperatorNames::Iteration => "iter", + ScriptOperatorNames::DisplayPrint => "display", + ScriptOperatorNames::DebugPrint => "debug", + } + } + + /// Parse an operator function into this enum + pub fn parse(name: impl AsRef) -> Option { + match name.as_ref() { + "add" => Some(ScriptOperatorNames::Addition), + "sub" => Some(ScriptOperatorNames::Subtraction), + "mul" => Some(ScriptOperatorNames::Multiplication), + "div" => Some(ScriptOperatorNames::Division), + "rem" => Some(ScriptOperatorNames::Remainder), + "neg" => Some(ScriptOperatorNames::Negation), + "pow" => Some(ScriptOperatorNames::Exponentiation), + "eq" => Some(ScriptOperatorNames::Equality), + "lt" => Some(ScriptOperatorNames::LessThanComparison), + "len" => Some(ScriptOperatorNames::Length), + "iter" => Some(ScriptOperatorNames::Iteration), + "display" => Some(ScriptOperatorNames::DisplayPrint), + "debug" => Some(ScriptOperatorNames::DebugPrint), + _ => None, + } + } +} diff --git a/crates/bevy_mod_scripting_bindings_domain/src/primitive.rs b/crates/bevy_mod_scripting_bindings_domain/src/primitive.rs new file mode 100644 index 0000000000..de064ed0a7 --- /dev/null +++ b/crates/bevy_mod_scripting_bindings_domain/src/primitive.rs @@ -0,0 +1,72 @@ +use bevy_mod_scripting_derive::DebugWithTypeInfo; + +/// A primitive type kind in the LAD file format. +/// +/// The docstrings on variants corresponding to Reflect types, are used to generate documentation for these primitives. +#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize, DebugWithTypeInfo)] +#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")] +#[serde(rename_all = "camelCase")] +#[allow(missing_docs)] +pub enum ReflectionPrimitiveKind { + Bool, + Isize, + I8, + I16, + I32, + I64, + I128, + Usize, + U8, + U16, + U32, + U64, + U128, + F32, + F64, + Char, + Str, + String, + OsString, + PathBuf, + FunctionCallContext, + DynamicFunction, + ScriptValue, + DynamicFunctionMut, + ReflectReference, + /// A primitive defined outside of BMS, useful for custom implementations of FromScript and IntoScript. + /// Downstream processors like mdbook plugins won't know how to treat these. + External(String), +} + +impl std::fmt::Display for ReflectionPrimitiveKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReflectionPrimitiveKind::Bool => f.write_str("Bool"), + ReflectionPrimitiveKind::Isize => f.write_str("Isize"), + ReflectionPrimitiveKind::I8 => f.write_str("I8"), + ReflectionPrimitiveKind::I16 => f.write_str("I16"), + ReflectionPrimitiveKind::I32 => f.write_str("I32"), + ReflectionPrimitiveKind::I64 => f.write_str("I64"), + ReflectionPrimitiveKind::I128 => f.write_str("I128"), + ReflectionPrimitiveKind::Usize => f.write_str("Usize"), + ReflectionPrimitiveKind::U8 => f.write_str("U8"), + ReflectionPrimitiveKind::U16 => f.write_str("U16"), + ReflectionPrimitiveKind::U32 => f.write_str("U32"), + ReflectionPrimitiveKind::U64 => f.write_str("U64"), + ReflectionPrimitiveKind::U128 => f.write_str("U128"), + ReflectionPrimitiveKind::F32 => f.write_str("F32"), + ReflectionPrimitiveKind::F64 => f.write_str("F64"), + ReflectionPrimitiveKind::Char => f.write_str("Char"), + ReflectionPrimitiveKind::Str => f.write_str("Str"), + ReflectionPrimitiveKind::String => f.write_str("String"), + ReflectionPrimitiveKind::OsString => f.write_str("OsString"), + ReflectionPrimitiveKind::PathBuf => f.write_str("PathBuf"), + ReflectionPrimitiveKind::FunctionCallContext => f.write_str("FunctionCallContext"), + ReflectionPrimitiveKind::DynamicFunction => f.write_str("DynamicFunction"), + ReflectionPrimitiveKind::ScriptValue => f.write_str("ScriptValue"), + ReflectionPrimitiveKind::DynamicFunctionMut => f.write_str("DynamicFunctionMut"), + ReflectionPrimitiveKind::ReflectReference => f.write_str("ReflectReference"), + ReflectionPrimitiveKind::External(e) => f.write_str(e.as_str()), + } + } +} diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 34d187e9d8..25ed1685d3 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -17,8 +17,6 @@ path = "src/lib.rs" [features] default = [] -# if enabled enables documentation updating in optimized builds -doc_always = [] [dependencies] diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 032b7718ca..6ddd081a7b 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -43,7 +43,6 @@ pub mod pipeline; pub mod runtime; pub mod script; pub mod script_system; - #[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)] /// Labels for various BMS systems pub enum ScriptingSystemSet { @@ -348,6 +347,7 @@ impl Plugin for BMSScriptingInfrastructurePlugin { app.register_type::(); app.register_type::>(); + app.register_type::(); app.register_type_data::, MarkAsCore>(); app.add_systems( diff --git a/crates/bevy_system_reflection/src/lib.rs b/crates/bevy_system_reflection/src/lib.rs index 932a22a672..85f897def1 100644 --- a/crates/bevy_system_reflection/src/lib.rs +++ b/crates/bevy_system_reflection/src/lib.rs @@ -487,8 +487,6 @@ mod test { fn system_e() {} - const BLESS_MODE: bool = false; - #[test] fn test_graph_is_as_expected() { // create empty schedule and graph it @@ -535,7 +533,7 @@ mod test { let expected = include_str!("../test_graph.dot"); let expected_path = manifest_dir_macros::file_path!("test_graph.dot"); - if BLESS_MODE { + if std::env::var("BLESS_MODE").is_ok() { std::fs::write(expected_path, normalize(&dot)).unwrap(); panic!("Bless mode is active"); } else { diff --git a/crates/lad_backends/lua_language_server_lad_backend/Cargo.toml b/crates/lad_backends/lua_language_server_lad_backend/Cargo.toml new file mode 100644 index 0000000000..bb265f446c --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "lua_language_server_lad_backend" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +readme.workspace = true + +[dependencies] +bevy_mod_scripting_bindings_domain = { workspace = true } +clap = { version = "4", features = ["derive"] } +# mdbook = "0.4" +anyhow = "1" +tera = "1.20" +strum = { version = "0.25", features = ["derive"] } +ladfile = { path = "../../ladfile", version = "0.6.0" } +env_logger = "0.11" +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +include_dir = "0.7" +indexmap = "2" +# serde_json = "1.0" +# regex = "1.11" + +[dev-dependencies] +assert_cmd = "2.0" +pretty_assertions = "1.4.1" + +[lints] +workspace = true + +[[bin]] +name = "lad-lls" +path = "src/main.rs" diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/convert.rs b/crates/lad_backends/lua_language_server_lad_backend/src/convert.rs new file mode 100644 index 0000000000..b4b47a8126 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/convert.rs @@ -0,0 +1,516 @@ +use bevy_mod_scripting_bindings_domain::ScriptOperatorNames; +use indexmap::IndexMap; +use ladfile::{LadFieldOrVariableKind, LadFile, LadFunction, LadTypeId, ReflectionPrimitiveKind}; + +use crate::{ + keywords::ForbiddenKeywords, + lua_declaration_file::{ + ClassField, FunctionParam, FunctionSignature, LuaClass, LuaDefinitionFile, LuaModule, + LuaOperator, LuaOperatorKind, LuaPrimitiveType, LuaType, + }, +}; + +pub fn convert_ladfile_to_lua_declaration_file( + ladfile: &ladfile::LadFile, +) -> Result { + let mut definition_file = LuaDefinitionFile { + modules: vec![], + diagnostics: vec![], + }; + + // // ignore primitive types + // let exclude_primitives = true; + let rust_types = ladfile.polymorphizied_types(false); + + // convert each rust type to a lua class with generics + + let mut lua_classes: IndexMap)> = + IndexMap::with_capacity(ladfile.types.len()); + + for (key, types) in rust_types.iter() { + // right now one class == one lad type id, when we can properly denormorphize types, we will + // be able to have multiple lad type ids per class + let lua_classes_for_type = + match convert_polymorphic_type_to_lua_classes(key, types.iter().copied(), ladfile) { + Ok(r) => r, + Err(e) => { + log::warn!("{e}"); + continue; + } + }; + + lua_classes.extend( + lua_classes_for_type + .into_iter() + .map(|(id, class, funcs)| (id, (class, funcs))), + ); + } + + for (type_id, lad_type) in ladfile.types.iter() { + let (lua_class, functions) = match lua_classes.get(type_id) { + Some(val) => val.clone(), + None => continue, + }; + // TODO: support all types + // .get(type_id) + // .ok_or_else(|| anyhow::anyhow!("Lua class not found for type ID: {}", type_id))?; + definition_file.modules.push(LuaModule { + name: lad_type.identifier.to_owned(), + classes: vec![lua_class.clone()], + functions, + ..Default::default() + }); + } + + let mut globals_module = LuaModule { + name: "globals".to_string(), + ..Default::default() + }; + for (name, instance) in ladfile.globals.iter() { + let class = match lad_instance_to_lua_type(ladfile, &instance.type_kind) { + Ok(c) => c, + Err(e) => { + log::warn!("Error generating global {name}: {e}. Using `any` type"); + LuaType::Any + } + }; + + let description = if instance.is_static { + "A static class allowing calls through the \".\" operator only. " + } else { + "An global instance of this type" + }; + + // ignore primitives + if matches!(class, LuaType::Primitive(..)) { + continue; + } + + globals_module + .globals + .push(crate::lua_declaration_file::TypeInstance { + name: name.to_string(), + definition: class, + description: Some(description.into()), + }) + } + definition_file.modules.push(globals_module); + + Ok(definition_file) +} + +const GENERIC_PLACEHOLDERS: [&str; 10] = ["T", "U", "V", "W", "X", "Y", "Z", "A", "B", "C"]; + +// /// Splits the given class into two, +// /// - The first one containing all of its non-static + static functions +// /// - The second one only containing its static functions +// pub fn split_static_class_out(class: LuaClass) -> (LuaClass, LuaClass) { +// let static_class = class.clone(); +// static_class. + +// (class, static_class) +// } + +// TODO: once https://github.com/bevyengine/bevy/issues/17117 is solved, we will be able to figure out +// where the generic types are actually used, for now we only know what they are per type. +pub fn convert_polymorphic_type_to_lua_classes<'a>( + polymorphic_type_key: &ladfile::PolymorphicTypeKey, + monomorphized_types: impl Iterator, + ladfile: &ladfile::LadFile, +) -> Result)>, anyhow::Error> { + let monomorphized_types: Vec<_> = monomorphized_types.collect(); + if monomorphized_types.len() > 1 || polymorphic_type_key.arity != 0 { + // TODO: support generics, currently bevy doesn't let you track back generic instantiations to their definition + return Err(anyhow::anyhow!( + "Type {} with arity {} is not supported yet, ignoring.", + polymorphic_type_key.identifier, + polymorphic_type_key.arity + )); + } + + let mut types = Vec::default(); + if let Some(lad_type_id) = monomorphized_types.first() { + let generics = GENERIC_PLACEHOLDERS[0..polymorphic_type_key.arity] + .iter() + .map(ToString::to_string) + .collect(); + + let documentation = ladfile + .get_type_documentation(lad_type_id) + .map(ToOwned::to_owned); + + let mut lua_fields = vec![]; + let mut lua_functions = vec![]; + let mut lua_operators = vec![]; + let mut parents = vec![]; + let name = polymorphic_type_key.identifier.to_string(); + + if let Some(lad_type) = ladfile.types.get(*lad_type_id) { + // add fields for the type + match &lad_type.layout { + ladfile::LadTypeLayout::Opaque => {} + ladfile::LadTypeLayout::MonoVariant(lad_variant) => match lad_variant { + ladfile::LadVariant::TupleStruct { name, fields } => { + for (idx, field) in fields.iter().enumerate() { + lua_fields.push(ClassField { + name: format!("[{}]", idx + 1), + ty: match lad_instance_to_lua_type(ladfile, &field.type_) { + Ok(ty) => ty, + Err(e) => { + log::warn!( + "error converting field {idx}: {e}. for tuple struct {name}" + ); + LuaType::Any + } + }, + scope: crate::lua_declaration_file::FieldScope::Public, + optional: false, + description: None, + }) + } + } + ladfile::LadVariant::Struct { name, fields } => { + for field in fields.iter() { + lua_fields.push(ClassField { + name: field.name.clone(), + ty: match lad_instance_to_lua_type(ladfile, &field.type_) { + Ok(ty) => ty, + Err(e) => { + log::warn!( + "error converting field {}: {e}. for struct {name}", + field.name + ); + LuaType::Any + } + }, + scope: crate::lua_declaration_file::FieldScope::Public, + optional: true, + description: None, + }) + } + } + ladfile::LadVariant::Unit { .. } => {} + }, + ladfile::LadTypeLayout::Enum(_) => { + // TODO: enums + } + } + + for function in &lad_type.associated_functions { + if let Some(function) = ladfile.functions.get(function) { + let lua_function = match lad_function_to_lua_function(ladfile, function) { + Ok(func) => func, + Err(err) => { + log::warn!( + "Error converting function: {} on namespace {:?}: {err}. Using empty definition", + function.identifier, + function.namespace + ); + FunctionSignature { + name: function.identifier.to_string().replace("-", "_"), + ..Default::default() + } + } + }; + + if function.metadata.is_operator { + match lua_function_to_operator(&lua_function) { + Some(Ok(op)) => lua_operators.push(op), + Some(Err(func)) => lua_functions.push(func), + None => { + log::warn!( + "Error converting operator function: {} on namespace {:?}. Skipping", + function.identifier, + function.namespace + ); + } + }; + } + lua_functions.push(lua_function); + } + } + + if lad_type.metadata.is_reflect && name != "ReflectReference" { + parents.push(String::from("ReflectReference")) + } + // if metadata.is_component { + // parents.push(String::from("ScriptComponentRegistration")) + // } + // if metadata.is_resource { + // parents.push(String::from("ScriptResourceRegistration")) + // } + } + + let class = LuaClass { + name, + parents, + fields: lua_fields, + generics, + documentation, + exact: true, + operators: lua_operators, + }; + + types.push(((*lad_type_id).clone(), class, lua_functions)); + } + Ok(types) +} + +/// converts a lua function to an operator if it matches the expected strucutre +pub fn lua_function_to_operator( + func: &FunctionSignature, +) -> Option> { + let operator = ScriptOperatorNames::parse(&func.name)?; + + // first arg is implied to be `self` + let (metamethod, second_arg, ret) = match operator { + ScriptOperatorNames::Addition => ( + LuaOperatorKind::Add, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Subtraction => ( + LuaOperatorKind::Sub, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Multiplication => ( + LuaOperatorKind::Mul, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Division => ( + LuaOperatorKind::Div, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Remainder => ( + LuaOperatorKind::Mod, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Negation => (LuaOperatorKind::Unm, None, func.returns.first()?), + ScriptOperatorNames::Exponentiation => ( + LuaOperatorKind::Pow, + Some(func.params.get(1)?), + func.returns.first()?, + ), + ScriptOperatorNames::Equality => { + return Some(Err(FunctionSignature { + name: "__eq".into(), + ..func.clone() + })); + } + ScriptOperatorNames::LessThanComparison => { + return Some(Err(FunctionSignature { + name: "__lt".into(), + ..func.clone() + })); + } + ScriptOperatorNames::Length => (LuaOperatorKind::Len, None, func.returns.first()?), + ScriptOperatorNames::Iteration => { + return Some(Err(FunctionSignature { + name: "__pairs".into(), + ..func.clone() + })); + } + ScriptOperatorNames::DisplayPrint | ScriptOperatorNames::DebugPrint => { + return Some(Err(FunctionSignature { + name: "__tostring".into(), + ..func.clone() + })); + } + }; + + Some(Ok(LuaOperator { + operation: metamethod, + param_type: second_arg.map(|a| a.ty.clone()), + return_type: ret.clone(), + })) +} + +// /// some operators aren't fully supported by lua language server. +// /// we implement those by converting to metatable entries, to signal the existence +// pub fn lua_operator_to_special_function(op: &LuaOperator) -> Option { +// Some(match op.operation { +// LuaOperatorKind::Eq => FunctionSignature { +// name: "__eq", +// params: , +// returns: (), +// async_fn: (), +// deprecated: (), +// nodiscard: (), +// package: (), +// overloads: (), +// generics: (), +// documentation: (), +// has_self: (), +// }, +// LuaOperatorKind::ToString => todo!(), +// LuaOperatorKind::Pairs => todo!(), +// _ => return None, +// }) +// } + +pub fn lad_function_to_lua_function( + ladfile: &LadFile, + function: &LadFunction, +) -> Result { + // overloads get a unique instantiation, but maybe that's wrong + let name = function.identifier.to_string(); + ForbiddenKeywords::is_forbidden_err(&function.identifier)?; + + let params = function + .arguments + .iter() + .filter(|a| { + !matches!( + a.kind, + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::FunctionCallContext) + ) + }) + .enumerate() + .map(|(idx, a)| { + let ident = a + .name + .as_ref() + .map(|v| v.to_string()) + .unwrap_or(format!("p{}", idx + 1)); + Ok(FunctionParam { + name: match ForbiddenKeywords::is_forbidden_err(&ident) { + Ok(_) => ident, + Err(_) => format!("_{ident}"), + }, + ty: lad_instance_to_lua_type(ladfile, &a.kind)?, + optional: matches!(a.kind, LadFieldOrVariableKind::Option(..)), + description: a.documentation.as_ref().map(|d| d.to_string()), + }) + }) + .collect::, anyhow::Error>>()?; + + let self_type = match &function.namespace { + ladfile::LadFunctionNamespace::Type(lad_type_id) => function + .arguments + .first() + .is_some_and(|a| match &a.kind { + LadFieldOrVariableKind::Ref(i) + | LadFieldOrVariableKind::Mut(i) + | LadFieldOrVariableKind::Val(i) => lad_type_id == i, + _ => false, + }) + .then_some(lad_type_id), + ladfile::LadFunctionNamespace::Global => None, + }; + + let returns = lad_instance_to_lua_type(ladfile, &function.return_type.kind)?; + + Ok(FunctionSignature { + name, + params, + returns: vec![returns], + async_fn: false, + deprecated: false, + nodiscard: false, + package: false, + overloads: vec![], + generics: vec![], + documentation: function.documentation.as_ref().map(|d| d.to_string()), + has_self: self_type.is_some(), + }) +} + +pub fn to_lua_many( + ladfile: &LadFile, + lad_types: &[ladfile::LadFieldOrVariableKind], +) -> Result, anyhow::Error> { + let lua_types = lad_types + .iter() + .map(|lad_type| lad_instance_to_lua_type(ladfile, lad_type)) + .collect::, _>>()?; + Ok(lua_types) +} + +pub fn lad_instance_to_lua_type( + ladfile: &LadFile, + lad_type: &ladfile::LadFieldOrVariableKind, +) -> Result { + Ok(match &lad_type { + ladfile::LadFieldOrVariableKind::Primitive(prim) => lad_primitive_to_lua_type(prim), + ladfile::LadFieldOrVariableKind::Ref(lad_type_id) + | ladfile::LadFieldOrVariableKind::Mut(lad_type_id) + | ladfile::LadFieldOrVariableKind::Val(lad_type_id) => { + if ladfile.get_type_generics(lad_type_id).is_none() { + LuaType::Alias(ladfile.get_type_identifier(lad_type_id, None).to_string()) + } else { + return Err(anyhow::anyhow!( + "Generic fields are not supported: {lad_type_id}", + )); + } + } + ladfile::LadFieldOrVariableKind::Option(lad_type_kind) => LuaType::Union(vec![ + lad_instance_to_lua_type(ladfile, lad_type_kind)?, + LuaType::Primitive(LuaPrimitiveType::Nil), + ]), + ladfile::LadFieldOrVariableKind::Vec(lad_type_kind) => { + LuaType::Array(Box::new(lad_instance_to_lua_type(ladfile, lad_type_kind)?)) + } + ladfile::LadFieldOrVariableKind::HashMap(key, value) => LuaType::Dictionary { + key: Box::new(lad_instance_to_lua_type(ladfile, key)?), + value: Box::new(lad_instance_to_lua_type(ladfile, value)?), + }, + ladfile::LadFieldOrVariableKind::HashSet(key) => LuaType::Dictionary { + key: Box::new(lad_instance_to_lua_type(ladfile, key)?), + value: Box::new(LuaType::Primitive(LuaPrimitiveType::Boolean)), + }, + ladfile::LadFieldOrVariableKind::InteropResult(lad_type_kind) => { + lad_instance_to_lua_type(ladfile, lad_type_kind)? // TODO: currently ignores the possibility of an error type, we should have a custom class abstraction here + } + ladfile::LadFieldOrVariableKind::Tuple(lad_type_kinds) => { + if lad_type_kinds.is_empty() { + LuaType::Primitive(LuaPrimitiveType::Nil) + } else { + LuaType::Tuple(to_lua_many(ladfile, lad_type_kinds)?) + } + } + ladfile::LadFieldOrVariableKind::Array(lad_type_kind, _) => { + LuaType::Array(Box::new(lad_instance_to_lua_type(ladfile, lad_type_kind)?)) + } + ladfile::LadFieldOrVariableKind::Union(lad_type_kinds) => { + LuaType::Union(to_lua_many(ladfile, lad_type_kinds)?) + } + ladfile::LadFieldOrVariableKind::Unknown(_) => LuaType::Any, + }) +} + +pub fn lad_primitive_to_lua_type(lad_primitive: &ReflectionPrimitiveKind) -> LuaType { + LuaType::Primitive(match lad_primitive { + ReflectionPrimitiveKind::Bool => LuaPrimitiveType::Boolean, + ReflectionPrimitiveKind::Isize + | ReflectionPrimitiveKind::I8 + | ReflectionPrimitiveKind::I16 + | ReflectionPrimitiveKind::I32 + | ReflectionPrimitiveKind::I64 + | ReflectionPrimitiveKind::I128 + | ReflectionPrimitiveKind::Usize + | ReflectionPrimitiveKind::U8 + | ReflectionPrimitiveKind::U16 + | ReflectionPrimitiveKind::U32 + | ReflectionPrimitiveKind::U64 + | ReflectionPrimitiveKind::U128 => LuaPrimitiveType::Integer, + ReflectionPrimitiveKind::F32 | ReflectionPrimitiveKind::F64 => LuaPrimitiveType::Number, + ReflectionPrimitiveKind::Char + | ReflectionPrimitiveKind::Str + | ReflectionPrimitiveKind::String + | ReflectionPrimitiveKind::OsString + | ReflectionPrimitiveKind::PathBuf => LuaPrimitiveType::String, + ReflectionPrimitiveKind::FunctionCallContext => return LuaType::Any, + ReflectionPrimitiveKind::DynamicFunction | ReflectionPrimitiveKind::DynamicFunctionMut => { + LuaPrimitiveType::Function + } + ReflectionPrimitiveKind::ReflectReference => { + return LuaType::Alias("ReflectReference".to_string()); + } + ReflectionPrimitiveKind::ScriptValue => return LuaType::Any, + ReflectionPrimitiveKind::External(_) => return LuaType::Any, + }) +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/keywords.rs b/crates/lad_backends/lua_language_server_lad_backend/src/keywords.rs new file mode 100644 index 0000000000..b5fd31b1f1 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/keywords.rs @@ -0,0 +1,38 @@ +use std::str::FromStr; + +#[derive(strum::EnumString)] +#[strum(serialize_all = "snake_case")] +pub(crate) enum ForbiddenKeywords { + End, + And, + Break, + Do, + Else, + Elseif, + False, + For, + Function, + If, + In, + Local, + Nil, + Not, + Or, + Repeat, + Return, + Then, + True, + Until, + While, +} + +impl ForbiddenKeywords { + /// Checks if a keyword is forbidden + pub fn is_forbidden_err(word: &str) -> anyhow::Result<&str> { + if ForbiddenKeywords::from_str(word).is_ok() { + Err(anyhow::anyhow!("{word} is a reserved keyword in lua")) + } else { + Ok(word) + } + } +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/lib.rs b/crates/lad_backends/lua_language_server_lad_backend/src/lib.rs new file mode 100644 index 0000000000..fbac8be151 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/lib.rs @@ -0,0 +1,46 @@ +//! Logic for generating Lua language server files from a LAD file. + +use std::path::Path; + +use anyhow::Context; + +use crate::{convert::convert_ladfile_to_lua_declaration_file, templating::render_template}; + +mod convert; +mod keywords; +mod lua_declaration_file; +mod plugin; +mod templating; +pub use plugin::*; + +/// Processess a LAD file and generates Lua language server files. +pub fn generate_lua_language_server_files( + ladfile: &ladfile::LadFile, + output_dir: &Path, + file_name: &Path, +) -> Result<(), anyhow::Error> { + let declaration_file = convert_ladfile_to_lua_declaration_file(ladfile)?; + + let output_path = output_dir.join(file_name); + std::fs::create_dir_all( + output_path + .parent() + .ok_or_else(|| anyhow::anyhow!("Output path has no parent"))?, + ) + .with_context(|| "failed to create output directories")?; + let context = tera::Context::from_serialize(&declaration_file).with_context(|| { + format!( + "Failed to serialize LuaModule for template rendering: {}", + file_name.as_os_str().display() + ) + })?; + + let rendered = render_template("declaration_file.tera", &context)?; + std::fs::write(&output_path, rendered).with_context(|| { + format!( + "Failed to write rendered template to file: {}", + output_path.display() + ) + })?; + Ok(()) +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/lua_declaration_file.rs b/crates/lad_backends/lua_language_server_lad_backend/src/lua_declaration_file.rs new file mode 100644 index 0000000000..c14ac85da7 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/lua_declaration_file.rs @@ -0,0 +1,438 @@ +#![allow(dead_code)] + +use serde::Serialize; + +/// Basic primitive types supported by Lua Language Server annotations. +/// +/// These correspond to the fundamental Lua types that can be used in type annotations. +/// +/// # Example +/// ```lua +/// ---@type nil +/// local my_nil = nil +/// +/// ---@type boolean +/// local my_bool = true +/// +/// ---@type string +/// local my_string = "hello" +/// +/// ---@type number +/// local my_number = 42.5 +/// +/// ---@type integer +/// local my_int = 42 +/// +/// ---@type function +/// local my_func = function() end +/// +/// ---@type table +/// local my_table = {} +/// +/// ---@type thread +/// local my_thread = coroutine.create(function() end) +/// +/// ---@type userdata +/// local my_userdata = io.stdout +/// +/// ---@type lightuserdata +/// local my_lightuserdata = some_c_pointer +/// ``` +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LuaPrimitiveType { + Nil, + Boolean, + String, + Number, + Integer, + Function, + Table, + Thread, + Userdata, + #[serde(rename = "lightuserdata")] + LightUserdata, +} + +/// Represents a Lua type (can be primitive, alias, union, array, function, etc.) +/// +/// Supports all Lua Language Server type annotations including complex types +/// like unions, arrays, generics, and table literals. +/// +/// # Examples +/// ```lua +/// ---@type string -- Primitive type +/// ---@type MyClass -- Alias type +/// ---@type string | number -- Union type +/// ---@type string[] -- Array type +/// ---@type [string, number] -- Tuple type +/// ---@type table -- Dictionary type +/// ---@type { name: string, age: number } -- Table literal +/// ---@type fun(x: number): string -- Function type +/// ---@type MyClass -- Generic type +/// ---@type "left" | "right" -- Literal types +/// ``` +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "kind", content = "value")] +pub enum LuaType { + Primitive(LuaPrimitiveType), // "number", "string", "boolean", etc. + Alias(String), + Union(Vec), + Array(Box), + Tuple(Vec), + Dictionary { + key: Box, + value: Box, + }, + Function(FunctionSignatureShort), + Generic { + name: String, + parent: Option>, + }, + Literal(String), // for literal types, e.g., '"left"' + Any, +} + +#[derive(Debug, Clone, Serialize)] +pub struct FunctionSignatureShort { + pub parameters: Vec<(String, LuaType)>, + pub return_type: Box, +} + +// Function-related definitions +/// Represents a function parameter in Lua Language Server annotations. +/// +/// # Examples +/// ```lua +/// ---@param name string -- Required parameter +/// ---@param age? number -- Optional parameter +/// ---@param callback fun(): nil -- Function parameter +/// ---@param ... string -- Variadic parameter +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct FunctionParam { + pub name: String, + pub ty: LuaType, + pub optional: bool, + pub description: Option, +} + +/// Represents a function signature with comprehensive annotation support. +/// +/// # Examples +/// ```lua +/// ---@async -- Async function +/// ---@deprecated -- Deprecated function +/// ---@nodiscard -- Return value should not be ignored +/// ---@package -- Package-private function +/// ---@generic T -- Generic function +/// ---@param name string -- Parameters +/// ---@param age? number +/// ---@return string -- Return types +/// ---@return number? +/// ---@overload fun(name: string): string -- Function overloads +/// function getName(name, age) end +/// ``` +#[derive(Debug, Clone, Serialize, Default)] +pub struct FunctionSignature { + pub name: String, + pub params: Vec, + pub returns: Vec, + pub async_fn: bool, + pub deprecated: bool, + pub nodiscard: bool, + pub package: bool, + pub overloads: Vec, + pub generics: Vec, + pub documentation: Option, + pub has_self: bool, +} + +// Class-related definitions +/// Field visibility scope for class members. +/// +/// # Examples +/// ```lua +/// ---@class MyClass +/// ---@field public_field string -- Public field (default) +/// ---@field private _private number -- Private field +/// ---@field protected _protected boolean -- Protected field +/// ---@field package _package table -- Package-private field +/// ``` +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum FieldScope { + #[serde(rename = "")] + Public, + Private, + Protected, + Package, +} + +/// Represents a class field with type, visibility, and optional status. +/// +/// # Examples +/// ```lua +/// ---@class Player +/// ---@field name string -- Required public field +/// ---@field age? number -- Optional field +/// ---@field private _id string -- Private field +/// ---@field protected _health number -- Protected field +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct ClassField { + pub name: String, + pub ty: LuaType, + pub scope: FieldScope, + pub optional: bool, + pub description: Option, +} + +// Operator-related definitions +/// Lua metamethod operators supported by the language server. +/// +/// These correspond to Lua metamethods that can be overloaded in classes. +/// +/// # Examples +/// ```lua +/// ---@class Vector +/// ---@operator add(Vector): Vector -- Overload + operator +/// ---@operator unm: Vector -- Overload unary - operator +/// ---@operator call(number): Vector -- Overload () operator +/// ---@operator len: number -- Overload # operator +/// ``` +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LuaOperatorKind { + Add, // + + Sub, // - + Mul, // * + Div, // / + #[serde(rename = "idiv")] + IDiv, // // + Mod, // % + Pow, // ^ + Unm, // unary - + Concat, // .. + Len, // # + Eq, // == + Lt, // < + Le, // <= + Call, // () + Index, // [] + #[serde(rename = "newindex")] + NewIndex, // []= + #[serde(rename = "tostring")] + ToString, // tostring() + Pairs, // pairs() + IPairs, // ipars() +} + +/// Represents an operator overload for a class. +/// +/// # Examples +/// ```lua +/// ---@class Vector +/// ---@operator add(Vector): Vector -- Binary operator with parameter +/// ---@operator unm: Vector -- Unary operator without parameter +/// ---@operator call(...): Vector -- Call operator with variadic parameters +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct LuaOperator { + pub operation: LuaOperatorKind, // e.g., "add", "unm", "call" + pub param_type: Option, + pub return_type: LuaType, +} + +/// Represents a Lua class with inheritance, fields, operators, and generic support. +/// +/// Classes can have inheritance, generic parameters, fields with visibility modifiers, +/// operator overloads, and comprehensive documentation. +/// +/// # Examples +/// ```lua +/// ---@class Vector -- Generic class +/// ---@field x number -- Public field +/// ---@field private _id string -- Private field +/// ---@operator add(Vector): Vector -- Operator overload +/// +/// ---@class Point : Vector -- Inheritance +/// ---@field z number -- Additional field +/// ``` +#[derive(Debug, Clone, Serialize, Default)] +pub struct LuaClass { + pub name: String, + pub exact: bool, + pub parents: Vec, + pub fields: Vec, + pub generics: Vec, + pub operators: Vec, + pub documentation: Option, +} + +// Type alias and enum definitions +/// Represents an enum variant for type aliases that act as enums. +/// +/// # Examples +/// ```lua +/// ---@alias Color +/// ---| "red" -- Red color +/// ---| "green" -- Green color +/// ---| "blue" -- Blue color +/// +/// ---@enum Direction +/// local Direction = { +/// UP = 1, -- Move up +/// DOWN = 2, -- Move down +/// LEFT = 3, -- Move left +/// RIGHT = 4, -- Move right +/// } +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct EnumVariant { + pub value: String, + pub description: Option, +} + +/// Represents a type alias (including enums) +/// +/// Type aliases can be simple type definitions or enum-like structures with specific values. +/// +/// # Examples +/// ```lua +/// ---@alias MyString string -- Simple alias +/// ---@alias StringOrNumber string | number -- Union alias +/// +/// ---@alias Color +/// ---| "red" +/// ---| "green" +/// ---| "blue" +/// +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct TypeAlias { + pub instance: TypeInstance, +} + +/// Represents a local/global +////// +/// # Examples +/// ```lua +/// ---@type MyType +/// MyType = {} +/// +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct TypeInstance { + pub name: String, + pub definition: LuaType, + pub description: Option, +} + +// Module and file-level definitions +/// Represents a module or meta file +/// +/// A module contains classes, type aliases, functions, enums, and other declarations. +/// Can be marked as a meta file for special handling by the language server. +/// +/// # Examples +/// ```lua +/// ---@meta +/// ---@module "mymodule" +/// +/// ---@class MyClass +/// local MyClass = {} +/// +/// ---@type string +/// local version = "1.0.0" +/// +/// return MyClass +/// ``` +#[derive(Debug, Clone, Serialize, Default)] +pub struct LuaModule { + pub name: String, + pub classes: Vec, + pub globals: Vec, + pub functions: Vec, + pub documentation: Option, + pub is_meta: bool, +} + +/// Represents a Lua definition file +/// +/// Contains all modules and diagnostic settings for a complete Lua type definition file. +/// Used to generate `.lua` files with proper annotations for the Lua Language Server. +/// +/// # Examples +/// ```lua +/// ---@meta +/// ---@diagnostic disable: lowercase-global +/// +/// ---@class MyClass +/// local MyClass = {} +/// +/// return MyClass +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct LuaDefinitionFile { + pub modules: Vec, + pub diagnostics: Vec, +} + +// Diagnostic-related definitions +/// Represents a diagnostic toggle annotation. +/// +/// Used to control which diagnostics are enabled or disabled for specific scopes. +/// +/// # Examples +/// ```lua +/// ---@diagnostic disable: lowercase-global -- Disable for entire file +/// ---@diagnostic disable-next-line: unused-local -- Disable for next line only +/// ---@diagnostic enable: undefined-global -- Re-enable specific diagnostic +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct DiagnosticToggle { + pub state: DiagnosticState, + pub diagnostics: Vec, + pub scope: DiagnosticScope, +} + +/// Diagnostic state (enable/disable) +/// +/// Controls whether diagnostics are enabled, disabled, or disabled for specific lines. +/// +/// # Examples +/// ```lua +/// ---@diagnostic enable: undefined-global -- Enable diagnostic +/// ---@diagnostic disable: lowercase-global -- Disable diagnostic +/// ---@diagnostic disable-next-line: unused-local -- Disable for next line +/// ---@diagnostic disable-line: missing-parameter -- Disable for current line +/// ``` +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum DiagnosticState { + Enable, + Disable, + #[serde(rename = "disable-next-line")] + DisableNextLine, + #[serde(rename = "disable-line")] + DisableLine, +} + +/// Where the diagnostic toggle applies +/// +/// Defines the scope where diagnostic settings take effect. +/// +/// # Examples +/// ```lua +/// ---@diagnostic disable: lowercase-global -- File scope +/// local x = 1 +/// ---@diagnostic disable-next-line: unused-local -- Next line scope +/// local unused = 2 +/// ``` +#[derive(Debug, Clone, Serialize)] +pub enum DiagnosticScope { + File, + Line(usize), + NextLine(usize), +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/main.rs b/crates/lad_backends/lua_language_server_lad_backend/src/main.rs new file mode 100644 index 0000000000..4766eeca86 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/main.rs @@ -0,0 +1,55 @@ +//! Language Agnostic Declaration (LAD) file format post processor for generating Lua language server files for the bevy_mod_scripting crate. + +use std::path::PathBuf; + +use clap::Parser; +use lua_language_server_lad_backend::generate_lua_language_server_files; + +#[derive(Debug, clap::Parser)] +/// Command line arguments for the Lua Language Server LAD backend. +pub struct Args { + /// Input LAD file path + #[clap(short, long, help = "LAD json input file")] + pub input: String, + + /// Output directory for the generated Lua language server files + #[clap( + short, + long, + help = "Output directory for the generated Lua language server file" + )] + pub output: PathBuf, + + #[clap(short, long, help = "The file name of the definition file")] + /// File name + pub filename: Option, +} +fn main() { + if let Err(e) = try_main() { + eprintln!("{e}"); + std::process::exit(1); + } +} + +fn try_main() -> Result<(), anyhow::Error> { + let args = Args::parse(); + + // Initialize the logger + env_logger::init(); + + // Log the input and output paths + log::trace!("Input LAD file: {}", args.input); + log::trace!("Output directory: {:?}", args.output); + + // Load the LAD file + let file = std::fs::read_to_string(&args.input) + .map_err(|e| anyhow::anyhow!("Failed to read LAD file {}: {}", args.input, e))?; + let ladfile = ladfile::parse_lad_file(&file)?; + + generate_lua_language_server_files( + &ladfile, + &args.output, + &args.filename.unwrap_or(PathBuf::from("bindings.lua")), + )?; + Ok(()) +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/plugin.rs b/crates/lad_backends/lua_language_server_lad_backend/src/plugin.rs new file mode 100644 index 0000000000..0d38c14598 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/plugin.rs @@ -0,0 +1,34 @@ +use std::{ + error::Error, + path::{Path, PathBuf}, +}; + +use ladfile::LadFilePlugin; + +use crate::generate_lua_language_server_files; + +/// A plugin which generates lua language definition files to a specified directory when run +#[derive(Clone)] +pub struct LuaLanguageServerLadPlugin { + /// The filename of the generated definition file + pub filename: PathBuf, +} + +impl Default for LuaLanguageServerLadPlugin { + fn default() -> Self { + Self { + filename: PathBuf::from("bindings.lua"), + } + } +} + +impl LadFilePlugin for LuaLanguageServerLadPlugin { + fn run(&self, ladfile: &ladfile::LadFile, path: &Path) -> Result<(), Box> { + generate_lua_language_server_files(ladfile, path, &self.filename) + .map_err(|e| e.into_boxed_dyn_error() as Box) + } + + fn name(&self) -> &'static str { + "Lua language server definition file generator" + } +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/src/templating.rs b/crates/lad_backends/lua_language_server_lad_backend/src/templating.rs new file mode 100644 index 0000000000..417b97b06e --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/src/templating.rs @@ -0,0 +1,40 @@ +use include_dir::{Dir, include_dir}; +use std::error::Error; + +pub const TEMPLATE_DIRECTORY: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates"); + +pub fn prepare_tera() -> Result { + let mut tera = tera::Tera::default(); + // Add the template directory to Tera + for file in TEMPLATE_DIRECTORY.files() { + let content_utf8 = file.contents_utf8().ok_or_else(|| { + anyhow::anyhow!("Failed to read template file: {}", file.path().display()) + })?; + + let template_name = file.path().to_string_lossy(); + tera.add_raw_template(&template_name, content_utf8) + .map_err(handle_tera_error)?; + } + + Ok(tera) +} + +fn handle_tera_error(error: tera::Error) -> anyhow::Error { + let inner_error_str = error + .source() + .and_then(|e| e.to_string().into()) + .unwrap_or_else(|| "No source available".to_string()); + anyhow::anyhow!("Tera error: {error}, {inner_error_str}") +} + +pub fn render_template( + template_name: &str, + context: &tera::Context, +) -> Result { + let tera = prepare_tera()?; + tera.get_template_names().for_each(|name| { + log::trace!("Available template: {name}"); + }); + tera.render(template_name, context) + .map_err(handle_tera_error) +} diff --git a/crates/lad_backends/lua_language_server_lad_backend/templates/declaration_file.tera b/crates/lad_backends/lua_language_server_lad_backend/templates/declaration_file.tera new file mode 100644 index 0000000000..7dc82e382e --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/templates/declaration_file.tera @@ -0,0 +1,129 @@ +---@meta +---@module "{{ modules | first | get(key="name")}}" +{# newline #} +{# newline #} +{%- for module in modules -%} {# module #} +{%- for class in module.classes -%} +---@class {{ class.name }} {%- if class.parents %} : {% endif -%}{%- for parent in class.parents -%}{{parent}}{%- if not loop.last-%},{%- endif -%}{%- endfor %} +{{ self::multiline_description(description=class.documentation) -}} +{%- for field in class.fields -%} +{{- self::class_field(field=field) -}} +{%- endfor -%} {#- fields -#} +{%- for operator in class.operators -%} +---@operator {{operator.operation}}{% if operator.param_type %}({{self::lua_type(ty=operator.param_type)}}){% endif %}: {{self::lua_type(ty=operator.return_type)}} +{% endfor -%} +{{ class.name }} = {} +{# newline #} +{%- for function in module.functions -%} +{# newline #} +{# newline #} +{%- if function.async_fn -%} +---@async +{# newline #} +{%- endif -%} +{%- if function.deprecated -%} +---@deprecated +{# newline #} +{%- endif -%} +{%- if function.no_discard -%} +---@nodiscard +{# newline #} +{%- endif -%} +{%- if function.package -%} +---@package +{# newline #} +{%- endif -%} +{%- for generic in function.generics -%} +---@generic {{ generic.name }} +{# newline #} +{%- endfor -%} +{%- for param in function.params -%} +{{- self::function_param(arg=param) -}} +{%- endfor -%} +{%- for return in function.returns -%} +---@return {{ self::lua_type(ty=return) }} +function {% if function.has_self -%}{{class.name}}:{%- else -%}{{class.name}}.{%- endif -%} {{ function.name }}( + {%- for param in function.params -%} + {{ param.name }} {%- if not loop.last -%},{%- endif -%} + {%- endfor -%} +) end +{# newline #} +{%- endfor -%} +{%- endfor -%} {# functions #} +{%- endfor -%} {# class #} +{# newline #} +{# newline #} +{%- for global in module.globals -%} +---@type {{self::lua_type(ty=global.definition)}} +{{ self::multiline_description(description=global.description) -}} +{{ global.name}} = {} +{# newline #} +{# newline #} +{%- endfor -%} + +{%- endfor -%} {# modules #} + +{%- macro class_field(field) -%} +---@field {{ field.scope }} {{ field.name }} {% if field.optional %}?{% endif %} {{ self::lua_type(ty=field.ty) -}} +{{- self::multiline_description(description=field.description) }} +{# newline -#} +{%- endmacro class_field -%} + +{%- macro lua_type(ty) -%} + {%- if ty.kind == "Primitive" -%} + {{ ty.value }} + {%- elif ty.kind == "Alias" -%} + {{ ty.value }} + {%- elif ty.kind == "Literal" -%} + {{ ty.value }} + {%- elif ty.kind == "Any" -%} + any + {%- elif ty.kind == "Array" -%} + {{ self::lua_type(ty=ty.value) }}[] + {%- elif ty.kind == "Union" -%} + {%- for subtype in ty.value -%} + {{ self::lua_type(ty=subtype) }} + {%- if not loop.last %} | {% endif -%} + {%- endfor -%} + {%- elif ty.kind == "Tuple" -%} + [ + {%- for subtype in ty.value -%} + {{ self::lua_type(ty=subtype) }} + {%- if not loop.last %}, {% endif -%} + {%- endfor -%} + ] + {%- elif ty.kind == "Dictionary" -%} + table<{{ self::lua_type(ty=ty.value.key) }}, {{ self::lua_type(ty=ty.value.value) }}> + {%- elif ty.kind == "Function" -%} + fun( + {%- for param in ty.value.parameters -%} + {{ param[0] }}: {{ self::lua_type(ty=param[1]) }} + {%- if not loop.last %}, {% endif -%} + {%- endfor -%} + ): {{ self::lua_type(ty=ty.value.returns) }} + {%- elif ty.kind == "Generic" -%} + {%- if ty.value.parent is defined and ty.value.parent is not null -%} + {{ ty.value.name }}<{{ self::lua_type(ty=ty.value.parent) }}> + {%- else -%} + {{ ty.value.name }} + {%- endif -%} + {%- else -%} + ??? {# fallback #} + {%- endif -%} +{%- endmacro lua_type -%} + +{%- macro function_param(arg) -%} +---@param {{ arg.name }} {{ self::lua_type(ty=arg.ty) }} {% if arg.optional %}?{%- endif -%} +{# newline #} +{# newline -#} +{{- self::multiline_description(description=arg.description) -}} +{%- endmacro function_param -%} + +{%- macro multiline_description(description) -%} +{%- if description -%} +{%- for line in description | split(pat="\n") -%} +--- {{ line }} +{% endfor -%} +{%- else -%} +{%- endif -%} +{%- endmacro multiline_description -%} \ No newline at end of file diff --git a/crates/lad_backends/lua_language_server_lad_backend/tests/.gitignore b/crates/lad_backends/lua_language_server_lad_backend/tests/.gitignore new file mode 100644 index 0000000000..7b7d2bd1f4 --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/tests/.gitignore @@ -0,0 +1,2 @@ +example_ladfile/test.lad.json +**/bindings.lua \ No newline at end of file diff --git a/crates/lad_backends/lua_language_server_lad_backend/tests/example_ladfile/expected.lua b/crates/lad_backends/lua_language_server_lad_backend/tests/example_ladfile/expected.lua new file mode 100644 index 0000000000..fd99bd68fa --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/tests/example_ladfile/expected.lua @@ -0,0 +1,169 @@ +---@meta +---@module "PlainStructType" + +---@class PlainStructType : ReflectReference +--- I am a simple plain struct type +---@field int_field ? integer +PlainStructType = {} + +---@param p1 PlainStructType +---@param p2 integer +---@return any +function PlainStructType:plain_struct_function(p1,p2) end + + +---@class Bool +--- A boolean value +Bool = {} + + +---@class Char +--- An 8-bit character +Char = {} + + +---@class DynamicFunction +--- A callable dynamic function +DynamicFunction = {} + + +---@class DynamicFunctionMut +--- A stateful and callable dynamic function +DynamicFunctionMut = {} + + +---@class F32 +--- A 32-bit floating point number +F32 = {} + + +---@class F64 +--- A 64-bit floating point number +F64 = {} + + +---@class FunctionCallContext +--- Function call context, if accepted by a function, means the function can access the world in arbitrary ways. +FunctionCallContext = {} + + +---@class I128 +--- A signed 128-bit integer +I128 = {} + + +---@class I16 +--- A signed 16-bit integer +I16 = {} + + +---@class I32 +--- A signed 32-bit integer +I32 = {} + + +---@class I64 +--- A signed 64-bit integer +I64 = {} + + +---@class I8 +--- A signed 8-bit integer +I8 = {} + + +---@class Isize +--- A signed pointer-sized integer +Isize = {} + + +---@class OsString +--- A heap allocated OS string +OsString = {} + + +---@class PathBuf +--- A heap allocated file path +PathBuf = {} + + +---@class ReflectReference +--- A reference to a reflectable type +ReflectReference = {} + + +---@class ScriptValue +--- A value representing the union of all representable values +ScriptValue = {} + + +---@class Str +--- A string slice +Str = {} + + +---@class String +--- A heap allocated string +String = {} + + +---@class U128 +--- An unsigned 128-bit integer +U128 = {} + + +---@class U16 +--- An unsigned 16-bit integer +U16 = {} + + +---@class U32 +--- An unsigned 32-bit integer +U32 = {} + + +---@class U64 +--- An unsigned 64-bit integer +U64 = {} + + +---@class U8 +--- An unsigned 8-bit integer +U8 = {} + + +---@class Usize +--- An unsigned pointer-sized integer +Usize = {} + + +---@class EnumType : ReflectReference +EnumType = {} + + +---@class TupleStructType : ReflectReference +--- I am a tuple test type +---@field [1] integer +---@field [2] string +TupleStructType = {} + + +---@class UnitType : ReflectReference +--- I am a unit test type +UnitType = {} + + + + +---@type any +--- A static class allowing calls through the "." operator only. +my_static_instance = {} + +---@type UnitType[] +--- An global instance of this type +my_non_static_instance = {} + +---@type table +--- An global instance of this type +map = {} + diff --git a/crates/lad_backends/lua_language_server_lad_backend/tests/integration_tests.rs b/crates/lad_backends/lua_language_server_lad_backend/tests/integration_tests.rs new file mode 100644 index 0000000000..9aa6a9f5df --- /dev/null +++ b/crates/lad_backends/lua_language_server_lad_backend/tests/integration_tests.rs @@ -0,0 +1,95 @@ +#![allow(missing_docs, clippy::expect_used, clippy::unwrap_used, clippy::panic)] + +use std::{fs::DirEntry, path::PathBuf}; + +use assert_cmd::{Command, cargo_bin}; +fn add_executable_dir_to_path() { + let command_path = Command::new(cargo_bin!("lad-lls")); + let command_path = command_path.get_program(); + let command_path = PathBuf::from(command_path); + let dir = command_path + .parent() + .expect("failed to get parent directory"); + let mut paths = std::env::split_paths(&std::env::var("PATH").expect("failed to get PATH")) + .collect::>(); + paths.insert(0, dir.to_owned()); + unsafe { + std::env::set_var( + "PATH", + std::env::join_paths(paths).expect("failed to join paths"), + ); + } +} + +// use cargo manifest dir +fn get_tests_dir() -> std::path::PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_dir = std::path::PathBuf::from(manifest_dir); + manifest_dir.join("tests") +} + +fn copy_example_ladfile_to_relevant_test(tests_dir: &std::path::Path) { + let ladfile = ladfile::EXAMPLE_LADFILE; + let book_ladfile_path = tests_dir.join("example_ladfile").join("test.lad.json"); + std::fs::write(book_ladfile_path, ladfile).expect("failed to copy LAD file"); +} + +#[test] +fn main() { + add_executable_dir_to_path(); + + let tests_dir = get_tests_dir(); + if !tests_dir.exists() { + std::fs::create_dir_all(&tests_dir).expect("failed to create tests directory"); + } + copy_example_ladfile_to_relevant_test(&tests_dir); + + // for each folder in tests_dir, run the binary with + // --input /test.lad.json + // --output /generated.lua + + let tests = std::fs::read_dir(&tests_dir) + .expect("failed to read tests directory") + .collect::, _>>() + .expect("failed to collect test entries"); + + if tests.is_empty() { + panic!("No tests found in the tests directory. Please add some test folders with LAD files") + } + + for entry in tests { + if entry.file_type().expect("failed to get file type").is_dir() { + let folder_path = entry.path(); + let ladfile_path = folder_path.join("test.lad.json"); + + Command::new(cargo_bin!("lad-lls")) + .arg("--input") + .arg(&ladfile_path) + .arg("--output") + .arg(&folder_path) + .assert() + .success(); + + // then compare the output with the expected.lua file + + let expected_path = folder_path.join("expected.lua"); + let expected_str = + std::fs::read_to_string(&expected_path).expect("failed to read expected.lua file"); + let generated_str = std::fs::read_to_string(folder_path.join("bindings.lua")) + .expect("failed to read bindings.lua file"); + + if std::env::var("BLESS_MODE").is_ok() { + std::fs::write(&expected_path, &generated_str) + .expect("failed to write expected.lua file"); + panic!("BLESS_MODE is enabled, please disable it to run the tests"); + } else { + pretty_assertions::assert_eq!( + expected_str, + generated_str, + "Generated Lua file does not match expected output for {}", + folder_path.display() + ); + } + } + } +} diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs index c170687517..cab3a106e9 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs @@ -2,14 +2,14 @@ use std::path::PathBuf; -use ladfile::{ArgumentVisitor, LadTypeId}; +use ladfile::{ArgumentVisitor, LadTypeId, LadVisitable}; use crate::markdown::MarkdownBuilder; pub(crate) struct MarkdownArgumentVisitor<'a> { ladfile: &'a ladfile::LadFile, buffer: MarkdownBuilder, - linkifier: Box Option + 'static>, + linkifier: Option PathBuf + 'static>>, pub raw_type_id_replacement: Option<&'static str>, } impl<'a> MarkdownArgumentVisitor<'a> { @@ -20,20 +20,18 @@ impl<'a> MarkdownArgumentVisitor<'a> { Self { ladfile, buffer: builder, - linkifier: Box::new(|_, _| None), + linkifier: None, raw_type_id_replacement: None, } } /// Create a new instance of the visitor with a custom linkifier function - pub fn new_with_linkifier< - F: Fn(LadTypeId, &'a ladfile::LadFile) -> Option + 'static, - >( + pub fn new_with_linkifier PathBuf + 'static>( ladfile: &'a ladfile::LadFile, linkifier: F, ) -> Self { let mut without = Self::new(ladfile); - without.linkifier = Box::new(linkifier); + without.linkifier = Some(Box::new(linkifier)); without } @@ -49,6 +47,25 @@ impl<'a> MarkdownArgumentVisitor<'a> { } impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { + fn walk_lad_type_id(&mut self, type_id: &LadTypeId) { + if let Some(prim) = self.ladfile.primitive_kind(type_id) { + self.visit_lad_bms_primitive_kind(prim); + } else { + self.visit_lad_type_id(type_id); + } + } + + fn visit_lad_bms_primitive_kind(&mut self, primitive_kind: &ladfile::ReflectionPrimitiveKind) { + let prim_id = primitive_kind.to_string(); + if let Some(linkifier) = &self.linkifier { + let link_value = (linkifier)(prim_id.clone()); + let link_value = link_value.to_string_lossy().to_string().replace("\\", "/"); + self.buffer.link(prim_id, link_value); + } else { + self.buffer.text(prim_id); + } + } + fn visit_lad_type_id(&mut self, type_id: &ladfile::LadTypeId) { // Write identifier let generics = self.ladfile.get_type_generics(type_id); @@ -66,36 +83,39 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { self.visit_lad_type_id(&generic.type_id); } self.buffer.text('>'); + } else if let Some(linkifier) = &self.linkifier { + // link the type, by building a string of the type linked to first + let mut sub_visitor = MarkdownArgumentVisitor::new(self.ladfile); + sub_visitor.raw_type_id_replacement = self.raw_type_id_replacement; + type_id.accept(&mut sub_visitor); + let linked_string = sub_visitor.build(); + let link_value = (linkifier)(linked_string); + let link_value = link_value.to_string_lossy().to_string().replace("\\", "/"); + self.buffer.link(type_identifier, link_value); } else { - // link the type - let link_value = (self.linkifier)(type_id.clone(), self.ladfile); - let link_display = type_identifier; - if let Some(link_value) = link_value { - // canonicalize to linux paths - let link_value = link_value.to_string_lossy().to_string().replace("\\", "/"); - - self.buffer.link(link_display, link_value); - } else { - self.buffer.text(link_display); - } + self.buffer.text(type_identifier); } } - fn walk_option(&mut self, inner: &ladfile::LadTypeKind) { + fn walk_option(&mut self, inner: &ladfile::LadFieldOrVariableKind) { // Write Optional self.buffer.text("Optional<"); self.visit(inner); self.buffer.text(">"); } - fn walk_vec(&mut self, inner: &ladfile::LadTypeKind) { + fn walk_vec(&mut self, inner: &ladfile::LadFieldOrVariableKind) { // Write Vec self.buffer.text("Vec<"); self.visit(inner); self.buffer.text(">"); } - fn walk_hash_map(&mut self, key: &ladfile::LadTypeKind, value: &ladfile::LadTypeKind) { + fn walk_hash_map( + &mut self, + key: &ladfile::LadFieldOrVariableKind, + value: &ladfile::LadFieldOrVariableKind, + ) { // Write HashMap self.buffer.text("HashMap<"); self.visit(key); @@ -104,7 +124,7 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { self.buffer.text(">"); } - fn walk_tuple(&mut self, inner: &[ladfile::LadTypeKind]) { + fn walk_tuple(&mut self, inner: &[ladfile::LadFieldOrVariableKind]) { // Write (inner1, inner2, ...) self.buffer.text("("); for (idx, arg) in inner.iter().enumerate() { @@ -116,7 +136,7 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { self.buffer.text(")"); } - fn walk_union(&mut self, inner: &[ladfile::LadTypeKind]) { + fn walk_union(&mut self, inner: &[ladfile::LadFieldOrVariableKind]) { // Write `T1 | T2` for (idx, arg) in inner.iter().enumerate() { self.visit(arg); @@ -126,7 +146,7 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { } } - fn walk_array(&mut self, inner: &ladfile::LadTypeKind, size: usize) { + fn walk_array(&mut self, inner: &ladfile::LadFieldOrVariableKind, size: usize) { // Write [inner; size] self.buffer.text("["); self.visit(inner); @@ -138,7 +158,7 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { #[cfg(test)] mod test { - use ladfile::LadTypeKind; + use ladfile::{LadFieldOrVariableKind, ReflectionPrimitiveKind}; use super::*; @@ -152,19 +172,15 @@ mod test { fn test_linkifier_visitor_creates_links() { let ladfile = setup_ladfile(); - let mut visitor = - MarkdownArgumentVisitor::new_with_linkifier(&ladfile, |type_id, ladfile| { - Some( - PathBuf::from("root\\asd") - .join(ladfile.get_type_identifier(&type_id, None).to_string()), - ) - }); + let mut visitor = MarkdownArgumentVisitor::new_with_linkifier(&ladfile, |str| { + PathBuf::from("root\\asd").join(str) + }); - let first_type_id = ladfile.types.first().unwrap().0; - visitor.visit_lad_type_id(first_type_id); + let second_type_id = ladfile.types.iter().nth(1).unwrap().0; + visitor.visit_lad_type_id(second_type_id); assert_eq!( visitor.buffer.build(), - "StructType<[usize](root/asd/usize)>" + "GenericStructType<[Usize](root/asd/Usize)>" ); } @@ -176,13 +192,13 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); visitor.visit_lad_type_id(first_type_id); - assert_eq!(visitor.buffer.build(), "StructType"); + assert_eq!(visitor.buffer.build(), "PlainStructType"); visitor.buffer.clear(); let second_type_id = ladfile.types.iter().nth(1).unwrap().0; visitor.visit_lad_type_id(second_type_id); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "GenericStructType"); } #[test] @@ -192,8 +208,8 @@ mod test { let first_type_id = ladfile.types.first().unwrap().0; let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Ref(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "StructType"); + visitor.visit(&LadFieldOrVariableKind::Ref(first_type_id.clone())); + assert_eq!(visitor.buffer.build(), "PlainStructType"); } #[test] @@ -203,8 +219,8 @@ mod test { let first_type_id = ladfile.types.first().unwrap().0; let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Mut(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "StructType"); + visitor.visit(&LadFieldOrVariableKind::Mut(first_type_id.clone())); + assert_eq!(visitor.buffer.build(), "PlainStructType"); } #[test] @@ -214,8 +230,8 @@ mod test { let first_type_id = ladfile.types.first().unwrap().0; let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Val(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "StructType"); + visitor.visit(&LadFieldOrVariableKind::Val(first_type_id.clone())); + assert_eq!(visitor.buffer.build(), "PlainStructType"); } #[test] @@ -224,10 +240,10 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Option(Box::new(LadTypeKind::Primitive( - ladfile::LadBMSPrimitiveKind::Bool, - )))); - assert_eq!(visitor.buffer.build(), "Optional"); + visitor.visit(&LadFieldOrVariableKind::Option(Box::new( + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::Bool), + ))); + assert_eq!(visitor.buffer.build(), "Optional"); } #[test] @@ -236,10 +252,10 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Vec(Box::new(LadTypeKind::Primitive( - ladfile::LadBMSPrimitiveKind::Bool, - )))); - assert_eq!(visitor.buffer.build(), "Vec"); + visitor.visit(&LadFieldOrVariableKind::Vec(Box::new( + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::Bool), + ))); + assert_eq!(visitor.buffer.build(), "Vec"); } #[test] @@ -248,12 +264,16 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::HashMap( - Box::new(LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::Bool)), - Box::new(LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::String)), + visitor.visit(&LadFieldOrVariableKind::HashMap( + Box::new(LadFieldOrVariableKind::Primitive( + ReflectionPrimitiveKind::Bool, + )), + Box::new(LadFieldOrVariableKind::Primitive( + ReflectionPrimitiveKind::String, + )), )); - assert_eq!(visitor.buffer.build(), "HashMap"); + assert_eq!(visitor.buffer.build(), "HashMap"); } #[test] @@ -263,19 +283,21 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); let first_type_id = ladfile.types.first().unwrap().0; - visitor.visit(&LadTypeKind::HashMap( - Box::new(LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::Bool)), - Box::new(LadTypeKind::Union(vec![ - LadTypeKind::Val(first_type_id.clone()), - LadTypeKind::Union(vec![ - LadTypeKind::Val(first_type_id.clone()), - LadTypeKind::Val(first_type_id.clone()), + visitor.visit(&LadFieldOrVariableKind::HashMap( + Box::new(LadFieldOrVariableKind::Primitive( + ReflectionPrimitiveKind::Bool, + )), + Box::new(LadFieldOrVariableKind::Union(vec![ + LadFieldOrVariableKind::Val(first_type_id.clone()), + LadFieldOrVariableKind::Union(vec![ + LadFieldOrVariableKind::Val(first_type_id.clone()), + LadFieldOrVariableKind::Val(first_type_id.clone()), ]), ])), )); assert_eq!( visitor.buffer.build(), - "HashMap | StructType | StructType>" + "HashMap" ); } @@ -285,11 +307,11 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Tuple(vec![ - LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::Bool), - LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::String), + visitor.visit(&LadFieldOrVariableKind::Tuple(vec![ + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::Bool), + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::String), ])); - assert_eq!(visitor.buffer.build(), "(bool, String)"); + assert_eq!(visitor.buffer.build(), "(Bool, String)"); } #[test] @@ -298,11 +320,13 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); - visitor.visit(&LadTypeKind::Array( - Box::new(LadTypeKind::Primitive(ladfile::LadBMSPrimitiveKind::Bool)), + visitor.visit(&LadFieldOrVariableKind::Array( + Box::new(LadFieldOrVariableKind::Primitive( + ReflectionPrimitiveKind::Bool, + )), 5, )); - assert_eq!(visitor.buffer.build(), "[bool; 5]"); + assert_eq!(visitor.buffer.build(), "[Bool; 5]"); } #[test] @@ -313,7 +337,7 @@ mod test { let first_type_id = ladfile.types.first().unwrap().0; - visitor.visit(&LadTypeKind::Unknown(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "StructType"); + visitor.visit(&LadFieldOrVariableKind::Unknown(first_type_id.clone())); + assert_eq!(visitor.buffer.build(), "PlainStructType"); } } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs index c4d50e3f99..da6b74df65 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, collections::HashSet, path::PathBuf}; use ladfile::{ - ArgumentVisitor, LadArgument, LadBMSPrimitiveKind, LadFile, LadFunction, LadInstance, LadType, - LadTypeId, LadTypeKind, LadTypeLayout, + LadArgument, LadFieldOrVariableKind, LadFile, LadFunction, LadInstance, LadTypeDefinition, + LadTypeId, LadTypeLayout, LadVisitable, ReflectionPrimitiveKind, }; use mdbook::book::{Chapter, SectionNumber}; @@ -12,26 +12,37 @@ use crate::{ markdown_vec, }; -fn print_type(ladfile: &LadFile, type_: &LadTypeId) -> String { - let mut visitor = MarkdownArgumentVisitor::new(ladfile); - visitor.visit_lad_type_id(type_); - visitor.build() -} - -fn print_type_with_replacement( +fn print_type( ladfile: &LadFile, - type_: &LadTypeId, - raw_type_id_replacement: &'static str, + type_: &dyn LadVisitable, + raw_type_id_replacement: Option<&'static str>, + linkifier_base_path_and_escape_opt: Option<(PathBuf, bool)>, ) -> String { let mut visitor = - MarkdownArgumentVisitor::new(ladfile).with_raw_type_id_replacement(raw_type_id_replacement); - visitor.visit_lad_type_id(type_); - visitor.build() + if let Some((linkifier_base_path, _)) = linkifier_base_path_and_escape_opt.clone() { + MarkdownArgumentVisitor::new_with_linkifier(ladfile, move |str| { + let printed_type = linkify_filename(str); + linkifier_base_path.join(printed_type).with_extension("md") + }) + } else { + MarkdownArgumentVisitor::new(ladfile) + }; + if let Some(replacement) = raw_type_id_replacement { + visitor = visitor.with_raw_type_id_replacement(replacement); + } + + type_.accept(&mut visitor); + let mut printed = visitor.build(); + if let Some((_, escape_opt)) = linkifier_base_path_and_escape_opt + && escape_opt + { + printed = escape_markdown(printed) + } + printed } -fn build_escaped_visitor(arg_visitor: MarkdownArgumentVisitor<'_>) -> String { - arg_visitor - .build() +fn escape_markdown(markdown: String) -> String { + markdown .replace("<", "\\<") .replace(">", "\\>") .replace("|", "\\|") @@ -50,7 +61,7 @@ pub(crate) enum SectionData<'a> { InstancesSummary, TypeDetail { lad_type_id: &'a LadTypeId, - lad_type: &'a LadType, + lad_type: &'a LadTypeDefinition, }, FunctionDetail { types_directory: PathBuf, @@ -140,8 +151,12 @@ impl<'a> Section<'a> { SectionData::TypeSummary { .. } => "Types".to_owned(), SectionData::FunctionSummary { .. } => "Functions".to_owned(), SectionData::InstancesSummary { .. } => "Globals".to_owned(), - SectionData::TypeDetail { lad_type_id, .. } => print_type(self.ladfile, lad_type_id), - SectionData::FunctionDetail { function, .. } => function.identifier.to_string(), + SectionData::TypeDetail { lad_type_id, .. } => { + print_type(self.ladfile, *lad_type_id, None, None) + } + SectionData::FunctionDetail { function, .. } => { + function.identifier_with_overload().to_string() + } } } @@ -349,6 +364,8 @@ impl<'a> Section<'a> { vec![ SectionItem::Layout { layout: &lad_type.layout, + ladfile: self.ladfile, + types_directory: PathBuf::from("./"), }, SectionItem::Description { lad_type }, SectionItem::Markdown { @@ -399,9 +416,11 @@ pub enum SectionItem<'a> { }, Layout { layout: &'a LadTypeLayout, + ladfile: &'a LadFile, + types_directory: PathBuf, }, Description { - lad_type: &'a LadType, + lad_type: &'a LadTypeDefinition, }, FunctionsSummary { functions: Vec<&'a LadFunction>, @@ -442,7 +461,11 @@ impl IntoMarkdown for SectionItem<'_> { fn to_markdown(&self, builder: &mut MarkdownBuilder) { match self { SectionItem::Markdown { markdown } => (markdown)(builder), - SectionItem::Layout { layout } => { + SectionItem::Layout { + layout, + ladfile, + types_directory, + } => { // process the variants here let opaque = layout.for_each_variant( |v, _i| match v { @@ -451,7 +474,14 @@ impl IntoMarkdown for SectionItem<'_> { true, fields .iter() - .map(|f| Markdown::new_paragraph(f.type_.to_string())) + .map(|f| { + Markdown::Raw(print_type( + ladfile, + &f.type_, + None, + Some((types_directory.clone(), true)), + )) + }) .collect(), ); } @@ -464,7 +494,12 @@ impl IntoMarkdown for SectionItem<'_> { markdown_vec![ Markdown::new_paragraph(f.name.clone()).bold(), Markdown::new_paragraph(":"), - f.type_.to_string() + Markdown::Raw(print_type( + ladfile, + &f.type_, + None, + Some((types_directory.clone(), true)) + )) ] }) .collect(), @@ -502,7 +537,7 @@ impl IntoMarkdown for SectionItem<'_> { builder.table(|builder| { builder.headers(vec!["Function", "Summary"]); for function in functions.iter() { - let first_col = function.identifier.to_string(); + let first_col = function.identifier_with_overload().to_string(); // first line with content from documentation trimmed to 100 chars let second_col = function @@ -514,7 +549,11 @@ impl IntoMarkdown for SectionItem<'_> { builder.row(markdown_vec![ Markdown::Link { text: Box::new(first_col), - url: format!("./{}/{}.md", functions_path, function.identifier), + url: format!( + "./{}/{}.md", + functions_path, + function.identifier_with_overload() + ), anchor: false }, Markdown::new_paragraph(second_col.to_string().replace("\n", " ")), @@ -534,9 +573,9 @@ impl IntoMarkdown for SectionItem<'_> { builder.table(|builder| { builder.headers(vec!["Type", "Summary"]); for type_ in types.iter() { - let printed_type_for_url = print_type(ladfile, type_); + let printed_type_for_url = print_type(ladfile, *type_, None, None); let printed_type_pretty = - print_type_with_replacement(ladfile, type_, "Unknown"); + print_type(ladfile, *type_, Some("Unknown"), None); let documentation = ladfile.get_type_documentation(type_); @@ -577,16 +616,8 @@ impl IntoMarkdown for SectionItem<'_> { .map(|(k, v)| { let name = k.to_string(); let types_directory = types_directory.clone(); - let mut arg_visitor = MarkdownArgumentVisitor::new_with_linkifier( - ladfile, - move |lad_type_id, ladfile| { - let printed_type = - linkify_filename(print_type(ladfile, &lad_type_id)); - Some(types_directory.join(printed_type).with_extension("md")) - }, - ); - arg_visitor.visit(&v.type_kind); - let escaped = build_escaped_visitor(arg_visitor); + let escaped = + print_type(ladfile, &v.type_kind, None, Some((types_directory, true))); (v.is_static, name, escaped) }) .collect::>(); @@ -625,7 +656,9 @@ impl IntoMarkdown for SectionItem<'_> { if function.arguments.iter().any(|a| { matches!( a.kind, - LadTypeKind::Primitive(LadBMSPrimitiveKind::FunctionCallContext) + LadFieldOrVariableKind::Primitive( + ReflectionPrimitiveKind::FunctionCallContext + ) ) }) { builder.raw( @@ -689,19 +722,13 @@ fn build_lad_function_argument_row( // we exclude function call context as it's not something scripts pass down if matches!( arg.kind, - LadTypeKind::Primitive(LadBMSPrimitiveKind::FunctionCallContext) + LadFieldOrVariableKind::Primitive(ReflectionPrimitiveKind::FunctionCallContext) ) { return; } let types_directory = types_directory.to_owned(); - let mut arg_visitor = - MarkdownArgumentVisitor::new_with_linkifier(ladfile, move |lad_type_id, ladfile| { - let printed_type = linkify_filename(print_type(ladfile, &lad_type_id)); - Some(types_directory.join(printed_type).with_extension("md")) - }); - arg_visitor.visit(&arg.kind); - let markdown = build_escaped_visitor(arg_visitor); + let escaped = print_type(ladfile, &arg.kind, None, Some((types_directory, false))); let arg_name = arg .name @@ -711,7 +738,7 @@ fn build_lad_function_argument_row( builder.row(markdown_vec![ Markdown::new_paragraph(arg_name).bold(), - Markdown::Raw(markdown), + Markdown::Raw(escaped), Markdown::Raw( arg.documentation .as_deref() diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/book_integration_tests.rs b/crates/lad_backends/mdbook_lad_preprocessor/tests/book_integration_tests.rs index 5ec59ac757..7525132160 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/book_integration_tests.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/book_integration_tests.rs @@ -2,10 +2,10 @@ use std::path::PathBuf; -use assert_cmd::Command; +use assert_cmd::{Command, cargo_bin}; fn add_executable_dir_to_path() { - let command_path = Command::cargo_bin("mdbook-lad-preprocessor") - .expect("failed to find mdbook-lad-preprocessor binary"); + let command_path = Command::new(cargo_bin!("mdbook-lad-preprocessor")); + let command_path = command_path.get_program(); let command_path = PathBuf::from(command_path); let dir = command_path @@ -87,12 +87,23 @@ fn test_on_example_ladfile() { book_files.contains(&book_file), "File not found: {book_file:?}" ); + + let book_content = std::fs::read_to_string(&book_file).expect("failed to read file"); + + if std::env::var("BLESS_MODE").is_ok() { + std::fs::write(&expected_file, book_content.clone()).unwrap(); + } + let expected_content = std::fs::read_to_string(&expected_file).expect("failed to read file"); - let book_content = std::fs::read_to_string(&book_file).expect("failed to read file"); + pretty_assertions::assert_eq!( normalize_file(expected_content), normalize_file(book_content) ); } + + if std::env::var("BLESS_MODE").is_ok() { + panic!("BLESS_MODE is enabled, re-run the test with this off") + } } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md index b0fa92768f..6ddc551b6a 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md @@ -6,11 +6,11 @@ | Name | Type | Documentation | | --- | --- | --- | -| **arg1** | [usize](../types/usize.md) | No Documentation 🚧 | +| **arg1** | [Usize](../types/usize.md) | No Documentation 🚧 | #### Returns | Name | Type | Documentation | | --- | --- | --- | -| **arg0** | [usize](../types/usize.md) | No Documentation 🚧 | +| **arg0** | [Usize](../types/usize.md) | No Documentation 🚧 | diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md index 0fbafcca88..c4350a74d5 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md @@ -19,5 +19,5 @@ Static type references, existing for the purpose of typed static function calls\ | Instance | Type | | --- | --- | -| `my_static_instance` | StructType\<[usize](./types/usize.md)\> | +| `my_static_instance` | GenericStructType\<[Usize](./types/usize.md)\> | diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md index d77185356f..ac5154c3dc 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md @@ -6,7 +6,33 @@ All registered reflect\-able types which can be constructed and directly manipul | Type | Summary | | --- | --- | -| [`StructType`](././types/structtypeusize.md) | I am a struct | +| [`PlainStructType`](././types/plainstructtype.md) | I am a simple plain struct type | +| [`GenericStructType`](././types/genericstructtypeusize.md) | I am a struct | +| [`Bool`](././types/bool.md) | A boolean value | +| [`Char`](././types/char.md) | An 8\-bit character | +| [`DynamicFunction`](././types/dynamicfunction.md) | A callable dynamic function | +| [`DynamicFunctionMut`](././types/dynamicfunctionmut.md) | A stateful and callable dynamic function | +| [`F32`](././types/f32.md) | A 32\-bit floating point number | +| [`F64`](././types/f64.md) | A 64\-bit floating point number | +| [`FunctionCallContext`](././types/functioncallcontext.md) | Function call context, if accepted by a function, means the function can access the world in arbitra\.\.\. | +| [`I128`](././types/i128.md) | A signed 128\-bit integer | +| [`I16`](././types/i16.md) | A signed 16\-bit integer | +| [`I32`](././types/i32.md) | A signed 32\-bit integer | +| [`I64`](././types/i64.md) | A signed 64\-bit integer | +| [`I8`](././types/i8.md) | A signed 8\-bit integer | +| [`Isize`](././types/isize.md) | A signed pointer\-sized integer | +| [`OsString`](././types/osstring.md) | A heap allocated OS string | +| [`PathBuf`](././types/pathbuf.md) | A heap allocated file path | +| [`ReflectReference`](././types/reflectreference.md) | A reference to a reflectable type | +| [`ScriptValue`](././types/scriptvalue.md) | A value representing the union of all representable values | +| [`Str`](././types/str.md) | A string slice | +| [`String`](././types/string.md) | A heap allocated string | +| [`U128`](././types/u128.md) | An unsigned 128\-bit integer | +| [`U16`](././types/u16.md) | An unsigned 16\-bit integer | +| [`U32`](././types/u32.md) | An unsigned 32\-bit integer | +| [`U64`](././types/u64.md) | An unsigned 64\-bit integer | +| [`U8`](././types/u8.md) | An unsigned 8\-bit integer | +| [`Usize`](././types/usize.md) | An unsigned pointer\-sized integer | | [`EnumType`](././types/enumtype.md) | No Documentation 🚧 | | [`TupleStructType`](././types/tuplestructtype.md) | I am a tuple test type | | [`UnitType`](././types/unittype.md) | I am a unit test type | diff --git a/crates/ladfile/Cargo.toml b/crates/ladfile/Cargo.toml index 88c3b34075..ec73dd0764 100644 --- a/crates/ladfile/Cargo.toml +++ b/crates/ladfile/Cargo.toml @@ -15,7 +15,7 @@ categories.workspace = true serde = { workspace = true, features = ["derive", "std"] } serde_json = { workspace = true, features = ["std"] } indexmap = { workspace = true, features = ["serde"] } - +bevy_mod_scripting_bindings_domain = { workspace = true } [features] default = ["visitor", "testfile"] diff --git a/crates/ladfile/src/lib.rs b/crates/ladfile/src/lib.rs index 54761ec339..e2e3d3f1f0 100644 --- a/crates/ladfile/src/lib.rs +++ b/crates/ladfile/src/lib.rs @@ -4,9 +4,15 @@ //! - Centralization, we want to centralize as much of the "documentation" logic in the building of this format. For example, instead of letting each backend parse argument docstrings from the function docstring, we can do this here, and let the backends concentrate on pure generation. //! - Rust centric, the format describes bindings from the Rust side, so we generate rust centric declarations. These can then freely be converted into whatever representaion necessary. -use std::borrow::Cow; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, +}; +pub use bevy_mod_scripting_bindings_domain::ReflectionPrimitiveKind; // re-export the thing we use use indexmap::IndexMap; +mod plugin; +pub use plugin::*; /// The current version of the LAD_VERSION format supported by this library. /// Earlier versions are not guaranteed to be supported. @@ -26,14 +32,11 @@ pub struct LadFile { pub globals: IndexMap, LadInstance>, /// The types defined in the LAD file. - pub types: IndexMap, + pub types: IndexMap, /// The functions defined in the LAD file. pub functions: IndexMap, - /// A mapping from type ids to primitive types - pub primitives: IndexMap, - /// A description of the LAD file and its contents in markdown #[serde(skip_serializing_if = "Option::is_none", default)] pub description: Option, @@ -47,7 +50,6 @@ impl LadFile { globals: IndexMap::new(), types: IndexMap::new(), functions: IndexMap::new(), - primitives: IndexMap::new(), description: None, } } @@ -58,10 +60,6 @@ impl LadFile { type_id: &LadTypeId, raw_type_id_replacement: Option<&'static str>, ) -> Cow<'static, str> { - if let Some(primitive) = self.primitives.get(type_id) { - return primitive.kind.lad_type_id().to_string().into(); - } - self.types .get(type_id) .map(|t| t.identifier.clone().into()) @@ -74,6 +72,13 @@ impl LadFile { }) } + /// Retrieves true if the type id corresponds to a primitive type. + pub fn primitive_kind(&self, type_id: &LadTypeId) -> Option<&ReflectionPrimitiveKind> { + self.types + .get(type_id) + .and_then(|t| t.metadata.mapped_to_primitive_kind.as_ref()) + } + /// Retrieves the generics of a type id if it is a generic type. pub fn get_type_generics(&self, type_id: &LadTypeId) -> Option<&[LadGeneric]> { self.types @@ -86,15 +91,60 @@ impl LadFile { self.types .get(type_id) .and_then(|t| t.documentation.as_deref()) - // try primitives - .or_else(|| { - self.primitives - .get(type_id) - .map(|p| p.documentation.as_ref()) - }) + } + + /// Retrieves the metadata section for the given lad type id. + /// Primitives don't contain metadata + pub fn get_type_metadata(&self, type_id: &LadTypeId) -> Option<&LadTypeMetadata> { + self.types.get(type_id).map(|t| &t.metadata) + } + + /// Retrieves all unique types, then groups them by their generics arity, + /// this grouping represents types as expected to be seen in rust source code. + /// + /// For example `Vec` and `Vec` will be grouped together as `Vec` with arity 1. + pub fn polymorphizied_types( + &self, + exclude_primitives: bool, + ) -> IndexMap> { + let mut types_by_identifier_and_arity: IndexMap> = + IndexMap::>::new(); + for type_id in self.types.keys() { + let arity = self.get_type_arity(type_id); + let identifier = self.get_type_identifier(type_id, None); + if exclude_primitives && self.primitive_kind(type_id).is_some() { + continue; + } + types_by_identifier_and_arity + .entry(PolymorphicTypeKey { identifier, arity }) + .or_default() + .insert(type_id); + } + + types_by_identifier_and_arity + } + + /// Returns the arity of a type, which is the number of generic parameters it has. + /// Types without generics will return 0, meaning they can be identified uniquely by their identifier. + pub fn get_type_arity(&self, type_id: &LadTypeId) -> usize { + self.types + .get(type_id) + .map(|t| t.generics.len()) + .unwrap_or(0) // primitives have no generics currently } } +/// A key for polymorphic types, used to group types by their identifier and arity. +/// +/// Each key would correspond to a unique rust type, such as `Vec` or `HashMap`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PolymorphicTypeKey { + /// The type identifier + pub identifier: Cow<'static, str>, + /// The arity of the type + pub arity: usize, +} + impl Default for LadFile { fn default() -> Self { Self::new() @@ -105,7 +155,7 @@ impl Default for LadFile { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct LadInstance { /// The kind of the instance - pub type_kind: LadTypeKind, + pub type_kind: LadFieldOrVariableKind, /// whether the instance is static or not /// @@ -141,6 +191,8 @@ pub struct LadFunction { pub namespace: LadFunctionNamespace, /// The identifier or name of the function. pub identifier: Cow<'static, str>, + /// If `Some`, signifies the function is an overload of another function + pub overload_index: Option, /// The argument information for the function. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub arguments: Vec, @@ -149,6 +201,35 @@ pub struct LadFunction { /// The documentation describing the function. #[serde(skip_serializing_if = "Option::is_none", default)] pub documentation: Option>, + /// Function metadata + pub metadata: LadFunctionMetadata, +} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +/// Additional data relevant to functions +pub struct LadFunctionMetadata { + /// True if the function represents a supported binary or unary operation + pub is_operator: bool, + /// Extra metadata to be populated by external plugins + #[serde(skip_serializing_if = "HashMap::is_empty", default)] + pub misc: HashMap, +} + +impl LadFunction { + /// Checks if the function is an overload, and if so parses the overload number and the true name. + pub fn as_overload(&self) -> Option<(Cow<'static, str>, usize)> { + self.overload_index + .map(|index| (self.identifier.clone(), index)) + } + + /// Qualifies the identifier of this function with its overload number. + /// Use this instead of the identifier if you require a unique name per function. + pub fn identifier_with_overload(&self) -> Cow<'static, str> { + if let Some(index) = self.overload_index { + format!("{}-{index}", self.identifier).into() + } else { + self.identifier.clone() + } + } } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -165,7 +246,7 @@ pub enum LadFunctionNamespace { /// An argument definition used in a LAD file. pub struct LadArgument { /// The kind and type of argument - pub kind: LadTypeKind, + pub kind: LadFieldOrVariableKind, /// The provided documentation for this argument. Normally derived from the function docstring. #[serde(skip_serializing_if = "Option::is_none", default)] @@ -177,8 +258,8 @@ pub struct LadArgument { } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -/// The kind of type in a LAD file. -/// There is a distinction between the "core" identity of a type +/// The kind of field or variable/parameter in a LAD file. +/// There is a distinction between the "core" definition/identity of a type /// and how it's used in various contexts. /// /// for example: @@ -187,7 +268,7 @@ pub struct LadArgument { /// /// In generating documents, it's convenient to distinguish a few core "containers" to provide useful information. #[serde(rename_all = "camelCase")] -pub enum LadTypeKind { +pub enum LadFieldOrVariableKind { /// a `Ref` wrapped argument Ref(LadTypeId), /// a `Mut` wrapped argument @@ -195,27 +276,46 @@ pub enum LadTypeKind { /// a `Val` wrapped argument Val(LadTypeId), /// an `Option` wrapped argument - Option(Box), + Option(Box), /// a `Vec` - Vec(Box), + Vec(Box), /// a `HashMap` - HashMap(Box, Box), + HashMap(Box, Box), + /// a `HashSet` + HashSet(Box), /// A `InteropResult` - InteropResult(Box), + InteropResult(Box), /// A tuple of arguments - Tuple(Vec), + Tuple(Vec), /// An array - Array(Box, usize), + Array(Box, usize), /// A primitive type, implementing `IntoScript` and `FromScript` natively in BMS. - Primitive(LadBMSPrimitiveKind), + Primitive(ReflectionPrimitiveKind), /// A union of two or more types - Union(Vec), + Union(Vec), /// An arbitrary type which is either unsupported, doesn't contain type information, or is generally unknown. /// /// This will be the variant used for external primitives as well. Unknown(LadTypeId), } +/// Utility for dispatching visitors on dyn traits +pub trait LadVisitable { + /// Runs the visitor on the given node + fn accept(&self, visitor: &mut dyn ArgumentVisitor); +} + +impl LadVisitable for LadTypeId { + fn accept(&self, visitor: &mut dyn ArgumentVisitor) { + visitor.visit_lad_type_id(self); + } +} + +impl LadVisitable for LadFieldOrVariableKind { + fn accept(&self, visitor: &mut dyn ArgumentVisitor) { + visitor.visit(self); + } +} /// A visitor pattern for running arbitrary logic on the hierarchy of arguments. /// /// Use cases are mostly to do with printing the arguments in a human readable format. @@ -223,10 +323,20 @@ pub enum LadTypeKind { #[cfg(feature = "visitor")] pub trait ArgumentVisitor { /// perform an action on a `LadTypeId`, by default noop - fn visit_lad_type_id(&mut self, type_id: &LadTypeId) {} + fn visit_lad_type_id(&mut self, type_id: &LadTypeId); + /// perform an action on a `LadBMSPrimitiveKind`, by default visits the type id of the primitive kind - fn visit_lad_bms_primitive_kind(&mut self, primitive_kind: &LadBMSPrimitiveKind) { - self.visit_lad_type_id(&primitive_kind.lad_type_id()); + fn visit_lad_bms_primitive_kind(&mut self, primitive_kind: &ReflectionPrimitiveKind); + + /// perform an action on a `Unknown`, by default visits the type id of the unknown type + fn visit_unknown(&mut self, type_id: &LadTypeId) { + self.visit_lad_type_id(type_id); + } + + /// walks the lad type_id structure, by default simply visits type_id's + /// Can be used to dispatch to primitives instead of the type maps to these + fn walk_lad_type_id(&mut self, type_id: &LadTypeId) { + self.visit_lad_type_id(type_id); } /// traverse a `Ref` wrapped argument, by default calls `visit` on the inner argument @@ -245,45 +355,50 @@ pub trait ArgumentVisitor { } /// traverse an `Option` wrapped argument, by default calls `visit` on the inner argument - fn walk_option(&mut self, inner: &LadTypeKind) { + fn walk_option(&mut self, inner: &LadFieldOrVariableKind) { self.visit(inner); } /// traverse a `Vec` wrapped argument, by default calls `visit` on the inner argument - fn walk_vec(&mut self, inner: &LadTypeKind) { + fn walk_vec(&mut self, inner: &LadFieldOrVariableKind) { self.visit(inner); } /// traverse a `HashMap` wrapped argument, by default calls `visit` on the key and value - fn walk_hash_map(&mut self, key: &LadTypeKind, value: &LadTypeKind) { + fn walk_hash_map(&mut self, key: &LadFieldOrVariableKind, value: &LadFieldOrVariableKind) { self.visit(key); self.visit(value); } + /// traverse a `HashMap` wrapped argument, by default calls `visit` on the key and value + fn walk_hash_set(&mut self, key: &LadFieldOrVariableKind) { + self.visit(key); + } + /// traverse an `InteropResult` wrapped argument, by default calls `visit` on the inner argument - fn walk_interop_result(&mut self, inner: &LadTypeKind) { + fn walk_interop_result(&mut self, inner: &LadFieldOrVariableKind) { self.visit(inner); } /// traverse a tuple of arguments, by default calls `visit` on each argument - fn walk_tuple(&mut self, inner: &[LadTypeKind]) { + fn walk_tuple(&mut self, inner: &[LadFieldOrVariableKind]) { for arg in inner { self.visit(arg); } } /// traverse an array of arguments, by default calls `visit` on the inner argument - fn walk_array(&mut self, inner: &LadTypeKind, size: usize) { + fn walk_array(&mut self, inner: &LadFieldOrVariableKind, size: usize) { self.visit(inner); } /// traverse a primitive argument, by default calls `visit` on the primitive kind - fn walk_primitive(&mut self, primitive_kind: &LadBMSPrimitiveKind) { + fn walk_primitive(&mut self, primitive_kind: &ReflectionPrimitiveKind) { self.visit_lad_bms_primitive_kind(primitive_kind); } /// traverse a union of arguments, by default calls `visit` on each argument - fn walk_union(&mut self, inner: &[LadTypeKind]) { + fn walk_union(&mut self, inner: &[LadFieldOrVariableKind]) { for arg in inner { self.visit(arg); } @@ -291,7 +406,7 @@ pub trait ArgumentVisitor { /// traverse an unknown argument, by default calls `visit` on the type id fn walk_unknown(&mut self, type_id: &LadTypeId) { - self.visit_lad_type_id(type_id); + self.visit_unknown(type_id); } /// Visit an argument kind, by default calls the appropriate walk method on each enum variant. @@ -299,105 +414,79 @@ pub trait ArgumentVisitor { /// Each walk variant will walk over nested kinds, and visit the leaf types. /// /// If you want to do something with the parent types, you WILL have to override each individual walk method. - fn visit(&mut self, kind: &LadTypeKind) { + fn visit(&mut self, kind: &LadFieldOrVariableKind) { match kind { - LadTypeKind::Ref(type_id) => self.walk_ref(type_id), - LadTypeKind::Mut(type_id) => self.walk_mut(type_id), - LadTypeKind::Val(type_id) => self.walk_val(type_id), - LadTypeKind::Option(inner) => self.walk_option(inner), - LadTypeKind::Vec(inner) => self.walk_vec(inner), - LadTypeKind::HashMap(key, value) => self.walk_hash_map(key, value), - LadTypeKind::InteropResult(inner) => self.walk_interop_result(inner), - LadTypeKind::Tuple(inner) => self.walk_tuple(inner), - LadTypeKind::Array(inner, size) => self.walk_array(inner, *size), - LadTypeKind::Primitive(primitive_kind) => self.walk_primitive(primitive_kind), - LadTypeKind::Union(inner) => self.walk_union(inner), - LadTypeKind::Unknown(type_id) => self.walk_unknown(type_id), + LadFieldOrVariableKind::Ref(type_id) => self.walk_ref(type_id), + LadFieldOrVariableKind::Mut(type_id) => self.walk_mut(type_id), + LadFieldOrVariableKind::Val(type_id) => self.walk_val(type_id), + LadFieldOrVariableKind::Option(inner) => self.walk_option(inner), + LadFieldOrVariableKind::Vec(inner) => self.walk_vec(inner), + LadFieldOrVariableKind::HashMap(key, value) => self.walk_hash_map(key, value), + LadFieldOrVariableKind::HashSet(key) => self.walk_hash_set(key), + LadFieldOrVariableKind::InteropResult(inner) => self.walk_interop_result(inner), + LadFieldOrVariableKind::Tuple(inner) => self.walk_tuple(inner), + LadFieldOrVariableKind::Array(inner, size) => self.walk_array(inner, *size), + LadFieldOrVariableKind::Primitive(primitive_kind) => { + self.walk_primitive(primitive_kind) + } + LadFieldOrVariableKind::Union(inner) => self.walk_union(inner), + LadFieldOrVariableKind::Unknown(type_id) => self.walk_unknown(type_id), } } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -/// A BMS primitive definition -pub struct LadBMSPrimitiveType { - /// The kind of primitive - pub kind: LadBMSPrimitiveKind, - /// The documentation describing the primitive - pub documentation: Cow<'static, str>, -} - -/// A primitive type kind in the LAD file format. -/// -/// The docstrings on variants corresponding to Reflect types, are used to generate documentation for these primitives. -#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(missing_docs)] -pub enum LadBMSPrimitiveKind { - Bool, - Isize, - I8, - I16, - I32, - I64, - I128, - Usize, - U8, - U16, - U32, - U64, - U128, - F32, - F64, - Char, - Str, - String, - OsString, - PathBuf, - FunctionCallContext, - DynamicFunction, - DynamicFunctionMut, - ReflectReference, -} +// #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +// /// A BMS primitive definition +// pub struct LadBMSPrimitiveType { +// /// The kind of primitive +// pub kind: ReflectionPrimitiveKind, +// /// The documentation describing the primitive +// pub documentation: Cow<'static, str>, +// } -impl LadBMSPrimitiveKind { - /// Get the corresponding type id for a primitive kind. - pub fn lad_type_id(self) -> LadTypeId { - match self { - LadBMSPrimitiveKind::Bool => LadTypeId::new_string_id("bool".into()), - LadBMSPrimitiveKind::Isize => LadTypeId::new_string_id("isize".into()), - LadBMSPrimitiveKind::I8 => LadTypeId::new_string_id("i8".into()), - LadBMSPrimitiveKind::I16 => LadTypeId::new_string_id("i16".into()), - LadBMSPrimitiveKind::I32 => LadTypeId::new_string_id("i32".into()), - LadBMSPrimitiveKind::I64 => LadTypeId::new_string_id("i64".into()), - LadBMSPrimitiveKind::I128 => LadTypeId::new_string_id("i128".into()), - LadBMSPrimitiveKind::Usize => LadTypeId::new_string_id("usize".into()), - LadBMSPrimitiveKind::U8 => LadTypeId::new_string_id("u8".into()), - LadBMSPrimitiveKind::U16 => LadTypeId::new_string_id("u16".into()), - LadBMSPrimitiveKind::U32 => LadTypeId::new_string_id("u32".into()), - LadBMSPrimitiveKind::U64 => LadTypeId::new_string_id("u64".into()), - LadBMSPrimitiveKind::U128 => LadTypeId::new_string_id("u128".into()), - LadBMSPrimitiveKind::F32 => LadTypeId::new_string_id("f32".into()), - LadBMSPrimitiveKind::F64 => LadTypeId::new_string_id("f64".into()), - LadBMSPrimitiveKind::Char => LadTypeId::new_string_id("char".into()), - LadBMSPrimitiveKind::Str => LadTypeId::new_string_id("str".into()), - LadBMSPrimitiveKind::String => LadTypeId::new_string_id("String".into()), - LadBMSPrimitiveKind::OsString => LadTypeId::new_string_id("OsString".into()), - LadBMSPrimitiveKind::PathBuf => LadTypeId::new_string_id("PathBuf".into()), - LadBMSPrimitiveKind::FunctionCallContext => { - LadTypeId::new_string_id("FunctionCallContext".into()) - } - LadBMSPrimitiveKind::DynamicFunction => { - LadTypeId::new_string_id("DynamicFunction".into()) - } - LadBMSPrimitiveKind::DynamicFunctionMut => { - LadTypeId::new_string_id("DynamicFunctionMut".into()) - } - LadBMSPrimitiveKind::ReflectReference => { - LadTypeId::new_string_id("ReflectReference".into()) - } - } - } -} +// impl LadBMSPrimitiveType { +// /// Get the corresponding type id for a primitive kind. +// pub fn lad_type_id(kind: ReflectionPrimitiveKind) -> LadTypeId { +// match kind { +// ReflectionPrimitiveKind::Bool => LadTypeId::new_string_id("bool".into()), +// ReflectionPrimitiveKind::Isize => LadTypeId::new_string_id("isize".into()), +// ReflectionPrimitiveKind::I8 => LadTypeId::new_string_id("i8".into()), +// ReflectionPrimitiveKind::I16 => LadTypeId::new_string_id("i16".into()), +// ReflectionPrimitiveKind::I32 => LadTypeId::new_string_id("i32".into()), +// ReflectionPrimitiveKind::I64 => LadTypeId::new_string_id("i64".into()), +// ReflectionPrimitiveKind::I128 => LadTypeId::new_string_id("i128".into()), +// ReflectionPrimitiveKind::Usize => LadTypeId::new_string_id("usize".into()), +// ReflectionPrimitiveKind::U8 => LadTypeId::new_string_id("u8".into()), +// ReflectionPrimitiveKind::U16 => LadTypeId::new_string_id("u16".into()), +// ReflectionPrimitiveKind::U32 => LadTypeId::new_string_id("u32".into()), +// ReflectionPrimitiveKind::U64 => LadTypeId::new_string_id("u64".into()), +// ReflectionPrimitiveKind::U128 => LadTypeId::new_string_id("u128".into()), +// ReflectionPrimitiveKind::F32 => LadTypeId::new_string_id("f32".into()), +// ReflectionPrimitiveKind::F64 => LadTypeId::new_string_id("f64".into()), +// ReflectionPrimitiveKind::Char => LadTypeId::new_string_id("char".into()), +// ReflectionPrimitiveKind::Str => LadTypeId::new_string_id("str".into()), +// ReflectionPrimitiveKind::String => LadTypeId::new_string_id("String".into()), +// ReflectionPrimitiveKind::OsString => LadTypeId::new_string_id("OsString".into()), +// ReflectionPrimitiveKind::PathBuf => LadTypeId::new_string_id("PathBuf".into()), +// ReflectionPrimitiveKind::FunctionCallContext => { +// LadTypeId::new_string_id("FunctionCallContext".into()) +// } +// ReflectionPrimitiveKind::DynamicFunction => { +// LadTypeId::new_string_id("DynamicFunction".into()) +// } +// ReflectionPrimitiveKind::DynamicFunctionMut => { +// LadTypeId::new_string_id("DynamicFunctionMut".into()) +// } +// ReflectionPrimitiveKind::ReflectReference => { +// LadTypeId::new_string_id("ReflectReference".into()) +// } +// ReflectionPrimitiveKind::ScriptValue => LadTypeId::new_string_id("ScriptValue".into()), +// ReflectionPrimitiveKind::External(external) => { +// LadTypeId::new_string_id(external.into()) +// } +// } +// } +// } #[derive( Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, @@ -423,7 +512,7 @@ impl LadTypeId { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] /// A type definition used in a LAD file. -pub struct LadType { +pub struct LadTypeDefinition { /// The identifier or name of the type. pub identifier: String, @@ -464,6 +553,29 @@ pub struct LadType { /// Backends can use this value to determine the order in which types are displayed. #[serde(default = "default_importance")] pub insignificance: usize, + + /// Additional metadata about the type. + pub metadata: LadTypeMetadata, +} + +/// Metadata either calculated from the type registry or added by plugins +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct LadTypeMetadata { + /// True if the type is a component according to the type registry + pub is_component: bool, + /// Returns true if the type is a component according to the type registry + pub is_resource: bool, + + /// True if the type actually implements reflect, some types can be + /// added as namespaces without actually implementing the Reflect trait + pub is_reflect: bool, + + /// Set to a primitive kind if this type is mapped to a primitive + pub mapped_to_primitive_kind: Option, + + /// Extra metadata sections that plugins can use to serialize other information + #[serde(skip_serializing_if = "HashMap::is_empty", default)] + pub misc: HashMap, } /// The default importance value for a type. @@ -556,7 +668,7 @@ pub enum LadVariant { pub struct LadField { /// The type of the field. #[serde(rename = "type")] - pub type_: LadTypeId, + pub type_: LadFieldOrVariableKind, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -566,7 +678,7 @@ pub struct LadNamedField { pub name: String, #[serde(rename = "type")] /// The type of the field. - pub type_: LadTypeId, + pub type_: LadFieldOrVariableKind, } /// A generic type definition used in a LAD file. @@ -578,12 +690,12 @@ pub struct LadGeneric { pub name: String, } -/// Parses a toml string into a LAD file. +/// Parses a json string into a LAD file. pub fn parse_lad_file(toml: &str) -> Result { serde_json::from_str(toml) } -/// Serializes a LAD file into a toml file. +/// Serializes a LAD file into a json file. pub fn serialize_lad_file(lad_file: &LadFile, pretty: bool) -> Result { if pretty { serde_json::to_string_pretty(lad_file) diff --git a/crates/ladfile/src/plugin.rs b/crates/ladfile/src/plugin.rs new file mode 100644 index 0000000000..e1e4eee287 --- /dev/null +++ b/crates/ladfile/src/plugin.rs @@ -0,0 +1,11 @@ +use std::{any::Any, error::Error, path::Path}; + +use crate::LadFile; + +/// A trait implemented by ladfile post-processors. +pub trait LadFilePlugin: Any { + /// A user friendly name of the pkugin + fn name(&self) -> &'static str; + /// Apply the ladfile plugin, given the specified output directory + fn run(&self, ladfile: &LadFile, output_dir: &Path) -> Result<(), Box>; +} diff --git a/crates/ladfile/test_assets/test.lad.json b/crates/ladfile/test_assets/test.lad.json index 10765db1e4..4a5643205e 100644 --- a/crates/ladfile/test_assets/test.lad.json +++ b/crates/ladfile/test_assets/test.lad.json @@ -3,7 +3,7 @@ "globals": { "my_static_instance": { "type_kind": { - "val": "ladfile_builder::test::StructType" + "val": "ladfile_builder::test::GenericStructType" }, "is_static": true }, @@ -37,36 +37,425 @@ } }, "types": { - "ladfile_builder::test::StructType": { - "identifier": "StructType", + "ladfile_builder::test::PlainStructType": { + "identifier": "PlainStructType", "crate": "ladfile_builder", - "path": "ladfile_builder::test::StructType", + "path": "ladfile_builder::test::PlainStructType", + "documentation": " I am a simple plain struct type", + "associated_functions": [ + "ladfile_builder::test::PlainStructType::plain_struct_function" + ], + "layout": { + "kind": "Struct", + "name": "PlainStructType", + "fields": [ + { + "name": "int_field", + "type": { + "primitive": "usize" + } + } + ] + }, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": true, + "mapped_to_primitive_kind": null + } + }, + "ladfile_builder::test::GenericStructType": { + "identifier": "GenericStructType", + "crate": "ladfile_builder", + "path": "ladfile_builder::test::GenericStructType", "generics": [ { - "type_id": "usize", + "type_id": "Usize", "name": "T" } ], "documentation": " I am a struct", "associated_functions": [ - "ladfile_builder::test::StructType::hello_world" + "ladfile_builder::test::GenericStructType::hello_world" ], "layout": { "kind": "Struct", - "name": "StructType", + "name": "GenericStructType", "fields": [ { "name": "field", - "type": "usize" + "type": { + "primitive": "usize" + } }, { "name": "field2", - "type": "usize" + "type": { + "primitive": "usize" + } } ] }, "generated": false, - "insignificance": 1000 + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": true, + "mapped_to_primitive_kind": null + } + }, + "Bool": { + "identifier": "Bool", + "path": "Bool", + "documentation": "A boolean value", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "bool" + } + }, + "Char": { + "identifier": "Char", + "path": "Char", + "documentation": "An 8-bit character", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "char" + } + }, + "DynamicFunction": { + "identifier": "DynamicFunction", + "path": "DynamicFunction", + "documentation": "A callable dynamic function", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "dynamicFunction" + } + }, + "DynamicFunctionMut": { + "identifier": "DynamicFunctionMut", + "path": "DynamicFunctionMut", + "documentation": "A stateful and callable dynamic function", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "dynamicFunctionMut" + } + }, + "F32": { + "identifier": "F32", + "path": "F32", + "documentation": "A 32-bit floating point number", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "f32" + } + }, + "F64": { + "identifier": "F64", + "path": "F64", + "documentation": "A 64-bit floating point number", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "f64" + } + }, + "FunctionCallContext": { + "identifier": "FunctionCallContext", + "path": "FunctionCallContext", + "documentation": "Function call context, if accepted by a function, means the function can access the world in arbitrary ways.", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "functionCallContext" + } + }, + "I128": { + "identifier": "I128", + "path": "I128", + "documentation": "A signed 128-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "i128" + } + }, + "I16": { + "identifier": "I16", + "path": "I16", + "documentation": "A signed 16-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "i16" + } + }, + "I32": { + "identifier": "I32", + "path": "I32", + "documentation": "A signed 32-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "i32" + } + }, + "I64": { + "identifier": "I64", + "path": "I64", + "documentation": "A signed 64-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "i64" + } + }, + "I8": { + "identifier": "I8", + "path": "I8", + "documentation": "A signed 8-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "i8" + } + }, + "Isize": { + "identifier": "Isize", + "path": "Isize", + "documentation": "A signed pointer-sized integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "isize" + } + }, + "OsString": { + "identifier": "OsString", + "path": "OsString", + "documentation": "A heap allocated OS string", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "osString" + } + }, + "PathBuf": { + "identifier": "PathBuf", + "path": "PathBuf", + "documentation": "A heap allocated file path", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "pathBuf" + } + }, + "ReflectReference": { + "identifier": "ReflectReference", + "path": "ReflectReference", + "documentation": "A reference to a reflectable type", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "reflectReference" + } + }, + "ScriptValue": { + "identifier": "ScriptValue", + "path": "ScriptValue", + "documentation": "A value representing the union of all representable values", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "scriptValue" + } + }, + "Str": { + "identifier": "Str", + "path": "Str", + "documentation": "A string slice", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "str" + } + }, + "String": { + "identifier": "String", + "path": "String", + "documentation": "A heap allocated string", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "string" + } + }, + "U128": { + "identifier": "U128", + "path": "U128", + "documentation": "An unsigned 128-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "u128" + } + }, + "U16": { + "identifier": "U16", + "path": "U16", + "documentation": "An unsigned 16-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "u16" + } + }, + "U32": { + "identifier": "U32", + "path": "U32", + "documentation": "An unsigned 32-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "u32" + } + }, + "U64": { + "identifier": "U64", + "path": "U64", + "documentation": "An unsigned 64-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "u64" + } + }, + "U8": { + "identifier": "U8", + "path": "U8", + "documentation": "An unsigned 8-bit integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "u8" + } + }, + "Usize": { + "identifier": "Usize", + "path": "Usize", + "documentation": "An unsigned pointer-sized integer", + "layout": null, + "generated": false, + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": false, + "mapped_to_primitive_kind": "usize" + } }, "ladfile_builder::test::EnumType": { "identifier": "EnumType", @@ -83,7 +472,9 @@ "fields": [ { "name": "field", - "type": "usize" + "type": { + "primitive": "usize" + } } ] }, @@ -92,16 +483,26 @@ "name": "TupleStruct", "fields": [ { - "type": "usize" + "type": { + "primitive": "usize" + } }, { - "type": "String" + "type": { + "primitive": "string" + } } ] } ], "generated": false, - "insignificance": 1000 + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": true, + "mapped_to_primitive_kind": null + } }, "ladfile_builder::test::TupleStructType": { "identifier": "TupleStructType", @@ -113,15 +514,25 @@ "name": "TupleStructType", "fields": [ { - "type": "usize" + "type": { + "primitive": "usize" + } }, { - "type": "String" + "type": { + "primitive": "string" + } } ] }, "generated": false, - "insignificance": 1000 + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": true, + "mapped_to_primitive_kind": null + } }, "ladfile_builder::test::UnitType": { "identifier": "UnitType", @@ -133,13 +544,20 @@ "name": "UnitType" }, "generated": false, - "insignificance": 1000 + "insignificance": 1000, + "metadata": { + "is_component": false, + "is_resource": false, + "is_reflect": true, + "mapped_to_primitive_kind": null + } } }, "functions": { "::hello_world": { "namespace": null, "identifier": "hello_world", + "overload_index": null, "arguments": [ { "kind": { @@ -152,11 +570,15 @@ "kind": { "primitive": "usize" } + }, + "metadata": { + "is_operator": false } }, - "ladfile_builder::test::StructType::hello_world": { - "namespace": "ladfile_builder::test::StructType", + "ladfile_builder::test::GenericStructType::hello_world": { + "namespace": "ladfile_builder::test::GenericStructType", "identifier": "hello_world", + "overload_index": null, "arguments": [ { "kind": { @@ -197,105 +619,35 @@ }, "documentation": "I am some docs for the return type, I provide a name for the return value too", "name": "return" + }, + "metadata": { + "is_operator": false } - } - }, - "primitives": { - "DynamicFunction": { - "kind": "dynamicFunction", - "documentation": "A callable dynamic function" }, - "DynamicFunctionMut": { - "kind": "dynamicFunctionMut", - "documentation": "A stateful and callable dynamic function" - }, - "FunctionCallContext": { - "kind": "functionCallContext", - "documentation": "Function call context, if accepted by a function, means the function can access the world in arbitrary ways." - }, - "OsString": { - "kind": "osString", - "documentation": "A heap allocated OS string" - }, - "PathBuf": { - "kind": "pathBuf", - "documentation": "A heap allocated file path" - }, - "ReflectReference": { - "kind": "reflectReference", - "documentation": "A reference to a reflectable type" - }, - "String": { - "kind": "string", - "documentation": "A heap allocated string" - }, - "bool": { - "kind": "bool", - "documentation": "A boolean value" - }, - "char": { - "kind": "char", - "documentation": "An 8-bit character" - }, - "f32": { - "kind": "f32", - "documentation": "A 32-bit floating point number" - }, - "f64": { - "kind": "f64", - "documentation": "A 64-bit floating point number" - }, - "i128": { - "kind": "i128", - "documentation": "A signed 128-bit integer" - }, - "i16": { - "kind": "i16", - "documentation": "A signed 16-bit integer" - }, - "i32": { - "kind": "i32", - "documentation": "A signed 32-bit integer" - }, - "i64": { - "kind": "i64", - "documentation": "A signed 64-bit integer" - }, - "i8": { - "kind": "i8", - "documentation": "A signed 8-bit integer" - }, - "isize": { - "kind": "isize", - "documentation": "A signed pointer-sized integer" - }, - "str": { - "kind": "str", - "documentation": "A string slice" - }, - "u128": { - "kind": "u128", - "documentation": "An unsigned 128-bit integer" - }, - "u16": { - "kind": "u16", - "documentation": "An unsigned 16-bit integer" - }, - "u32": { - "kind": "u32", - "documentation": "An unsigned 32-bit integer" - }, - "u64": { - "kind": "u64", - "documentation": "An unsigned 64-bit integer" - }, - "u8": { - "kind": "u8", - "documentation": "An unsigned 8-bit integer" - }, - "usize": { - "kind": "usize", - "documentation": "An unsigned pointer-sized integer" + "ladfile_builder::test::PlainStructType::plain_struct_function": { + "namespace": "ladfile_builder::test::PlainStructType", + "identifier": "plain_struct_function", + "overload_index": null, + "arguments": [ + { + "kind": { + "ref": "ladfile_builder::test::PlainStructType" + } + }, + { + "kind": { + "primitive": "usize" + } + } + ], + "return_type": { + "kind": { + "unknown": "ladfile_builder::test::PlainStructType" + } + }, + "metadata": { + "is_operator": false + } } }, "description": "## Hello gentlemen\n I am markdown file.\n - hello\n - world" diff --git a/crates/ladfile_builder/Cargo.toml b/crates/ladfile_builder/Cargo.toml index 39ed50bd5f..ee41d6ffc1 100644 --- a/crates/ladfile_builder/Cargo.toml +++ b/crates/ladfile_builder/Cargo.toml @@ -11,6 +11,10 @@ keywords.workspace = true categories.workspace = true readme.workspace = true +[features] +default = [] +lua_language_server_files = ["lua_language_server_lad_backend"] + [dependencies] bevy_app = { workspace = true, default-features = false, features = [] } bevy_ecs = { workspace = true, default-features = false, features = [] } @@ -18,7 +22,9 @@ bevy_platform = { workspace = true, default-features = false, features = [] } bevy_log = { workspace = true, default-features = false, features = [] } bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } +bevy_mod_scripting_bindings_domain = { workspace = true } bevy_reflect = { workspace = true, features = ["documentation"] } +lua_language_server_lad_backend = { workspace = true, optional = true } ladfile = { workspace = true } regex = { workspace = true } diff --git a/crates/ladfile_builder/src/lib.rs b/crates/ladfile_builder/src/lib.rs index be099c86e3..e0ef72a231 100644 --- a/crates/ladfile_builder/src/lib.rs +++ b/crates/ladfile_builder/src/lib.rs @@ -1,18 +1,13 @@ //! Parsing definitions for the LAD (Language Agnostic Decleration) file format. pub mod plugin; -use std::{ - any::TypeId, - borrow::Cow, - cmp::{max, min}, - ffi::OsString, - path::PathBuf, +use bevy_ecs::{ + reflect::{ReflectComponent, ReflectResource}, + world::World, }; - -use bevy_ecs::world::World; use bevy_log::warn; use bevy_mod_scripting_bindings::{ - MarkAsCore, MarkAsGenerated, MarkAsSignificant, ReflectReference, + MarkAsCore, MarkAsGenerated, MarkAsSignificant, ReflectReference, ScriptValue, docgen::{ TypedThrough, info::FunctionInfo, @@ -22,44 +17,106 @@ use bevy_mod_scripting_bindings::{ namespace::Namespace, script_function::{DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext}, }, - match_by_type, + into_through_type_info, }; +pub use bevy_mod_scripting_bindings_domain::*; // re-export the thing we use use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{NamedField, TypeInfo, TypeRegistry, Typed, UnnamedField}; use ladfile::*; +use std::{ + any::TypeId, + borrow::Cow, + cmp::{max, min}, + ffi::OsString, + path::PathBuf, +}; /// We can assume that the types here will be either primitives /// or reflect types, as the rest will be covered by typed wrappers /// so just check -fn primitive_from_type_id(type_id: TypeId) -> Option { - match_by_type!(match type_id { - i: bool => return Some(LadBMSPrimitiveKind::Bool), - i: isize => return Some(LadBMSPrimitiveKind::Isize), - i: i8 => return Some(LadBMSPrimitiveKind::I8), - i: i16 => return Some(LadBMSPrimitiveKind::I16), - i: i32 => return Some(LadBMSPrimitiveKind::I32), - i: i64 => return Some(LadBMSPrimitiveKind::I64), - i: i128 => return Some(LadBMSPrimitiveKind::I128), - i: usize => return Some(LadBMSPrimitiveKind::Usize), - i: u8 => return Some(LadBMSPrimitiveKind::U8), - i: u16 => return Some(LadBMSPrimitiveKind::U16), - i: u32 => return Some(LadBMSPrimitiveKind::U32), - i: u64 => return Some(LadBMSPrimitiveKind::U64), - i: u128 => return Some(LadBMSPrimitiveKind::U128), - i: f32 => return Some(LadBMSPrimitiveKind::F32), - i: f64 => return Some(LadBMSPrimitiveKind::F64), - i: char => return Some(LadBMSPrimitiveKind::Char), - i: &'static str => return Some(LadBMSPrimitiveKind::Str), - i: str => return Some(LadBMSPrimitiveKind::Str), - i: String => return Some(LadBMSPrimitiveKind::String), - i: OsString => return Some(LadBMSPrimitiveKind::OsString), - i: PathBuf => return Some(LadBMSPrimitiveKind::PathBuf), - i: FunctionCallContext => return Some(LadBMSPrimitiveKind::FunctionCallContext), - i: DynamicScriptFunction => return Some(LadBMSPrimitiveKind::DynamicFunction), - i: DynamicScriptFunctionMut => return Some(LadBMSPrimitiveKind::DynamicFunctionMut), - i: ReflectReference => return Some(LadBMSPrimitiveKind::ReflectReference) - }); - None +fn primitive_from_type_id(type_id: TypeId) -> Option { + Some(if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Bool + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Isize + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I8 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I16 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::I128 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Usize + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U8 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U16 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::U128 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::F32 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::F64 + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::Char + } else if type_id == TypeId::of::<&'static str>() { + ReflectionPrimitiveKind::Str + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::String + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::OsString + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::PathBuf + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::FunctionCallContext + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::DynamicFunction + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::DynamicFunctionMut + } else if type_id == TypeId::of::() { + ReflectionPrimitiveKind::ReflectReference + } else { + return None; + }) +} + +fn type_id_from_primitive(kind: &ReflectionPrimitiveKind) -> Option { + Some(match kind { + ReflectionPrimitiveKind::Bool => TypeId::of::(), + ReflectionPrimitiveKind::Isize => TypeId::of::(), + ReflectionPrimitiveKind::I8 => TypeId::of::(), + ReflectionPrimitiveKind::I16 => TypeId::of::(), + ReflectionPrimitiveKind::I32 => TypeId::of::(), + ReflectionPrimitiveKind::I64 => TypeId::of::(), + ReflectionPrimitiveKind::I128 => TypeId::of::(), + ReflectionPrimitiveKind::Usize => TypeId::of::(), + ReflectionPrimitiveKind::U8 => TypeId::of::(), + ReflectionPrimitiveKind::U16 => TypeId::of::(), + ReflectionPrimitiveKind::U32 => TypeId::of::(), + ReflectionPrimitiveKind::U64 => TypeId::of::(), + ReflectionPrimitiveKind::U128 => TypeId::of::(), + ReflectionPrimitiveKind::F32 => TypeId::of::(), + ReflectionPrimitiveKind::F64 => TypeId::of::(), + ReflectionPrimitiveKind::Char => TypeId::of::(), + ReflectionPrimitiveKind::Str => TypeId::of::<&'static str>(), + ReflectionPrimitiveKind::String => TypeId::of::(), + ReflectionPrimitiveKind::OsString => TypeId::of::(), + ReflectionPrimitiveKind::PathBuf => TypeId::of::(), + ReflectionPrimitiveKind::FunctionCallContext => TypeId::of::(), + ReflectionPrimitiveKind::DynamicFunction => TypeId::of::(), + ReflectionPrimitiveKind::DynamicFunctionMut => TypeId::of::(), + ReflectionPrimitiveKind::ReflectReference => TypeId::of::(), + ReflectionPrimitiveKind::ScriptValue => TypeId::of::(), + ReflectionPrimitiveKind::External(_) => return None, + }) } /// A builder for constructing LAD files. @@ -86,33 +143,35 @@ impl<'t> LadFileBuilder<'t> { /// Create a new LAD file builder loaded with primitives. pub fn new(type_registry: &'t TypeRegistry) -> Self { + use ReflectionPrimitiveKind::*; let mut builder = Self::new_empty(type_registry); builder - .add_bms_primitive::("A boolean value") - .add_bms_primitive::("A signed pointer-sized integer") - .add_bms_primitive::("A signed 8-bit integer") - .add_bms_primitive::("A signed 16-bit integer") - .add_bms_primitive::("A signed 32-bit integer") - .add_bms_primitive::("A signed 64-bit integer") - .add_bms_primitive::("A signed 128-bit integer") - .add_bms_primitive::("An unsigned pointer-sized integer") - .add_bms_primitive::("An unsigned 8-bit integer") - .add_bms_primitive::("An unsigned 16-bit integer") - .add_bms_primitive::("An unsigned 32-bit integer") - .add_bms_primitive::("An unsigned 64-bit integer") - .add_bms_primitive::("An unsigned 128-bit integer") - .add_bms_primitive::("A 32-bit floating point number") - .add_bms_primitive::("A 64-bit floating point number") - .add_bms_primitive::("An 8-bit character") - .add_bms_primitive::<&'static str>("A string slice") - .add_bms_primitive::("A heap allocated string") - .add_bms_primitive::("A heap allocated OS string") - .add_bms_primitive::("A heap allocated file path") - .add_bms_primitive::("Function call context, if accepted by a function, means the function can access the world in arbitrary ways.") - .add_bms_primitive::("A callable dynamic function") - .add_bms_primitive::("A stateful and callable dynamic function") - .add_bms_primitive::("A reference to a reflectable type"); + .add_bms_primitive(Bool,"A boolean value") + .add_bms_primitive(Isize, "A signed pointer-sized integer") + .add_bms_primitive(I8, "A signed 8-bit integer") + .add_bms_primitive(I16, "A signed 16-bit integer") + .add_bms_primitive(I32, "A signed 32-bit integer") + .add_bms_primitive(I64, "A signed 64-bit integer") + .add_bms_primitive(I128, "A signed 128-bit integer") + .add_bms_primitive(Usize, "An unsigned pointer-sized integer") + .add_bms_primitive(U8, "An unsigned 8-bit integer") + .add_bms_primitive(U16, "An unsigned 16-bit integer") + .add_bms_primitive(U32, "An unsigned 32-bit integer") + .add_bms_primitive(U64, "An unsigned 64-bit integer") + .add_bms_primitive(U128, "An unsigned 128-bit integer") + .add_bms_primitive(F32, "A 32-bit floating point number") + .add_bms_primitive(F64, "A 64-bit floating point number") + .add_bms_primitive(Char, "An 8-bit character") + .add_bms_primitive(Str, "A string slice") + .add_bms_primitive(String, "A heap allocated string") + .add_bms_primitive(OsString, "A heap allocated OS string") + .add_bms_primitive(PathBuf, "A heap allocated file path") + .add_bms_primitive(FunctionCallContext, "Function call context, if accepted by a function, means the function can access the world in arbitrary ways.") + .add_bms_primitive(DynamicFunction, "A callable dynamic function") + .add_bms_primitive(DynamicFunctionMut, "A stateful and callable dynamic function") + .add_bms_primitive(ScriptValue, "A value representing the union of all representable values") + .add_bms_primitive(ReflectReference, "A reference to a reflectable type"); builder } @@ -132,20 +191,35 @@ impl<'t> LadFileBuilder<'t> { /// Add a BMS primitive to the LAD file. /// Will do nothing if the type is not a BMS primitive. - pub fn add_bms_primitive( + pub fn add_bms_primitive( &mut self, + primitive: ReflectionPrimitiveKind, docs: impl Into>, ) -> &mut Self { - let type_id = self.lad_id_from_type_id(TypeId::of::()); - let kind = match primitive_from_type_id(TypeId::of::()) { - Some(primitive) => primitive, - None => return self, - }; - self.file.primitives.insert( - type_id, - LadBMSPrimitiveType { - kind, - documentation: docs.into(), + let type_ident = primitive.to_string(); + let lad_type_id = LadTypeId::new_string_id(type_ident.clone().into()); + if let Some(type_id) = type_id_from_primitive(&primitive) { + self.type_id_mapping.insert(type_id, lad_type_id.clone()); + } + self.file.types.insert( + lad_type_id, + LadTypeDefinition { + identifier: type_ident.clone(), + crate_: None, + path: type_ident, + generics: vec![], + documentation: Some(docs.into().to_string()), + associated_functions: vec![], + layout: LadTypeLayout::Opaque, + generated: false, + insignificance: default_importance(), + metadata: LadTypeMetadata { + is_component: false, + is_resource: false, + is_reflect: false, + mapped_to_primitive_kind: Some(primitive), + misc: Default::default(), + }, }, ); self @@ -217,7 +291,7 @@ impl<'t> LadFileBuilder<'t> { &mut self, key: impl Into>, is_static: bool, - type_kind: LadTypeKind, + type_kind: LadFieldOrVariableKind, ) -> &mut Self { self.file.globals.insert( key.into(), @@ -245,7 +319,7 @@ impl<'t> LadFileBuilder<'t> { let lad_type_id = self.lad_id_from_type_id(std::any::TypeId::of::()); self.file.types.insert( lad_type_id, - LadType { + LadTypeDefinition { identifier, crate_: crate_.map(|s| s.to_owned()), path, @@ -255,6 +329,13 @@ impl<'t> LadFileBuilder<'t> { layout: LadTypeLayout::Opaque, generated: false, insignificance: default_importance(), + metadata: LadTypeMetadata { + is_component: false, + is_resource: false, + is_reflect: false, + mapped_to_primitive_kind: primitive_from_type_id(std::any::TypeId::of::()), + misc: Default::default(), + }, }, ); self @@ -275,6 +356,9 @@ impl<'t> LadFileBuilder<'t> { let mut insignificance = default_importance(); let mut generated = false; + let mut is_component = false; + let mut is_resource = false; + let is_reflect = true; if let Some(registration) = registration { if registration.contains::() { generated = true; @@ -285,10 +369,16 @@ impl<'t> LadFileBuilder<'t> { if registration.contains::() { insignificance = default_importance() / 4; } + if registration.contains::() { + is_resource = true + } + if registration.contains::() { + is_component = true + } } let type_id = self.lad_id_from_type_id(type_info.type_id()); - let lad_type = LadType { + let lad_type = LadTypeDefinition { identifier: type_info .type_path_table() .ident() @@ -312,6 +402,13 @@ impl<'t> LadFileBuilder<'t> { layout: self.lad_layout_from_type_info(type_info), generated, insignificance, + metadata: LadTypeMetadata { + is_component, + is_resource, + is_reflect, + mapped_to_primitive_kind: primitive_from_type_id(type_info.type_id()), + misc: Default::default(), + }, }; self.file.types.insert(type_id, lad_type); self @@ -336,6 +433,9 @@ impl<'t> LadFileBuilder<'t> { self.add_through_type_info(til); self.add_through_type_info(tir); } + TypedWrapperKind::HashSet(t) => { + self.add_through_type_info(t); + } TypedWrapperKind::Array(ti, _) => { self.add_through_type_info(ti); } @@ -354,6 +454,7 @@ impl<'t> LadFileBuilder<'t> { ThroughTypeInfo::TypeInfo(type_info) => { self.add_type_info(type_info); } + ThroughTypeInfo::Primitive(_) => {} } self @@ -380,9 +481,22 @@ impl<'t> LadFileBuilder<'t> { let (main_docstring, arg_docstrings, return_docstring) = Self::split_docstring(function_info.docs.as_ref().unwrap_or(&default_docstring)); + let mut identifier = function_info.name.as_ref(); + let mut overload_index = None; + if identifier.contains("-") { + let mut parts = identifier.split("-"); + if let Some(less_overload) = parts.next() { + identifier = less_overload; + } + if let Some(number) = parts.next() { + overload_index = number.parse::().ok() + } + } + let function_id = self.lad_function_id_from_info(function_info); let lad_function = LadFunction { - identifier: function_info.name.clone(), + identifier: identifier.to_owned().into(), + overload_index, arguments: function_info .arg_info .clone() @@ -390,7 +504,9 @@ impl<'t> LadFileBuilder<'t> { .map(|arg| { let kind = match &arg.type_info { Some(through_type) => self.lad_type_kind_from_through_type(through_type), - None => LadTypeKind::Unknown(self.lad_id_from_type_id(arg.type_id)), + None => { + LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(arg.type_id)) + } }; LadArgument { kind, @@ -411,7 +527,7 @@ impl<'t> LadFileBuilder<'t> { .clone() .map(|info| self.lad_type_kind_from_through_type(&info)) .unwrap_or_else(|| { - LadTypeKind::Unknown( + LadFieldOrVariableKind::Unknown( self.lad_id_from_type_id(function_info.return_info.type_id), ) }), @@ -423,6 +539,10 @@ impl<'t> LadFileBuilder<'t> { LadFunctionNamespace::Type(self.lad_id_from_type_id(type_id)) } }, + metadata: LadFunctionMetadata { + is_operator: ScriptOperatorNames::parse(identifier).is_some(), + misc: Default::default(), + }, }; self.file.functions.insert(function_id, lad_function); self @@ -530,7 +650,6 @@ impl<'t> LadFileBuilder<'t> { }); file.functions.sort_keys(); - file.primitives.sort_keys(); } file @@ -673,7 +792,7 @@ impl<'t> LadFileBuilder<'t> { fields: fields .map(|field| LadNamedField { name: field.name().to_string(), - type_: self.lad_id_from_type_id(field.type_id()), + type_: self.lad_type_kind_from_type_id(field.type_id()), }) .collect(), } @@ -688,7 +807,7 @@ impl<'t> LadFileBuilder<'t> { name, fields: fields .map(|field| LadField { - type_: self.lad_id_from_type_id(field.type_id()), + type_: self.lad_type_kind_from_type_id(field.type_id()), }) .collect(), } @@ -744,6 +863,18 @@ impl<'t> LadFileBuilder<'t> { } } + /// Should only be used on fields, as those are never going to contain + /// untyped structures, i.e. are going to be fully reflectable + fn lad_type_kind_from_type_id(&mut self, type_id: TypeId) -> LadFieldOrVariableKind { + if let Some(type_info) = self.type_registry.get_type_info(type_id) { + let through_type_info = into_through_type_info(type_info); + self.lad_type_kind_from_through_type(&through_type_info) + } else { + LadFieldOrVariableKind::Unknown(self.lad_id_from_type_id(type_id)) + } + } + + /// Figures out whether the type is a primitive or not and creates the right type id fn lad_id_from_type_id(&mut self, type_id: TypeId) -> LadTypeId { // a special exception if type_id == std::any::TypeId::of::() { @@ -755,7 +886,7 @@ impl<'t> LadFileBuilder<'t> { } let new_id = match primitive_from_type_id(type_id) { - Some(primitive) => primitive.lad_type_id(), + Some(primitive) => LadTypeId::new_string_id(primitive.to_string().into()), None => { if let Some(info) = self.type_registry.get_type_info(type_id) { LadTypeId::new_string_id(info.type_path_table().path().into()) @@ -780,7 +911,10 @@ impl<'t> LadFileBuilder<'t> { LadFunctionId::new_string_id(format!("{}::{}", namespace_string, function_info.name)) } - fn lad_type_kind_from_through_type(&mut self, through_type: &ThroughTypeInfo) -> LadTypeKind { + fn lad_type_kind_from_through_type( + &mut self, + through_type: &ThroughTypeInfo, + ) -> LadFieldOrVariableKind { match through_type { ThroughTypeInfo::UntypedWrapper { through_type, @@ -788,36 +922,41 @@ impl<'t> LadFileBuilder<'t> { .. } => match wrapper_kind { UntypedWrapperKind::Ref => { - LadTypeKind::Ref(self.lad_id_from_type_id(through_type.type_id())) + LadFieldOrVariableKind::Ref(self.lad_id_from_type_id(through_type.type_id())) } UntypedWrapperKind::Mut => { - LadTypeKind::Mut(self.lad_id_from_type_id(through_type.type_id())) + LadFieldOrVariableKind::Mut(self.lad_id_from_type_id(through_type.type_id())) } UntypedWrapperKind::Val => { - LadTypeKind::Val(self.lad_id_from_type_id(through_type.type_id())) + LadFieldOrVariableKind::Val(self.lad_id_from_type_id(through_type.type_id())) } }, ThroughTypeInfo::TypedWrapper(typed_wrapper_kind) => match typed_wrapper_kind { - TypedWrapperKind::Vec(through_type_info) => LadTypeKind::Vec(Box::new( + TypedWrapperKind::Vec(through_type_info) => LadFieldOrVariableKind::Vec(Box::new( self.lad_type_kind_from_through_type(through_type_info), )), TypedWrapperKind::HashMap(through_type_info, through_type_info1) => { - LadTypeKind::HashMap( + LadFieldOrVariableKind::HashMap( Box::new(self.lad_type_kind_from_through_type(through_type_info)), Box::new(self.lad_type_kind_from_through_type(through_type_info1)), ) } - TypedWrapperKind::Array(through_type_info, size) => LadTypeKind::Array( + TypedWrapperKind::HashSet(through_type_info) => LadFieldOrVariableKind::HashSet( + Box::new(self.lad_type_kind_from_through_type(through_type_info)), + ), + TypedWrapperKind::Array(through_type_info, size) => LadFieldOrVariableKind::Array( Box::new(self.lad_type_kind_from_through_type(through_type_info)), *size, ), - TypedWrapperKind::Option(through_type_info) => LadTypeKind::Option(Box::new( - self.lad_type_kind_from_through_type(through_type_info), - )), - TypedWrapperKind::InteropResult(through_type_info) => LadTypeKind::InteropResult( + TypedWrapperKind::Option(through_type_info) => LadFieldOrVariableKind::Option( Box::new(self.lad_type_kind_from_through_type(through_type_info)), ), - TypedWrapperKind::Tuple(through_type_infos) => LadTypeKind::Tuple( + TypedWrapperKind::InteropResult(through_type_info) => { + LadFieldOrVariableKind::InteropResult(Box::new( + self.lad_type_kind_from_through_type(through_type_info), + )) + } + TypedWrapperKind::Tuple(through_type_infos) => LadFieldOrVariableKind::Tuple( through_type_infos .iter() .map(|through_type_info| { @@ -825,7 +964,7 @@ impl<'t> LadFileBuilder<'t> { }) .collect(), ), - TypedWrapperKind::Union(through_type_infos) => LadTypeKind::Union( + TypedWrapperKind::Union(through_type_infos) => LadFieldOrVariableKind::Union( through_type_infos .iter() .map(|through_type_info| { @@ -836,10 +975,15 @@ impl<'t> LadFileBuilder<'t> { }, ThroughTypeInfo::TypeInfo(type_info) => { match primitive_from_type_id(type_info.type_id()) { - Some(primitive) => LadTypeKind::Primitive(primitive), - None => LadTypeKind::Unknown(self.lad_id_from_type_id(type_info.type_id())), + Some(primitive) => LadFieldOrVariableKind::Primitive(primitive), + None => LadFieldOrVariableKind::Unknown( + self.lad_id_from_type_id(type_info.type_id()), + ), } } + ThroughTypeInfo::Primitive(reflection_primitive_kind) => { + LadFieldOrVariableKind::Primitive(reflection_primitive_kind.clone()) + } } } } @@ -1055,23 +1199,32 @@ mod test { ); } - /// Set to true to put output into test_assets. - const BLESS_TEST_FILE: bool = false; - #[test] fn test_serializes_as_expected() { let mut type_registry = TypeRegistry::default(); #[derive(Reflect)] /// I am a struct - struct StructType { + struct GenericStructType { /// hello from field field: usize, /// hello from field 2 field2: T, } - impl TypedThrough for StructType { + impl TypedThrough for GenericStructType { + fn through_type_info() -> ThroughTypeInfo { + ThroughTypeInfo::TypeInfo(Self::type_info()) + } + } + + #[derive(Reflect)] + /// I am a simple plain struct type + struct PlainStructType { + int_field: usize, + } + + impl TypedThrough for PlainStructType { fn through_type_info() -> ThroughTypeInfo { ThroughTypeInfo::TypeInfo(Self::type_info()) } @@ -1104,20 +1257,31 @@ mod test { TupleStruct(usize, #[doc = "asd"] String), } - type_registry.register::>(); + type_registry.register::>(); type_registry.register::(); type_registry.register::(); type_registry.register::(); + type_registry.register::(); + + let plain_struct_function = + |_: Ref, _: usize| PlainStructType { int_field: 2 }; + let plain_struct_function_info = plain_struct_function.get_function_info( + "plain_struct_function".into(), + PlainStructType::into_namespace(), + ); let function = |_: ReflectReference, _: usize| 2usize; let function_info = function - .get_function_info("hello_world".into(), StructType::::into_namespace()) + .get_function_info( + "hello_world".into(), + GenericStructType::::into_namespace(), + ) .with_docs("hello docs"); let function_with_complex_args = |_: ReflectReference, _: (usize, String), _: Option>>| 2usize; let function_with_complex_args_info = function_with_complex_args - .get_function_info("hello_world".into(), StructType::::into_namespace()) + .get_function_info("hello_world".into(), GenericStructType::::into_namespace()) .with_arg_names(&["ref_", "tuple", "option_vec_ref_wrapper"]) .with_docs( "Arguments: ".to_owned() @@ -1141,14 +1305,16 @@ mod test { let mut lad_file = LadFileBuilder::new(&type_registry) .set_description("## Hello gentlemen\n I am markdown file.\n - hello\n - world") .set_sorted(true) + .add_function_info(&plain_struct_function_info) .add_function_info(&function_info) .add_function_info(&global_function_info) .add_function_info(&function_with_complex_args_info) - .add_type::>() + .add_type::>() .add_type::() .add_type::() + .add_type::() .add_type_info(EnumType::type_info()) - .add_instance::>>("my_static_instance", true) + .add_instance::>>("my_static_instance", true) .add_instance::>>("my_non_static_instance", false) .add_instance::>>("map", false) .build(); @@ -1159,7 +1325,7 @@ mod test { normalize_file(&mut serialized); - if BLESS_TEST_FILE { + if std::env::var("BLESS_MODE").is_ok() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let path_to_test_assets = std::path::Path::new(&manifest_dir) .join("..") diff --git a/crates/ladfile_builder/src/plugin.rs b/crates/ladfile_builder/src/plugin.rs index 80f5e6e897..26741f2f32 100644 --- a/crates/ladfile_builder/src/plugin.rs +++ b/crates/ladfile_builder/src/plugin.rs @@ -1,6 +1,6 @@ //! Plugins for bevy which allow generating ladfiles at startup -use std::path::PathBuf; +use std::{ops::Deref, path::PathBuf, sync::Arc}; use ::{ bevy_app::{App, Plugin, Startup}, @@ -11,25 +11,44 @@ use bevy_mod_scripting_bindings::{ DummyScriptFunctionRegistry, IntoNamespace, function::{namespace::Namespace, script_function::AppScriptFunctionRegistry}, globals::AppScriptGlobalsRegistry, + into_through_type_info, }; -use ladfile::{LadTypeKind, default_importance}; +use ladfile::{LadFieldOrVariableKind, LadFilePlugin, default_importance}; use crate::LadFileBuilder; /// Plugin which enables the generation of LAD files at runtime for the purposes of creating documentation and other goodies. /// /// When added, will automatically generate a LAD file on the Startup schedule -#[derive(Default)] -pub struct ScriptingDocgenPlugin(LadFileSettings); +#[derive(Default, Clone)] +pub struct ScriptingFilesGenerationPlugin(LadFileSettingsArc); + +/// Stores the settings for the generated Ladfile +#[derive(Resource, Default, Clone)] +pub struct LadFileSettingsArc(pub Arc); + +impl Deref for LadFileSettingsArc { + type Target = LadFileSettings; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} -#[derive(Resource, Clone)] /// Stores the settings for the generated Ladfile pub struct LadFileSettings { - /// The path at which to generate the LAD file. If relative, will be relative from the assets directory - /// The extension should be `json.lad` + /// Can be used to decide whether or not to generate ladfiles and associated files at runtime + /// by default enabled. + pub enabled: bool, + + /// if set the file name in the output directory to write the ladfile. Not saved by default + pub ladfile_filename: Option, + + /// The path at which to generate the LAD file and other outputs from each processor. /// - /// By default this will be `assets/bindings.lad.json` - pub path: PathBuf, + /// By default this will be the current working directory. + pub output_directory: PathBuf, + /// The description to use for the LAD file, by default it's empty pub description: &'static str, @@ -40,33 +59,56 @@ pub struct LadFileSettings { /// Whether to pretty print the output JSON. By default this is true (slay) pub pretty: bool, + + /// Processors to apply to the generated ladfile + pub processors: Vec>, } impl Default for LadFileSettings { fn default() -> Self { Self { - path: PathBuf::from("bindings.lad.json"), + enabled: true, + ladfile_filename: None, + output_directory: PathBuf::from("."), description: "", pretty: true, exclude_types_containing_unregistered: true, + processors: ScriptingFilesGenerationPlugin::default_processors(), } } } -impl ScriptingDocgenPlugin { - /// Create a new instance of the plugin with the given path +impl ScriptingFilesGenerationPlugin { + /// The default processors according to currently set features + #[allow(unused_mut, clippy::vec_init_then_push)] + pub fn default_processors() -> Vec> { + let mut processors = Vec::default(); + + #[cfg(feature = "lua_language_server_files")] + processors.push(Box::new( + lua_language_server_lad_backend::LuaLanguageServerLadPlugin::default(), + ) as Box); + processors + } + + /// Create a new instance of the plugin responsible for setting up the ladfile processing pipeline pub fn new( - path: PathBuf, + enabled: bool, + output_directory: PathBuf, + ladfile_filename: Option, description: &'static str, exclude_types_containing_unregistered: bool, pretty: bool, ) -> Self { - Self(LadFileSettings { - path, + Self(LadFileSettingsArc(Arc::new(LadFileSettings { + enabled, + output_directory, + ladfile_filename, description, pretty, exclude_types_containing_unregistered, - }) + processors: Self::default_processors(), + }))) } } @@ -115,7 +157,10 @@ pub fn generate_lad_file( continue; } - builder.add_type_info(type_info); + // we don't really care about the Option in Option as that is magically worked around in the entire API + // get the "pure" types + let through_type_info = into_through_type_info(type_info); + builder.add_through_type_info(&through_type_info); // find functions on the namespace for (_, function) in function_registry @@ -146,33 +191,41 @@ pub fn generate_lad_file( builder.add_through_type_info(type_info); builder.lad_type_kind_from_through_type(type_info) } else { - LadTypeKind::Val(builder.lad_id_from_type_id(global.type_id)) + LadFieldOrVariableKind::Val(builder.lad_id_from_type_id(global.type_id)) }; builder.add_instance_manually(key.to_string(), false, kind); } let file = builder.build(); + let directory = &settings.output_directory; - let mut path = PathBuf::from("assets"); - path.push(settings.path.clone()); - - // generate - let file = match ladfile::serialize_lad_file(&file, settings.pretty) { - Ok(file) => file, - Err(e) => { - error!("Error serializing LAD file: {}", e); - return; + for processor in settings.processors.iter() { + bevy_log::info!("Running ladfile processor: '{}'", processor.name()); + if let Err(e) = processor.run(&file, directory) { + bevy_log::error!("Error in running ladfile processor: {e}") } - }; + } - // save - match std::fs::write(&path, file) { - Ok(_) => { - info!("Successfully generated LAD file at {:?}", path); - } - Err(e) => { - error!("Error saving LAD file to {:?}: {}", path, e); + if let Some(filename) = &settings.ladfile_filename { + let path = directory.join(filename); + // generate + let file = match ladfile::serialize_lad_file(&file, settings.pretty) { + Ok(file) => file, + Err(e) => { + error!("Error serializing LAD file: {}", e); + return; + } + }; + + // save + match std::fs::write(&path, file) { + Ok(_) => { + info!("Successfully generated LAD file at {:?}", path); + } + Err(e) => { + error!("Error saving LAD file to {:?}: {}", path, e); + } } } } @@ -182,20 +235,22 @@ fn generate_lad_file_system( function_registry: Res, dummy_function_registry: Res, global_registry: Res, - settings: Res, + settings: Res, ) { generate_lad_file( &type_registry, &function_registry, &dummy_function_registry, &global_registry, - &settings, + &settings.0, ); } -impl Plugin for ScriptingDocgenPlugin { +impl Plugin for ScriptingFilesGenerationPlugin { fn build(&self, app: &mut App) { - app.insert_resource(self.0.clone()); - app.add_systems(Startup, generate_lad_file_system); + if self.0.0.enabled { + app.insert_resource(self.0.clone()); + app.add_systems(Startup, generate_lad_file_system); + } } } diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 7740b36ac4..97641ce03e 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -45,6 +45,7 @@ bevy_platform = { workspace = true, default-features = false, features = [] } bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_display = { workspace = true } bevy_mod_scripting_bindings = { workspace = true } +bevy_mod_scripting_bindings_domain = { workspace = true } bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_script = { workspace = true } mlua = { workspace = true, features = ["vendored", "send", "macros"] } diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs index e501e47183..d2cc9737f6 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -3,6 +3,7 @@ use std::any::TypeId; use bevy_mod_scripting_bindings::{ ReflectReference, ThreadWorldContainer, error::InteropError, script_value::ScriptValue, }; +use bevy_mod_scripting_bindings_domain::ScriptOperatorNames; use bevy_mod_scripting_display::OrFakeId; use mlua::{ExternalError, MetaMethod, UserData, UserDataMethods}; @@ -115,7 +116,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "sub", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Subtraction.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -137,7 +143,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "add", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Addition.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -159,7 +170,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "mul", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Multiplication.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -181,7 +197,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "div", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Division.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -203,7 +224,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "rem", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Remainder.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -222,7 +248,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_)]; let out = world - .try_call_overloads(target_type_id, "neg", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Negation.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }); @@ -243,7 +274,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "pow", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Exponentiation.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -265,7 +301,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "eq", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::Equality.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -287,7 +328,12 @@ impl UserData for LuaReflectReference { .or_fake_id(); let args = vec![ScriptValue::Reference(self_), other]; let out = world - .try_call_overloads(target_type_id, "lt", args, LUA_CALLER_CONTEXT) + .try_call_overloads( + target_type_id, + ScriptOperatorNames::LessThanComparison.script_function_name(), + args, + LUA_CALLER_CONTEXT, + ) .map_err(IntoMluaError::to_lua_error)?; Ok(LuaScriptValue(out)) }, @@ -323,7 +369,10 @@ impl UserData for LuaReflectReference { .world; let iter_func = world - .lookup_function([TypeId::of::()], "iter") + .lookup_function( + [TypeId::of::()], + ScriptOperatorNames::Iteration.script_function_name(), + ) .map_err(|f| { InteropError::missing_function( f, @@ -349,7 +398,10 @@ impl UserData for LuaReflectReference { let reflect_reference: ReflectReference = self_.into(); let func = world - .lookup_function([TypeId::of::()], "display") + .lookup_function( + [TypeId::of::()], + ScriptOperatorNames::DisplayPrint.script_function_name(), + ) .map_err(|f| { InteropError::missing_function( f, diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index f62f56a21a..387f724134 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -12,6 +12,11 @@ - [Callbacks](./Summary/callbacks.md) - [Script Systems](./ScriptSystems/introduction.md) - [Examples](./Examples/introduction.md) +- [Scripting IDE Integrations](./ScriptTooling/summary.md) + +# Script Tooling + +- [LADfiles](./ScriptTooling/introduction.md) # Script Pipeline diff --git a/docs/src/ScriptTooling/introduction.md b/docs/src/ScriptTooling/introduction.md new file mode 100644 index 0000000000..94bf390167 --- /dev/null +++ b/docs/src/ScriptTooling/introduction.md @@ -0,0 +1,51 @@ +## LADfiles +BMS provides a custom Language Agnostic Declaration format (LAD), which can be used to integrate with other tooling. + +If you use `#[script_bindings]` macros, these files will magically contain your code comments, bindings as well as everything else required to drive other powerful integrations. + +The declaration file is generated from the type registry and serves as a light abstraction layer simplifying the traversal of all +available types and globals as well as functions. + +You can customize how / where and if these files are stored using the main BMS plugin group: +```rust,ignore + app.add_plugins(BMSPlugin.set::( + ScriptingFilesGenerationPlugin::new( + true, // enabled, you can use a compilation feature to disable this here + PathBuf::from("assets").join("definitions"), + Some(PathBuf::from("bindings.lad.json")), // do also save the ladfile itself + "Core BMS framework bindings", + true, + true, + ), + )); +``` + +This plugin is only available when one of the sub features (like `lua_language_server_files`) mentioned in this chapter is enabled. + + +You might not want to run this pipeline in your final binary, but rather bundle some of the generated files into some sort of development pack for modding. You can use compiler flags like `#[cfg(not(debug_assertions))]` to disable ladfile generation at runtime, or simply disable the lower level features within BMS to avoid compiling related dependencies too. + +## Lua Language Server + +
+ This feature is in early stages, the definitions provide 90% of the way there, but there are rough edges that will need to be worked out +
+ +[Luals](https://github.com/LuaLS/lua-language-server) or LLS, is an open source language server which integrates with many IDE's. + +It is powered by lua specific annotation or definition files which BMS can generate directly from its own LADfiles. + +To enable this simply enable the `lua_language_server_files` feature, and a `bindings.lua` definition file will be generated in the LADfile output directory in the `Startup` schedule. + +Script writers can then use this generated file by pointing their `.luarc.json` file to these definitions: +```json +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "workspace.library": [ + "assets/definitions" + ], + "runtime.version": "Lua 5.4", + "hint.enable": false, +} +``` + diff --git a/docs/src/Summary/scripting-ide-integrations.md b/docs/src/Summary/scripting-ide-integrations.md new file mode 100644 index 0000000000..485af5f03a --- /dev/null +++ b/docs/src/Summary/scripting-ide-integrations.md @@ -0,0 +1,3 @@ +# Scripting IDE Integrations + +See the [script tooling](../ScriptTooling/summary.md) section to find out more about how BMS integrates with other tooling to make scripting easier. \ No newline at end of file diff --git a/examples/docgen.rs b/examples/docgen.rs index 3da9fead12..92ebaf1620 100644 --- a/examples/docgen.rs +++ b/examples/docgen.rs @@ -1,32 +1,35 @@ +use bevy::prelude::PluginGroup; use bevy::{DefaultPlugins, app::App, ecs::reflect::AppTypeRegistry}; -use bevy_mod_scripting::ScriptFunctionsPlugin; +use bevy_mod_scripting::BMSPlugin; use bevy_mod_scripting_bindings::{ - DummyScriptFunctionRegistry, - function::script_function::AppScriptFunctionRegistry, - globals::{AppScriptGlobalsRegistry, core::CoreScriptGlobalsPlugin}, + DummyScriptFunctionRegistry, function::script_function::AppScriptFunctionRegistry, + globals::AppScriptGlobalsRegistry, }; -use bevy_mod_scripting_core::BMSScriptingInfrastructurePlugin; -use ladfile_builder::plugin::{LadFileSettings, ScriptingDocgenPlugin, generate_lad_file}; - +use ladfile_builder::plugin::{ + LadFileSettingsArc, ScriptingFilesGenerationPlugin, generate_lad_file, +}; +use std::path::PathBuf; fn main() -> std::io::Result<()> { let mut app = App::new(); // headless bevy, kinda, I want to include as many plugins as I can which actually // provide reflected type definitions, but exclude anything that runs rendering stuff. app.add_plugins(DefaultPlugins); - // docgen + scripting - app.add_plugins(( - // normally the global plugin is included as part of each scripting plugin, here we just take - // the definitions by themselves - CoreScriptGlobalsPlugin::default(), - ScriptFunctionsPlugin, - BMSScriptingInfrastructurePlugin::default(), + // this example is used to drive the generated docs on the official BMS book + app.add_plugins(BMSPlugin.set::( + ScriptingFilesGenerationPlugin::new( + true, // enabled, you can use a compilation feature to disable this here + PathBuf::from("assets").join("definitions"), + Some(PathBuf::from("bindings.lad.json")), // do also save the ladfile itself + "Core BMS framework bindings", + true, + true, + ), )); // there are two ways to generate the ladfile // 1. add the docgen plugin and run your app as normal - app.add_plugins(ScriptingDocgenPlugin::default()); app.finish(); app.cleanup(); // running update once will do the trick @@ -56,17 +59,18 @@ fn main() -> std::io::Result<()> { .unwrap() .clone(); - let settings = LadFileSettings { - description: "Core BMS framework bindings", - ..Default::default() - }; + let settings = app + .world() + .get_resource::() + .unwrap() + .clone(); generate_lad_file( &type_registry, &function_registry, &dummy_function_registry, &global_registry, - &settings, + &settings.0, ); // bah bye, the generated file will be found in assets/ diff --git a/release-plz.toml b/release-plz.toml index 94721dffc0..1b33a2525d 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -72,6 +72,10 @@ version_group = "main" name = "bevy_mod_scripting_functions" version_group = "main" +[[package]] +name = "lua_language_server_lad_backend" +version_group = "main" + [[package]] name = "ladfile" git_release_enable = true diff --git a/src/lib.rs b/src/lib.rs index 51abdb5713..2385adbaf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod display { pub mod bindings { pub use bevy_mod_scripting_bindings::*; + pub use bevy_mod_scripting_bindings_domain::*; } pub mod core { @@ -32,6 +33,11 @@ pub mod rhai { pub use bevy_mod_scripting_rhai::*; } +#[cfg(feature = "lua_language_server_files")] +pub mod ladfile { + pub use ladfile_builder::*; +} + use bevy_app::plugin_group; use bevy_mod_scripting_bindings::CoreScriptGlobalsPlugin; use bevy_mod_scripting_core::BMSScriptingInfrastructurePlugin; @@ -47,5 +53,7 @@ plugin_group! { bevy_mod_scripting_lua:::LuaScriptingPlugin, #[custom(cfg(feature = "rhai"))] bevy_mod_scripting_rhai:::RhaiScriptingPlugin, + #[custom(cfg(feature = "lua_language_server_files"))] + ladfile_builder::plugin:::ScriptingFilesGenerationPlugin } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b4b018306e..e598e57bce 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -58,6 +58,9 @@ enum Feature { BevySpriteBindings, BevyTextBindings, + // Ladfile + LuaLanguageServerFiles, + // Lua Lua51, Lua52, @@ -149,7 +152,9 @@ impl IntoFeatureGroup for Feature { | Feature::BevySceneBindings | Feature::BevySpriteBindings | Feature::BevyTextBindings => FeatureGroup::BMSFeatureNotInPowerset, - Feature::CoreFunctions | Feature::ProfileWithTracy => FeatureGroup::BMSFeature, // don't use wildcard here, we want to be explicit + Feature::CoreFunctions + | Feature::ProfileWithTracy + | Feature::LuaLanguageServerFiles => FeatureGroup::BMSFeature, // don't use wildcard here, we want to be explicit } } } @@ -172,6 +177,7 @@ impl Default for Features { Feature::BevyTransformBindings, Feature::BevyColorBindings, Feature::BevyCorePipelineBindings, + Feature::LuaLanguageServerFiles, ]) } } @@ -1434,7 +1440,8 @@ impl Xtasks { Self::example(app_settings.clone(), "docgen".to_owned())?; // copy the `/assets/bindings.lad.json` file to it's path in the book - let ladfile_path = Self::relative_workspace_dir(&app_settings, "assets/bindings.lad.json")?; + let ladfile_path = + Self::relative_workspace_dir(&app_settings, "assets/definitions/bindings.lad.json")?; let destination_path = Self::relative_workspace_dir(&app_settings, "docs/src/ladfiles/bindings.lad.json")?; @@ -1816,6 +1823,10 @@ impl Xtasks { test_args.push("--exclude".to_owned()); test_args.push("xtask".to_owned()); + if std::env::var("BLESS_MODE").is_ok() { + test_args.push("--no-fail-fast".to_owned()) + } + Self::run_workspace_command( &app_settings, "test",