Krolyxon
Commit 43502f8

Branches

Tags

Refs →

bench51cratesbench51-guisrcpanesdisasm.rs

193 lines · 6.4 KB · Rust

1//! Disassembly window: every instruction of the build with its address,
2//! bytes and label, decoded from code memory as it is now.
3
4use bench51_core::sim::disasm;
5use bench51_core::syntax::{Tok, tokens};
6use eframe::egui::{self, Align2, CursorIcon, FontId, Id, Rect, Sense, Ui, pos2, vec2};
7
8use crate::state::{Action, State};
9use crate::theme::{ACCENT, palette};
10use crate::widgets;
11
12struct Row {
13 line: usize,
14 addr: u16,
15 len: usize,
16 label: String,
17 /// Source text for DB/DW lines, which aren't instructions.
18 data: Option<String>,
19}
20
21fn rows(source: &[String], build: &bench51_core::session::Build) -> Vec<Row> {
22 let mut out = Vec::new();
23 for (line, lo) in build.out.lines.iter().enumerate() {
24 let (Some(addr), false) = (lo.addr, lo.bytes.is_empty()) else {
25 continue;
26 };
27 let text = source.get(line).map_or("", String::as_str);
28 let toks = tokens(text);
29 let mut label = String::new();
30 let mut data = None;
31 for (r, t) in &toks {
32 match t {
33 Tok::Label if label.is_empty() => label = text[r.clone()].to_string(),
34 Tok::Directive => {
35 let rest = &text[r.start..];
36 let rest = rest.split(';').next().unwrap_or(rest).trim_end();
37 data = Some(rest.to_string());
38 break;
39 }
40 Tok::Mnemonic => break,
41 _ => {}
42 }
43 }
44 out.push(Row {
45 line,
46 addr,
47 len: lo.bytes.len(),
48 label,
49 data,
50 });
51 }
52 out
53}
54
55pub fn show(ui: &mut Ui, st: &mut State) {
56 let p = palette(ui.ctx());
57 let Some(doc) = st.docs.get_mut(st.active) else {
58 widgets::empty_state(ui, "No program", "Open or create a source file.");
59 return;
60 };
61 let doc_id = doc.id;
62 let s = &mut doc.session;
63 let Some(build) = &s.build else {
64 widgets::empty_state(ui, "Not built", "Press F7 to assemble.");
65 return;
66 };
67 let rows = rows(&build.source, build);
68 let font = FontId::monospace(13.0);
69 let cw = ui.fonts_mut(|f| f.glyph_width(&font, '0'));
70 let row_h = ui.fonts_mut(|f| f.row_height(&font)) + 6.0;
71 let col_addr = 20.0;
72 let col_bytes = col_addr + 6.0 * cw;
73 let col_label = col_bytes + 10.0 * cw;
74 let col_insn = col_label + 10.0 * cw;
75
76 // Header.
77 let (hdr, _) = ui.allocate_exact_size(vec2(ui.available_width(), 18.0), Sense::hover());
78 for (x, name) in [
79 (col_addr, "Addr"),
80 (col_bytes, "Bytes"),
81 (col_label, "Label"),
82 (col_insn, "Instruction"),
83 ] {
84 ui.painter().text(
85 pos2(hdr.left() + x, hdr.center().y),
86 Align2::LEFT_CENTER,
87 name.to_uppercase(),
88 FontId::monospace(crate::theme::LABEL),
89 p.text_disabled,
90 );
91 }
92 ui.add_space(4.0);
93
94 let pc = s.sim.pc;
95 let show_pc = !s.running || s.speed <= 2;
96 let pc_row = rows.iter().position(|r| r.addr == pc);
97 let bps = s.breakpoints.clone();
98
99 // Follow the PC whenever it moves while stopped.
100 let last_id = Id::unique(("disasm_last_pc", doc_id));
101 let last = ui.data(|d| d.get_temp::<u16>(last_id));
102 let mut area = egui::ScrollArea::vertical()
103 .id_salt(("disasm", doc_id))
104 .auto_shrink([false; 2]);
105 if show_pc && last != Some(pc) {
106 ui.data_mut(|d| d.insert_temp(last_id, pc));
107 if let Some(i) = pc_row {
108 let view = ui.available_height();
109 area = area.vertical_scroll_offset((i as f32 * row_h - view / 2.0).max(0.0));
110 }
111 }
112
113 let mut toggle = None;
114 let mut goto = None;
115 area.show_rows(ui, row_h, rows.len(), |ui, range| {
116 ui.spacing_mut().item_spacing.y = 0.0;
117 for i in range {
118 let r = &rows[i];
119 let (rect, resp) =
120 ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
121 let painter = ui.painter();
122 let cy = rect.center().y;
123 let is_pc = show_pc && pc_row == Some(i);
124 if is_pc {
125 painter.rect_filled(rect, 0.0, p.surface_raised);
126 painter.rect_filled(
127 Rect::from_min_size(rect.min, vec2(2.0, rect.height())),
128 0.0,
129 ACCENT,
130 );
131 }
132 if bps.contains(&r.line) {
133 painter.circle_filled(pos2(rect.left() + 10.0, cy), 4.0, ACCENT);
134 }
135 let text = |x: f32, s: String, c| {
136 painter.text(
137 pos2(rect.left() + x, cy),
138 Align2::LEFT_CENTER,
139 s,
140 font.clone(),
141 c,
142 );
143 };
144 text(
145 col_addr,
146 format!("{:04X}", r.addr),
147 if is_pc {
148 p.text_display
149 } else {
150 p.text_secondary
151 },
152 );
153 let bytes: Vec<String> = (0..r.len.min(3))
154 .map(|k| format!("{:02X}", s.sim.code[r.addr.wrapping_add(k as u16) as usize]))
155 .collect();
156 let more = if r.len > 3 { "…" } else { "" };
157 text(col_bytes, bytes.join(" ") + more, p.text_disabled);
158 text(col_label, r.label.clone(), p.text_display);
159 match &r.data {
160 Some(d) => text(col_insn, d.clone(), p.text_secondary),
161 None => text(
162 col_insn,
163 disasm::disassemble(&s.sim.code, r.addr).text,
164 if is_pc {
165 p.text_display
166 } else {
167 p.text_primary
168 },
169 ),
170 }
171 let resp = resp.on_hover_cursor(CursorIcon::Default);
172 if resp.clicked()
173 && resp
174 .interact_pointer_pos()
175 .is_some_and(|pos| pos.x < rect.left() + col_addr)
176 {
177 toggle = Some(r.line);
178 } else if resp.double_clicked() {
179 goto = Some(r.line);
180 }
181 }
182 });
183 if let Some(line) = toggle {
184 s.toggle_breakpoint(line);
185 }
186 if let Some(line) = goto {
187 st.actions.push(Action::Goto {
188 doc: doc_id,
189 line,
190 col: 0,
191 });
192 }
193}