Skip to main content

i8051_disassembler/render/
sdas.rs

1use crate::address::AddressValue;
2use crate::db::DataType;
3use crate::render::Line;
4use crate::render::data::{DataChunk, DataHeuristics};
5
6pub struct SdasWriter {
7    code: String,
8    used_registers: [bool; 256],
9}
10
11impl Default for SdasWriter {
12    fn default() -> Self {
13        Self {
14            code: String::new(),
15            used_registers: [false; 256],
16        }
17    }
18}
19
20impl SdasWriter {
21    pub fn write(&mut self, string: &str) {
22        self.code.push_str(string);
23    }
24
25    pub fn write_line(&mut self, line: &Line) {
26        line_to_sdas(self, line);
27    }
28
29    pub fn use_named_register(&mut self, register: u8) {
30        self.used_registers[register as usize] = true;
31    }
32
33    pub fn into_string(self) -> String {
34        let mut prefix = String::new();
35        let mut register_count = 0;
36        for i in 0..32 {
37            if self.used_registers[i] {
38                if register_count == 0 {
39                    prefix.push_str("; Registers:\n");
40                }
41                register_count += 1;
42                prefix.push_str(&format!("R{}_BANK{} = {}\n", i % 8, i / 8, i));
43            }
44        }
45        if register_count > 0 {
46            prefix.push_str("\n");
47        }
48
49        format!("{prefix}{}", self.code)
50    }
51}
52
53fn line_to_sdas(writer: &mut SdasWriter, line: &Line) {
54    let s = match line {
55        Line::Org { addr } => format!(".org 0x{addr:X}\n"),
56        Line::Blank => "\n".to_string(),
57        Line::Comment { text, .. } => format!("; {text}\n"),
58        Line::Label { name, .. } => format!("{name}:\n"),
59        Line::Instruction { text, direct, .. } => {
60            if let Some(direct) = direct {
61                writer.use_named_register(*direct);
62            }
63            format!("{text}\n")
64        }
65        Line::Data {
66            addr,
67            bytes,
68            data_type,
69            ..
70        } => match data_type {
71            DataType::Word => emit_words(bytes),
72            DataType::Byte => emit_bytes(*addr, bytes),
73            _ => emit_bytes(*addr, bytes),
74        },
75        Line::Raw { addr, bytes } => {
76            let body = emit_unknown_bytes(*addr, bytes);
77            if body.is_empty() {
78                return;
79            }
80            format!("; Unknown bytes at 0x{addr:04X}\n{body}")
81        }
82        Line::Function {
83            name,
84            signature,
85            length,
86            noreturn,
87            ..
88        } => {
89            let sig = signature.as_deref().unwrap_or("()");
90            let mut line = format!("{name}: ; fn {name}{sig}");
91            if *noreturn {
92                line.push_str("; noreturn");
93            } else {
94                line.push_str(&format!("; len = 0x{length:X}"));
95            }
96            format!("{line}\n")
97        }
98    };
99    writer.write(&s);
100}
101
102fn emit_bytes(address: AddressValue, bytes: &[u8]) -> String {
103    let heuristics = DataHeuristics::default();
104    emit_chunks(
105        &heuristics,
106        address,
107        heuristics.iterate(address, None, bytes),
108    )
109}
110
111fn emit_unknown_bytes(base_addr: AddressValue, bytes: &[u8]) -> String {
112    let heuristics = DataHeuristics::default();
113    emit_chunks(
114        &heuristics,
115        base_addr,
116        heuristics.iterate(base_addr, None, bytes),
117    )
118}
119
120fn emit_db_line(row: &[u8]) -> String {
121    let db = row
122        .iter()
123        .map(|b| format!("0x{b:02X}"))
124        .collect::<Vec<_>>()
125        .join(", ");
126    format!("    .db {db}\n")
127}
128
129fn emit_chunks<'a>(
130    heuristics: &DataHeuristics,
131    mut addr: AddressValue,
132    chunks: impl IntoIterator<Item = DataChunk<'a, u8>>,
133) -> String {
134    let mut out = String::new();
135    for chunk in chunks {
136        match chunk {
137            DataChunk::Literal(row) => {
138                for sub in heuristics.literal_rows(addr, row) {
139                    out.push_str(&emit_db_line(sub));
140                }
141                addr += row.len() as AddressValue;
142            }
143            DataChunk::Run(value, len) => {
144                match value {
145                    0 => out.push_str(&format!("    .ds {len}\n")),
146                    value => out.push_str(&format!(
147                        "    .rept {len}\n        .db 0x{value:02X}\n    .endm\n"
148                    )),
149                }
150                addr += len as AddressValue;
151            }
152            DataChunk::BlockRun(row, count) => {
153                let db = row
154                    .iter()
155                    .map(|b| format!("0x{b:02X}"))
156                    .collect::<Vec<_>>()
157                    .join(", ");
158                out.push_str(&format!("    .rept {count}\n        .db {db}\n    .endm\n"));
159                addr += (row.len() * count) as AddressValue;
160            }
161        }
162    }
163    out
164}
165
166fn emit_words(bytes: &[u8]) -> String {
167    let (pairs, tail) = bytes.split_at(bytes.len() - bytes.len() % 2);
168    let words: Vec<String> = pairs
169        .chunks_exact(2)
170        .map(|pair| format!("0x{:04X}", u16::from(pair[0]) | (u16::from(pair[1]) << 8)))
171        .collect();
172    let mut out = String::new();
173    if !words.is_empty() {
174        out.push_str(&format!("    .dw {}\n", words.join(", ")));
175    }
176    if !tail.is_empty() {
177        // TODO: this probably needs some better handling, likely a generic feature instead
178        // of just for words
179        out.push_str("; WARNING: Leftover bytes\n");
180    }
181    out
182}
183
184pub fn prefix() -> String {
185    let mut s = String::new();
186    for i in 0..32 {
187        s.push_str(&format!("R{}BANK{} = {}\n", i % 8, i / 8, i));
188    }
189    s
190}