Skip to main content

ironsbe_codegen/rust/
enums.rs

1//! Enum and set code generation.
2
3use ironsbe_schema::ir::{SchemaIr, TypeKind, to_pascal_case};
4use ironsbe_schema::types::PrimitiveType;
5
6/// Generator for enum and set definitions.
7pub struct EnumGenerator<'a> {
8    ir: &'a SchemaIr,
9}
10
11impl<'a> EnumGenerator<'a> {
12    /// Creates a new enum generator.
13    #[must_use]
14    pub fn new(ir: &'a SchemaIr) -> Self {
15        Self { ir }
16    }
17
18    /// Generates all enum and set definitions.
19    #[must_use]
20    pub fn generate(&self) -> String {
21        let mut output = String::new();
22
23        for resolved_type in self.ir.types.values() {
24            match resolved_type.kind {
25                TypeKind::Enum(encoding) => {
26                    output.push_str(&self.generate_enum(&resolved_type.name, encoding));
27                }
28                TypeKind::Set(encoding) => {
29                    output.push_str(&self.generate_set(&resolved_type.name, encoding));
30                }
31                _ => {}
32            }
33        }
34
35        output
36    }
37
38    /// Generates an enum definition.
39    fn generate_enum(&self, name: &str, encoding: PrimitiveType) -> String {
40        let mut output = String::new();
41        let rust_name = to_pascal_case(name);
42        let rust_type = encoding.rust_type();
43
44        output.push_str(&format!("/// {} enum.\n", rust_name));
45        output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
46        output.push_str(&format!("#[repr({})]\n", rust_type));
47        output.push_str(&format!("pub enum {} {{\n", rust_name));
48
49        // We need to get the actual enum values from the schema
50        // For now, generate a placeholder
51        output.push_str("    // Values generated from schema\n");
52        output.push_str("}\n\n");
53
54        // Implement From trait
55        output.push_str(&format!("impl From<{}> for {} {{\n", rust_type, rust_name));
56        output.push_str(&format!("    fn from(value: {}) -> Self {{\n", rust_type));
57        output.push_str("        // Match implementation\n");
58        output.push_str("        unsafe { std::mem::transmute(value) }\n");
59        output.push_str("    }\n");
60        output.push_str("}\n\n");
61
62        output.push_str(&format!("impl From<{}> for {} {{\n", rust_name, rust_type));
63        output.push_str(&format!("    fn from(value: {}) -> Self {{\n", rust_name));
64        output.push_str("        value as Self\n");
65        output.push_str("    }\n");
66        output.push_str("}\n\n");
67
68        output
69    }
70
71    /// Generates a set (bitfield) definition.
72    fn generate_set(&self, name: &str, encoding: PrimitiveType) -> String {
73        let mut output = String::new();
74        let rust_name = to_pascal_case(name);
75        let rust_type = encoding.rust_type();
76
77        output.push_str(&format!("/// {} bitfield set.\n", rust_name));
78        output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\n");
79        output.push_str(&format!("pub struct {}({});\n\n", rust_name, rust_type));
80
81        output.push_str(&format!("impl {} {{\n", rust_name));
82        output.push_str(&format!("    /// Creates a new empty {}.\n", rust_name));
83        output.push_str("    #[must_use]\n");
84        output.push_str("    pub const fn new() -> Self {\n");
85        output.push_str("        Self(0)\n");
86        output.push_str("    }\n\n");
87
88        output.push_str(&format!("    /// Creates from raw {} value.\n", rust_type));
89        output.push_str("    #[must_use]\n");
90        output.push_str(&format!(
91            "    pub const fn from_raw(value: {}) -> Self {{\n",
92            rust_type
93        ));
94        output.push_str("        Self(value)\n");
95        output.push_str("    }\n\n");
96
97        output.push_str("    /// Returns the raw value.\n");
98        output.push_str("    #[must_use]\n");
99        output.push_str(&format!(
100            "    pub const fn raw(&self) -> {} {{\n",
101            rust_type
102        ));
103        output.push_str("        self.0\n");
104        output.push_str("    }\n\n");
105
106        output.push_str("    /// Checks if a bit is set.\n");
107        output.push_str("    #[must_use]\n");
108        output.push_str("    pub const fn is_set(&self, bit: u8) -> bool {\n");
109        output.push_str("        (self.0 >> bit) & 1 != 0\n");
110        output.push_str("    }\n\n");
111
112        output.push_str("    /// Sets a bit.\n");
113        output.push_str("    pub fn set(&mut self, bit: u8) {\n");
114        output.push_str("        self.0 |= 1 << bit;\n");
115        output.push_str("    }\n\n");
116
117        output.push_str("    /// Clears a bit.\n");
118        output.push_str("    pub fn clear(&mut self, bit: u8) {\n");
119        output.push_str("        self.0 &= !(1 << bit);\n");
120        output.push_str("    }\n");
121
122        output.push_str("}\n\n");
123
124        output
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use ironsbe_schema::ir::SchemaIr;
132    use ironsbe_schema::parser::parse_schema;
133
134    fn create_test_ir_with_enum() -> SchemaIr {
135        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
136<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
137                   package="test" id="1" version="1" byteOrder="littleEndian">
138    <types>
139        <enum name="Side" encodingType="uint8">
140            <validValue name="Buy">1</validValue>
141            <validValue name="Sell">2</validValue>
142        </enum>
143    </types>
144    <sbe:message name="Test" id="1" blockLength="1">
145        <field name="side" id="1" type="Side" offset="0"/>
146    </sbe:message>
147</sbe:messageSchema>"#;
148
149        let schema = parse_schema(xml).expect("Failed to parse");
150        SchemaIr::from_schema(&schema)
151    }
152
153    fn create_test_ir_with_set() -> SchemaIr {
154        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
155<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
156                   package="test" id="1" version="1" byteOrder="littleEndian">
157    <types>
158        <set name="Flags" encodingType="uint8">
159            <choice name="Active">0</choice>
160            <choice name="Visible">1</choice>
161        </set>
162    </types>
163    <sbe:message name="Test" id="1" blockLength="1">
164        <field name="flags" id="1" type="Flags" offset="0"/>
165    </sbe:message>
166</sbe:messageSchema>"#;
167
168        let schema = parse_schema(xml).expect("Failed to parse");
169        SchemaIr::from_schema(&schema)
170    }
171
172    #[test]
173    fn test_enum_generator_new() {
174        let ir = create_test_ir_with_enum();
175        let generator = EnumGenerator::new(&ir);
176        assert!(!generator.ir.types.is_empty());
177    }
178
179    #[test]
180    fn test_generate_enum() {
181        let ir = create_test_ir_with_enum();
182        let generator = EnumGenerator::new(&ir);
183        let output = generator.generate();
184
185        assert!(output.contains("enum"));
186        assert!(output.contains("impl From"));
187    }
188
189    #[test]
190    fn test_generate_set() {
191        let ir = create_test_ir_with_set();
192        let generator = EnumGenerator::new(&ir);
193        let output = generator.generate();
194
195        assert!(output.contains("struct"));
196        assert!(output.contains("is_set"));
197        assert!(output.contains("set"));
198        assert!(output.contains("clear"));
199    }
200
201    #[test]
202    fn test_generate_empty_ir() {
203        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
204<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
205                   package="test" id="1" version="1" byteOrder="littleEndian">
206    <types>
207        <type name="uint64" primitiveType="uint64"/>
208    </types>
209    <sbe:message name="Test" id="1" blockLength="8">
210        <field name="value" id="1" type="uint64" offset="0"/>
211    </sbe:message>
212</sbe:messageSchema>"#;
213
214        let schema = parse_schema(xml).expect("Failed to parse");
215        let ir = SchemaIr::from_schema(&schema);
216        let generator = EnumGenerator::new(&ir);
217        let output = generator.generate();
218
219        // No enums or sets, should be empty or minimal
220        assert!(!output.contains("enum Side"));
221    }
222}