mod function;
pub mod generator;
mod class;
mod module;
pub use class::{
TypedClassBuilder, TypedDataFields, TypedDataMethods, TypedDataDocumentation, TypedUserData, WrappedBuilder,
};
pub use module::{TypedModule, TypedModuleBuilder, TypedModuleFields, TypedModuleMethods};
use std::{
borrow::Cow, cell::{Cell, RefCell}, collections::{BTreeMap, BTreeSet, HashMap, HashSet}, marker::PhantomData, rc::Rc, sync::{Arc, Mutex}
};
use function::Return;
pub use function::{Param, TypedFunction};
use mlua::{IntoLua, MetaMethod, Value, Variadic};
pub trait Typed {
fn ty() -> Type;
fn as_param() -> Param {
Param {
doc: None,
name: None,
ty: Self::ty(),
}
}
}
macro_rules! impl_static_typed {
{
$(
$($target: ty)|*
=> $name: literal),*
$(,)?
} => {
$(
$(
impl Typed for $target {
fn ty() -> Type {
Type::named($name)
}
}
)*
)*
};
}
macro_rules! impl_static_typed_generic {
{
$(
$(for<$($lt: lifetime),+> $target: ty)|*
=> $name: literal),*
$(,)?
} => {
$(
$(
impl<$($lt,)+> Typed for $target {
fn ty() -> Type {
Type::named($name)
}
}
)*
)*
};
}
impl_static_typed! {
mlua::LightUserData => "lightuserdata",
mlua::Error => "error",
String | &str => "string",
u8 | u16 | u32 | u64 | usize | u128 | i8 | i16 | i32 | i64 | isize | i128 => "integer",
f32 | f64 => "number",
bool => "boolean",
mlua::Function => "fun()",
mlua::Table => "table",
mlua::AnyUserData => "userdata",
mlua::String => "string",
mlua::Thread => "thread",
}
impl_static_typed_generic! {
for<'a> Cow<'a, str> => "string",
}
impl Typed for mlua::Value {
fn ty() -> Type {
Type::Single("any".into())
}
}
impl<T: Typed> Typed for Variadic<T> {
fn ty() -> Type {
Type::any()
}
}
impl<T: Typed> Typed for Option<T> {
fn ty() -> Type {
Type::Union(vec![T::ty(), Type::Single("nil".into())])
}
}
impl<T: IntoLuaTypeLiteral> From<T> for Type {
fn from(value: T) -> Self {
Type::Single(value.into_lua_type_literal().into())
}
}
impl<T: Typed> Typed for Arc<T> {
fn ty() -> Type {
T::ty()
}
}
impl<T: Typed> Typed for Rc<T> {
fn ty() -> Type {
T::ty()
}
}
impl<T: Typed> Typed for Cell<T> {
fn ty() -> Type {
T::ty()
}
}
impl<T: Typed> Typed for RefCell<T> {
fn ty() -> Type {
T::ty()
}
}
impl<T: Typed> Typed for Mutex<T> {
fn ty() -> Type {
T::ty()
}
}
impl<T: Typed> Typed for PhantomData<T> {
fn ty() -> Type {
T::ty()
}
}
impl<const N: usize> From<[Type;N]> for Type {
fn from(value: [Type;N]) -> Self {
Type::Tuple(Vec::from(value))
}
}
impl<I: Typed, const N: usize> Typed for [I; N] {
fn ty() -> Type {
Type::Array(I::ty().into())
}
}
impl<I: Typed> Typed for Vec<I> {
fn ty() -> Type {
Type::Array(I::ty().into())
}
}
impl<I: Typed> Typed for &[I] {
fn ty() -> Type {
Type::Array(I::ty().into())
}
}
impl<I: Typed> Typed for HashSet<I> {
fn ty() -> Type {
Type::Array(I::ty().into())
}
}
impl<I: Typed> Typed for BTreeSet<I> {
fn ty() -> Type {
Type::Array(I::ty().into())
}
}
impl<K, V> Typed for BTreeMap<K, V>
where
K: Typed,
V: Typed,
{
fn ty() -> Type {
Type::Map(K::ty().into(), V::ty().into())
}
}
impl<K, V> Typed for HashMap<K, V>
where
K: Typed,
V: Typed,
{
fn ty() -> Type {
Type::Map(K::ty().into(), V::ty().into())
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum Index {
Int(usize),
Str(Cow<'static, str>),
}
impl std::fmt::Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Int(num) => write!(f, "[{num}]"),
Self::Str(val) => if val.chars().any(|v| !v.is_alphanumeric() && v != '_') {
write!(f, r#"["{val}"]"#)
} else {
write!(f, "{val}")
}
}
}
}
impl IntoLua for Index {
fn into_lua(self, lua: &mlua::Lua) -> mlua::prelude::LuaResult<Value> {
match self {
Self::Int(num) => Ok(mlua::Value::Integer(num as mlua::Integer)),
Self::Str(val) => val.into_lua(lua)
}
}
}
impl From<MetaMethod> for Index {
fn from(value: MetaMethod) -> Self {
Self::Str(value.as_ref().to_string().into())
}
}
impl From<Cow<'static, str>> for Index {
fn from(value: Cow<'static, str>) -> Self {
Self::Str(value)
}
}
impl From<&'static str> for Index {
fn from(value: &'static str) -> Self {
Self::Str(value.into())
}
}
impl From<String> for Index {
fn from(value: String) -> Self {
Self::Str(value.into())
}
}
impl From<usize> for Index {
fn from(value: usize) -> Self {
Self::Int(value)
}
}
#[derive(Debug, Clone, PartialEq, strum::AsRefStr, strum::EnumIs, PartialOrd, Eq, Ord, Hash)]
pub enum Type {
Single(Cow<'static, str>),
Value(Box<Type>),
Alias(Box<Type>),
Tuple(Vec<Type>),
Table(BTreeMap<Index, Type>),
Union(Vec<Type>),
Array(Box<Type>),
Map(Box<Type>, Box<Type>),
Function {
params: Vec<Param>,
returns: Vec<Return>,
},
Enum(Vec<Type>),
Class(Box<TypedClassBuilder>),
Module(Box<TypedModuleBuilder>),
}
impl<T: Into<Type>> std::ops::BitOr<T> for Type {
type Output = Self;
fn bitor(self, rhs: T) -> Self::Output {
match (self, rhs.into()) {
(Self::Union(mut types), Self::Union(other_types)) => {
for ty in other_types {
if !types.contains(&ty) {
types.push(ty);
}
}
Self::Union(types)
}
(Self::Union(mut types), other) => {
if !types.contains(&other) {
types.push(other)
}
Self::Union(types)
}
(current, other) => {
if current == other {
current
} else {
Self::Union(Vec::from([current, other]))
}
}
}
}
}
impl Type {
pub fn literal<T: IntoLuaTypeLiteral>(value: T) -> Self {
Self::Single(value.into_lua_type_literal().into())
}
pub fn named(value: impl Into<Cow<'static, str>>) -> Self {
Self::Single(value.into())
}
pub fn string() -> Self {
Self::Single("string".into())
}
pub fn integer() -> Self {
Self::Single("integer".into())
}
pub fn number() -> Self {
Self::Single("number".into())
}
pub fn boolean() -> Self {
Self::Single("boolean".into())
}
pub fn nil() -> Self {
Self::Single("nil".into())
}
pub fn any() -> Self {
Self::Single("any".into())
}
pub fn lightuserdata() -> Self {
Self::Single("lightuserdata".into())
}
pub fn thread() -> Self {
Self::Single("thread".into())
}
pub fn r#enum(
types: impl IntoIterator<Item = Type>,
) -> Self {
Self::Enum(types.into_iter().collect())
}
pub fn alias(ty: Type) -> Self {
Self::Alias(Box::new(ty))
}
pub fn array(ty: Type) -> Self {
Self::Array(Box::new(ty))
}
pub fn map(key: Type, value: Type) -> Self {
Self::Map(Box::new(key), Box::new(value))
}
pub fn union(types: impl IntoIterator<Item = Type>) -> Self {
Self::Union(types.into_iter().collect())
}
pub fn tuple(types: impl IntoIterator<Item = Type>) -> Self {
Self::Tuple(types.into_iter().collect())
}
pub fn class(class: TypedClassBuilder) -> Self {
Self::Class(Box::new(class))
}
pub fn module(module: TypedModuleBuilder) -> Self {
Self::Module(Box::new(module))
}
pub fn function<Params: TypedMultiValue, Response: TypedMultiValue>() -> Self {
Self::Function {
params: Params::get_types_as_params(),
returns: Response::get_types()
.into_iter()
.map(|ty| Return { doc: None, ty })
.collect(),
}
}
pub fn table(items: impl IntoIterator<Item=(Index, Type)>) -> Self {
Self::Table(items.into_iter().collect())
}
}
pub trait IntoLuaTypeLiteral {
fn into_lua_type_literal(self) -> String;
}
impl IntoLuaTypeLiteral for String {
fn into_lua_type_literal(self) -> String {
format!("\"{self}\"")
}
}
impl IntoLuaTypeLiteral for &String {
fn into_lua_type_literal(self) -> String {
format!("\"{self}\"")
}
}
impl IntoLuaTypeLiteral for &str {
fn into_lua_type_literal(self) -> String {
format!("\"{self}\"")
}
}
macro_rules! impl_type_literal {
($($lit: ty),* $(,)?) => {
$(
impl IntoLuaTypeLiteral for $lit {
fn into_lua_type_literal(self) -> String {
self.to_string()
}
}
impl IntoLuaTypeLiteral for &$lit {
fn into_lua_type_literal(self) -> String {
self.to_string()
}
}
)*
};
}
impl_type_literal!{
u8, u16, u32, u64, usize, u128,
i8, i16, i32, i64, isize, i128,
f32, f64
}
impl_type_literal!{bool}
pub trait TypedMultiValue {
fn get_types() -> Vec<Type> {
Self::get_types_as_params()
.into_iter()
.map(|v| v.ty)
.collect::<Vec<_>>()
}
fn get_types_as_returns() -> Vec<Return> {
Self::get_types_as_params()
.into_iter()
.map(|v| Return {
doc: None,
ty: v.ty,
})
.collect::<Vec<_>>()
}
fn get_types_as_params() -> Vec<Param>;
}
macro_rules! impl_typed_multi_value {
() => (
impl TypedMultiValue for () {
#[allow(unused_mut)]
#[allow(non_snake_case)]
fn get_types_as_params() -> Vec<Param> {
Vec::new()
}
}
);
($($name:ident) +) => (
impl<$($name,)* > TypedMultiValue for ($($name,)*)
where $($name: Typed,)*
{
#[allow(unused_mut)]
#[allow(non_snake_case)]
fn get_types_as_params() -> Vec<Param> {
Vec::from([
$($name::as_param(),)*
])
}
}
);
}
impl<A> TypedMultiValue for A
where
A: Typed,
{
fn get_types_as_params() -> Vec<Param> {
Vec::from([A::as_param()])
}
}
impl_typed_multi_value!(A B C D E F G H I J K L M N O P);
impl_typed_multi_value!(A B C D E F G H I J K L M N O);
impl_typed_multi_value!(A B C D E F G H I J K L M N);
impl_typed_multi_value!(A B C D E F G H I J K L M);
impl_typed_multi_value!(A B C D E F G H I J K L);
impl_typed_multi_value!(A B C D E F G H I J K);
impl_typed_multi_value!(A B C D E F G H I J);
impl_typed_multi_value!(A B C D E F G H I);
impl_typed_multi_value!(A B C D E F G H);
impl_typed_multi_value!(A B C D E F G);
impl_typed_multi_value!(A B C D E F);
impl_typed_multi_value!(A B C D E);
impl_typed_multi_value!(A B C D);
impl_typed_multi_value!(A B C);
impl_typed_multi_value!(A B);
impl_typed_multi_value!(A);
impl_typed_multi_value!();
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct Field {
pub ty: Type,
pub doc: Option<Cow<'static, str>>,
}
impl Field {
pub fn new(ty: Type, doc: impl IntoDocComment) -> Self {
Self {
ty,
doc: doc.into_doc_comment()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Func {
pub params: Vec<Param>,
pub returns: Vec<Return>,
pub doc: Option<Cow<'static, str>>,
}
impl Func {
pub fn new<Params, Returns>(doc: impl IntoDocComment) -> Self
where
Params: TypedMultiValue,
Returns: TypedMultiValue,
{
Self {
params: Params::get_types_as_params(),
returns: Returns::get_types_as_returns(),
doc: doc.into_doc_comment()
}
}
}
pub trait IntoDocComment {
fn into_doc_comment(self) -> Option<Cow<'static, str>>;
}
impl IntoDocComment for String {
fn into_doc_comment(self) -> Option<Cow<'static, str>> {
Some(self.into())
}
}
impl IntoDocComment for &str {
fn into_doc_comment(self) -> Option<Cow<'static, str>> {
Some(self.to_string().into())
}
}
impl IntoDocComment for () {
fn into_doc_comment(self) -> Option<Cow<'static, str>> {
None
}
}
impl IntoDocComment for Option<String> {
fn into_doc_comment(self) -> Option<Cow<'static, str>> {
self.map(|v| v.into())
}
}