cpclib_asm/assembler/
delayed_command.rs

1use std::collections::BTreeMap;
2
3use codespan_reporting::diagnostic::Severity;
4use cpclib_common::event::EventObserver;
5use cpclib_common::itertools::Itertools;
6use cpclib_sna::{
7    AceBreakPoint, AceBrkRuntimeMode, AdvancedRemuBreakPoint, RemuBreakPoint, WabpAnyBreakpoint,
8    WinapeBreakPoint
9};
10#[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
11use {cpclib_common::rayon::prelude::*, rayon_cond::CondIterator};
12
13use super::report::SavedFile;
14use super::save_command::SaveCommand;
15use super::string::PreprocessedFormattedString;
16use super::{Env, EnvEventObserver};
17use crate::error::{AssemblerError, build_simple_error_message};
18use crate::preamble::Z80Span;
19
20trait DelayedCommand {}
21
22#[derive(Debug, Clone)]
23pub struct PrintCommand {
24    pub(crate) prefix: Option<String>,
25    pub(crate) span: Option<Z80Span>,
26    pub(crate) print_or_error: either::Either<PreprocessedFormattedString, AssemblerError>
27}
28
29impl PrintCommand {
30    pub fn relocate(&mut self, span: Z80Span) {
31        self.span.replace(span);
32    }
33}
34#[derive(Debug, Clone)]
35pub struct FailedAssertCommand {
36    failure: AssemblerError
37}
38
39/// Expect an assert error or a exval error
40impl From<AssemblerError> for FailedAssertCommand {
41    fn from(failure: AssemblerError) -> Self {
42        Self { failure }
43    }
44}
45
46impl DelayedCommand for PrintCommand {}
47
48impl DelayedCommand for FailedAssertCommand {}
49
50impl PrintCommand {
51    #[inline]
52    pub fn string_or_error(&self) -> Result<String, AssemblerError> {
53        match &self.print_or_error {
54            either::Either::Left(msg) => {
55                // TODO improve printting + integrate z80span information
56                let file_location = if let Some(span) = &self.span {
57                    let fname = span.filename();
58                    let (line, col) = span.relative_line_and_column();
59
60                    Some((fname, line, col))
61                }
62                else {
63                    None
64                };
65
66                // duplicate code to speed it up
67                let repr = match (&self.prefix, file_location) {
68                    (Some(prefix), Some(loc)) => {
69                        format!("{}{}:{}:{} PRINT: {}", prefix, loc.0, loc.1, loc.2, msg)
70                    },
71
72                    (Some(prefix), None) => {
73                        format!("{} PRINT: {}", prefix, msg)
74                    },
75
76                    (None, Some(loc)) => {
77                        format!("{}:{}:{} PRINT: {}", loc.0, loc.1, loc.2, msg)
78                    },
79
80                    (None, None) => {
81                        format!("PRINT: {}", msg)
82                    }
83                };
84
85                Ok(repr)
86            },
87            either::Either::Right(e) => Err(e.clone())
88        }
89    }
90
91    // XXX The code is the same than string_or_error
92    #[inline]
93    pub fn execute(&self, writer: &dyn EnvEventObserver) -> Result<(), AssemblerError> {
94        match &self.print_or_error {
95            either::Either::Left(msg) => {
96                // TODO improve printting + integrate z80span information
97                let file_location = if let Some(span) = &self.span {
98                    let fname = span.filename();
99                    let (line, col) = span.relative_line_and_column();
100
101                    Some((fname, line, col))
102                }
103                else {
104                    None
105                };
106
107                // duplicate code to speed it up
108                match (&self.prefix, file_location) {
109                    (Some(prefix), Some(loc)) => {
110                        writer.emit_stdout(&format!(
111                            "{}{}:{}:{} PRINT: {}\n",
112                            prefix, loc.0, loc.1, loc.2, msg
113                        ))
114                    },
115
116                    (Some(prefix), None) => {
117                        writer.emit_stdout(&format!("{} PRINT: {}\n", prefix, msg))
118                    },
119
120                    (None, Some(loc)) => {
121                        writer.emit_stdout(&format!("{}:{}:{} PRINT: {}", loc.0, loc.1, loc.2, msg))
122                    },
123
124                    (None, None) => writer.emit_stdout(&format!("PRINT: {}", msg))
125                };
126
127                Ok(())
128            },
129            either::Either::Right(e) => Err(e.clone())
130        }
131    }
132
133    #[inline]
134    pub fn is_print(&self) -> bool {
135        self.print_or_error.is_left()
136    }
137}
138#[derive(Debug, Clone)]
139
140pub struct PauseCommand(Option<Z80Span>);
141
142impl From<Option<Z80Span>> for PauseCommand {
143    fn from(s: Option<Z80Span>) -> Self {
144        Self(s)
145    }
146}
147
148impl PauseCommand {
149    #[inline]
150    pub fn execute(&self, writer: &dyn EnvEventObserver) -> Result<(), AssemblerError> {
151        let msg = "PAUSE - press enter to continue.";
152        writer.emit_stdout(
153            &(if let Some(span) = &self.0 {
154                build_simple_error_message(msg, span, Severity::Note)
155            }
156            else {
157                msg.to_owned()
158            })
159            .to_string()
160        );
161
162        let mut buf = String::new();
163        std::io::stdin().read_line(&mut buf).unwrap();
164        Ok(())
165    }
166
167    pub fn relocate(&mut self, span: Z80Span) {
168        self.0.replace(span);
169    }
170}
171
172#[derive(Debug, Clone)]
173pub enum PrintOrPauseCommand {
174    Print(PrintCommand),
175    Pause(PauseCommand)
176}
177
178impl From<PrintCommand> for PrintOrPauseCommand {
179    fn from(p: PrintCommand) -> Self {
180        PrintOrPauseCommand::Print(p)
181    }
182}
183
184impl From<PauseCommand> for PrintOrPauseCommand {
185    fn from(p: PauseCommand) -> Self {
186        PrintOrPauseCommand::Pause(p)
187    }
188}
189
190impl PrintOrPauseCommand {
191    pub fn execute(&self, writer: &dyn EnvEventObserver) -> Result<(), AssemblerError> {
192        match self {
193            PrintOrPauseCommand::Print(p) => p.execute(writer),
194            PrintOrPauseCommand::Pause(p) => p.execute(writer)
195        }
196    }
197
198    pub fn relocate(&mut self, span: Z80Span) {
199        match self {
200            PrintOrPauseCommand::Print(p) => p.relocate(span),
201            PrintOrPauseCommand::Pause(p) => p.relocate(span)
202        }
203    }
204}
205
206/// Information for a breakpoint:
207/// TODO: add condition
208#[derive(Debug, Clone)]
209pub struct BreakpointCommand {
210    pub(crate) brk: InnerBreakpointCommand,
211    pub(crate) span: Option<Z80Span>
212}
213
214impl BreakpointCommand {
215    pub fn info_repr(&self) -> String {
216        match &self.brk {
217            InnerBreakpointCommand::Simple(brk) => {
218                format! {"PC=&{:X}@{}", brk.address, brk.page}
219            },
220            InnerBreakpointCommand::Advanced(brk) => {
221                format! {"{}", brk}
222            }
223        }
224    }
225}
226
227#[derive(Debug, Clone)]
228pub enum InnerBreakpointCommand {
229    Simple(BreakPointCommandSimple),
230    Advanced(AdvancedRemuBreakPoint)
231}
232
233impl From<AdvancedRemuBreakPoint> for InnerBreakpointCommand {
234    fn from(value: AdvancedRemuBreakPoint) -> Self {
235        Self::Advanced(value)
236    }
237}
238
239impl From<BreakPointCommandSimple> for InnerBreakpointCommand {
240    fn from(value: BreakPointCommandSimple) -> Self {
241        Self::Simple(value)
242    }
243}
244
245#[derive(Debug, Clone)]
246pub struct BreakPointCommandSimple {
247    pub(crate) address: u16,
248    pub(crate) page: u8
249}
250
251impl<T: Into<InnerBreakpointCommand>> From<(T, Option<Z80Span>)> for BreakpointCommand {
252    fn from(value: (T, Option<Z80Span>)) -> Self {
253        Self {
254            brk: value.0.into(),
255            span: value.1
256        }
257    }
258}
259
260impl BreakpointCommand {
261    pub fn new_simple(address: u16, page: u8, span: Option<Z80Span>) -> Self {
262        (BreakPointCommandSimple { address, page }, span).into()
263    }
264
265    // Convert when possible
266    pub fn winape(&self) -> Option<WinapeBreakPoint> {
267        match &self.brk {
268            InnerBreakpointCommand::Simple(brk) => {
269                Some(WinapeBreakPoint::new(brk.address, brk.page))
270            },
271            _ => None
272        }
273    }
274
275    // Convert when possible. ATTENTION, I have not implemented all the case
276    pub fn ace(&self) -> Option<AceBreakPoint> {
277        match &self.brk {
278            InnerBreakpointCommand::Simple(brk) => {
279                Some(AceBreakPoint::new_execution(
280                    brk.address,
281                    AceBrkRuntimeMode::Break,
282                    cpclib_sna::AceMemMapType::Undefined
283                ))
284            },
285            _ => None
286        }
287    }
288
289    pub fn remu(&self) -> RemuBreakPoint {
290        match &self.brk {
291            InnerBreakpointCommand::Simple(brk) => RemuBreakPoint::Memory(brk.address, brk.page),
292            InnerBreakpointCommand::Advanced(brk) => RemuBreakPoint::Advanced(brk.clone())
293        }
294    }
295
296    pub fn wabp(&self) -> WabpAnyBreakpoint {
297        match &self.brk {
298            InnerBreakpointCommand::Simple(brk) => WabpAnyBreakpoint::new(brk.address),
299            InnerBreakpointCommand::Advanced(advanced_remu_break_point) => {
300                unimplemented!("{advanced_remu_break_point} not converted in wabp")
301            }
302        }
303    }
304}
305
306#[derive(Debug, Clone, Default)]
307pub struct DelayedCommands {
308    failed_assert_commands: Vec<FailedAssertCommand>,
309    save_commands: BTreeMap<u8, Vec<SaveCommand>>, // commands are ordered per ga_mmr
310    print_commands: Vec<PrintOrPauseCommand>,
311    breakpoint_commands: Vec<BreakpointCommand>
312}
313
314impl DelayedCommands {
315    pub fn clear(&mut self) {
316        self.failed_assert_commands.clear();
317        self.save_commands.clear();
318        self.print_commands.clear();
319        self.breakpoint_commands.clear();
320    }
321}
322
323/// Commands addition
324impl DelayedCommands {
325    pub fn add_breakpoint_command(&mut self, command: BreakpointCommand) {
326        self.breakpoint_commands.push(command);
327    }
328
329    pub fn add_save_command(&mut self, command: SaveCommand) {
330        self.save_commands
331            .entry(command.ga_mmr())
332            .or_default()
333            .push(command);
334    }
335
336    pub fn get_save_mmrs(&self) -> Vec<u8> {
337        self.save_commands.keys().cloned().collect_vec()
338    }
339
340    /// can save in parallel if all commands can be saved in parallel (we are strict because we miss lots of parallelism)
341    pub fn can_save_in_parallel(&self) -> bool {
342        self.save_commands
343            .values()
344            .all(|s| s.iter().all(|s| s.can_be_saved_in_parallel()))
345    }
346
347    pub fn add_failed_assert_command(&mut self, command: FailedAssertCommand) {
348        self.failed_assert_commands.push(command);
349    }
350
351    pub fn add_print_command(&mut self, command: PrintCommand) {
352        self.add_print_or_pause_command(command.into());
353    }
354
355    pub fn add_pause_command(&mut self, command: PauseCommand) {
356        self.add_print_or_pause_command(command.into());
357    }
358
359    pub fn add_print_or_pause_command(&mut self, command: PrintOrPauseCommand) {
360        self.print_commands.push(command)
361    }
362}
363
364/// Commands execution
365impl DelayedCommands {
366    /// Execute the commands that correspond to the appropriate mmr configuration
367    pub fn execute_save(&self, env: &Env, ga_mmr: u8) -> Result<Vec<SavedFile>, AssemblerError> {
368        #[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
369        let iter = CondIterator::new(&self.save_commands, self.can_save_in_parallel());
370        #[cfg(any(target_arch = "wasm32", not(feature = "rayon")))]
371        let iter = self.save_commands.iter();
372
373        let res = iter
374            .filter_map(|(save_mmr, save_cmd)| {
375                if *save_mmr == ga_mmr {
376                    Some(save_cmd)
377                }
378                else {
379                    None
380                }
381            })
382            .flatten()
383            .map(|cmd| cmd.execute_on(env))
384            .collect::<Result<Vec<_>, AssemblerError>>()?;
385
386        Ok(res)
387    }
388
389    pub fn nb_files_to_save(&self) -> usize {
390        self.save_commands.len()
391    }
392
393    /// Return Ok if no assertion error, Err otherwise
394    pub fn collect_assert_failure(&self) -> Result<(), AssemblerError> {
395        if self.failed_assert_commands.is_empty() {
396            Ok(())
397        }
398        else {
399            Err(AssemblerError::MultipleErrors {
400                errors: self
401                    .failed_assert_commands
402                    .iter()
403                    .map(|a| a.failure.clone())
404                    .collect_vec()
405            })
406        }
407    }
408
409    /// XXX Current version completly ignore pause. TODO find a way to reactivate
410    pub fn execute_print_or_pause(
411        &self,
412        writer: &dyn EnvEventObserver
413    ) -> Result<(), AssemblerError> {
414        let iter = self.print_commands.iter();
415
416        let errors = iter
417            .filter_map(|c| {
418                match c {
419                    PrintOrPauseCommand::Print(p) => {
420                        if p.is_print() {
421                            p.execute(writer);
422                            None
423                        }
424                        else {
425                            Some(p.print_or_error.as_ref().right().unwrap().clone())
426                        }
427                    },
428                    PrintOrPauseCommand::Pause(p) => {
429                        p.execute(writer);
430                        None
431                    }
432                }
433            })
434            .collect::<Vec<_>>();
435
436        if errors.is_empty() {
437            Ok(())
438        }
439        else {
440            Err(AssemblerError::MultipleErrors { errors })
441        }
442    }
443}
444
445impl DelayedCommands {
446    pub fn print_commands(&self) -> &[PrintOrPauseCommand] {
447        &self.print_commands
448    }
449
450    pub fn print_commands_mut(&mut self) -> &mut [PrintOrPauseCommand] {
451        &mut self.print_commands
452    }
453
454    pub fn failed_assert_commands(&self) -> &[FailedAssertCommand] {
455        &self.failed_assert_commands
456    }
457
458    pub fn failed_assert_commands_mut(&mut self) -> &mut [FailedAssertCommand] {
459        &mut self.failed_assert_commands
460    }
461}
462
463impl DelayedCommands {
464    pub fn collect_breakpoints(&self) -> &[BreakpointCommand] {
465        &self.breakpoint_commands
466    }
467}