Skip to main content

rusteal_codegen/rust_gen/
enums.rs

1use 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}