cpclib_asm/assembler/
listing_output.rs

1use std::fmt::{Debug, Display};
2use std::io::Write;
3use std::ops::Deref;
4use std::sync::{Arc, RwLock};
5
6use cpclib_common::itertools::Itertools;
7use cpclib_common::smallvec::SmallVec;
8use cpclib_tokens::ExprResult;
9use cpclib_tokens::symbols::{MemoryPhysicalAddress, PhysicalAddress};
10
11use crate::preamble::{LocatedToken, LocatedTokenInner, MayHaveSpan, SourceString};
12/// Generate an output listing.
13/// Can be useful to detect issues
14
15#[derive(PartialEq)]
16pub enum TokenKind {
17    Hidden,
18    Label(String),
19    Set(String),
20    MacroCall,
21    MacroDefine(String),
22    Displayable
23}
24
25impl TokenKind {
26    fn is_displayable(&self) -> bool {
27        self == &TokenKind::Displayable
28    }
29}
30pub struct ListingOutput {
31    /// Writer that will contains the listing/
32    /// The listing is produced line by line and not token per token
33    writer: Box<dyn Write + Send + Sync>,
34    /// Filename of the current line
35    current_fname: Option<String>,
36    activated: bool,
37
38    /// Bytes collected at the current line
39    current_line_bytes: SmallVec<[u8; 4]>,
40    /// Complete source
41    current_source: Option<&'static str>,
42    /// Line number and line content.
43    current_line_group: Option<(u32, String)>, // clone view of the line XXX avoid this clone
44
45    current_first_address: u32,
46    current_address_kind: AddressKind,
47    current_physical_address: PhysicalAddress,
48    crunched_section_counter: usize,
49    current_token_kind: TokenKind,
50    deferred_for_line: Vec<String>,
51    counter_update: Vec<String>
52}
53#[derive(PartialEq)]
54pub enum AddressKind {
55    Address,
56    CrunchedArea,
57    Mixed,
58    None
59}
60
61impl Display for AddressKind {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(
64            f,
65            "{}",
66            match self {
67                AddressKind::Address => ' ',
68                AddressKind::CrunchedArea => 'C',
69                AddressKind::Mixed => 'M',
70                AddressKind::None => 'N'
71            }
72        )
73    }
74}
75
76impl Debug for ListingOutput {
77    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        Ok(())
79    }
80}
81
82impl ListingOutput {
83    /// Build a new ListingOutput that will write everyting in writter
84    pub fn new<W: 'static + Write + Send + Sync>(writer: W) -> Self {
85        Self {
86            writer: Box::new(writer),
87            current_fname: None,
88            activated: false,
89            current_line_bytes: Default::default(),
90            current_line_group: None,
91            current_source: None,
92            current_first_address: 0,
93            current_address_kind: AddressKind::None,
94            crunched_section_counter: 0,
95            current_physical_address: MemoryPhysicalAddress::new(0, 0).into(),
96            current_token_kind: TokenKind::Hidden,
97            deferred_for_line: Default::default(),
98            counter_update: Vec::new()
99        }
100    }
101
102    fn bytes_per_line(&self) -> usize {
103        8
104    }
105
106    /// Check if the token is for the same source
107    fn token_is_on_same_source(&self, token: &LocatedToken) -> bool {
108        match &self.current_source {
109            Some(current_source) => {
110                std::ptr::eq(token.context().source.as_ptr(), current_source.as_ptr())
111            },
112            None => false
113        }
114    }
115
116    /// Check if the token is for the same line than the previous token
117    fn token_is_on_same_line(&self, token: &LocatedToken) -> bool {
118        match &self.current_line_group {
119            Some((current_location, _current_line)) => {
120                self.token_is_on_same_source(token)
121                    && *current_location == token.span().location_line()
122            },
123            None => false
124        }
125    }
126
127    fn extract_code(token: &LocatedToken) -> String {
128        match token {
129            LocatedToken {
130                inner: either::Left(LocatedTokenInner::Macro { .. } | LocatedTokenInner::Repeat(..)),
131                span,
132                ..
133            } => {
134                // 		self.need_to_cut = true;
135                span.as_str().to_string()
136            },
137
138            _ => {
139                // 			self.need_to_cut = false;
140                unsafe {
141                    std::str::from_utf8_unchecked(token.span().get_line_beginning().as_bytes())
142                }
143                .to_owned()
144            }
145        }
146    }
147
148    /// Add a token for the current line
149    fn add_token(
150        &mut self,
151        token: &LocatedToken,
152        bytes: &[u8],
153        address: u32,
154        address_kind: AddressKind,
155        physical_address: PhysicalAddress
156    ) {
157        if !self.activated {
158            return;
159        }
160
161        // dbg!(token);
162
163        let fname_handling = self.manage_fname(token);
164
165        // Check if the current line has to drawn in a different way
166        let specific_content = match &self.current_token_kind {
167            TokenKind::Hidden => None,
168            TokenKind::Label(l) => {
169                Some(format!(
170                    "{:04X} {:05X} {l}",
171                    self.current_first_address,
172                    match self.current_physical_address {
173                        PhysicalAddress::Memory(adr) => adr.offset_in_cpc(),
174                        PhysicalAddress::Bank(adr) => adr.address() as _,
175                        PhysicalAddress::Cpr(adr) => adr.address() as _
176                    }
177                ))
178            },
179            TokenKind::Set(label) => {
180                Some(format!(
181                    "{:04X} {} {label}",
182                    self.current_first_address, "?????"
183                ))
184            },
185            TokenKind::MacroCall | TokenKind::Displayable => None,
186            TokenKind::MacroDefine(name) => Some(format!("MACRO      {name}"))
187        };
188
189        // if so, defer its output
190        if let Some(specific_content) = &specific_content {
191            self.deferred_for_line.push(specific_content.clone());
192        }
193
194        {
195            // !self.token_is_on_same_line(token)
196            if true {
197                // if specific_content.is_some() && fname_handling.is_some() {
198                // writeln!(self.writer, "{}", fname_handling.take().unwrap()).unwrap();
199                // }
200                // handle previous line
201                if !self.token_is_on_same_line(token) {
202                    self.process_current_line(); // request a display
203                }
204
205                // handle the new line
206
207                // replace the objects of interest
208                self.current_source =
209                    Some(unsafe { std::mem::transmute(token.context().complete_source()) });
210
211                // TODO manage differently for macros and so on
212                // let current_line = current_line.split("\n").next().unwrap_or(current_line);
213                self.current_line_group =
214                    Some((token.span().location_line(), Self::extract_code(token)));
215                self.current_first_address = address;
216                self.current_physical_address = physical_address;
217                self.current_address_kind = AddressKind::None;
218                self.manage_fname(token);
219            }
220            else {
221                // update the line
222                self.current_line_group =
223                    Some((token.span().location_line(), Self::extract_code(token)));
224            }
225        }
226
227        self.current_line_bytes.extend_from_slice(bytes);
228        self.current_address_kind = if self.current_address_kind == AddressKind::None {
229            address_kind
230        }
231        else if self.current_address_kind != address_kind {
232            AddressKind::Mixed
233        }
234        else {
235            address_kind
236        };
237
238        if let Some(line) = fname_handling {
239            writeln!(self.writer, "{}", line).unwrap();
240        }
241
242        self.current_token_kind = match token.deref() {
243            LocatedTokenInner::Label(l) => TokenKind::Label(l.to_string()),
244            LocatedTokenInner::Equ { label, .. } | LocatedTokenInner::Assign { label, .. } => {
245                TokenKind::Set(label.to_string())
246            },
247            LocatedTokenInner::Macro { name, .. } => TokenKind::MacroDefine(name.to_string()),
248            LocatedTokenInner::MacroCall(..)
249            | LocatedTokenInner::Org { .. }
250            | LocatedTokenInner::Comment(..)
251            | LocatedTokenInner::Include(..)
252            | LocatedTokenInner::Repeat(..) => TokenKind::Displayable,
253            _ => TokenKind::Hidden
254        };
255    }
256
257    pub fn process_current_line(&mut self) {
258        // retrieve the line
259        let (line_number, line) = match &self.current_line_group {
260            Some((idx, line)) => (idx, line),
261            None => return
262        };
263
264        // build the iterators over the line representation of source code and data
265        let mut line_representation = line.split("\n");
266        let data_representation = &self
267            .current_line_bytes
268            .iter()
269            .chunks(self.bytes_per_line())
270            .into_iter()
271            .map(|c| c.map(|b| format!("{:02X}", b)).join(" "))
272            .collect_vec();
273        let mut data_representation = data_representation.iter();
274
275        // TODO manage missing end of files/blocks if needed
276
277        let delta = line_representation.clone().count();
278        // TODO add the line representation ?
279        for specific_content in self.deferred_for_line.iter() {
280            let lines = line.split("\n");
281            let lines_count = lines.clone().count(); // line number corresponds to the VERY LAST line and not the FIRST one
282            for (line_delta, line) in lines.into_iter().enumerate() {
283                writeln!(
284                    self.writer,
285                    "{:37}{:4} {}",
286                    if line_delta == 0 {
287                        specific_content
288                    }
289                    else {
290                        ""
291                    },
292                    line_number + delta as u32 + line_delta as u32 - lines_count as u32,
293                    line
294                )
295                .unwrap();
296            }
297        }
298        self.deferred_for_line.clear();
299
300        // draw all lines that correspond to the instructions to output
301        let mut idx = 0;
302        loop {
303            let current_inner_line = line_representation.next();
304            let current_inner_data = data_representation.next();
305
306            if current_inner_data.is_none() && current_inner_line.is_none() {
307                break;
308            }
309
310            let loc_representation = if current_inner_line.is_none() {
311                "    ".to_owned()
312            }
313            else {
314                format!("{:04X}", self.current_first_address)
315            };
316
317            // Physical address is only printed if it differs from the code address
318            let offset = match self.current_physical_address {
319                PhysicalAddress::Memory(adr) => adr.offset_in_cpc(),
320                PhysicalAddress::Bank(adr) => adr.address() as _,
321                PhysicalAddress::Cpr(adr) => adr.address() as _
322            };
323            let phys_addr_representation =
324                if current_inner_line.is_none() || offset == self.current_first_address {
325                    "      ".to_owned()
326                }
327                else {
328                    format!("{:05X}{}", offset, self.current_address_kind)
329                };
330
331            let line_nb_representation = if current_inner_line.is_none() {
332                "    ".to_owned()
333            }
334            else {
335                format!("{:4}", line_number + idx)
336            };
337
338            // missing instruction must be added manually using TokenKind
339            if !self.current_line_bytes.is_empty() || self.current_token_kind.is_displayable() {
340                writeln!(
341                    self.writer,
342                    "{loc_representation} {phys_addr_representation} {:bytes_width$} {line_nb_representation} {}",
343                    current_inner_data.unwrap_or(&"".to_owned()),
344                    current_inner_line.map(|line| line.trim_end()).unwrap_or(""),
345                    bytes_width = self.bytes_per_line() * 3
346                )
347                .unwrap();
348            }
349
350            idx += 1;
351        }
352
353        if !self.current_line_bytes.is_empty() || self.current_token_kind.is_displayable() {
354            for counter in self.counter_update.iter() {
355                self.writer
356                    .write(format!("{}\n", counter).as_bytes())
357                    .unwrap();
358            }
359            self.counter_update.clear();
360        }
361
362        // cleanup all the fields of the current line
363        self.current_line_group = None;
364        self.current_source = None;
365        self.current_line_bytes.clear();
366    }
367
368    pub fn finish(&mut self) {
369        self.process_current_line();
370        if !self.deferred_for_line.is_empty() {
371            panic!()
372        }
373    }
374
375    /// Print filename if needed
376    pub fn manage_fname(&mut self, token: &LocatedToken) -> Option<String> {
377        // 	dbg!(token);
378
379        let ctx = &token.span().state;
380        let fname = ctx
381            .filename()
382            .map(|p| p.as_os_str().to_str().unwrap_or("<NO FNAME>").to_string())
383            .or_else(|| ctx.context_name().map(|s| s.to_owned()));
384
385        match fname {
386            Some(fname) => {
387                let print = match self.current_fname.as_ref() {
388                    Some(current_fname) => *current_fname != fname,
389                    None => true
390                };
391
392                if print {
393                    self.current_fname = Some(fname.clone());
394                    Some(format!("Context: {}", fname))
395                }
396                else {
397                    None
398                }
399            },
400            None => None
401        }
402    }
403
404    pub fn on(&mut self) {
405        self.activated = true;
406    }
407
408    pub fn off(&mut self) {
409        self.finish();
410        self.activated = false;
411    }
412
413    pub fn enter_crunched_section(&mut self) {
414        self.crunched_section_counter += 1;
415    }
416
417    pub fn leave_crunched_section(&mut self) {
418        self.crunched_section_counter -= 1;
419    }
420}
421
422/// This structure collects the necessary information to feed the output
423#[derive(Clone)]
424pub struct ListingOutputTrigger {
425    /// the token read before collecting the bytes
426    /// Because each token can have a different lifespan, we store them using a pointer
427    pub(crate) token: Option<*const LocatedToken>,
428    /// the bytes progressively collected
429    pub(crate) bytes: Vec<u8>,
430    pub(crate) start: u32,
431    pub(crate) physical_address: PhysicalAddress,
432    pub(crate) builder: Arc<RwLock<ListingOutput>>
433}
434
435unsafe impl Sync for ListingOutputTrigger {}
436
437impl ListingOutputTrigger {
438    pub fn write_byte(&mut self, b: u8) {
439        self.bytes.push(b);
440    }
441
442    pub fn new_token(
443        &mut self,
444        new: *const LocatedToken,
445        code: u32,
446        kind: AddressKind,
447        physical_address: PhysicalAddress
448    ) {
449        // Retreive the previous token and handle it
450        if let Some(token) = &self.token {
451            self.builder.write().unwrap().add_token(
452                unsafe { &**token },
453                &self.bytes,
454                self.start,
455                kind,
456                self.physical_address
457            );
458        }
459
460        self.token.replace(new); // TODO remove that clone that is memory/time eager
461
462        // TODO double check if these lines are current. I doubt it is the case when having severl instructions per line
463        self.bytes.clear();
464        self.start = code;
465        self.physical_address = physical_address;
466    }
467
468    /// Override the address value by the expression result
469    /// BUGGY when it is not a number ...
470    pub fn replace_code_address(&mut self, address: &ExprResult) {
471        Self::result_to_address(address).map(|a| self.start = a);
472    }
473
474    /// Applies the conversion when possible
475    fn result_to_address(address: &ExprResult) -> Option<u32> {
476        match address {
477            ExprResult::Float(_f) => None,
478            ExprResult::Value(v) => Some(*v as _),
479            ExprResult::Char(v) => Some(*v as _),
480            ExprResult::Bool(b) => Some(if *b { 1 } else { 0 }),
481            ExprResult::String(s) => Some(s.len() as _),
482            ExprResult::List(l) => Some(l.len() as _),
483            ExprResult::Matrix {
484                width,
485                height,
486                content: _
487            } => Some((*width * *height) as _)
488        }
489    }
490
491    pub fn replace_physical_address(&mut self, address: PhysicalAddress) {
492        self.physical_address = address;
493    }
494
495    pub fn finish(&mut self) {
496        if let Some(token) = &self.token {
497            self.builder.write().unwrap().add_token(
498                unsafe { &**token },
499                &self.bytes,
500                self.start,
501                AddressKind::Address,
502                self.physical_address
503            );
504        }
505        self.builder.write().unwrap().finish();
506    }
507
508    pub fn on(&mut self) {
509        self.builder.write().unwrap().on();
510    }
511
512    pub fn off(&mut self) {
513        self.builder.write().unwrap().off();
514    }
515
516    pub fn enter_crunched_section(&mut self) {
517        self.builder.write().unwrap().enter_crunched_section();
518    }
519
520    pub fn leave_crunched_section(&mut self) {
521        self.builder.write().unwrap().leave_crunched_section();
522    }
523
524    pub fn repeat_iteration(&mut self, counter: &str, value: Option<&ExprResult>) {
525        let line = if let Some(value) = value {
526            let value = Self::result_to_address(value);
527            if let Some(value) = value {
528                format!("{value:04X} ????? {counter}")
529            }
530            else {
531                format!("???? ????? {counter}")
532            }
533        }
534        else {
535            format!("???? ???? {counter}")
536        };
537
538        self.builder.write().unwrap().counter_update.push(line);
539    }
540}