#![feature(assert_matches)]
#![feature(specialization)]
#![feature(exact_size_is_empty)]
#![feature(exclusive_range_pattern)]
#![feature(let_chains)]
#![feature(box_patterns)]
#![feature(box_into_inner)]
#![feature(string_extend_from_within)]
#![recursion_limit = "256"]
#![feature(map_try_insert)]
#![feature(get_mut_unchecked)]
#![feature(stmt_expr_attributes)]
pub mod implementation;
pub mod parser;
pub mod assembler;
pub mod disass;
pub mod preamble;
pub mod error;
mod crunchers;
pub mod progress;
#[cfg(feature = "basm")]
pub mod basm_utils;
use std::fmt::Debug;
use std::io::Write;
use std::sync::{Arc, RwLock};
use cpclib_disc::amsdos::*;
use cpclib_sna::Snapshot;
use preamble::function::FunctionBuilder;
use preamble::processed_token::ProcessedToken;
use preamble::*;
use self::listing_output::ListingOutput;
#[derive(Debug, Clone)]
pub struct AssemblingOptions {
case_sensitive: bool,
symbols: cpclib_tokens::symbols::SymbolsTable,
output_builder: Option<Arc<RwLock<ListingOutput>>>,
snapshot_model: Option<Snapshot>
}
impl Default for AssemblingOptions {
fn default() -> Self {
Self {
case_sensitive: true,
symbols: cpclib_tokens::symbols::SymbolsTable::default(),
output_builder: None,
snapshot_model: None
}
}
}
#[allow(missing_docs)]
impl AssemblingOptions {
pub fn new_case_sensitive() -> Self {
Self::default()
}
pub fn new_case_insensitive() -> Self {
let mut options = Self::new_case_sensitive();
options.case_sensitive = false;
options
}
pub fn new_with_table(symbols: &cpclib_tokens::symbols::SymbolsTable) -> Self {
let mut options = Self::default();
options.set_symbols(symbols);
options
}
pub fn set_case_sensitive(&mut self, val: bool) -> &mut Self {
self.case_sensitive = val;
self
}
pub fn set_snapshot_model(&mut self, mut sna: Snapshot) -> &mut Self {
sna.unwrap_memory_chunks();
self.snapshot_model = Some(sna);
self
}
pub fn set_symbols(&mut self, val: &cpclib_tokens::symbols::SymbolsTable) -> &mut Self {
self.symbols = val.clone();
self
}
pub fn symbols(&self) -> &cpclib_tokens::symbols::SymbolsTable {
&self.symbols
}
pub fn symbols_mut(&mut self) -> &mut cpclib_tokens::symbols::SymbolsTable {
&mut self.symbols
}
pub fn case_sensitive(&self) -> bool {
self.case_sensitive
}
pub fn snapshot_model(&self) -> Option<&Snapshot> {
self.snapshot_model.as_ref()
}
pub fn write_listing_output<W: 'static + Write + Send + Sync>(
&mut self,
writer: W
) -> &mut Self {
self.output_builder = Some(Arc::new(RwLock::new(ListingOutput::new(writer))));
self.output_builder
.as_mut()
.map(|b| b.write().unwrap().on());
self
}
}
pub fn assemble(code: &str) -> Result<Vec<u8>, AssemblerError> {
let options = EnvOptions::default();
assemble_with_options(code, options).map(|(bytes, _symbols)| bytes)
}
pub fn assemble_with_options(
code: &str,
options: EnvOptions
) -> Result<(Vec<u8>, cpclib_tokens::symbols::SymbolsTable), AssemblerError> {
let builder = options.parse_options().clone().context_builder();
let tokens = parser::parse_z80_with_context_builder(code, builder)?;
assemble_tokens_with_options(&tokens, options)
}
pub fn assemble_tokens_with_options<
'tokens,
T: 'static + Visited + ToSimpleToken + Clone + ListingElement + Sync + MayHaveSpan
>(
tokens: &'tokens [T],
options: EnvOptions
) -> Result<(Vec<u8>, cpclib_tokens::symbols::SymbolsTable), AssemblerError>
where
<T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
<<T as cpclib_tokens::ListingElement>::TestKind as cpclib_tokens::TestKindElement>::Expr:
implementation::expression::ExprEvaluationExt,
ProcessedToken<'tokens, T>: FunctionBuilder
{
let (_tok, env) = assembler::visit_tokens_all_passes_with_options(tokens, options)
.map_err(|(_, _, e)| AssemblerError::AlreadyRenderedError(e.to_string()))?;
Ok((env.produced_bytes(), env.symbols().as_ref().clone()))
}
pub fn assemble_to_amsdos_file(
code: &str,
amsdos_filename: &str
) -> Result<AmsdosFile, AssemblerError> {
let amsdos_filename = AmsdosFileName::try_from(amsdos_filename)?;
let tokens = parser::parse_z80_str(code)?;
let options = EnvOptions::default();
let (_, env) = assembler::visit_tokens_all_passes_with_options(&tokens, options)
.map_err(|(_, _, e)| AssemblerError::AlreadyRenderedError(e.to_string()))?;
Ok(AmsdosFile::binary_file_from_buffer(
&amsdos_filename,
env.loading_address().unwrap() as u16,
env.execution_address().unwrap() as u16,
&env.produced_bytes()
)?)
}
#[cfg(test)]
mod test_super {
use super::*;
#[test]
fn simple_test_assemble() {
let code = "
org 0
db 1, 2
db 3, 4
";
let bytes = assemble(code).unwrap_or_else(|e| panic!("Unable to assemble {}: {}", code, e));
assert_eq!(bytes.len(), 4);
assert_eq!(bytes, vec![1, 2, 3, 4]);
}
#[test]
fn located_test_assemble() {
let code = "
org 0x100
db 1, 2
db 3, 4
";
let bytes = assemble(code).unwrap_or_else(|e| panic!("Unable to assemble {}: {}", code, e));
assert_eq!(bytes, vec![1, 2, 3, 4]);
}
#[test]
fn case_verification() {
let code = "
ld hl, TruC
Truc
";
let options = AssemblingOptions::new_case_sensitive();
let options = EnvOptions::from(options);
println!("{:?}", assemble_with_options(code, options.clone()));
assert!(assemble_with_options(code, options).is_err());
let options = AssemblingOptions::new_case_insensitive();
let options = EnvOptions::from(options);
println!("{:?}", assemble_with_options(code, options.clone()));
assert!(assemble_with_options(code, options).is_ok());
}
#[test]
fn test_size() {
let mut env = Default::default();
dbg!(assemble_call_jr_or_jp(
Mnemonic::Jp,
None,
&DataAccess::Expression(Expr::Value(0)),
&mut env
)
.unwrap());
assert_eq!(
Token::OpCode(
Mnemonic::Jp,
None,
Some(DataAccess::Expression(Expr::Value(0))),
None
)
.number_of_bytes(),
Ok(3)
);
assert_eq!(
Token::OpCode(
Mnemonic::Jr,
None,
Some(DataAccess::Expression(Expr::Value(0))),
None
)
.number_of_bytes(),
Ok(2)
);
assert_eq!(
Token::OpCode(
Mnemonic::Jr,
Some(DataAccess::FlagTest(FlagTest::NC)),
Some(DataAccess::Expression(Expr::Value(0))),
None
)
.number_of_bytes(),
Ok(2)
);
assert_eq!(
Token::OpCode(
Mnemonic::Push,
Some(DataAccess::Register16(Register16::De)),
None,
None
)
.number_of_bytes(),
Ok(1)
);
assert_eq!(
Token::OpCode(
Mnemonic::Dec,
Some(DataAccess::Register8(Register8::A)),
None,
None
)
.number_of_bytes(),
Ok(1)
);
}
#[test]
fn test_listing() {
let mut listing = Listing::from_str(" nop").expect("unable to assemble");
assert_eq!(listing.estimated_duration().unwrap(), 1);
listing.set_duration(100);
assert_eq!(listing.estimated_duration().unwrap(), 100);
}
fn code_test(code: &'static str) {
let asm_options = AssemblingOptions::new_case_insensitive();
let env_options = EnvOptions::new(ParserOptions::default(), asm_options);
let res = assemble_with_options(code, env_options);
res.unwrap();
}
#[test]
fn rasm_pagetag1() {
let code = "
bankset 0
org #5000
label1
bankset 1
org #9000
label2
bankset 2
assert {page}label1==0xC0
assert {page}label2==0xC6
assert {pageset}label1==#C0
assert {pageset}label2==#C2
assert $ == 0x0000
assert $$ == 0x0000
nop";
code_test(code);
}
#[test]
fn test_duration() {
let listing = Listing::from_str(
"
pop de ; 3
"
)
.expect("Unable to assemble this code");
println!("{}", listing.to_string());
assert_eq!(listing.estimated_duration().unwrap(), 3);
let listing = Listing::from_str(
"
inc l ; 1
"
)
.expect("Unable to assemble this code");
println!("{}", listing.to_string());
assert_eq!(listing.estimated_duration().unwrap(), 1);
let listing = Listing::from_str(
"
ld (hl), e ; 2
"
)
.expect("Unable to assemble this code");
println!("{}", listing.to_string());
assert_eq!(listing.estimated_duration().unwrap(), 2);
let listing = Listing::from_str(
"
ld (hl), d ; 2
"
)
.expect("Unable to assemble this code");
println!("{}", listing.to_string());
assert_eq!(listing.estimated_duration().unwrap(), 2);
let listing = Listing::from_str(
"
pop de ; 3
inc l ; 1
ld (hl), e ; 2
inc l ; 1
ld (hl), d ; 2
"
)
.expect("Unable to assemble this code");
println!("{}", listing.to_string());
assert_eq!(listing.estimated_duration().unwrap(), (3 + 1 + 2 + 1 + 2));
}
#[test]
fn test_real1() {
let code = " RUN 0x50, 0xc0";
code_test(code);
let code = r" if {bank}$ == 0
RUN 0x50, 0xc0
endif
";
code_test(code);
}
}