cpclib_asm/
lib.rs

1#![deny(deprecated)]
2#![feature(assert_matches)]
3#![feature(specialization)]
4#![feature(exact_size_is_empty)]
5#![feature(exclusive_range_pattern)]
6#![feature(let_chains)]
7#![feature(box_patterns)]
8#![feature(box_into_inner)]
9#![feature(string_extend_from_within)]
10#![recursion_limit = "256"]
11#![feature(map_try_insert)]
12#![feature(get_mut_unchecked)]
13#![feature(stmt_expr_attributes)]
14#![feature(slice_take)]
15#![feature(write_all_vectored)]
16
17// mod rewrite;
18/// Implementation of various behavior for the tokens of cpclib_tokens
19pub mod implementation;
20
21/// All the stuff to parse z80 code.
22pub mod parser;
23
24/// Production of the bytecodes from the tokens.
25pub mod assembler;
26
27pub mod disass;
28
29pub mod preamble;
30
31pub mod error;
32
33mod crunchers;
34
35pub mod orgams;
36pub mod progress;
37
38use std::fmt::Debug;
39use std::io::Write;
40use std::sync::{Arc, RwLock};
41
42use cpclib_disc::amsdos::*;
43use cpclib_sna::Snapshot;
44use enumflags2::{BitFlags, bitflags};
45use preamble::function::FunctionBuilder;
46use preamble::processed_token::ProcessedToken;
47pub use preamble::*;
48
49use self::listing_output::ListingOutput;
50
51#[bitflags]
52#[repr(u8)]
53#[derive(Copy, Clone, Debug, PartialEq)]
54pub enum AssemblingOptionFlags {
55    /// Set to consider that the assembler pay attention to the case of the labels
56    CaseSensitive,
57    // Set to include SYMB in sna chunks
58    SnaSymb,
59    // Set to include BRKS in sna chunks
60    SnaBrks,
61    // Set to include BRKC in sna chunks
62    SnaBrkc,
63    // Set to include REMU in sna chunks
64    SnaRemu,
65    // Save remu chunk in a file
66    RemuInFile,
67    // Save wabp chunck in a file
68    WabpInFile,
69    // generate breakpoint as code
70    BreakpointAsOpcode
71}
72
73impl AssemblingOptionFlags {
74    pub fn from_chunk(chunk: &str) -> Option<Self> {
75        match chunk {
76            "SYMB" => Some(Self::SnaSymb),
77            "BRKS" => Some(Self::SnaBrks),
78            "BRKC" => Some(Self::SnaBrkc),
79            "REMU" => Some(Self::SnaRemu),
80            _ => None
81        }
82    }
83}
84
85/// Configuration of the assembler. By default the assembler is case sensitive and has no symbol
86#[derive(Debug, Clone)]
87pub struct AssemblingOptions {
88    flags: BitFlags<AssemblingOptionFlags>,
89
90    /// Contains some symbols that could be used during assembling
91    symbols: cpclib_tokens::symbols::SymbolsTable,
92    output_builder: Option<Arc<RwLock<ListingOutput>>>,
93    /// The snapshot may be prefiled with a dedicated snapshot
94    snapshot_model: Option<Snapshot>,
95    amsdos_behavior: AmsdosAddBehavior,
96    enable_warnings: bool,
97    force_void: bool,
98    debug: bool
99}
100
101impl Default for AssemblingOptions {
102    fn default() -> Self {
103        Self {
104            flags: AssemblingOptionFlags::CaseSensitive
105                | AssemblingOptionFlags::SnaBrkc
106                | AssemblingOptionFlags::SnaBrks
107                | AssemblingOptionFlags::SnaSymb
108                | AssemblingOptionFlags::SnaRemu,
109            symbols: cpclib_tokens::symbols::SymbolsTable::default(),
110            output_builder: None,
111            snapshot_model: None,
112            amsdos_behavior: AmsdosAddBehavior::FailIfPresent,
113            enable_warnings: true,
114            force_void: true,
115            debug: false
116        }
117    }
118}
119
120#[allow(missing_docs)]
121impl AssemblingOptions {
122    pub fn new_case_sensitive() -> Self {
123        Self::default()
124    }
125
126    pub fn new_case_insensitive() -> Self {
127        let mut options = Self::new_case_sensitive();
128        options.set_case_sensitive(false);
129        options
130    }
131
132    /// Creation an option object with the given symbol table
133    pub fn new_with_table(symbols: &cpclib_tokens::symbols::SymbolsTable) -> Self {
134        let mut options = Self::default();
135        options.set_symbols(symbols);
136        options
137    }
138
139    pub fn set_flag(&mut self, flag: AssemblingOptionFlags, val: bool) -> &mut Self {
140        self.flags.set(flag, val);
141        self
142    }
143
144    pub fn get_flag(&self, flag: AssemblingOptionFlags) -> bool {
145        self.flags.contains(flag)
146    }
147
148    pub fn disable_warnings(&mut self) -> &mut Self {
149        self.enable_warnings = false;
150        self
151    }
152
153    /// Specify if the assembler must be case sensitive or not
154    pub fn set_case_sensitive(&mut self, val: bool) -> &mut Self {
155        self.set_flag(AssemblingOptionFlags::CaseSensitive, val);
156        self
157    }
158
159    pub fn set_save_behavior(&mut self, behavior: AmsdosAddBehavior) -> &mut Self {
160        self.amsdos_behavior = behavior;
161        self
162    }
163
164    pub fn set_snapshot_model(&mut self, mut sna: Snapshot) -> &mut Self {
165        sna.unwrap_memory_chunks();
166        self.snapshot_model = Some(sna);
167        self
168    }
169
170    /// Specify a symbol table to copy
171    pub fn set_symbols(&mut self, val: &cpclib_tokens::symbols::SymbolsTable) -> &mut Self {
172        self.symbols = val.clone();
173        self
174    }
175
176    pub fn symbols(&self) -> &cpclib_tokens::symbols::SymbolsTable {
177        &self.symbols
178    }
179
180    pub fn symbols_mut(&mut self) -> &mut cpclib_tokens::symbols::SymbolsTable {
181        &mut self.symbols
182    }
183
184    pub fn case_sensitive(&self) -> bool {
185        self.get_flag(AssemblingOptionFlags::CaseSensitive)
186    }
187
188    pub fn debug(&self) -> bool {
189        self.debug
190    }
191
192    pub fn set_debug(&mut self, debug: bool) {
193        self.debug = debug;
194    }
195
196    pub fn snapshot_model(&self) -> Option<&Snapshot> {
197        self.snapshot_model.as_ref()
198    }
199
200    pub fn save_behavior(&self) -> AmsdosAddBehavior {
201        self.amsdos_behavior
202    }
203
204    pub fn force_void(&self) -> bool {
205        self.force_void
206    }
207
208    pub fn set_force_void(&mut self, force_void: bool) -> &mut Self {
209        self.force_void = force_void;
210        self
211    }
212
213    pub fn write_listing_output<W: 'static + Write + Send + Sync>(
214        &mut self,
215        writer: W
216    ) -> &mut Self {
217        self.output_builder = Some(Arc::new(RwLock::new(ListingOutput::new(writer))));
218        if let Some(b) = self.output_builder.as_mut() {
219            b.write().unwrap().on()
220        }
221        self
222    }
223}
224
225/// Assemble a piece of code and returns the associated list of bytes.
226pub fn assemble(code: &str) -> Result<Vec<u8>, AssemblerError> {
227    let options = EnvOptions::default();
228    // let options = AssemblingOptions::new_with_table(table);
229    assemble_with_options(code, options).map(|(bytes, _symbols)| bytes)
230}
231
232/// Assemble a piece of code and returns the associates liste of bytes as well as the generated reference table.
233pub fn assemble_with_options(
234    code: &str,
235    options: EnvOptions
236) -> Result<(Vec<u8>, cpclib_tokens::symbols::SymbolsTable), AssemblerError> {
237    let builder = options.parse_options().clone().context_builder();
238    let tokens = parser::parse_z80_with_context_builder(code, builder)?;
239    assemble_tokens_with_options(&tokens, options)
240}
241
242/// Assemble the predifined list of tokens
243pub fn assemble_tokens_with_options<
244    'tokens,
245    T: 'static + Visited + ToSimpleToken + Clone + ListingElement + Sync + MayHaveSpan
246>(
247    tokens: &'tokens [T],
248    options: EnvOptions
249) -> Result<(Vec<u8>, cpclib_tokens::symbols::SymbolsTable), AssemblerError>
250where
251    <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
252    <<T as cpclib_tokens::ListingElement>::TestKind as cpclib_tokens::TestKindElement>::Expr:
253        implementation::expression::ExprEvaluationExt,
254    ProcessedToken<'tokens, T>: FunctionBuilder
255{
256    let (_tok, env) = assembler::visit_tokens_all_passes_with_options(tokens, options)
257        .map_err(|(_, _, e)| AssemblerError::AlreadyRenderedError(e.to_string()))?;
258    Ok((env.produced_bytes(), env.symbols().into()))
259}
260
261/// Build the code and store it inside a file supposed to be injected in a dsk
262/// XXX probably crash if filename is not coherent
263/// //
264pub fn assemble_to_amsdos_file(
265    code: &str,
266    amsdos_filename: &str,
267    options: EnvOptions
268) -> Result<AmsdosFile, AssemblerError> {
269    let amsdos_filename = AmsdosFileName::try_from(amsdos_filename)?;
270
271    let tokens = parser::parse_z80_str(code)?;
272
273    let (_, env) = assembler::visit_tokens_all_passes_with_options(&tokens, options)
274        .map_err(|(_, _, e)| AssemblerError::AlreadyRenderedError(e.to_string()))?;
275
276    Ok(AmsdosFile::binary_file_from_buffer(
277        &amsdos_filename,
278        env.loading_address().unwrap(),
279        env.execution_address().unwrap(),
280        &env.produced_bytes()
281    )?)
282}
283
284#[cfg(test)]
285mod test_super {
286
287    use super::*;
288
289    #[test]
290    fn simple_test_assemble() {
291        let code = "
292		org 0
293		db 1, 2
294		db 3, 4
295		";
296
297        let bytes = assemble(code).unwrap_or_else(|e| panic!("Unable to assemble {}: {}", code, e));
298        assert_eq!(bytes.len(), 4);
299        assert_eq!(bytes, vec![1, 2, 3, 4]);
300    }
301
302    #[test]
303    fn located_test_assemble() {
304        let code = "
305		org 0x100
306		db 1, 2
307		db 3, 4
308		";
309
310        let bytes = assemble(code).unwrap_or_else(|e| panic!("Unable to assemble {}: {}", code, e));
311        assert_eq!(bytes, vec![1, 2, 3, 4]);
312    }
313
314    #[test]
315    fn case_verification() {
316        let code = "
317		ld hl, TruC
318Truc
319		";
320
321        let options = AssemblingOptions::new_case_sensitive();
322        let options = EnvOptions::from(options);
323        println!("{:?}", assemble_with_options(code, options.clone()));
324        assert!(assemble_with_options(code, options).is_err());
325
326        let options = AssemblingOptions::new_case_insensitive();
327        let options = EnvOptions::from(options);
328        println!("{:?}", assemble_with_options(code, options.clone()));
329        assert!(assemble_with_options(code, options).is_ok());
330    }
331
332    #[test]
333    fn test_size() {
334        let mut env = Default::default();
335        dbg!(
336            assemble_call_jr_or_jp(
337                Mnemonic::Jp,
338                None,
339                &DataAccess::Expression(Expr::Value(0)),
340                &mut env
341            )
342            .unwrap()
343        );
344        assert_eq!(
345            Token::OpCode(
346                Mnemonic::Jp,
347                None,
348                Some(DataAccess::Expression(Expr::Value(0))),
349                None
350            )
351            .number_of_bytes(),
352            Ok(3)
353        );
354
355        assert_eq!(
356            Token::OpCode(
357                Mnemonic::Jr,
358                None,
359                Some(DataAccess::Expression(Expr::Value(0))),
360                None
361            )
362            .number_of_bytes(),
363            Ok(2)
364        );
365
366        assert_eq!(
367            Token::OpCode(
368                Mnemonic::Jr,
369                Some(DataAccess::FlagTest(FlagTest::NC)),
370                Some(DataAccess::Expression(Expr::Value(0))),
371                None
372            )
373            .number_of_bytes(),
374            Ok(2)
375        );
376
377        assert_eq!(
378            Token::OpCode(
379                Mnemonic::Push,
380                Some(DataAccess::Register16(Register16::De)),
381                None,
382                None
383            )
384            .number_of_bytes(),
385            Ok(1)
386        );
387
388        assert_eq!(
389            Token::OpCode(
390                Mnemonic::Dec,
391                Some(DataAccess::Register8(Register8::A)),
392                None,
393                None
394            )
395            .number_of_bytes(),
396            Ok(1)
397        );
398    }
399
400    #[test]
401    fn test_listing() {
402        let mut listing = Listing::from_str("   nop").expect("unable to assemble");
403        assert_eq!(listing.estimated_duration().unwrap(), 1);
404        listing.set_duration(100);
405        assert_eq!(listing.estimated_duration().unwrap(), 100);
406    }
407
408    fn code_test(code: &'static str) {
409        let asm_options = AssemblingOptions::new_case_insensitive();
410        let env_options = EnvOptions::new(ParserOptions::default(), asm_options, Arc::new(()));
411        let res = assemble_with_options(code, env_options);
412        res.map_err(|e| eprintln!("{e}")).unwrap();
413    }
414
415    /// Test stolen to rasm
416    #[test]
417    fn rasm_pagetag1() {
418        let code = "  
419        bankset 0
420        org #5000
421label1
422        bankset 1
423        org #9000
424label2
425        bankset 2
426        assert {page}label1==0xC0
427        assert {page}label2==0xC6 
428        assert {pageset}label1==#C0
429        assert {pageset}label2==#C2
430        assert $ == 0x0000
431        assert $$ == 0x0000
432        nop";
433        code_test(code);
434    }
435    // /// This test currently does not pass
436    // #[test]
437    // fn rasm_pagetag2() {
438    // let code = "
439    // bankset 0
440    // call maroutine
441    //
442    // bank 4
443    // org #C000
444    // autreroutine
445    // nop
446    // ret
447    //
448    // bank 5
449    // org #8000
450    // maroutine
451    // ldir
452    // ret
453    //
454    // bankset 2
455    // org #9000
456    // troize
457    // nop
458    // assert {page}maroutine==#7FC5
459    // assert {pageset}maroutine==#7FC2
460    // assert {page}autreroutine==#7FC4
461    // assert {pageset}autreroutine==#7FC2
462    // assert {page}troize==#7FCE
463    // assert {pageset}troize==#7FCA";
464    // rasm_test(code);
465    //
466    // }
467    // #define AUTOTEST_PAGETAG3	"buildsna:bank 2:assert {bank}$==2:assert {page}$==0x7FC0:assert {pageset}$==#7FC0:" \
468    // "bankset 1:org #4000:assert {bank}$==5:assert {page}$==0x7FC5:assert {pageset}$==#7FC2"
469
470    #[test]
471    fn test_duration() {
472        let listing = Listing::from_str(
473            "
474            pop de      ; 3
475        "
476        )
477        .expect("Unable to assemble this code");
478        println!("{}", listing.to_string());
479        assert_eq!(listing.estimated_duration().unwrap(), 3);
480
481        let listing = Listing::from_str(
482            "
483            inc l       ; 1
484        "
485        )
486        .expect("Unable to assemble this code");
487        println!("{}", listing.to_string());
488        assert_eq!(listing.estimated_duration().unwrap(), 1);
489
490        let listing = Listing::from_str(
491            "
492            ld (hl), e  ; 2
493        "
494        )
495        .expect("Unable to assemble this code");
496        println!("{}", listing.to_string());
497        assert_eq!(listing.estimated_duration().unwrap(), 2);
498
499        let listing = Listing::from_str(
500            "
501            ld (hl), d  ; 2
502        "
503        )
504        .expect("Unable to assemble this code");
505        println!("{}", listing.to_string());
506        assert_eq!(listing.estimated_duration().unwrap(), 2);
507
508        let listing = Listing::from_str(
509            "
510            pop de      ; 3
511            inc l       ; 1
512            ld (hl), e  ; 2
513            inc l       ; 1
514            ld (hl), d  ; 2
515        "
516        )
517        .expect("Unable to assemble this code");
518        println!("{}", listing.to_string());
519        assert_eq!(listing.estimated_duration().unwrap(), (3 + 1 + 2 + 1 + 2));
520    }
521
522    #[test]
523    fn test_real1() {
524        let code = "RUN 0x50, 0xc0";
525        code_test(code);
526
527        let code = r"    if {bank}$ == 0
528            RUN 0x50, 0xc0
529        endif
530        ";
531        code_test(code);
532    }
533}