clifford_codegen/codegen/
conversions.rs1use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8
9use crate::algebra::Algebra;
10use crate::spec::{AlgebraSpec, TypeSpec};
11
12pub struct ConversionsGenerator<'a> {
47 spec: &'a AlgebraSpec,
49 algebra: &'a Algebra,
51}
52
53impl<'a> ConversionsGenerator<'a> {
54 pub fn new(spec: &'a AlgebraSpec, algebra: &'a Algebra) -> Self {
56 Self { spec, algebra }
57 }
58
59 pub fn generate_conversions_file(&self) -> TokenStream {
61 let header = self.generate_header();
62 let imports = self.generate_imports();
63 let to_mv = self.generate_all_to_multivector();
64 let from_mv = self.generate_all_from_multivector();
65
66 quote! {
67 #header
68 #imports
69
70 #to_mv
74
75 #from_mv
79 }
80 }
81
82 fn generate_header(&self) -> TokenStream {
84 let name = &self.spec.name;
85 let header_doc = format!(
86 r#"//! Multivector conversions for {}.
87//!
88//! This file is auto-generated by clifford-codegen.
89//! Do not edit manually."#,
90 name
91 );
92
93 header_doc.parse().unwrap_or_else(|_| quote! {})
94 }
95
96 fn generate_imports(&self) -> TokenStream {
98 let signature_name = self.generate_signature_name();
99 let type_names: Vec<_> = self
100 .spec
101 .types
102 .iter()
103 .filter(|t| t.alias_of.is_none())
104 .map(|t| format_ident!("{}", t.name))
105 .collect();
106
107 quote! {
108 use crate::algebra::Multivector;
109 use crate::basis::Blade;
110 use crate::scalar::Float;
111 use crate::signature::#signature_name;
112 use super::types::{#(#type_names),*};
113 }
114 }
115
116 fn generate_signature_name(&self) -> proc_macro2::Ident {
121 let sig = &self.spec.signature;
122 let (p, q, r) = (sig.p, sig.q, sig.r);
123
124 let sig_name = match (p, q, r) {
126 (2, 0, 0) => "Euclidean2",
128 (3, 0, 0) => "Euclidean3",
129
130 (2, 0, 1) => "Projective2",
132 (3, 0, 1) => "Projective3",
133
134 (4, 1, 0) => "Conformal3",
136
137 (1, 3, 0) => "Minkowski4",
139
140 _ => {
142 return format_ident!("Cl{}_{}_{}", p, q, r);
143 }
144 };
145 format_ident!("{}", sig_name)
146 }
147
148 fn generate_all_to_multivector(&self) -> TokenStream {
150 let impls: Vec<TokenStream> = self
151 .spec
152 .types
153 .iter()
154 .filter(|t| t.alias_of.is_none())
155 .map(|ty| self.generate_to_multivector(ty))
156 .collect();
157
158 quote! { #(#impls)* }
159 }
160
161 fn generate_to_multivector(&self, ty: &TypeSpec) -> TokenStream {
163 let name = format_ident!("{}", ty.name);
164 let signature_name = self.generate_signature_name();
165
166 let num_blades = 1usize << self.algebra.dim();
168 let mut coeffs: Vec<TokenStream> = vec![quote! { T::zero() }; num_blades];
169
170 for field in &ty.fields {
171 let field_name = format_ident!("{}", field.name);
172 let idx = field.blade_index;
173 if field.sign < 0 {
176 coeffs[idx] = quote! { -value.#field_name() };
177 } else {
178 coeffs[idx] = quote! { value.#field_name() };
179 }
180 }
181
182 quote! {
183 impl<T: Float> From<#name<T>> for Multivector<T, #signature_name> {
184 fn from(value: #name<T>) -> Self {
185 Self::from_coeffs(&[#(#coeffs),*])
186 }
187 }
188 }
189 }
190
191 fn generate_all_from_multivector(&self) -> TokenStream {
193 let impls: Vec<TokenStream> = self
194 .spec
195 .types
196 .iter()
197 .filter(|t| t.alias_of.is_none())
198 .map(|ty| self.generate_from_multivector(ty))
199 .collect();
200
201 quote! { #(#impls)* }
202 }
203
204 fn generate_from_multivector(&self, ty: &TypeSpec) -> TokenStream {
210 let name = format_ident!("{}", ty.name);
211 let signature_name = self.generate_signature_name();
212
213 let field_extracts: Vec<TokenStream> = ty
215 .fields
216 .iter()
217 .map(|field| {
218 let idx = field.blade_index;
219 if field.sign < 0 {
222 quote! { -mv.get(Blade::from_index(#idx)) }
223 } else {
224 quote! { mv.get(Blade::from_index(#idx)) }
225 }
226 })
227 .collect();
228
229 let constructor = quote! { Self::new_unchecked(#(#field_extracts),*) };
230
231 let doc = quote! {
232 };
237
238 quote! {
239 impl<T: Float> From<Multivector<T, #signature_name>> for #name<T> {
240 #doc
241 fn from(mv: Multivector<T, #signature_name>) -> Self {
242 #constructor
243 }
244 }
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::spec::parse_spec;
253
254 #[test]
255 fn generates_to_multivector() {
256 let spec = parse_spec(include_str!("../../algebras/euclidean2.toml")).unwrap();
257 let algebra = Algebra::euclidean(2);
258 let generator = ConversionsGenerator::new(&spec, &algebra);
259
260 let tokens = generator.generate_conversions_file();
261 let code = tokens.to_string();
262
263 assert!(code.contains("From < Vector"));
264 assert!(code.contains("for Multivector"));
265 }
266
267 #[test]
268 fn generates_from_multivector() {
269 let spec = parse_spec(include_str!("../../algebras/euclidean2.toml")).unwrap();
270 let algebra = Algebra::euclidean(2);
271 let generator = ConversionsGenerator::new(&spec, &algebra);
272
273 let tokens = generator.generate_conversions_file();
274 let code = tokens.to_string();
275
276 assert!(code.contains("From < Multivector"));
277 assert!(code.contains("for Vector"));
278 }
279}