1use std::collections::BTreeMap;
2use std::range::Range;
3
4use serde::{Deserialize, Serialize};
5
6use crate::address::{AREA_ORDER, AddressSpace, AddressValue, PhysicalAddr, Xref};
7use crate::command::{Command, Environment};
8use crate::labels::{ImplicitLabels, LabelCollector};
9pub use crate::note::{
10 Note, NoteAddressIndex, NoteDb, NoteField, NoteGlobalIndex, NoteId, NotePath, Notes,
11};
12pub use crate::region::{ByteRange, Region};
13use crate::render::Line;
14use crate::render::sdas::SdasWriter;
15
16pub struct Db {
17 regions: BTreeMap<AddressSpace, Region>,
18 pub notes: NoteDb,
19}
20
21impl Db {
22 pub fn new() -> Self {
23 Self {
24 regions: BTreeMap::new(),
25 notes: NoteDb::default(),
26 }
27 }
28
29 pub fn region(&self, space: AddressSpace) -> Option<&Region> {
30 self.regions.get(&space)
31 }
32
33 pub fn region_mut(&mut self, space: AddressSpace) -> &mut Region {
34 self.regions.entry(space).or_insert_with(Region::new)
35 }
36
37 pub fn xrefs_to(&self, target: &PhysicalAddr) -> Vec<Xref> {
38 let mut xrefs = Vec::new();
39 for (&space, region) in &self.regions {
40 xrefs.extend(region.xrefs_to(space, target));
41 }
42 xrefs
43 }
44
45 pub fn xrefs_from(&self, source: &PhysicalAddr) -> Vec<Xref> {
46 let Some(region) = self.regions.get(&source.space) else {
47 return Vec::new();
48 };
49 region.xrefs_from(source)
50 }
51
52 fn implicit_labels(&self) -> ImplicitLabels {
53 let mut label_collector = LabelCollector::default();
54 for (&space, region) in &self.regions {
55 region.collect_refs(space, &mut label_collector);
56 }
57 label_collector.into_implicit_labels()
58 }
59
60 pub fn render(&self, space: AddressSpace) -> Vec<Line> {
61 let implicit_labels = self.implicit_labels();
62
63 self.regions
64 .get(&space)
65 .map(|region| region.render(space, &implicit_labels))
66 .unwrap_or_default()
67 }
68
69 pub fn render_range(
70 &self,
71 space: AddressSpace,
72 start: AddressValue,
73 end: AddressValue,
74 ) -> Vec<Line> {
75 self.render(space)
76 .into_iter()
77 .filter(|line| {
78 let addr = line.addr();
79 addr >= start && addr < end
80 })
81 .collect()
82 }
83
84 pub fn to_sdas(&self) -> String {
85 let mut writer = SdasWriter::default();
86 let implicit_labels = self.implicit_labels();
87
88 for &space in &AREA_ORDER {
89 let Some(region) = self.regions.get(&space) else {
90 continue;
91 };
92 writer.write(space.area_header());
93 for line in region.render(space, &implicit_labels) {
94 writer.write_line(&line);
95 }
96 }
97
98 writer.into_string()
99 }
100
101 pub fn to_commands(&self) -> Vec<Command> {
102 let mut commands = Vec::new();
103 for (&space, region) in &self.regions {
104 commands.extend(region.to_commands(space));
105 }
106 commands
107 }
108
109 pub fn apply(
110 &mut self,
111 command: Command,
112 env: Option<&dyn Environment>,
113 ) -> Result<Vec<Command>, Error> {
114 command.apply(self, env)
115 }
116
117 pub fn space_usage(&self, space: AddressSpace) -> SpaceUsage {
119 self.regions
120 .get(&space)
121 .map(Region::space_usage)
122 .unwrap_or_default()
123 }
124
125 pub fn clear_note(
126 &mut self,
127 id: &NoteId,
128 ) -> Option<(AddressSpace, crate::address::AddressRange, Note)> {
129 self.notes.clear_address(id)
130 }
131
132 pub fn note_tip(&self) -> Option<NoteId> {
133 self.notes.tip()
134 }
135
136 pub fn create_note(&mut self, content: impl Into<String>) -> Note {
137 self.notes.create(content)
138 }
139
140 pub fn get_notes_overlapping(
141 &self,
142 space: AddressSpace,
143 range: impl std::ops::RangeBounds<AddressValue>,
144 ) -> Vec<&Note> {
145 self.notes.get_notes_overlapping(space, range)
146 }
147
148 pub fn get_notes_inside(
149 &self,
150 space: AddressSpace,
151 range: impl std::ops::RangeBounds<AddressValue>,
152 ) -> Vec<&Note> {
153 self.notes.get_notes_inside(space, range)
154 }
155}
156
157impl Default for Db {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Function {
165 pub addr: PhysicalAddr,
166 pub name: String,
167 pub signature: Option<String>,
168 pub length: AddressValue,
169 pub noreturn: bool,
170}
171
172#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
173pub enum DataType {
174 Byte,
175 Word,
176 Dword,
177 Qword,
178 Reference(Box<DataType>),
179 Equivalent(Box<DataType>, String),
180 Array(Box<DataType>, usize),
181 String(usize),
182 Struct(Vec<DataType>),
183}
184
185#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
186pub enum OperandOverride {
187 Label(String),
188 LabelOffset { label: String, offset: i32 },
189 Text(String),
190}
191
192#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
193pub enum Equivalent {
194 Code(Vec<Option<OperandOverride>>),
195 Data(DataType, AddressValue),
196}
197
198impl Equivalent {
199 pub fn kind(&self) -> EquivalentKind {
200 match self {
201 Self::Code(_) => EquivalentKind::Code,
202 Self::Data(_, _) => EquivalentKind::Data,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
208pub enum EquivalentKind {
209 Code,
210 Data,
211}
212
213#[derive(Debug, Clone, Eq, PartialEq)]
214pub struct EquivalentRange {
215 pub end: AddressValue,
216 pub equivalent: Equivalent,
217}
218
219#[derive(Debug, Clone, Eq, PartialEq)]
220pub enum EquivalentAt<'a> {
221 Undefined(Range<AddressValue>),
222 Defined {
223 start: AddressValue,
224 range: &'a EquivalentRange,
225 },
226}
227
228impl<'a> EquivalentAt<'a> {
229 pub fn is_defined(&self) -> bool {
230 matches!(self, Self::Defined { .. })
231 }
232}
233
234#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
235pub struct SpaceUsage {
236 pub code: AddressValue,
238 pub data: AddressValue,
240 pub undefined: AddressValue,
242}
243
244impl SpaceUsage {
245 pub fn total(&self) -> AddressValue {
246 self.code
247 .saturating_add(self.data)
248 .saturating_add(self.undefined)
249 }
250}
251
252#[derive(Debug)]
253pub enum Error {
254 NoEnvironment,
255 Overlap(AddressValue),
256 InvalidAddress(AddressValue),
257 InvalidEquivalent,
258 NotUndefined(AddressValue),
259 Io(std::io::Error),
260}
261
262#[cfg(test)]
263mod tests {
264 use std::collections::HashMap;
265 use std::io;
266
267 use super::*;
268 use crate::address::XrefType;
269 use crate::command::Command;
270 use pretty_assertions::assert_eq;
271
272 static TEST_BINARY: [u8; 12] = [
273 0x02, 0x00, 0x10, 0x74, 0x01, 0xF0, 0x90, 0x00, 0x10, 0x93, 0x80, 0xF7, ];
280
281 struct TestEnvironment {
282 files: HashMap<String, Vec<u8>>,
283 }
284
285 impl TestEnvironment {
286 fn new() -> Self {
287 Self {
288 files: HashMap::new(),
289 }
290 }
291
292 fn with_file(mut self, name: impl Into<String>, bytes: Vec<u8>) -> Self {
293 self.files.insert(name.into(), bytes);
294 self
295 }
296 }
297
298 impl Environment for TestEnvironment {
299 fn load_file_bytes(
300 &self,
301 file: &str,
302 offset: usize,
303 size: AddressValue,
304 ) -> Result<Vec<u8>, io::Error> {
305 let data = self.files.get(file).ok_or_else(|| {
306 io::Error::new(io::ErrorKind::NotFound, format!("file not found: {file}"))
307 })?;
308 let end = offset.saturating_add(size as usize);
309 if end > data.len() {
310 return Err(io::Error::new(
311 io::ErrorKind::UnexpectedEof,
312 "read past end of file",
313 ));
314 }
315 Ok(data[offset..end].to_vec())
316 }
317 }
318
319 fn apply_all(db: &mut Db, commands: Vec<Command>, env: &TestEnvironment) {
320 for command in commands {
321 db.apply(command, Some(env)).unwrap();
322 }
323 }
324
325 fn make_test_db() -> Db {
326 let mut db = Db::new();
327
328 let code = db.region_mut(AddressSpace::Code);
329 code.set_bytes("test.bin", 0, 0, &TEST_BINARY);
330
331 code.set_label(0, "start");
332 code.set_equivalent(0, Equivalent::Code(vec![])).unwrap();
333
334 code.set_comment(3, "Start of loop");
335 code.set_label(3, "loop");
336 code.set_equivalent(3, Equivalent::Code(vec![])).unwrap();
337 code.set_equivalent(5, Equivalent::Code(vec![])).unwrap();
338 code.set_equivalent(6, Equivalent::Code(vec![])).unwrap();
339 code.set_equivalent(9, Equivalent::Code(vec![])).unwrap();
340 code.set_equivalent(10, Equivalent::Code(vec![])).unwrap();
341 db
342 }
343
344 #[test]
345 fn test_db() {
346 let db = make_test_db();
347 assert_eq!(
348 db.xrefs_to(&PhysicalAddr {
349 space: AddressSpace::Code,
350 offset: 3
351 }),
352 vec![Xref {
353 xref_type: XrefType::Jump,
354 from: PhysicalAddr {
355 space: AddressSpace::Code,
356 offset: 10
357 },
358 to: PhysicalAddr {
359 space: AddressSpace::Code,
360 offset: 3
361 },
362 }]
363 );
364
365 assert_eq!(
366 db.xrefs_from(&PhysicalAddr {
367 space: AddressSpace::Code,
368 offset: 10
369 }),
370 vec![Xref {
371 xref_type: XrefType::Jump,
372 from: PhysicalAddr {
373 space: AddressSpace::Code,
374 offset: 10
375 },
376 to: PhysicalAddr {
377 space: AddressSpace::Code,
378 offset: 3
379 },
380 }]
381 );
382
383 let expected = r#"
384.area CODE (CODE,ABS)
385.org 0x0
386
387start:
388 LJMP loc_0010
389; Start of loop
390loop:
391 MOV A,#01
392 MOVX @DPTR,A
393 MOV DPTR,#0x0010
394 MOVC A,@A+DPTR
395 SJMP loop
396loc_0010:
397 "#;
398 assert_eq!(db.to_sdas().trim(), expected.trim());
399 }
400
401 #[test]
402 fn test_db_to_commands() {
403 let db = make_test_db();
404 let commands = db.to_commands();
405 let env = TestEnvironment::new().with_file("test.bin", TEST_BINARY.to_vec());
406 let mut new_db = Db::new();
407 for command in commands {
408 let env =
409 matches!(command, Command::MapBytes { .. }).then_some(&env as &dyn Environment);
410 new_db.apply(command, env).expect("command should apply");
411 }
412 assert_eq!(new_db.to_sdas(), db.to_sdas());
413 }
414
415 #[test]
416 fn test_db_space_usage() {
417 let db = make_test_db();
418 assert_eq!(
419 db.space_usage(AddressSpace::Code),
420 SpaceUsage {
421 code: 12,
422 data: 0,
423 undefined: 0,
424 }
425 );
426 }
427
428 #[test]
429 fn map_bytes_command_undo() {
430 let env = TestEnvironment::new()
431 .with_file("test.bin", vec![1, 2, 3])
432 .with_file("other.bin", vec![4, 5]);
433 let mut db = Db::new();
434 db.apply(
435 Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 3),
436 Some(&env),
437 )
438 .unwrap();
439
440 let code = db.region(AddressSpace::Code).unwrap();
441 assert_eq!(code.bytes_at(0, 3), vec![1, 2, 3]);
442
443 let undo = db
444 .apply(
445 Command::map_bytes(AddressSpace::Code, 0, "other.bin", 0, 2),
446 Some(&env),
447 )
448 .unwrap();
449 assert_eq!(
450 db.region(AddressSpace::Code).unwrap().bytes_at(0, 2),
451 vec![4, 5]
452 );
453
454 apply_all(&mut db, undo, &env);
455 assert_eq!(
456 db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
457 vec![1, 2, 3]
458 );
459 }
460
461 #[test]
462 fn clear_bytes_command_undo() {
463 let env = TestEnvironment::new().with_file("test.bin", vec![1, 2, 3, 4, 5]);
464 let mut db = Db::new();
465 db.apply(
466 Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 5),
467 Some(&env),
468 )
469 .unwrap();
470
471 let undo = db
472 .apply(Command::clear_bytes(AddressSpace::Code, 1, 2), None)
473 .unwrap();
474 assert_eq!(
475 db.region(AddressSpace::Code).unwrap().bytes_at(0, 5),
476 vec![1, 4, 5]
477 );
478
479 apply_all(&mut db, undo, &env);
480 assert_eq!(
481 db.region(AddressSpace::Code).unwrap().bytes_at(0, 5),
482 vec![1, 2, 3, 4, 5]
483 );
484 }
485
486 #[test]
487 fn set_constant_bytes_command_undo() {
488 let env = TestEnvironment::new().with_file("test.bin", vec![1, 2, 3]);
489 let mut db = Db::new();
490 db.apply(
491 Command::map_bytes(AddressSpace::Code, 0, "test.bin", 0, 3),
492 Some(&env),
493 )
494 .unwrap();
495
496 let undo = db
497 .apply(
498 Command::set_constant_bytes(AddressSpace::Code, 0, 2, 0xFF),
499 None,
500 )
501 .unwrap();
502 assert_eq!(
503 db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
504 vec![0xFF, 0xFF, 3]
505 );
506
507 apply_all(&mut db, undo, &env);
508 assert_eq!(
509 db.region(AddressSpace::Code).unwrap().bytes_at(0, 3),
510 vec![1, 2, 3]
511 );
512 }
513}