retroshield_z80_workbench/
codegen.rs1use std::collections::HashMap;
6use std::fs::File;
7use std::io::Write;
8
9#[derive(Clone)]
11pub struct RomConfig {
12 pub org: u16,
14 pub stack_top: u16,
16 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
30pub 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 pub fn new() -> Self {
42 Self::with_config(RomConfig::default())
43 }
44
45 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 pub fn config(&self) -> &RomConfig {
58 &self.config
59 }
60
61 pub fn pos(&self) -> u16 {
63 self.config.org + self.rom.len() as u16
64 }
65
66 pub fn size(&self) -> usize {
68 self.rom.len()
69 }
70
71 pub fn unique_label(&mut self, prefix: &str) -> String {
73 self.unique_counter += 1;
74 format!("_{}_{}", prefix, self.unique_counter)
75 }
76
77 pub fn emit(&mut self, bytes: &[u8]) {
81 self.rom.extend_from_slice(bytes);
82 }
83
84 pub fn emit_byte(&mut self, b: u8) {
86 self.rom.push(b);
87 }
88
89 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 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 pub fn emit_string_raw(&mut self, s: &str) {
105 for b in s.bytes() {
106 self.rom.push(b);
107 }
108 }
109
110 pub fn label(&mut self, name: &str) {
114 self.labels.insert(name.to_string(), self.pos());
115 }
116
117 pub fn has_label(&self, name: &str) -> bool {
119 self.labels.contains_key(name)
120 }
121
122 pub fn get_label(&self, name: &str) -> Option<u16> {
124 self.labels.get(name).copied()
125 }
126
127 pub fn fixup(&mut self, name: &str) {
129 self.fixups.push((self.rom.len(), name.to_string()));
130 self.emit_word(0); }
132
133 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 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; let offset = (target as i32 - current as i32) as i8;
152 self.emit_byte(offset as u8);
153 }
154
155 pub fn rom(&self) -> &[u8] {
159 &self.rom
160 }
161
162 pub fn rom_mut(&mut self) -> &mut Vec<u8> {
164 &mut self.rom
165 }
166
167 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 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 let mut checksum: u8 = len;
184 checksum = checksum.wrapping_add((addr >> 8) as u8);
185 checksum = checksum.wrapping_add(addr as u8);
186 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 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]); }
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]); }
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']); }
251
252 #[test]
253 fn test_labels_and_fixups() {
254 let mut cg = CodeGen::new();
255 cg.emit(&[0xC3]); cg.fixup("target");
257 cg.emit(&[0x00]); cg.label("target");
259 cg.emit(&[0xC9]); cg.resolve_fixups();
261
262 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]); 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}