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