rusteal_codegen/rust_gen/
enums.rs1use crate::naming::escape_reserved;
4use crate::schema::EnumInfo;
5
6pub fn generate_enum(e: &EnumInfo) -> String {
8 let mut out = String::with_capacity(2048);
9
10 let name = &e.name;
11
12 let mut seen_values = std::collections::HashSet::new();
14 let mut seen_names = std::collections::HashSet::new();
15 let mut variants: Vec<(&str, i64)> = Vec::new();
16 for (variant_name, value) in &e.pairs {
17 if variant_name.ends_with("_MAX")
19 || variant_name == "MAX"
20 || variant_name.contains("__MAX")
21 {
22 continue;
23 }
24 if !seen_values.insert(*value) {
26 continue;
27 }
28 let clean = strip_enum_prefix(variant_name, &e.name);
30 let sanitized = sanitize_variant_name(clean);
31 if !seen_names.insert(sanitized) {
32 continue;
33 }
34 variants.push((variant_name, *value));
35 }
36
37 if variants.is_empty() {
38 let repr = underlying_to_repr(&e.underlying_type);
40 out.push_str(&format!(
41 "// Empty enum {name}\n\
42 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n\
43 pub struct {name}(pub {repr});\n"
44 ));
45 out.push_str(&ue_enum_impl(name, repr, "self.0 as i64", "Some(Self(value as {repr}))"));
46 generate_newtype_container_element(&mut out, name, repr);
47 return out;
48 }
49
50 let has_negative = variants.iter().any(|(_, v)| *v < 0);
54 let repr = if has_negative {
55 underlying_to_signed_repr(&e.underlying_type)
56 } else {
57 underlying_to_repr(&e.underlying_type)
58 };
59
60 if has_negative {
62 let mut seen_normalized = std::collections::HashSet::new();
63 variants.retain(|(_, v)| {
64 let normalized = normalize_to_signed(*v, &e.underlying_type);
65 seen_normalized.insert(normalized)
66 });
67 }
68
69 out.push_str(&format!(
71 "#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n\
72 #[repr({repr})]\n\
73 pub enum {name} {{\n"
74 ));
75
76 for (variant_name, value) in &variants {
77 let clean_name = strip_enum_prefix(variant_name, name);
78 let safe_name = escape_reserved(&sanitize_variant_name(clean_name));
79 let display_value = if has_negative {
80 normalize_to_signed(*value, &e.underlying_type)
81 } else {
82 *value
83 };
84 out.push_str(&format!(" {safe_name} = {display_value},\n"));
85 }
86
87 out.push_str("}\n");
88
89 out.push_str(&format!(
91 "\nimpl {name} {{\n pub fn from_value(v: {repr}) -> Option<Self> {{\n match v {{\n"
92 ));
93 for (variant_name, value) in &variants {
94 let clean_name = strip_enum_prefix(variant_name, name);
95 let safe_name = escape_reserved(&sanitize_variant_name(clean_name));
96 let display_value = if has_negative {
97 normalize_to_signed(*value, &e.underlying_type)
98 } else {
99 *value
100 };
101 out.push_str(&format!(" {display_value} => Some({name}::{safe_name}),\n"));
102 }
103 out.push_str(" _ => None,\n }\n }\n\n");
104
105 out.push_str(" pub fn display_name(&self) -> &'static str {\n match self {\n");
107 for (variant_name, _) in &variants {
108 let clean_name = strip_enum_prefix(variant_name, name);
109 let safe_name = escape_reserved(&sanitize_variant_name(clean_name));
110 out.push_str(&format!(
111 " {name}::{safe_name} => \"{clean_name}\",\n"
112 ));
113 }
114 out.push_str(" }\n }\n}\n");
115
116 out.push_str(&ue_enum_impl(name, repr, "self as {repr} as i64", "Self::from_value(value as {repr})"));
118
119 generate_enum_container_element(&mut out, name, repr);
121
122 out
123}
124
125fn ue_enum_impl(name: &str, repr: &str, to_i64: &str, from_i64: &str) -> String {
128 let to_i64 = to_i64.replace("{repr}", repr);
129 let from_i64 = from_i64.replace("{repr}", repr);
130 let name_len = name.len();
131 format!(
132 "\nimpl rusteal_core::UeEnum for {name} {{\n\
133 \x20 type Repr = {repr};\n\n\
134 \x20 fn static_enum() -> rusteal_core::UClassHandle {{\n\
135 \x20 static CACHE: std::sync::OnceLock<rusteal_core::UClassHandle> = std::sync::OnceLock::new();\n\
136 \x20 *CACHE.get_or_init(|| unsafe {{\n\
137 \x20 rusteal_core::ffi_dispatch::reflection_find_enum(b\"{name}\\0\".as_ptr(), {name_len})\n\
138 \x20 }})\n\
139 \x20 }}\n\n\
140 \x20 fn to_i64(self) -> i64 {{\n\
141 \x20 {to_i64}\n\
142 \x20 }}\n\n\
143 \x20 fn from_i64(value: i64) -> Option<Self> {{\n\
144 \x20 {from_i64}\n\
145 \x20 }}\n\
146 }}\n"
147 )
148}
149
150fn underlying_to_repr(ut: &str) -> &'static str {
152 match ut {
153 "uint8" => "u8",
154 "int8" => "i8",
155 "uint16" => "u16",
156 "int16" => "i16",
157 "uint32" => "u32",
158 "int32" => "i32",
159 "uint64" => "u64",
160 "int64" => "i64",
161 _ => "u8",
162 }
163}
164
165fn underlying_to_signed_repr(ut: &str) -> &'static str {
167 match ut {
168 "uint8" => "i8",
169 "int8" => "i8",
170 "uint16" => "i16",
171 "int16" => "i16",
172 "uint32" => "i32",
173 "int32" => "i32",
174 "uint64" => "i64",
175 "int64" => "i64",
176 _ => "i8",
177 }
178}
179
180fn strip_enum_prefix<'a>(variant: &'a str, enum_name: &str) -> &'a str {
183 if let Some(rest) = variant.strip_prefix(enum_name)
184 && let Some(rest) = rest.strip_prefix("::")
185 {
186 return rest;
187 }
188 variant
189}
190
191fn normalize_to_signed(value: i64, underlying_type: &str) -> i64 {
194 match underlying_type {
195 "uint8" => (value as u8) as i8 as i64,
196 "int8" => (value as i8) as i64,
197 "uint16" => (value as u16) as i16 as i64,
198 "int16" => (value as i16) as i64,
199 "uint32" => (value as u32) as i32 as i64,
200 "int32" => (value as i32) as i64,
201 _ => value,
202 }
203}
204
205fn generate_enum_container_element(out: &mut String, name: &str, repr: &str) {
207 out.push_str(&format!(
208 "\nunsafe impl rusteal_core::ContainerElement for {name} {{\n\
209 \x20 const BUF_SIZE: u32 = std::mem::size_of::<{repr}>() as u32;\n\n\
210 \x20 #[inline]\n\
211 \x20 unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self {{\n\
212 \x20 unsafe {{ std::mem::transmute::<{repr}, Self>((buf as *const {repr}).read_unaligned()) }}\n\
213 \x20 }}\n\n\
214 \x20 #[inline]\n\
215 \x20 unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 {{\n\
216 \x20 unsafe {{\n\
217 \x20 let raw: {repr} = std::mem::transmute(*self);\n\
218 \x20 (buf as *mut {repr}).write_unaligned(raw);\n\
219 \x20 }}\n\
220 \x20 std::mem::size_of::<{repr}>() as u32\n\
221 \x20 }}\n\
222 }}\n"
223 ));
224}
225
226fn generate_newtype_container_element(out: &mut String, name: &str, repr: &str) {
228 out.push_str(&format!(
229 "\nunsafe impl rusteal_core::ContainerElement for {name} {{\n\
230 \x20 const BUF_SIZE: u32 = std::mem::size_of::<{repr}>() as u32;\n\n\
231 \x20 #[inline]\n\
232 \x20 unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self {{\n\
233 \x20 unsafe {{ Self((buf as *const {repr}).read_unaligned()) }}\n\
234 \x20 }}\n\n\
235 \x20 #[inline]\n\
236 \x20 unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 {{\n\
237 \x20 unsafe {{ (buf as *mut {repr}).write_unaligned(self.0) }};\n\
238 \x20 std::mem::size_of::<{repr}>() as u32\n\
239 \x20 }}\n\
240 }}\n"
241 ));
242}
243
244fn sanitize_variant_name(name: &str) -> String {
246 let mut result = String::with_capacity(name.len());
247 for (i, ch) in name.chars().enumerate() {
248 if ch.is_alphanumeric() || ch == '_' {
249 if i == 0 && ch.is_ascii_digit() {
250 result.push('_');
251 }
252 result.push(ch);
253 } else {
254 result.push('_');
255 }
256 }
257 if result.is_empty() {
258 result.push_str("_Unknown");
259 }
260 result
261}