Skip to main content

i8051_disassembler/
command.rs

1use std::io;
2
3use crate::address::{AddressRange, AddressSpace, AddressValue};
4use crate::db::{Equivalent, Error, Function, Note, NoteId};
5use serde::{Deserialize, Serialize};
6
7pub trait Environment {
8    fn load_file_bytes(
9        &self,
10        file: &str,
11        offset: usize,
12        size: AddressValue,
13    ) -> Result<Vec<u8>, io::Error>;
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum Command {
18    SetLabel {
19        space: AddressSpace,
20        offset: AddressValue,
21        label: Option<String>,
22    },
23    SetEquivalent {
24        space: AddressSpace,
25        offset: AddressValue,
26        equivalent: Equivalent,
27    },
28    AutoDisassemble {
29        space: AddressSpace,
30        start: AddressValue,
31    },
32    ClearEquivalents {
33        space: AddressSpace,
34        offset: AddressValue,
35        size: AddressValue,
36    },
37    SetComment {
38        space: AddressSpace,
39        offset: AddressValue,
40        comment: Option<String>,
41    },
42    MapBytes {
43        space: AddressSpace,
44        offset: AddressValue,
45        file: String,
46        file_offset: usize,
47        size: AddressValue,
48    },
49    ClearBytes {
50        space: AddressSpace,
51        offset: AddressValue,
52        size: AddressValue,
53    },
54    SetConstantBytes {
55        space: AddressSpace,
56        offset: AddressValue,
57        size: AddressValue,
58        value: u8,
59    },
60    SetFunction {
61        space: AddressSpace,
62        offset: AddressValue,
63        function: Option<Function>,
64    },
65    SetNote {
66        space: AddressSpace,
67        range: AddressRange,
68        note: Note,
69    },
70    ClearNote {
71        id: NoteId,
72    },
73}
74
75impl Command {
76    pub fn set_label(space: AddressSpace, offset: AddressValue, label: impl Into<String>) -> Self {
77        Self::SetLabel {
78            space,
79            offset,
80            label: Some(label.into()),
81        }
82    }
83
84    pub fn clear_label(space: AddressSpace, offset: AddressValue) -> Self {
85        Self::SetLabel {
86            space,
87            offset,
88            label: None,
89        }
90    }
91
92    pub fn auto_disassemble(space: AddressSpace, start: AddressValue) -> Self {
93        Self::AutoDisassemble { space, start }
94    }
95
96    pub fn set_equivalent(
97        space: AddressSpace,
98        offset: AddressValue,
99        equivalent: Equivalent,
100    ) -> Self {
101        Self::SetEquivalent {
102            space,
103            offset,
104            equivalent,
105        }
106    }
107
108    pub fn clear_equivalents(
109        space: AddressSpace,
110        offset: AddressValue,
111        size: AddressValue,
112    ) -> Self {
113        Self::ClearEquivalents {
114            space,
115            offset,
116            size,
117        }
118    }
119
120    pub fn set_comment(
121        space: AddressSpace,
122        offset: AddressValue,
123        comment: impl Into<String>,
124    ) -> Self {
125        Self::SetComment {
126            space,
127            offset,
128            comment: Some(comment.into()),
129        }
130    }
131
132    pub fn clear_comment(space: AddressSpace, offset: AddressValue) -> Self {
133        Self::SetComment {
134            space,
135            offset,
136            comment: None,
137        }
138    }
139
140    pub fn map_bytes(
141        space: AddressSpace,
142        offset: AddressValue,
143        file: impl Into<String>,
144        file_offset: usize,
145        size: AddressValue,
146    ) -> Self {
147        Self::MapBytes {
148            space,
149            offset,
150            file: file.into(),
151            file_offset,
152            size,
153        }
154    }
155
156    pub fn clear_bytes(space: AddressSpace, offset: AddressValue, size: AddressValue) -> Self {
157        Self::ClearBytes {
158            space,
159            offset,
160            size,
161        }
162    }
163
164    pub fn set_constant_bytes(
165        space: AddressSpace,
166        offset: AddressValue,
167        size: AddressValue,
168        value: u8,
169    ) -> Self {
170        Self::SetConstantBytes {
171            space,
172            offset,
173            size,
174            value,
175        }
176    }
177
178    pub fn set_function(space: AddressSpace, offset: AddressValue, function: Function) -> Self {
179        Self::SetFunction {
180            space,
181            offset,
182            function: Some(function),
183        }
184    }
185
186    pub fn clear_function(space: AddressSpace, offset: AddressValue) -> Self {
187        Self::SetFunction {
188            space,
189            offset,
190            function: None,
191        }
192    }
193
194    pub fn apply(
195        self,
196        db: &mut crate::db::Db,
197        env: Option<&dyn Environment>,
198    ) -> Result<Vec<Command>, crate::db::Error> {
199        match self {
200            Command::SetLabel {
201                space,
202                offset,
203                label,
204            } => {
205                let region = db.region_mut(space);
206                let before = region.get_label(offset).map(str::to_owned);
207                match label.as_deref() {
208                    Some(label) => region.set_label(offset, label),
209                    None => region.clear_label(offset),
210                }
211                Ok(vec![Command::SetLabel {
212                    space,
213                    offset,
214                    label: before,
215                }])
216            }
217            Command::SetEquivalent {
218                space,
219                offset,
220                equivalent,
221            } => {
222                let region = db.region_mut(space);
223                let span = region.equivalent_span(offset, &equivalent)?;
224                let before = region.snapshot_equivalents(offset, span);
225                region.set_equivalent(offset, equivalent)?;
226                let mut undo = vec![Command::ClearEquivalents {
227                    space,
228                    offset,
229                    size: span,
230                }];
231                for (start, range) in before {
232                    undo.push(Command::SetEquivalent {
233                        space,
234                        offset: start,
235                        equivalent: range.equivalent,
236                    });
237                }
238                Ok(undo)
239            }
240            Command::AutoDisassemble { space, start } => {
241                let region = db.region_mut(space);
242                let addresses = region.auto_disassemble(start).success;
243                Ok(addresses
244                    .into_iter()
245                    .map(|addr| Command::ClearEquivalents {
246                        space,
247                        offset: addr,
248                        size: 1,
249                    })
250                    .collect())
251            }
252            Command::ClearEquivalents {
253                space,
254                offset,
255                size,
256            } => {
257                let region = db.region_mut(space);
258                let before = region.snapshot_equivalents(offset, size);
259                region.clear_equivalents(offset, size);
260                let mut undo = Vec::new();
261                for (start, range) in before {
262                    undo.push(Command::SetEquivalent {
263                        space,
264                        offset: start,
265                        equivalent: range.equivalent,
266                    });
267                }
268                Ok(undo)
269            }
270            Command::SetComment {
271                space,
272                offset,
273                comment,
274            } => {
275                let region = db.region_mut(space);
276                let before = region.get_comment(offset).map(str::to_owned);
277                match comment.as_deref() {
278                    Some(comment) => region.set_comment(offset, comment),
279                    None => region.clear_comment(offset),
280                }
281                Ok(vec![Command::SetComment {
282                    space,
283                    offset,
284                    comment: before,
285                }])
286            }
287            Command::MapBytes {
288                space,
289                offset,
290                file,
291                file_offset,
292                size,
293            } => {
294                let region = db.region_mut(space);
295                let Some(env) = env else {
296                    return Err(Error::NoEnvironment);
297                };
298                let bytes = env
299                    .load_file_bytes(&file, file_offset, size)
300                    .map_err(Error::Io)?;
301                let size = bytes.len() as AddressValue;
302                let before = region.snapshot_byte_ranges(offset, size);
303                region.map_bytes(&file, file_offset, offset, &bytes);
304                Ok(undo_byte_ranges(space, offset, size, before))
305            }
306            Command::ClearBytes {
307                space,
308                offset,
309                size,
310            } => {
311                let region = db.region_mut(space);
312                let before = region.snapshot_byte_ranges(offset, size);
313                region.clear_bytes(offset, size);
314                Ok(undo_byte_ranges(space, offset, size, before))
315            }
316            Command::SetConstantBytes {
317                space,
318                offset,
319                size,
320                value,
321            } => {
322                let region = db.region_mut(space);
323                let before = region.snapshot_byte_ranges(offset, size);
324                region.set_constant(offset, size, value);
325                Ok(undo_byte_ranges(space, offset, size, before))
326            }
327            Command::SetFunction {
328                space,
329                offset,
330                function,
331            } => {
332                let region = db.region_mut(space);
333                let before = region.get_function(offset).cloned();
334                match function {
335                    Some(function) => region.set_function(function),
336                    None => region.clear_function(offset),
337                }
338                Ok(vec![Command::SetFunction {
339                    space,
340                    offset,
341                    function: before,
342                }])
343            }
344            Command::SetNote { space, range, note } => {
345                let id = note.id.clone();
346                let before = db.notes.note_range(space, &id).and_then(|old_range| {
347                    db.notes
348                        .get(&id)
349                        .cloned()
350                        .map(|old_note| (old_range, old_note))
351                });
352                db.notes.set_address(space, range, note);
353                match before {
354                    Some((old_range, old_note)) => Ok(vec![Command::SetNote {
355                        space,
356                        range: old_range,
357                        note: old_note,
358                    }]),
359                    None => Ok(vec![Command::ClearNote { id }]),
360                }
361            }
362            Command::ClearNote { id } => {
363                let Some((space, range, note)) = db.notes.clear_address(&id) else {
364                    return Ok(vec![]);
365                };
366                Ok(vec![Command::SetNote { space, range, note }])
367            }
368        }
369    }
370}
371
372fn undo_byte_ranges(
373    space: AddressSpace,
374    offset: AddressValue,
375    size: AddressValue,
376    ranges: Vec<(AddressValue, crate::region::ByteRange)>,
377) -> Vec<Command> {
378    use crate::region::ByteRange;
379
380    let mut undo = vec![Command::ClearBytes {
381        space,
382        offset,
383        size,
384    }];
385    for (start, range) in ranges {
386        match range {
387            ByteRange::Mapped(file, file_offset, data) => {
388                undo.push(Command::MapBytes {
389                    space,
390                    offset: start,
391                    file,
392                    file_offset,
393                    size: data.len() as AddressValue,
394                });
395            }
396            ByteRange::Constant(count, value) => {
397                undo.push(Command::SetConstantBytes {
398                    space,
399                    offset: start,
400                    size: count as AddressValue,
401                    value,
402                });
403            }
404        }
405    }
406    undo
407}