cpclib_asm/assembler/
file.rsuse std::collections::VecDeque;
use std::fs::File;
use std::io::Read;
use std::ops::Deref;
use cpclib_common::camino::{Utf8Path, Utf8PathBuf};
use cpclib_common::itertools::Itertools;
use cpclib_disc::amsdos::{AmsdosFileName, AmsdosHeader, AmsdosManagerNonMut};
use cpclib_disc::disc::Disc;
use cpclib_disc::edsk::{ExtendedDsk, Head};
use either::Either;
use super::embedded::EmbeddedFiles;
use super::Env;
use crate::error::AssemblerError;
use crate::preamble::ParserOptions;
use crate::progress::Progress;
struct Fname<'a, 'b>(Either<&'a Utf8Path, (&'a str, &'b Env)>);
impl<'a, 'b> Deref for Fname<'a, 'b> {
type Target = Either<&'a Utf8Path, (&'a str, &'b Env)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> From<&'a Utf8Path> for Fname<'a, '_> {
fn from(value: &'a Utf8Path) -> Self {
Self(Either::Left(value))
}
}
impl<'a> From<&'a str> for Fname<'a, '_> {
fn from(value: &'a str) -> Self {
let p: &Utf8Path = value.into();
p.into()
}
}
impl<'a, 'b> From<(&'a str, &'b Env)> for Fname<'a, 'b> {
fn from(value: (&'a str, &'b Env)) -> Self {
Self(Either::Right(value))
}
}
pub const DSK_SEPARATOR: char = '#';
pub fn get_filename<S: AsRef<str>>(
fname: S,
options: &ParserOptions,
env: Option<&Env>
) -> Result<Utf8PathBuf, AssemblerError> {
let fname = fname.as_ref();
let components = fname.split(DSK_SEPARATOR).collect_vec();
let real_fname = components.first().unwrap();
let res = options.get_path_for(real_fname, env).map_err(|e| {
match e {
either::Either::Left(asm) => asm,
either::Either::Right(tested) => {
AssemblerError::AssemblingError {
msg: format!("{} not found. Tested {:?}", fname, tested)
}
},
}
})?;
let res = if components.len() == 2 {
let mut s = res.to_string();
s.push(DSK_SEPARATOR);
s.push_str(components.last().unwrap());
Utf8PathBuf::from(&s)
}
else {
res
};
Ok(res)
}
pub fn load_file<'a, 'b, F: Into<Fname<'a, 'b>>>(
fname: F,
options: &ParserOptions
) -> Result<(VecDeque<u8>, Option<AmsdosHeader>), AssemblerError> {
let fname = fname.into();
let true_fname = match &fname.deref() {
either::Either::Right((p, env)) => get_filename(p, options, Some(env))?,
either::Either::Left(p) => p.into()
};
let mut parts = true_fname.as_str().split(DSK_SEPARATOR).rev().collect_vec();
let (data, header) = if parts.len() == 1 {
let data = load_file_raw(fname, options)?;
let mut data = VecDeque::from(data);
let header = if data.len() >= 128 {
let header = AmsdosHeader::from_buffer(data.as_slices().0);
if header.represent_a_valid_file() {
data.drain(..128);
Some(header)
}
else {
None
}
}
else {
None
};
(data, header)
}
else {
let pc_fname = parts.pop().unwrap();
let amsdos_fname = parts.pop().unwrap();
let disc: Box<ExtendedDsk> = if pc_fname.to_ascii_uppercase().ends_with(".DSK") {
Box::new(ExtendedDsk::open(pc_fname).map_err(|e| AssemblerError::AssemblingError { msg: e })?)
} else {
unimplemented!("Need to code loading of {pc_fname}. Disc trait needs to be simplifed by removing all generic parameters :(");
};
let manager = AmsdosManagerNonMut::new_from_disc(&disc, Head::A);
let file = manager
.get_file(AmsdosFileName::try_from(amsdos_fname)?)
.ok_or_else(|| {
AssemblerError::AssemblingError {
msg: format!("Unable to get {amsdos_fname}")
}
})?;
let header = file.header();
let data = VecDeque::from_iter(file.content().iter().cloned());
(data, header)
};
Ok((data, header))
}
pub fn load_file_raw<'a, 'b, F: Into<Fname<'a, 'b>>>(
fname: F,
options: &ParserOptions
) -> Result<Vec<u8>, AssemblerError> {
let fname = fname.into();
let fname = match &fname.deref() {
either::Either::Right((p, env)) => get_filename(p, options, Some(env))?,
either::Either::Left(p) => p.into()
};
let fname_repr = fname.as_str();
let progress = if options.show_progress {
Progress::progress().add_load(fname_repr);
Some(fname_repr)
}
else {
None
};
let content = if fname_repr.starts_with("inner://") {
EmbeddedFiles::get(fname_repr)
.ok_or(AssemblerError::IOError {
msg: format!("Unable to open {:?}; it is not embedded.", fname_repr)
})?
.data
.to_vec()
}
else {
let mut f = File::open(&fname).map_err(|e| {
AssemblerError::IOError {
msg: format!("Unable to open {:?}. {}", fname, e)
}
})?;
let mut content = Vec::new();
f.read_to_end(&mut content).map_err(|e| {
AssemblerError::IOError {
msg: format!("Unable to read {:?}. {}", fname, e)
}
})?;
content
};
if let Some(progress) = progress {
Progress::progress().remove_load(progress);
}
Ok(content)
}
pub fn read_source<P: AsRef<Utf8Path>>(
fname: P,
options: &ParserOptions
) -> Result<String, AssemblerError> {
let fname = fname.as_ref();
let (mut content, header_removed) = load_file(fname, options)?;
assert!(header_removed.is_none());
let content = content.make_contiguous();
Ok(String::from_utf8_lossy(content).into_owned())
}
#[cfg(all(feature = "chardetng", not(target_arch = "wasm32")))]
pub fn handle_source_encoding(_fname: &str, content: &[u8]) -> Result<String, AssemblerError> {
let mut decoder = chardetng::EncodingDetector::new();
decoder.feed(content, true);
let encoding = decoder.guess(None, true);
let content = encoding.decode(content).0;
let content = content.into_owned();
Ok(content)
}
#[cfg(any(not(feature = "chardetng"), target_arch = "wasm32"))]
pub fn handle_source_encoding(_fname: &str, _content: &[u8]) -> Result<String, AssemblerError> {
unimplemented!(
"i have deactivated this stuff to speed up everything. Let's consider each source is UTF8!"
)
}