From c4ee47647a5bf1a06ae88ebb9410117c927868ee Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Mon, 12 Oct 2026 01:31:00 -0400 Subject: Major refactor, generate relation traits with macros --- Cargo.lock | 3 + Cargo.toml | 1 + crates/elfmacros/Cargo.toml | 4 +- crates/elfmacros/src/lib.rs | 182 +++++++++++++++++++++ crates/elfmind/Cargo.toml | 1 + crates/elfmind/src/character.rs | 21 --- crates/elfmind/src/item.rs | 6 - crates/elfmind/src/knowledge.rs | 187 +++++++++++---------- crates/elfmind/src/knowledge/relation.rs | 243 +-------------------------- crates/elfmind/src/knowledge/traits.rs | 273 +++++++++++++++++++++++++++++++ crates/elfmind/src/lib.rs | 8 +- crates/elfmind/src/main.rs | 49 +++--- crates/elfmind/src/noun.rs | 19 +++ crates/elfmind/src/noun/character.rs | 41 +++++ crates/elfmind/src/noun/item.rs | 20 +++ crates/elfmind/src/noun/time.rs | 16 ++ crates/elfmind/src/time.rs | 1 - 17 files changed, 680 insertions(+), 395 deletions(-) delete mode 100644 crates/elfmind/src/character.rs delete mode 100644 crates/elfmind/src/item.rs create mode 100644 crates/elfmind/src/knowledge/traits.rs create mode 100644 crates/elfmind/src/noun.rs create mode 100644 crates/elfmind/src/noun/character.rs create mode 100644 crates/elfmind/src/noun/item.rs create mode 100644 crates/elfmind/src/noun/time.rs delete mode 100644 crates/elfmind/src/time.rs diff --git a/Cargo.lock b/Cargo.lock index e1b26e9..581c0b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -772,6 +772,8 @@ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" name = "elfmacros" version = "0.1.0" dependencies = [ + "itertools 0.15.0", + "proc-macro2", "quote", "syn 3.0.5", ] @@ -784,6 +786,7 @@ dependencies = [ "bincode", "bitflags 2.13.1", "clap", + "elfmacros", "enum-map", "itertools 0.15.0", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 83d51da..1e8bb8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ enum-map = { version = "2.7.3", features = ["serde"] } # enum map serde = { version = "1.0.228", features = ["derive"] } # serialization bincode = { version = "2.0.1", features = ["serde"] } # binary serialization itertools = "0.15.0" # iterator tools +proc-macro2 = "1.0.107" syn = "3.0.5" # parser for proc macros quote = "1.0.47" # quasiquotation for proc macros smallbox = { version = "0.8.8", features = ["coerce"] } # store small objects inline, large ones on the heap diff --git a/crates/elfmacros/Cargo.toml b/crates/elfmacros/Cargo.toml index 9c97e8d..e286820 100644 --- a/crates/elfmacros/Cargo.toml +++ b/crates/elfmacros/Cargo.toml @@ -8,5 +8,7 @@ authors.workspace = true proc-macro = true [dependencies] +proc-macro2 = { workspace = true } syn = { workspace = true } -quote = { workspace = true } \ No newline at end of file +quote = { workspace = true } +itertools = { workspace = true } \ No newline at end of file diff --git a/crates/elfmacros/src/lib.rs b/crates/elfmacros/src/lib.rs index e69de29..e7f05a2 100644 --- a/crates/elfmacros/src/lib.rs +++ b/crates/elfmacros/src/lib.rs @@ -0,0 +1,182 @@ +use proc_macro2::TokenStream; +use quote::{quote, TokenStreamExt}; + +fn title_to_kebab_case(s: &str) -> String { + let mut ret = String::new(); + let mut start = 0; + for (end, _) in s.match_indices(|c: char| c.is_ascii_uppercase()) { + if start != end { + ret.push_str(&s[start..end].to_ascii_lowercase()); + ret.push_str("-"); + } + start = end; + } + ret.push_str(&s[start..].to_ascii_lowercase()); + // s.split_inclusive(|c: char| c.is_ascii_uppercase()) + // .map(|s| s.to_ascii_lowercase()) + // .join("-") + ret +} + +#[proc_macro_derive(Unifiable)] +pub fn derive_unifiable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + let name = input.ident; + let en = if let syn::Data::Enum(e) = &input.data { e } else { + return proc_macro::TokenStream::from(quote! { compile_error!("can only derive unifiable for an enum"); }) + }; + let mut cases = TokenStream::new(); + for v in en.variants.iter() { + let vnm = &v.ident; + cases.append_all( + quote! { + (Self :: #vnm (a), Self::Concrete:: #vnm (b) ) => a.unify(b, u), + }, + ); + } + let expanded = quote! { + impl crate::knowledge::Unifiable for #name { + fn unify(&self, other: &Self::Concrete, u: crate::knowledge::Unification) -> Option { + match (self, other) { + #cases + _ => None, + } + } + } + }; + proc_macro::TokenStream::from(expanded) +} + +#[proc_macro_derive(Substitutable)] +pub fn derive_substitutable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + let name = input.ident; + let en = if let syn::Data::Enum(e) = &input.data { e } else { + return proc_macro::TokenStream::from(quote! { compile_error!("can only derive substitutable for an enum"); }) + }; + let mut subcases = TokenStream::new(); + let mut mvcases = TokenStream::new(); + for v in en.variants.iter() { + let vnm = &v.ident; + subcases.append_all( + quote! { + Self :: #vnm (x) => Self :: #vnm (x.substitute(u)), + }, + ); + mvcases.append_all( + quote! { + Self :: #vnm (x) => Self :: #vnm (x.map_variables(f)), + }, + ); + } + let expanded = quote! { + impl crate::knowledge::Substitutable for #name { + fn substitute(&self, u: &crate::knowledge::Unification) -> Self { + match self { + #subcases + } + } + fn map_variables(&self, f: &mut F) -> Self where F: FnMut(crate::knowledge::Var) -> Var { + match self { + #mvcases + } + } + } + }; + proc_macro::TokenStream::from(expanded) +} + +#[proc_macro_derive(Concrete)] +pub fn derive_concrete(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + let name = input.ident; + let en = if let syn::Data::Enum(e) = &input.data { e } else { + return proc_macro::TokenStream::from(quote! { compile_error!("can only derive concretizable for an enum"); }) + }; + let hnm = syn::Ident::new(&format!("{}Hole", name), name.span()); + let dnm = syn::Ident::new(&format!("{}Discriminants", name), name.span()); + let mut cases = TokenStream::new(); + let mut dcases = TokenStream::new(); + let mut dispcases = TokenStream::new(); + let mut variants = TokenStream::new(); + for v in en.variants.iter() { + let vnm = &v.ident; + let kebab = title_to_kebab_case(&vnm.to_string()); + let mut vtypes = TokenStream::new(); + let mut tuple = false; + if let syn::Fields::Unnamed(fu) = &v.fields { + for f in fu.unnamed.iter() { + if let syn::Type::Tuple(tu) = &f.ty { + tuple = true; + for tty in tu.elems.iter() { + vtypes.append_all(quote! { + crate::knowledge::Hole < #tty > , + }); + } + } else { + let vty = &f.ty; + vtypes.append_all(quote! { + crate::knowledge::Hole < #vty > , + }); + } + } + } + if tuple { + variants.append_all(quote! { + #vnm (( #vtypes )) , + }); + } else { + variants.append_all(quote! { + #vnm ( #vtypes ) , + }); + } + cases.append_all(quote! { + Self :: #vnm (a) => Some( #name :: #vnm ( a.concretize()? )) , + }); + dcases.append_all(quote! { + Self :: #vnm {..} => #dnm :: #vnm , + }); + dispcases.append_all( + quote! { + Self :: #vnm (x) => { write!(f, "{}", #kebab)?; x.display_tuple(f) }, + }, + ); + } + let expanded = quote! { + #[derive(Debug, Clone, PartialEq, Eq, Hash, elfmacros::Unifiable, elfmacros::Substitutable)] + pub enum #hnm { + #variants + } + impl crate::knowledge::Concretizable for #hnm { + type Concrete = #name; + fn concretize(&self) -> Option { + match self { + #cases + _ => None, + } + } + } + impl #hnm { + pub fn discriminant(&self) -> #dnm { + match self { + #dcases + } + } + } + impl std::fmt::Display for #hnm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #dispcases + } + } + } + impl std::fmt::Display for #name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #dispcases + } + } + } + }; + proc_macro::TokenStream::from(expanded) +} diff --git a/crates/elfmind/Cargo.toml b/crates/elfmind/Cargo.toml index 982623d..73d4712 100644 --- a/crates/elfmind/Cargo.toml +++ b/crates/elfmind/Cargo.toml @@ -12,6 +12,7 @@ name = "elfmind" path = "src/main.rs" [dependencies] +elfmacros = { path = "../elfmacros" } teleia = { workspace = true } teleia_macros = { workspace = true } bitflags = { workspace = true } diff --git a/crates/elfmind/src/character.rs b/crates/elfmind/src/character.rs deleted file mode 100644 index bb50605..0000000 --- a/crates/elfmind/src/character.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Character { - Clonk, - Naesten, - Jonze, - Bobo, - Frogrilla, - Hynzzy, - Orbital, - Colin, - Meisaka, - Ctrl, - Antlers, - Tyumici, - Steeledshield, - Creep, - Max, - Hostile, - Owlex, - Penguin, -} diff --git a/crates/elfmind/src/item.rs b/crates/elfmind/src/item.rs deleted file mode 100644 index 5b9182d..0000000 --- a/crates/elfmind/src/item.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Item { - Apple, - BallroomKey, - Lumi, -} diff --git a/crates/elfmind/src/knowledge.rs b/crates/elfmind/src/knowledge.rs index a610328..97ff118 100644 --- a/crates/elfmind/src/knowledge.rs +++ b/crates/elfmind/src/knowledge.rs @@ -1,86 +1,45 @@ -pub mod relation; -pub use relation::*; +pub mod traits; pub use traits::*; +pub mod relation; pub use relation::*; -use crate::time::*; -use crate::item::*; -use crate::character::*; +pub use crate::noun::*; use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use itertools::Itertools; +use strum::IntoDiscriminant; use enum_map::EnumMap; -use strum::IntoDiscriminant; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Var { index: u64 } - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Value { - Var(Var), - Item(Item), - Character(Character), - Time(Time), -} -impl From for Value { fn from(x: Var) -> Self {Value::Var(x)} } -impl From for Value { fn from(x: Item) -> Self {Value::Item(x)} } -impl From for Value { fn from(x: Character) -> Self {Value::Character(x)} } -impl From