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
17pub mod implementation;
20
21pub mod parser;
23
24pub 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 CaseSensitive,
57 SnaSymb,
59 SnaBrks,
61 SnaBrkc,
63 SnaRemu,
65 RemuInFile,
67 WabpInFile,
69 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#[derive(Debug, Clone)]
87pub struct AssemblingOptions {
88 flags: BitFlags<AssemblingOptionFlags>,
89
90 symbols: cpclib_tokens::symbols::SymbolsTable,
92 output_builder: Option<Arc<RwLock<ListingOutput>>>,
93 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 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 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 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
225pub fn assemble(code: &str) -> Result<Vec<u8>, AssemblerError> {
227 let options = EnvOptions::default();
228 assemble_with_options(code, options).map(|(bytes, _symbols)| bytes)
230}
231
232pub 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
242pub 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
261pub 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]
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 #[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}