cpclib_asm/implementation/
listing.rs

1use std::borrow::Borrow;
2use std::collections::HashMap;
3use std::fmt;
4
5use cpclib_common::camino::Utf8Path;
6use cpclib_tokens::tokens::*;
7
8use crate::EnvOptions;
9use crate::error::*;
10use crate::implementation::expression::*;
11use crate::implementation::tokens::*;
12use crate::preamble::parse_z80_str;
13
14/// Additional methods for the listings
15pub trait ListingExt {
16    fn add_code<S: AsRef<str> + core::fmt::Display>(
17        &mut self,
18        code: S
19    ) -> Result<(), AssemblerError>;
20
21    /// Assemble the listing (without context) and returns the bytes
22    fn to_bytes(&self) -> Result<Vec<u8>, AssemblerError> {
23        let options = EnvOptions::default();
24        self.to_bytes_with_options(options)
25    }
26
27    fn to_bytes_with_options(&self, option: EnvOptions) -> Result<Vec<u8>, AssemblerError>;
28
29    /// Compute the size of the listing by assembling it.
30    /// The listing has a size only if its tokens has a size
31    fn number_of_bytes(&self) -> Result<usize, AssemblerError> {
32        Ok(self.to_bytes()?.len())
33    }
34    fn fallback_number_of_bytes(&self) -> Result<usize, String>;
35
36    /// Get the execution duration.
37    /// If field `duration` is set, returns it. Otherwise, compute it
38    fn estimated_duration(&self) -> Result<usize, AssemblerError>;
39    /// Save the listing on disc in a string version
40    fn save<P: AsRef<Utf8Path>>(&self, path: P) -> ::std::io::Result<()> {
41        use std::fs::File;
42        use std::io::prelude::*;
43
44        // Open a file in write-only mode, returns `io::Result<File>`
45        let mut file = File::create(path.as_ref())?;
46        file.write_all(self.to_string().as_bytes())?;
47
48        Ok(())
49    }
50
51    fn to_string(&self) -> String;
52
53    /// Generate a string that contains also the bytes
54    /// panic even for simple corner cases
55    fn to_enhanced_string(&self) -> String;
56
57    /// Modify the listing to inject labels at the given addresses
58    fn inject_labels<S: Borrow<str>>(&mut self, labels: HashMap<u16, S>);
59}
60
61impl ListingExt for Listing {
62    /// Add additional tokens, that need to be parsed from a string, to the listing
63    fn add_code<S: AsRef<str> + core::fmt::Display>(
64        &mut self,
65        code: S
66    ) -> Result<(), AssemblerError> {
67        parse_z80_str(code.as_ref().trim_end()).map(|local_tokens| {
68            let mut local_tokens = local_tokens.as_listing().to_vec();
69            self.listing_mut().append(&mut local_tokens);
70        })
71    }
72
73    fn to_bytes_with_options(&self, options: EnvOptions) -> Result<Vec<u8>, AssemblerError> {
74        let (_, env) =
75            crate::assembler::visit_tokens_all_passes_with_options(self.listing(), options)
76                .map_err(|(_, _, e)| AssemblerError::AlreadyRenderedError(e.to_string()))?;
77        Ok(env.produced_bytes())
78    }
79
80    /// Get the execution duration.
81    /// If field `duration` is set, returns it. Otherwise, compute it
82    fn estimated_duration(&self) -> Result<usize, AssemblerError> {
83        if let Some(duration) = self.duration() {
84            Ok(duration)
85        }
86        else {
87            let mut duration = 0;
88            for token in self.listing().iter() {
89                duration += token.estimated_duration()?;
90            }
91            Ok(duration)
92        }
93    }
94
95    fn to_string(&self) -> String {
96        PrintableListing::from(self).to_string()
97    }
98
99    fn to_enhanced_string(&self) -> String {
100        todo!()
101    }
102
103    /// Panic if Org is not one of the first instructions
104    fn inject_labels<S: Borrow<str>>(&mut self, mut labels: HashMap<u16, S>) {
105        use cpclib_tokens::builder::{equ, label};
106
107        let mut current_address: Option<u16> = None;
108        let mut current_idx = 0;
109        let mut nb_labels_added = 0;
110
111        // inject labels at the appropriate address if any
112        while current_idx < self.len() && !labels.is_empty() {
113            if let Some(current_address) = &current_address {
114                if let Some(new_label) = labels.remove(current_address) {
115                    self.listing_mut()
116                        .insert(current_idx, label(new_label.borrow()));
117                    nb_labels_added += 1;
118                }
119            }
120
121            let current_instruction = &self.listing()[current_idx];
122
123            let next_address = if let Token::Org { val1: address, .. } = current_instruction {
124                current_address = Some(address.eval().unwrap().int().unwrap() as u16);
125                current_address
126            }
127            else {
128                let nb_bytes = current_instruction.number_of_bytes().unwrap();
129                match current_address {
130                    Some(address) => Some(address + nb_bytes as u16),
131                    None => {
132                        if nb_bytes != 0 {
133                            panic!("Unable to run if assembling address is unknown")
134                        }
135                        else {
136                            None
137                        }
138                    },
139                }
140            };
141
142            current_idx += 1;
143            current_address = next_address;
144        }
145
146        // inject all the remaining ones
147        for (next_address, next_label) in labels.into_iter() {
148            self.listing_mut()
149                .insert(0, equ(next_label.borrow(), next_address));
150        }
151    }
152
153    fn fallback_number_of_bytes(&self) -> Result<usize, String> {
154        self.iter().map(|t| t.fallback_number_of_bytes()).sum()
155    }
156}
157
158/// Workaround to display a Lisitng as we cannot implement display there....
159pub struct PrintableListing<'a>(&'a Listing);
160impl<'a> From<&'a Listing> for PrintableListing<'a> {
161    fn from(src: &'a Listing) -> PrintableListing<'a> {
162        PrintableListing(src)
163    }
164}
165impl fmt::Display for PrintableListing<'_> {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        for token in self.0.listing().iter() {
168            match token {
169                Token::Label(_) | Token::Equ { .. } | Token::Comment(_) => (),
170                _ => {
171                    write!(f, "\t")?;
172                }
173            }
174            // write!(f, "{} ; {:?} {:?} nops {:?} bytes\n", token, token, token.estimated_duration(), token.number_of_bytes())?;
175            writeln!(f, "{}", token)?;
176        }
177
178        Ok(())
179    }
180}
181
182/// Generate a listing from a string
183pub trait ListingFromStr {
184    fn from_str(s: &str) -> Result<Listing, AssemblerError>;
185}
186
187impl ListingFromStr for Listing {
188    fn from_str(s: &str) -> Result<Listing, AssemblerError> {
189        crate::parser::parse_z80_str(s).map(|ll| ll.as_listing())
190    }
191}
192
193#[derive(Default)]
194pub enum ListingSelectorStrategy {
195    #[default]
196    Speed,
197    Size
198}
199#[derive(Default)]
200pub struct ListingSelector {
201    choices: Vec<Listing>,
202    strategy: ListingSelectorStrategy
203}
204
205impl ListingSelector {
206    pub fn add(mut self, lst: Listing) -> Self {
207        self.choices.push(lst);
208        self
209    }
210
211    pub fn select(mut self) -> Listing {
212        let key_fn: Box<dyn Fn(&Listing) -> (usize, usize)> = match self.strategy {
213            ListingSelectorStrategy::Speed => {
214                Box::new(|l: &Listing| {
215                    (
216                        l.estimated_duration().unwrap(),
217                        l.number_of_bytes().unwrap()
218                    )
219                })
220            },
221            ListingSelectorStrategy::Size => {
222                Box::new(|l: &Listing| {
223                    (
224                        l.number_of_bytes().unwrap(),
225                        l.estimated_duration().unwrap()
226                    )
227                })
228            },
229        };
230
231        self.choices.sort_by_cached_key(key_fn);
232        self.choices.into_iter().next().unwrap()
233    }
234}