1use i8051::{ControlFlow, Instruction};
2use std::collections::BTreeMap;
3use std::range::Range;
4
5use crate::address::{
6 AddressSpace, AddressValue, PhysicalAddr, Xref, branch_target, branch_target_operand_index,
7 xrefs_from_instruction, xrefs_to_target,
8};
9use crate::command::Command;
10use crate::db::{
11 Equivalent, EquivalentAt, EquivalentRange, Error, Function, Line, OperandOverride,
12};
13use crate::labels::{ImplicitLabels, LabelCollector, Labels};
14
15#[derive(Debug, Clone)]
16pub enum ByteRange {
17 Mapped(String, usize, Vec<u8>),
18 Constant(AddressValue, u8),
19}
20
21pub struct Region {
22 byte_ranges: BTreeMap<AddressValue, ByteRange>,
23 equivalents: BTreeMap<AddressValue, EquivalentRange>,
24 labels: BTreeMap<AddressValue, String>,
25 comments: BTreeMap<AddressValue, String>,
26 functions: BTreeMap<AddressValue, Function>,
27}
28
29impl Region {
30 pub fn new() -> Self {
31 Self {
32 byte_ranges: BTreeMap::new(),
33 equivalents: BTreeMap::new(),
34 labels: BTreeMap::new(),
35 comments: BTreeMap::new(),
36 functions: BTreeMap::new(),
37 }
38 }
39
40 pub fn set_bytes(
41 &mut self,
42 file: &str,
43 file_offset: usize,
44 offset: AddressValue,
45 bytes: &[u8],
46 ) {
47 self.map_bytes(file, file_offset, offset, bytes);
48 }
49
50 pub fn map_bytes(
51 &mut self,
52 file: &str,
53 file_offset: usize,
54 offset: AddressValue,
55 bytes: &[u8],
56 ) {
57 if !bytes.is_empty() {
58 self.clear_bytes(offset, bytes.len() as AddressValue);
59 }
60 self.byte_ranges.insert(
61 offset,
62 ByteRange::Mapped(file.to_string(), file_offset, bytes.to_vec()),
63 );
64 }
65
66 pub fn set_constant(&mut self, offset: AddressValue, size: AddressValue, value: u8) {
67 if size == 0 {
68 return;
69 }
70 self.clear_bytes(offset, size);
71 self.byte_ranges
72 .insert(offset, ByteRange::Constant(size, value));
73 }
74
75 pub(crate) fn snapshot_byte_ranges(
76 &self,
77 offset: AddressValue,
78 size: AddressValue,
79 ) -> Vec<(AddressValue, ByteRange)> {
80 if size == 0 {
81 return Vec::new();
82 }
83 let end = offset.saturating_add(size);
84 self.byte_ranges
85 .iter()
86 .filter(|(start, range)| range_end(**start, range) > offset && **start < end)
87 .map(|(start, range)| (*start as AddressValue, range.clone()))
88 .collect()
89 }
90
91 pub fn clear_bytes(&mut self, offset: AddressValue, size: AddressValue) {
92 if size == 0 {
93 return;
94 }
95 let end = offset.saturating_add(size);
96 let mut kept = BTreeMap::new();
97
98 for (&start, range) in &self.byte_ranges {
99 match range {
100 ByteRange::Mapped(file, file_offset, data) => {
101 let range_end = start.saturating_add(data.len() as AddressValue);
102 if range_end <= offset || start >= end {
103 kept.insert(start, range.clone());
104 continue;
105 }
106 if start < offset {
107 let keep_len = offset - start;
108 kept.insert(
109 start,
110 ByteRange::Mapped(
111 file.clone(),
112 *file_offset,
113 data[..keep_len as usize].to_vec(),
114 ),
115 );
116 }
117 if range_end > end {
118 let skip = end.saturating_sub(start);
119 kept.insert(
120 end,
121 ByteRange::Mapped(
122 file.clone(),
123 file_offset.saturating_add(skip as usize),
124 data[skip as _..].to_vec(),
125 ),
126 );
127 }
128 }
129 ByteRange::Constant(count, value) => {
130 let range_end = start.saturating_add(*count);
131 if range_end <= offset || start >= end {
132 kept.insert(start, range.clone());
133 continue;
134 }
135 if start < offset {
136 kept.insert(start, ByteRange::Constant(offset - start, *value));
137 }
138 if range_end > end {
139 kept.insert(end, ByteRange::Constant(range_end - end, *value));
140 }
141 }
142 }
143 }
144
145 self.byte_ranges = kept;
146 }
147
148 pub fn set_equivalent(
149 &mut self,
150 offset: AddressValue,
151 equivalent: Equivalent,
152 ) -> Result<&EquivalentRange, Error> {
153 if matches!(self.equivalent_at(offset), EquivalentAt::Defined { .. }) {
154 return Err(Error::NotUndefined(offset));
155 }
156
157 let span = self.equivalent_span(offset, &equivalent)?;
158 self.validate_equivalent_bounds(offset, span)?;
159 self.validate_no_equivalent_overlap(offset, span)?;
160
161 self.equivalents.insert(
162 offset,
163 EquivalentRange {
164 end: offset.saturating_add(span),
165 equivalent,
166 },
167 );
168 Ok(&self.equivalents[&offset])
169 }
170
171 pub fn clear_equivalents(&mut self, offset: AddressValue, size: AddressValue) {
172 if size == 0 {
173 return;
174 }
175 let end = offset.saturating_add(size);
176 self.equivalents
177 .retain(|&start, range| range.end <= offset || start >= end);
178 }
179
180 pub fn snapshot_equivalents(
181 &self,
182 offset: AddressValue,
183 size: AddressValue,
184 ) -> Vec<(AddressValue, EquivalentRange)> {
185 if size == 0 {
186 return Vec::new();
187 }
188 let end = offset.saturating_add(size);
189 self.equivalents
190 .iter()
191 .filter(|(start, range)| ranges_overlap(**start, range.end, offset, end))
192 .map(|(&start, range)| (start, range.clone()))
193 .collect()
194 }
195
196 pub fn get_equivalent(&self, offset: AddressValue) -> EquivalentAt<'_> {
197 self.equivalent_at(offset)
198 }
199
200 fn equivalent_at(&self, offset: AddressValue) -> EquivalentAt<'_> {
201 if let Some((&start, range)) = self.equivalents.range(..=offset).next_back() {
202 if offset < range.end {
203 return EquivalentAt::Defined { start, range };
204 }
205 }
206 EquivalentAt::Undefined(self.undefined_range_at(offset))
207 }
208
209 fn undefined_range_at(&self, offset: AddressValue) -> Range<AddressValue> {
210 let after = offset.saturating_add(1);
211 let next_eq = self.equivalents.range(after..).next().map(|(&k, _)| k);
212 let next_lbl = self.labels.range(after..).next().map(|(&k, _)| k);
213 let next_cmt = self.comments.range(after..).next().map(|(&k, _)| k);
214 let end = [Some(self.end()), next_eq, next_lbl, next_cmt]
215 .into_iter()
216 .flatten()
217 .min()
218 .unwrap_or(self.end());
219 (offset..end).into()
220 }
221
222 pub fn set_label(&mut self, offset: AddressValue, label: &str) {
223 self.labels
224 .insert(offset as AddressValue, label.to_string());
225 }
226
227 pub fn clear_label(&mut self, offset: AddressValue) {
228 self.labels.remove(&(offset as AddressValue));
229 }
230
231 pub fn get_label(&self, offset: AddressValue) -> Option<&str> {
232 self.labels
233 .get(&(offset as AddressValue))
234 .map(String::as_str)
235 }
236
237 pub fn set_comment(&mut self, offset: AddressValue, comment: &str) {
238 self.comments
239 .insert(offset as AddressValue, comment.to_string());
240 }
241
242 pub fn clear_comment(&mut self, offset: AddressValue) {
243 self.comments.remove(&(offset as AddressValue));
244 }
245
246 pub fn get_comment(&self, offset: AddressValue) -> Option<&str> {
247 self.comments
248 .get(&(offset as AddressValue))
249 .map(String::as_str)
250 }
251
252 pub fn set_function(&mut self, function: Function) {
253 self.functions
254 .insert(function.addr.offset as AddressValue, function);
255 }
256
257 pub fn get_function(&self, offset: AddressValue) -> Option<&Function> {
258 self.functions.get(&(offset as AddressValue))
259 }
260
261 pub fn clear_function(&mut self, offset: AddressValue) {
262 self.functions.remove(&(offset as AddressValue));
263 }
264
265 pub fn bytes_at(&self, offset: AddressValue, size: AddressValue) -> Vec<u8> {
266 (0..size)
267 .filter_map(|i| self.read_byte(offset + i))
268 .collect()
269 }
270
271 pub(crate) fn render(
272 &self,
273 space: AddressSpace,
274 implicit_labels: &ImplicitLabels,
275 ) -> Vec<Line> {
276 let mut lines = Vec::new();
277 let start = self.start();
278 let end = self.end();
279 if start >= end {
280 return lines;
281 }
282
283 let default_labels = Labels::default();
284 let labels = implicit_labels.get(&space).unwrap_or(&default_labels);
285
286 let mut addr = start;
287 let mut need_org = true;
288 while addr < end {
289 if need_org {
290 lines.push(Line::Org { addr });
291 lines.push(Line::Blank);
292 need_org = false;
293 }
294
295 if let Some(function) = self.get_function(addr) {
296 lines.push(Line::Function {
297 addr,
298 name: function.name.clone(),
299 signature: function.signature.clone(),
300 length: function.length,
301 noreturn: function.noreturn,
302 });
303 }
304 if let Some(comment) = self.get_comment(addr) {
305 lines.push(Line::Comment {
306 addr,
307 text: comment.to_string(),
308 });
309 }
310 if let Some(label) = self.get_label(addr) {
311 lines.push(Line::Label {
312 addr,
313 name: label.to_string(),
314 });
315 } else if let Some(label) = labels.get(&addr) {
316 lines.push(Line::Label {
317 addr,
318 name: label.to_string(),
319 });
320 }
321
322 match self.get_equivalent(addr) {
323 EquivalentAt::Defined { start: _, range } => match &range.equivalent {
324 Equivalent::Code(overrides) => {
325 let insn = self
326 .decode_at(addr)
327 .expect("validated code equivalent must decode");
328 let text = self.format_instruction(addr, &insn, overrides);
329 lines.push(Line::Instruction {
330 addr,
331 text,
332 bytes: insn.bytes().to_vec(),
333 });
334 addr = range.end;
335 }
336 Equivalent::Data(data_type, size) => {
337 let bytes = self.bytes_at(addr, *size);
338 lines.push(Line::Data {
339 addr,
340 data_type: data_type.clone(),
341 bytes,
342 });
343 addr = range.end;
344 }
345 },
346 EquivalentAt::Undefined(undefined) => {
347 let span = self.raw_run_until_next_annotation(addr, undefined.end, &labels);
348 if span == 0 {
349 if let Some((&next_mapped, _)) =
350 self.byte_ranges.range(addr.saturating_add(1)..).next()
351 {
352 if next_mapped < undefined.end {
353 addr = next_mapped;
354 need_org = true;
355 continue;
356 }
357 }
358 addr += 1;
359 continue;
360 }
361 let bytes = self.bytes_at(addr, span);
362 lines.push(Line::Raw { addr, bytes });
363 addr += span;
364 }
365 }
366 }
367
368 lines
369 }
370
371 pub(crate) fn to_commands(&self, space: AddressSpace) -> Vec<Command> {
372 let mut commands = Vec::new();
373 for (&offset, range) in &self.byte_ranges {
374 match range {
375 ByteRange::Mapped(file, file_offset, data) => {
376 commands.push(Command::map_bytes(
377 space,
378 offset,
379 file.clone(),
380 *file_offset,
381 data.len() as AddressValue,
382 ));
383 }
384 ByteRange::Constant(size, value) => {
385 commands.push(Command::set_constant_bytes(space, offset, *size, *value));
386 }
387 }
388 }
389 for (&offset, equivalent_range) in &self.equivalents {
390 commands.push(Command::set_equivalent(
391 space,
392 offset,
393 equivalent_range.equivalent.clone(),
394 ));
395 }
396 for (&offset, label) in &self.labels {
397 commands.push(Command::set_label(space, offset, label.clone()));
398 }
399 for (&offset, comment) in &self.comments {
400 commands.push(Command::set_comment(space, offset, comment.clone()));
401 }
402 for (&offset, function) in &self.functions {
403 commands.push(Command::set_function(space, offset, function.clone()));
404 }
405 commands
406 }
407
408 pub(crate) fn xrefs_to(&self, space: AddressSpace, target: &PhysicalAddr) -> Vec<Xref> {
409 let mut xrefs = Vec::new();
410 for (&offset, equivalent_range) in &self.equivalents {
411 if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
412 continue;
413 }
414 let Some(instruction) = self.decode_at(offset) else {
415 continue;
416 };
417 let source = PhysicalAddr {
418 space,
419 offset: offset as AddressValue,
420 };
421 xrefs.extend(xrefs_to_target(&instruction, source, target));
422 }
423 xrefs
424 }
425
426 pub(crate) fn xrefs_from(&self, source: &PhysicalAddr) -> Vec<Xref> {
427 let offset = source.offset;
428 let Some(equivalent_range) = self.equivalents.get(&offset) else {
429 return Vec::new();
430 };
431 if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
432 return Vec::new();
433 }
434 let Some(instruction) = self.decode_at(offset) else {
435 return Vec::new();
436 };
437 xrefs_from_instruction(&instruction, *source)
438 }
439
440 pub(crate) fn collect_refs(&self, space: AddressSpace, refs: &mut LabelCollector) {
442 for (&offset, equivalent_range) in &self.equivalents {
443 if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
444 continue;
445 }
446 let Some(instruction) = self.decode_at(offset) else {
447 continue;
448 };
449 xrefs_from_instruction(&instruction, PhysicalAddr { space, offset })
450 .into_iter()
451 .for_each(|xref| {
452 if self.get_label(xref.to.offset).is_none() {
453 refs.collect(xref.to.space, xref.to.offset, None);
454 }
455 });
456 }
457 }
458
459 fn start(&self) -> AddressValue {
460 [
461 self.byte_ranges.keys().copied().min(),
462 self.equivalents.keys().copied().min(),
463 self.labels.keys().copied().min(),
464 self.comments.keys().copied().min(),
465 ]
466 .into_iter()
467 .flatten()
468 .min()
469 .unwrap_or(0) as AddressValue
470 }
471
472 fn end(&self) -> AddressValue {
473 let mut end = 0;
474 for (&start, range) in &self.byte_ranges {
475 let range_end = match range {
476 ByteRange::Mapped(_, _, data) => start.saturating_add(data.len() as AddressValue),
477 ByteRange::Constant(size, _) => start.saturating_add(*size),
478 };
479 end = end.max(range_end);
480 }
481 for range in self.equivalents.values() {
482 end = end.max(range.end);
483 }
484 end.max(self.start()) as AddressValue
485 }
486
487 fn read_byte(&self, offset: AddressValue) -> Option<u8> {
488 let (&start, range) = self.byte_ranges.range(..=offset).next_back()?;
489 match range {
490 ByteRange::Mapped(_, _, data) => data.get((offset - start) as usize).copied(),
491 ByteRange::Constant(size, value) if offset - start < *size => Some(*value),
492 ByteRange::Constant(_, _) => None,
493 }
494 }
495
496 fn decode_at(&self, address: AddressValue) -> Option<Instruction> {
497 let mut available = Vec::with_capacity(Instruction::MAX_LENGTH);
498
499 for i in 0..Instruction::MAX_LENGTH as AddressValue {
500 if let Some(b) = self.read_byte(address + i) {
501 available.push(b);
502 } else {
503 break;
504 }
505 }
506 if available.is_empty() {
507 return None;
508 }
509
510 let ins = Instruction::decode_from_bytes(address as _, &available);
511 if ins.len() > available.len() {
512 return None;
513 }
514
515 Some(ins)
516 }
517
518 pub(crate) fn equivalent_span(
519 &self,
520 offset: AddressValue,
521 equivalent: &Equivalent,
522 ) -> Result<AddressValue, Error> {
523 match equivalent {
524 Equivalent::Code(_) => self
525 .decode_at(offset)
526 .map(|insn| insn.len() as AddressValue)
527 .ok_or(Error::InvalidEquivalent),
528 Equivalent::Data(_, size) => Ok(*size),
529 }
530 }
531
532 fn validate_equivalent_bounds(
533 &self,
534 offset: AddressValue,
535 span: AddressValue,
536 ) -> Result<(), Error> {
537 for i in 0..span {
538 if self.read_byte(offset + i).is_none() {
539 return Err(Error::InvalidAddress(offset + i));
540 }
541 }
542 Ok(())
543 }
544
545 fn validate_no_equivalent_overlap(
546 &self,
547 offset: AddressValue,
548 span: AddressValue,
549 ) -> Result<(), Error> {
550 let end = offset.saturating_add(span);
551 if let Some((&other_start, other)) = self.equivalents.range(..end).next_back() {
552 if other.end > offset {
553 return Err(Error::Overlap(other_start));
554 }
555 }
556 Ok(())
557 }
558
559 fn raw_run_until_next_annotation(
560 &self,
561 addr: AddressValue,
562 limit: AddressValue,
563 implicit_labels: &Labels,
564 ) -> AddressValue {
565 let after = addr.saturating_add(1);
566 let mut boundary = limit;
567 if let Some((&start, _)) = self.equivalents.range(after..).next() {
568 boundary = boundary.min(start);
569 }
570 if let Some((&start, _)) = self.labels.range(after..).next() {
571 boundary = boundary.min(start);
572 }
573 if let Some((&start, _)) = self.comments.range(after..).next() {
574 boundary = boundary.min(start);
575 }
576 if let Some((&start, _)) = implicit_labels.range(after..).next() {
577 boundary = boundary.min(start);
578 }
579
580 let mut end = addr;
581 while end < boundary {
582 if self.read_byte(end).is_none() {
583 break;
584 }
585 end += 1;
586 }
587 end - addr
588 }
589
590 fn format_instruction(
591 &self,
592 _addr: AddressValue,
593 insn: &Instruction,
594 overrides: &[Option<OperandOverride>],
595 ) -> String {
596 let decoded = insn.as_string();
597 let mut merged = overrides.to_vec();
598
599 if let (Some(target), Some(idx)) = (branch_target(insn), branch_target_operand_index(insn))
600 {
601 while merged.len() <= idx {
602 merged.push(None);
603 }
604 if merged[idx].is_none() {
605 if let Some(label) = self.get_label(target) {
606 merged[idx] = Some(OperandOverride::Label(label.to_string()));
607 }
608 }
609 }
610
611 let text = if merged.iter().all(|o| o.is_none()) {
612 decoded
613 } else {
614 apply_operand_overrides(&decoded, &merged)
615 };
616 sdas_indent_instruction(&text)
617 }
618
619 pub fn auto_disassemble(&mut self, start: u32) -> Vec<AddressValue> {
622 let mut addresses = Vec::new();
623 let mut queue = Vec::new();
624 queue.push(start);
625 while let Some(addr) = queue.pop() {
626 if self.get_equivalent(addr).is_defined() {
627 continue;
628 }
629 addresses.push(addr);
630 let Ok(_) = self.set_equivalent(addr, Equivalent::Code(vec![])) else {
631 return addresses;
632 };
633 if let Some(ins) = self.decode_at(addr) {
634 let flow = ins.control_flow();
635 match flow {
636 ControlFlow::Continue(addr) => queue.push(addr),
637 ControlFlow::Call(next, addr) => {
638 queue.push(next);
639 queue.push(addr);
640 }
641 ControlFlow::Choice(next, addr) => {
642 queue.push(next);
643 queue.push(addr);
644 }
645 ControlFlow::Diverge => {
646 continue;
647 }
648 }
649 }
650 }
651 addresses
652 }
653}
654
655fn sdas_indent_instruction(text: &str) -> String {
656 if let Some((mnemonic, operands)) = text.split_once(' ') {
657 format!(" {mnemonic:<8}{operands}")
658 } else {
659 format!(" {text}")
660 }
661}
662
663fn split_instruction(decoded: &str) -> (&str, Vec<&str>) {
664 let (mnemonic, rest) = decoded.split_once(' ').unwrap_or((decoded, ""));
665 let operands = if rest.is_empty() {
666 Vec::new()
667 } else {
668 rest.split(',').map(str::trim).collect()
669 };
670 (mnemonic, operands)
671}
672
673fn apply_operand_overrides(decoded: &str, overrides: &[Option<OperandOverride>]) -> String {
674 if overrides.is_empty() {
675 return decoded.to_string();
676 }
677 let (mnemonic, operands) = split_instruction(decoded);
678 let mut out = mnemonic.to_string();
679 let operand_count = operands.len().max(overrides.len());
680 for idx in 0..operand_count {
681 if idx > 0 {
682 out.push(',');
683 } else {
684 out.push(' ');
685 }
686 match overrides.get(idx).and_then(|o| o.as_ref()) {
687 Some(OperandOverride::Label(label)) => out.push_str(label),
688 Some(OperandOverride::LabelOffset { label, offset }) => {
689 if *offset >= 0 {
690 out.push_str(&format!("{label}+{offset}"));
691 } else {
692 out.push_str(&format!("{label}{offset}"));
693 }
694 }
695 Some(OperandOverride::Text(text)) => out.push_str(text),
696 None => {
697 if let Some(default) = operands.get(idx) {
698 out.push_str(default);
699 }
700 }
701 }
702 }
703 out
704}
705
706fn ranges_overlap(
707 a_start: AddressValue,
708 a_end: AddressValue,
709 b_start: AddressValue,
710 b_end: AddressValue,
711) -> bool {
712 a_start < b_end && b_start < a_end
713}
714
715fn range_end(start: AddressValue, range: &ByteRange) -> AddressValue {
716 match range {
717 ByteRange::Mapped(_, _, data) => start.saturating_add(data.len() as AddressValue),
718 ByteRange::Constant(size, _) => start.saturating_add(*size),
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use crate::db::{DataType, OperandOverride};
726
727 #[test]
728 fn overlapping_equivalents_are_rejected() {
729 let mut region = Region::new();
730 region.set_bytes(
731 "test.bin",
732 0,
733 0,
734 &[0x02, 0x00, 0x10, 0x74, 0x01, 0x00, 0x00, 0x00],
735 );
736 region.set_equivalent(0, Equivalent::Code(vec![])).unwrap();
737 assert!(matches!(
738 region.set_equivalent(1, Equivalent::Code(vec![])),
739 Err(Error::NotUndefined(1))
740 ));
741 region.set_equivalent(6, Equivalent::Code(vec![])).unwrap();
742 assert!(matches!(
743 region.set_equivalent(4, Equivalent::Data(DataType::Byte, 3)),
744 Err(Error::Overlap(6))
745 ));
746 }
747
748 #[test]
749 fn clear_bytes_splits_straddling_range() {
750 let mut region = Region::new();
751 region.set_bytes("test.bin", 0, 0, &[1, 2, 3, 4, 5]);
752 region.clear_bytes(1, 2);
753 assert_eq!(region.bytes_at(0, 5), vec![1, 4, 5]);
754 }
755
756 #[test]
757 fn decode_at_does_not_require_bytes_at_zero() {
758 let mut region = Region::new();
759 region.set_bytes("test.bin", 0, 0x100, &[0x74, 0x42]);
760 let insn = region.decode_at(0x100).unwrap();
761 assert_eq!(insn.len(), 2);
762 assert_eq!(insn.as_string(), "MOV A,#0x42");
763 }
764
765 #[test]
766 fn decode_at_requires_full_instruction_length() {
767 let mut region = Region::new();
768 region.set_bytes("test.bin", 0, 0, &[0x02, 0x00]);
769 assert!(
770 region.decode_at(0).is_none(),
771 "Expected None, got {:?}",
772 region.decode_at(0).unwrap().as_string()
773 );
774 }
775
776 #[test]
777 fn operand_override_preserves_other_operands() {
778 let mut region = Region::new();
779 region.set_bytes("test.bin", 0, 0, &[0xB5, 0x20, 0x10]);
780 region.set_label(0x13, "target");
781 region
782 .set_equivalent(
783 0,
784 Equivalent::Code(vec![
785 None,
786 None,
787 Some(OperandOverride::Label("target".into())),
788 ]),
789 )
790 .unwrap();
791 let implicit_labels = ImplicitLabels::default();
792 let lines = region.render(AddressSpace::Code, &implicit_labels);
793 let insn = lines
794 .iter()
795 .find_map(|line| match line {
796 Line::Instruction { text, .. } => Some(text.clone()),
797 _ => None,
798 })
799 .unwrap();
800 assert!(insn.contains("RAM(32),target"));
801 }
802
803 #[test]
804 fn render_emits_org_after_unmapped_gap() {
805 let mut region = Region::new();
806 region.set_bytes("test.bin", 0, 0, &[1, 2, 3]);
807 region.set_bytes("test.bin", 3, 0x10, &[4, 5]);
808 let implicit_labels = ImplicitLabels::default();
809 let lines = region.render(AddressSpace::Code, &implicit_labels);
810 let orgs: Vec<_> = lines
811 .iter()
812 .filter_map(|line| match line {
813 Line::Org { addr } => Some(*addr),
814 _ => None,
815 })
816 .collect();
817 assert_eq!(orgs, vec![0, 0x10]);
818 }
819}