cpclib_basic/
lib.rs

1pub mod binary_parser;
2/// Paring related functions for basic.
3pub mod string_parser;
4/// Basic token encoding.
5pub mod tokens;
6
7use std::fmt::{self};
8
9use cpclib_common::winnow::Parser;
10use cpclib_common::winnow::ascii::space0;
11use cpclib_sna::Snapshot;
12use string_parser::parse_basic_program;
13use thiserror::Error;
14use tokens::BasicToken;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17/// Basic line index represtation. Can be by line number of position in the list
18pub enum BasicProgramLineIdx {
19    /// The basic line is indexed by its position in the listing
20    Index(usize),
21    /// The basic line is indexed by its real number
22    Number(u16)
23}
24
25#[derive(Debug, Error, PartialEq, Eq, Clone)]
26#[allow(missing_docs)]
27pub enum BasicError {
28    #[error("Line does not exist: {:?}", idx)]
29    UnknownLine { idx: BasicProgramLineIdx },
30    #[error("{}", msg)]
31    ParseError { msg: String },
32    #[error("Exponent Overflow")]
33    ExponentOverflow
34}
35
36/// Basic line of code representation
37#[derive(Debug, Clone)]
38pub struct BasicLine {
39    /// Basic number of the line
40    line_number: u16,
41    /// Tokens of the basic line
42    tokens: Vec<BasicToken>,
43    /// Length of the line when we do not have to use the real length (ie, we play to hide lines)
44    forced_length: Option<u16>
45}
46
47impl fmt::Display for BasicLine {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(f, "{} ", self.line_number)?;
50        for token in self.tokens().iter() {
51            write!(f, "{token}")?;
52        }
53        Ok(())
54    }
55}
56
57#[allow(missing_docs)]
58impl BasicLine {
59    pub fn line_number(&self) -> u16 {
60        self.line_number
61    }
62
63    /// Create a line with its content
64    pub fn new(line_number: u16, tokens: &[BasicToken]) -> Self {
65        Self {
66            line_number,
67            tokens: tokens.to_vec(),
68            forced_length: None
69        }
70    }
71
72    pub fn add_length(&mut self, length: u16) {
73        let current = self.expected_length();
74        self.force_length(current + length);
75    }
76
77    pub fn force_length(&mut self, length: u16) {
78        self.forced_length = Some(length);
79    }
80
81    /// Return the forced line length or the real line length if not specified
82    pub fn expected_length(&self) -> u16 {
83        match self.forced_length {
84            Some(val) => val,
85            None => self.real_complete_length()
86        }
87    }
88
89    /// Return the byte size taken by the tokens
90    pub fn real_length(&self) -> u16 {
91        self.tokens_as_bytes().len() as _
92    }
93
94    pub fn real_complete_length(&self) -> u16 {
95        self.real_length() + 2 + 2 + 1
96    }
97
98    /// Returns the number of tokens
99    pub fn len(&self) -> usize {
100        self.tokens().len()
101    }
102
103    /// Verify if there are tokens
104    pub fn is_empty(&self) -> bool {
105        self.tokens().is_empty()
106    }
107
108    pub fn tokens_as_bytes(&self) -> Vec<u8> {
109        self.tokens
110            .iter()
111            .flat_map(BasicToken::as_bytes)
112            .collect::<Vec<u8>>()
113    }
114
115    /// Returns the encoded line.
116    /// - 2 bytes for data length -- Could be different than reallity when playing with hidden lines
117    /// - 2 bytes for line number
118    /// - n bytes for tokens
119    /// - 1 bytes for end of line marker
120    pub fn as_bytes(&self) -> Vec<u8> {
121        let size = self.expected_length();
122
123        let mut content = vec![
124            (size % 256) as u8,
125            (size / 256) as u8,
126            (self.line_number % 256) as u8,
127            (self.line_number / 256) as u8,
128        ];
129        content.extend_from_slice(&self.tokens_as_bytes());
130        content.push(0);
131
132        content
133    }
134
135    pub fn tokens(&self) -> &[BasicToken] {
136        &self.tokens
137    }
138}
139
140/// Encode a complete basic program
141#[derive(Debug, Clone)]
142pub struct BasicProgram {
143    /// The ensemble of lines of the basic program
144    lines: Vec<BasicLine>
145}
146
147impl fmt::Display for BasicProgram {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        for line in &self.lines {
150            writeln!(f, "{line}")?;
151        }
152        Ok(())
153    }
154}
155
156#[allow(missing_docs)]
157impl BasicProgram {
158    /// Create the program from a list of lines
159    pub fn new(lines: Vec<BasicLine>) -> Self {
160        Self { lines }
161    }
162
163    /// Create the program from a code to parse
164    pub fn parse<S: AsRef<str>>(code: S) -> Result<Self, BasicError> {
165        let input = code.as_ref();
166        match (parse_basic_program, space0).parse(input) {
167            Ok((prog, _)) => Ok(prog),
168            Err(e) => {
169                Err(BasicError::ParseError {
170                    msg: format!("Error while parsing the Basic content: {e}")
171                })
172            },
173
174            _ => unreachable!()
175        }
176    }
177
178    /// Add a line to the program. If a line with the same number already exists, it is replaced
179    pub fn add_line(&mut self, line: BasicLine) {
180        self.lines.push(line);
181    }
182
183    /// Returns a mutable reference on the requested line if exists
184    pub fn get_line_mut(&mut self, idx: BasicProgramLineIdx) -> Option<&mut BasicLine> {
185        match self.line_idx_as_valid_index(idx) {
186            Ok(BasicProgramLineIdx::Index(index)) => self.lines.get_mut(index),
187            _ => None
188        }
189    }
190
191    /// Returns a reference on the requested line if exists
192    pub fn get_line(&mut self, idx: BasicProgramLineIdx) -> Option<&BasicLine> {
193        match self.line_idx_as_valid_index(idx) {
194            Ok(BasicProgramLineIdx::Index(index)) => self.lines.get(index),
195            _ => None
196        }
197    }
198
199    /// Returns true if the index corresponds to the very first line. False if it does not correspond or does not exists
200    pub fn is_first_line(&self, idx: BasicProgramLineIdx) -> bool {
201        match self.line_idx_as_valid_index(idx) {
202            Ok(BasicProgramLineIdx::Index(0)) => true,
203            _ => false
204        }
205    }
206
207    /// Returns the previous index of idx if it exists
208    pub fn previous_idx(&self, idx: BasicProgramLineIdx) -> Option<BasicProgramLineIdx> {
209        match self.line_idx_as_valid_index(idx) {
210            Ok(BasicProgramLineIdx::Index(index)) => {
211                if index == 0 {
212                    None
213                }
214                else {
215                    Some(BasicProgramLineIdx::Index(index - 1))
216                }
217            },
218            Err(_e) => None,
219            _ => unreachable!()
220        }
221    }
222
223    /// Check if the program contains the requested line
224    pub fn has_line(&self, idx: BasicProgramLineIdx) -> bool {
225        self.line_idx_as_valid_index(idx).is_ok()
226    }
227
228    /// Return the line index in the index format if the line exists
229    fn line_idx_as_valid_index(
230        &self,
231        idx: BasicProgramLineIdx
232    ) -> Result<BasicProgramLineIdx, BasicError> {
233        match &idx {
234            BasicProgramLineIdx::Index(index) => {
235                if self.lines.len() <= *index {
236                    Err(BasicError::UnknownLine { idx })
237                }
238                else {
239                    Ok(idx)
240                }
241            },
242
243            BasicProgramLineIdx::Number(number) => {
244                match self.get_index_of_line_number(*number) {
245                    Some(index) => Ok(BasicProgramLineIdx::Index(index)),
246                    None => Err(BasicError::UnknownLine { idx })
247                }
248            },
249        }
250    }
251
252    /// For a given line number, returns the index in the list of lines
253    fn get_index_of_line_number(&self, number: u16) -> Option<usize> {
254        self.lines
255            .iter()
256            .enumerate()
257            .filter_map(move |(index, line)| {
258                if line.line_number == number {
259                    Some(index)
260                }
261                else {
262                    None
263                }
264            })
265            .collect::<Vec<_>>()
266            .first()
267            .cloned()
268    }
269
270    /// https://cpcrulez.fr/applications_protect-protection_logiciel_n42_ACPC.htm
271    /// 64nops2
272    pub fn hide_line(&mut self, current_idx: BasicProgramLineIdx) -> Result<(), BasicError> {
273        if !self.has_line(current_idx) {
274            Err(BasicError::UnknownLine { idx: current_idx })
275        }
276        else if self.is_first_line(current_idx) {
277            // Locomotive basic stat to list lines from 1
278            self.lines[0].line_number = 0;
279            Ok(())
280        }
281        else {
282            match self.previous_idx(current_idx) {
283                Some(previous_idx) => {
284                    let current_length = self.get_line(current_idx).unwrap().real_complete_length(); // TODO handle the case where they are multiple hidden
285                    self.get_line_mut(previous_idx)
286                        .unwrap()
287                        .add_length(current_length);
288                    self.get_line_mut(current_idx).unwrap().force_length(0);
289                    Ok(())
290                },
291                None => Err(BasicError::UnknownLine { idx: current_idx })
292            }
293        }
294    }
295
296    pub fn hide_lines(&mut self, lines: &[u16]) -> Result<(), BasicError> {
297        match lines.len() {
298            0 => Ok(()),
299            1 => self.hide_line(BasicProgramLineIdx::Number(lines[0])),
300            _ => {
301                unimplemented!(
302                    "The current version is only able to hide one line. I can still implement multiline version if needed"
303                )
304            }
305        }
306    }
307
308    /// Generate the byte stream for the given program
309    pub fn as_bytes(&self) -> Vec<u8> {
310        let mut bytes = self
311            .lines
312            .iter()
313            .flat_map(BasicLine::as_bytes)
314            .collect::<Vec<u8>>();
315        bytes.resize(bytes.len() + 3, 0);
316        bytes
317    }
318
319    pub fn as_sna(&self) -> Result<Snapshot, String> {
320        let bytes = self.as_bytes();
321        let mut sna = Snapshot::new_6128()?;
322        sna.unwrap_memory_chunks();
323        sna.add_data(&bytes, 0x170).map_err(|e| format!("{e:?}"))?;
324        Ok(sna)
325    }
326}
327
328#[allow(clippy::let_unit_value)]
329#[allow(clippy::shadow_unrelated)]
330#[cfg(test)]
331pub mod test {
332
333    use super::*;
334
335    #[test]
336    fn parse_complete() {
337        let code = "10 call &0: call &0\n";
338        BasicProgram::parse(code).expect("Unable to produce basic tokens");
339
340        let code1 = "10 call &0: call &0";
341        BasicProgram::parse(code1).expect("Unable to produce basic tokens");
342
343        let code2 = "10 ' blabla bla\n20 ' blab bla bal\n30 call &180";
344        BasicProgram::parse(code2).expect("Unable to produce basic tokens");
345    }
346
347    #[test]
348    fn parse_correct() {
349        let code = "10 CALL &1234";
350        let prog = BasicProgram::parse(code).unwrap();
351        let bytes = prog.as_bytes();
352        let expected = [10, 0, 10, 0, 131, 32, 28, 0x34, 0x12, 0, 0, 0, 0];
353
354        assert_eq!(&bytes, &expected);
355
356        let code = "10 CALL &1234\n20 CALL &1234";
357        let prog = BasicProgram::parse(code).unwrap();
358        let bytes = prog.as_bytes();
359        let expected = [
360            10, 0, 10, 0, 131, 32, 28, 0x34, 0x12, 0, 10, 0, 20, 0, 131, 32, 28, 0x34, 0x12, 0, 0,
361            0, 0
362        ];
363
364        assert_eq!(&bytes, &expected);
365    }
366
367    #[test]
368    fn hide1() {
369        let code = "10 CALL &1234";
370        let mut prog = BasicProgram::parse(code).unwrap();
371        prog.hide_line(BasicProgramLineIdx::Number(10)).unwrap();
372        let bytes = prog.as_bytes();
373        let expected = vec![10, 0, 0, 0, 131, 32, 28, 0x34, 0x12, 0, 0, 0, 0];
374
375        assert_eq!(bytes, expected);
376    }
377
378    #[test]
379    fn indices() {
380        let code = "10 CALL &1234\n20 CALL &1234";
381        let prog = BasicProgram::parse(code).unwrap();
382
383        assert_eq!(
384            Ok(BasicProgramLineIdx::Index(0)),
385            prog.line_idx_as_valid_index(BasicProgramLineIdx::Index(0))
386        );
387        assert_eq!(
388            Ok(BasicProgramLineIdx::Index(1)),
389            prog.line_idx_as_valid_index(BasicProgramLineIdx::Index(1))
390        );
391        assert_eq!(
392            Err(BasicError::UnknownLine {
393                idx: BasicProgramLineIdx::Index(2)
394            }),
395            prog.line_idx_as_valid_index(BasicProgramLineIdx::Index(2))
396        );
397
398        assert_eq!(
399            Some(BasicProgramLineIdx::Index(0)),
400            prog.previous_idx(BasicProgramLineIdx::Index(1))
401        );
402        assert_eq!(None, prog.previous_idx(BasicProgramLineIdx::Index(0)));
403
404        assert!(prog.has_line(BasicProgramLineIdx::Number(10)));
405        assert!(prog.has_line(BasicProgramLineIdx::Number(20)));
406        assert!(!prog.has_line(BasicProgramLineIdx::Number(30)));
407        assert!(prog.has_line(BasicProgramLineIdx::Index(0)));
408        assert!(prog.has_line(BasicProgramLineIdx::Index(1)));
409        assert!(!prog.has_line(BasicProgramLineIdx::Index(2)));
410    }
411
412    #[test]
413    fn hide2() {
414        let code = "10 CALL &1234\n20 CALL &1234";
415        let mut prog = BasicProgram::parse(code).unwrap();
416        assert!(prog.has_line(BasicProgramLineIdx::Number(20)));
417        prog.hide_line(BasicProgramLineIdx::Number(20)).unwrap();
418        let bytes = prog.as_bytes();
419        let expected = vec![
420            20, 0, 10, 0, 131, 32, 28, 0x34, 0x12, 0, 00, 0, 20, 0, 131, 32, 28, 0x34, 0x12, 0, 0,
421            0, 0,
422        ];
423
424        assert_eq!(bytes, expected);
425    }
426}