retroshield_z80_workbench/
codegen.rs

1//! Core Z80 code generation engine
2//!
3//! Provides the fundamental emit/label/fixup machinery for building Z80 ROMs.
4
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::Write;
8
9/// Configuration for ROM generation
10#[derive(Clone)]
11pub struct RomConfig {
12    /// Origin address (where ROM starts in memory)
13    pub org: u16,
14    /// Stack top address
15    pub stack_top: u16,
16    /// RAM start address
17    pub ram_start: u16,
18}
19
20impl Default for RomConfig {
21    fn default() -> Self {
22        Self {
23            org: 0x0000,
24            stack_top: 0x3FFF,
25            ram_start: 0x2000,
26        }
27    }
28}
29
30/// Core code generator
31pub struct CodeGen {
32    rom: Vec<u8>,
33    labels: HashMap<String, u16>,
34    fixups: Vec<(usize, String)>,
35    config: RomConfig,
36    unique_counter: u32,
37}
38
39impl CodeGen {
40    /// Create a new code generator with default config
41    pub fn new() -> Self {
42        Self::with_config(RomConfig::default())
43    }
44
45    /// Create a new code generator with custom config
46    pub fn with_config(config: RomConfig) -> Self {
47        Self {
48            rom: Vec::new(),
49            labels: HashMap::new(),
50            fixups: Vec::new(),
51            config,
52            unique_counter: 0,
53        }
54    }
55
56    /// Get the ROM configuration
57    pub fn config(&self) -> &RomConfig {
58        &self.config
59    }
60
61    /// Get current emit position (address)
62    pub fn pos(&self) -> u16 {
63        self.config.org + self.rom.len() as u16
64    }
65
66    /// Get current ROM size in bytes
67    pub fn size(&self) -> usize {
68        self.rom.len()
69    }
70
71    /// Generate a unique label name
72    pub fn unique_label(&mut self, prefix: &str) -> String {
73        self.unique_counter += 1;
74        format!("_{}_{}", prefix, self.unique_counter)
75    }
76
77    // ========== Core Emit Functions ==========
78
79    /// Emit raw bytes
80    pub fn emit(&mut self, bytes: &[u8]) {
81        self.rom.extend_from_slice(bytes);
82    }
83
84    /// Emit a single byte
85    pub fn emit_byte(&mut self, b: u8) {
86        self.rom.push(b);
87    }
88
89    /// Emit a 16-bit word (little-endian)
90    pub fn emit_word(&mut self, word: u16) {
91        self.rom.push(word as u8);
92        self.rom.push((word >> 8) as u8);
93    }
94
95    /// Emit a null-terminated string
96    pub fn emit_string(&mut self, s: &str) {
97        for b in s.bytes() {
98            self.rom.push(b);
99        }
100        self.rom.push(0);
101    }
102
103    /// Emit a string without null terminator
104    pub fn emit_string_raw(&mut self, s: &str) {
105        for b in s.bytes() {
106            self.rom.push(b);
107        }
108    }
109
110    // ========== Label Management ==========
111
112    /// Define a label at current position
113    pub fn label(&mut self, name: &str) {
114        self.labels.insert(name.to_string(), self.pos());
115    }
116
117    /// Check if a label exists
118    pub fn has_label(&self, name: &str) -> bool {
119        self.labels.contains_key(name)
120    }
121
122    /// Get label address (if defined)
123    pub fn get_label(&self, name: &str) -> Option<u16> {
124        self.labels.get(name).copied()
125    }
126
127    /// Record a fixup for later resolution (emits placeholder word)
128    pub fn fixup(&mut self, name: &str) {
129        self.fixups.push((self.rom.len(), name.to_string()));
130        self.emit_word(0); // Placeholder
131    }
132
133    /// Resolve all fixups - call after all code is emitted
134    pub fn resolve_fixups(&mut self) {
135        for (offset, name) in &self.fixups {
136            let addr = *self.labels.get(name).unwrap_or_else(|| {
137                panic!("Undefined label: {}", name)
138            });
139            self.rom[*offset] = addr as u8;
140            self.rom[*offset + 1] = (addr >> 8) as u8;
141        }
142    }
143
144    /// Emit a relative jump offset (for JR, DJNZ)
145    /// target_label must already be defined
146    pub fn emit_relative(&mut self, target_label: &str) {
147        let target = *self.labels.get(target_label).unwrap_or_else(|| {
148            panic!("Undefined label for relative jump: {}", target_label)
149        });
150        let current = self.pos() + 1; // +1 because offset is from after the offset byte
151        let offset = (target as i32 - current as i32) as i8;
152        self.emit_byte(offset as u8);
153    }
154
155    // ========== Output ==========
156
157    /// Get the raw ROM bytes
158    pub fn rom(&self) -> &[u8] {
159        &self.rom
160    }
161
162    /// Get mutable access to ROM bytes (for patching relative jumps, etc.)
163    pub fn rom_mut(&mut self) -> &mut Vec<u8> {
164        &mut self.rom
165    }
166
167    /// Write ROM to binary file
168    pub fn write_bin(&self, path: &str) -> std::io::Result<()> {
169        let mut file = File::create(path)?;
170        file.write_all(&self.rom)?;
171        Ok(())
172    }
173
174    /// Write ROM as Intel HEX format
175    pub fn write_hex(&self, path: &str) -> std::io::Result<()> {
176        let mut file = File::create(path)?;
177
178        for (i, chunk) in self.rom.chunks(16).enumerate() {
179            let addr = self.config.org + (i * 16) as u16;
180            let len = chunk.len() as u8;
181
182            // Calculate checksum
183            let mut checksum: u8 = len;
184            checksum = checksum.wrapping_add((addr >> 8) as u8);
185            checksum = checksum.wrapping_add(addr as u8);
186            // Record type 00 = data
187            for &b in chunk {
188                checksum = checksum.wrapping_add(b);
189            }
190            checksum = (!checksum).wrapping_add(1);
191
192            write!(file, ":{:02X}{:04X}00", len, addr)?;
193            for &b in chunk {
194                write!(file, "{:02X}", b)?;
195            }
196            writeln!(file, "{:02X}", checksum)?;
197        }
198
199        // End of file record
200        writeln!(file, ":00000001FF")?;
201        Ok(())
202    }
203}
204
205impl Default for CodeGen {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_emit_basic() {
217        let mut cg = CodeGen::new();
218        cg.emit(&[0x00, 0x01, 0x02]);
219        assert_eq!(cg.size(), 3);
220        assert_eq!(cg.rom(), &[0x00, 0x01, 0x02]);
221    }
222
223    #[test]
224    fn test_emit_byte() {
225        let mut cg = CodeGen::new();
226        cg.emit_byte(0xAA);
227        cg.emit_byte(0xBB);
228        assert_eq!(cg.rom(), &[0xAA, 0xBB]);
229    }
230
231    #[test]
232    fn test_emit_word() {
233        let mut cg = CodeGen::new();
234        cg.emit_word(0x1234);
235        assert_eq!(cg.rom(), &[0x34, 0x12]); // Little-endian
236    }
237
238    #[test]
239    fn test_emit_string() {
240        let mut cg = CodeGen::new();
241        cg.emit_string("Hi");
242        assert_eq!(cg.rom(), &[b'H', b'i', 0x00]); // Null-terminated
243    }
244
245    #[test]
246    fn test_emit_string_raw() {
247        let mut cg = CodeGen::new();
248        cg.emit_string_raw("Hi");
249        assert_eq!(cg.rom(), &[b'H', b'i']); // No null terminator
250    }
251
252    #[test]
253    fn test_labels_and_fixups() {
254        let mut cg = CodeGen::new();
255        cg.emit(&[0xC3]); // JP
256        cg.fixup("target");
257        cg.emit(&[0x00]); // NOP
258        cg.label("target");
259        cg.emit(&[0xC9]); // RET
260        cg.resolve_fixups();
261
262        // JP should point to address 4 (org=0, JP=1, addr=2, NOP=1, target=4)
263        assert_eq!(cg.rom()[1], 0x04);
264        assert_eq!(cg.rom()[2], 0x00);
265    }
266
267    #[test]
268    fn test_unique_label() {
269        let mut cg = CodeGen::new();
270        let l1 = cg.unique_label("loop");
271        let l2 = cg.unique_label("loop");
272        assert_ne!(l1, l2);
273    }
274
275    #[test]
276    fn test_has_label() {
277        let mut cg = CodeGen::new();
278        assert!(!cg.has_label("foo"));
279        cg.label("foo");
280        assert!(cg.has_label("foo"));
281    }
282
283    #[test]
284    fn test_get_label() {
285        let mut cg = CodeGen::new();
286        cg.emit(&[0x00, 0x00, 0x00]); // 3 bytes
287        cg.label("here");
288        assert_eq!(cg.get_label("here"), Some(3));
289        assert_eq!(cg.get_label("nowhere"), None);
290    }
291
292    #[test]
293    fn test_pos() {
294        let mut cg = CodeGen::new();
295        assert_eq!(cg.pos(), 0);
296        cg.emit(&[0x00, 0x00]);
297        assert_eq!(cg.pos(), 2);
298        cg.emit_word(0x1234);
299        assert_eq!(cg.pos(), 4);
300    }
301
302    #[test]
303    fn test_config() {
304        let config = RomConfig {
305            org: 0x8000,
306            stack_top: 0xFFFF,
307            ram_start: 0xC000,
308        };
309        let mut cg = CodeGen::with_config(config);
310        assert_eq!(cg.pos(), 0x8000);
311        cg.emit(&[0x00]);
312        assert_eq!(cg.pos(), 0x8001);
313    }
314
315    #[test]
316    fn test_rom_mut() {
317        let mut cg = CodeGen::new();
318        cg.emit(&[0x00, 0x00, 0x00]);
319        cg.rom_mut()[1] = 0xFF;
320        assert_eq!(cg.rom(), &[0x00, 0xFF, 0x00]);
321    }
322
323    #[test]
324    fn test_default() {
325        let cg = CodeGen::default();
326        assert_eq!(cg.size(), 0);
327        assert_eq!(cg.pos(), 0);
328    }
329}