1use std::borrow::Cow;
2use std::collections::VecDeque;
3use std::fs::File;
4use std::io::Read;
5use std::ops::Deref;
6
7use cpclib_common::camino::{Utf8Path, Utf8PathBuf};
8use cpclib_common::itertools::Itertools;
9use cpclib_disc::amsdos::{AmsdosFileName, AmsdosHeader, AmsdosManagerNonMut};
10use cpclib_disc::disc::Disc;
11use cpclib_disc::edsk::{ExtendedDsk, Head};
12use either::Either;
13
14use super::Env;
15use super::embedded::EmbeddedFiles;
16use crate::error::AssemblerError;
17use crate::preamble::ParserOptions;
18use crate::progress::Progress;
19
20pub struct Fname<'a, 'b>(Either<&'a Utf8Path, (&'a str, &'b Env)>);
21
22impl<'a, 'b> Deref for Fname<'a, 'b> {
23 type Target = Either<&'a Utf8Path, (&'a str, &'b Env)>;
24
25 fn deref(&self) -> &Self::Target {
26 &self.0
27 }
28}
29
30impl<'a> From<&'a Utf8Path> for Fname<'a, '_> {
31 fn from(value: &'a Utf8Path) -> Self {
32 Self(Either::Left(value))
33 }
34}
35
36impl<'a> From<&'a str> for Fname<'a, '_> {
37 fn from(value: &'a str) -> Self {
38 let p: &Utf8Path = value.into();
39 p.into()
40 }
41}
42
43impl<'a, 'b> From<(&'a str, &'b Env)> for Fname<'a, 'b> {
44 fn from(value: (&'a str, &'b Env)) -> Self {
45 Self(Either::Right(value))
46 }
47}
48
49pub enum AnyFileNameOwned {
50 InImage { image: String, content: String },
51 Standard(String)
52}
53
54impl From<&AnyFileName<'_>> for AnyFileNameOwned {
55 fn from(value: &AnyFileName) -> Self {
56 match value {
57 AnyFileName::InImage { image, content } => Self::new_in_image(*image, *content),
58 AnyFileName::Standard(content) => Self::new_standard(*content)
59 }
60 }
61}
62
63impl<'fname> From<&'fname AnyFileNameOwned> for AnyFileName<'fname> {
64 fn from(value: &'fname AnyFileNameOwned) -> Self {
65 match value {
66 AnyFileNameOwned::InImage {
67 image,
68 content: amsdos
69 } => {
70 AnyFileName::InImage {
71 image: image.as_str(),
72 content: amsdos.as_str()
73 }
74 },
75 AnyFileNameOwned::Standard(fname) => AnyFileName::Standard(fname.as_str())
76 }
77 }
78}
79
80impl From<&str> for AnyFileNameOwned {
81 fn from(value: &str) -> Self {
82 let any = AnyFileName::from(value);
83 AnyFileNameOwned::from(&any)
84 }
85}
86
87impl AnyFileNameOwned {
88 pub fn new_standard<S: Into<String>>(fname: S) -> Self {
89 Self::Standard(fname.into())
90 }
91
92 pub fn new_in_image<S1: Into<String>, S2: Into<String>>(image: S1, amsdos: S2) -> Self {
93 Self::InImage {
94 image: image.into(),
95 content: amsdos.into()
96 }
97 }
98
99 pub fn as_any_filename(&self) -> AnyFileName {
100 AnyFileName::from(self)
101 }
102}
103
104pub enum AnyFileName<'fname> {
106 InImage {
107 image: &'fname str,
108 content: &'fname str
109 },
110 Standard(&'fname str)
111}
112
113impl<'fname> AnyFileName<'fname> {
114 const DSK_SEPARATOR: char = '#';
115
116 pub fn new_standard(fname: &'fname str) -> Self {
117 Self::Standard(fname)
118 }
119
120 pub fn new_in_image(image: &'fname str, amsdos: &'fname str) -> Self {
121 Self::InImage {
122 image,
123 content: amsdos
124 }
125 }
126
127 pub fn use_image(&self) -> bool {
128 match self {
129 AnyFileName::InImage { .. } => true,
130 _ => false
131 }
132 }
133
134 pub fn image_filename(&self) -> Option<&str> {
135 match self {
136 AnyFileName::InImage { image, .. } => Some(image),
137 AnyFileName::Standard(_) => None
138 }
139 }
140
141 pub fn content_filename(&self) -> &str {
142 match self {
143 AnyFileName::InImage {
144 image,
145 content: amsdos
146 } => amsdos,
147 AnyFileName::Standard(content) => content
148 }
149 }
150
151 pub fn basm_fname(&self) -> Cow<str> {
152 match self {
153 AnyFileName::InImage { image, content } => {
154 Cow::Owned(format!("{}{}{}", image, Self::DSK_SEPARATOR, content))
155 },
156 AnyFileName::Standard(content) => Cow::Borrowed(content)
157 }
158 }
159
160 fn base_filename(&self) -> &str {
161 match self {
162 AnyFileName::InImage {
163 image,
164 content: amsdos
165 } => image,
166 AnyFileName::Standard(f) => f
167 }
168 }
169
170 pub fn path_for_base_filename(
171 &self,
172 options: &ParserOptions,
173 env: Option<&Env>
174 ) -> Result<Utf8PathBuf, AssemblerError> {
175 let real_fname = self.base_filename();
176
177 let res = options.get_path_for(real_fname, env).map_err(|e| {
178 match e {
179 either::Either::Left(asm) => asm,
180 either::Either::Right(tested) => {
181 AssemblerError::AssemblingError {
182 msg: format!("{} not found. Tested {:?}", self.base_filename(), tested)
183 }
184 },
185 }
186 })?;
187
188 let res = if self.image_filename().is_some() {
189 let mut s = res.to_string();
190 s.push(Self::DSK_SEPARATOR);
191 s.push_str(self.content_filename());
192 Utf8PathBuf::from(&s)
193 }
194 else {
195 res
196 };
197
198 Ok(res)
199 }
200}
201
202impl<'fname> From<&'fname str> for AnyFileName<'fname> {
203 fn from(fname: &'fname str) -> Self {
204 const IMAGES_EXT: &[&str] = &[".dsk", ".edsk", ".hfe"];
205
206 let components = fname.split(Self::DSK_SEPARATOR).collect_vec();
207 match components[..] {
208 [fname] => AnyFileName::Standard(fname),
209 [first, second] => {
210 let is_image = IMAGES_EXT
211 .iter()
212 .any(|ext| first.to_ascii_lowercase().ends_with(ext));
213 if is_image {
214 AnyFileName::InImage {
215 image: first,
216 content: second
217 }
218 }
219 else {
220 AnyFileName::Standard(fname)
221 }
222 },
223 _ => {
224 todo!(
225 "Need to handle case where fname as several {}",
226 Self::DSK_SEPARATOR
227 )
228 }
229 }
230 }
231}
232
233pub fn get_filename_to_read<S: AsRef<str>>(
234 fname: S,
235 options: &ParserOptions,
236 env: Option<&Env>
237) -> Result<Utf8PathBuf, AssemblerError> {
238 let fname = fname.as_ref();
239
240 AnyFileName::from(fname).path_for_base_filename(options, env)
241}
242
243pub fn load_file<'a, 'b, F: Into<Fname<'a, 'b>>>(
248 fname: F,
249 options: &ParserOptions
250) -> Result<(VecDeque<u8>, Option<AmsdosHeader>), AssemblerError> {
251 let fname = fname.into();
252 let true_fname = match &fname.deref() {
253 either::Either::Right((p, env)) => get_filename_to_read(p, options, Some(env))?,
254 either::Either::Left(p) => p.into()
255 };
256
257 let true_name = true_fname.as_str();
258 let any_filename: AnyFileName<'_> = true_name.into();
259 let (data, header) = if !any_filename.use_image() {
260 let data = load_file_raw(any_filename.content_filename(), options)?;
264 let mut data = VecDeque::from(data);
265
266 let header = if data.len() >= 128 {
268 let header = AmsdosHeader::from_buffer(data.as_slices().0);
270
271 if (header.file_length() + 128) as usize == data.len() {
274 data.drain(..128);
275 Some(header)
276 }
277 else {
278 None
279 }
280 }
281 else {
282 None
283 };
284
285 (data, header)
286 }
287 else {
288 let image_fname = any_filename.image_filename().unwrap();
290 let amsdos_fname = any_filename.content_filename();
291
292 let disc: Box<ExtendedDsk> = if image_fname.to_ascii_uppercase().ends_with(".DSK") {
293 Box::new(ExtendedDsk::open(image_fname).map_err(|e| AssemblerError::AssemblingError { msg: e })?)
294
295 } else {
296 unimplemented!("Need to code loading of {image_fname}. Disc trait needs to be simplifed by removing all generic parameters :(");
297 };
298
299 let manager = AmsdosManagerNonMut::new_from_disc(&disc, Head::A);
300 let file = manager
301 .get_file(AmsdosFileName::try_from(amsdos_fname)?)
302 .ok_or_else(|| {
303 AssemblerError::AssemblingError {
304 msg: format!("Unable to get {amsdos_fname}")
305 }
306 })?;
307
308 let header = file.header();
309 let data = VecDeque::from_iter(file.content().iter().cloned());
310
311 (data, header)
312 };
313
314 Ok((data, header))
315}
316
317pub fn load_file_raw<'a, 'b, F: Into<Fname<'a, 'b>>>(
319 fname: F,
320 options: &ParserOptions
321) -> Result<Vec<u8>, AssemblerError> {
322 let fname = fname.into();
323
324 let fname = match &fname.deref() {
326 either::Either::Right((p, env)) => get_filename_to_read(p, options, Some(env))?,
327 either::Either::Left(p) => p.into()
328 };
329
330 let fname_repr = fname.as_str();
331
332 let progress = if options.show_progress {
333 Progress::progress().add_load(fname_repr);
334 Some(fname_repr)
335 }
336 else {
337 None
338 };
339
340 let content = if fname_repr.starts_with("inner://") {
342 EmbeddedFiles::get(fname_repr)
344 .ok_or(AssemblerError::IOError {
345 msg: format!("Unable to open {:?}; it is not embedded.", fname_repr)
346 })?
347 .data
348 .to_vec()
349 }
350 else {
351 let mut f = File::open(&fname).map_err(|e| {
353 AssemblerError::IOError {
354 msg: format!("Unable to open {:?}. {}", fname, e)
355 }
356 })?;
357
358 let mut content = Vec::new();
359 f.read_to_end(&mut content).map_err(|e| {
360 AssemblerError::IOError {
361 msg: format!("Unable to read {:?}. {}", fname, e)
362 }
363 })?;
364
365 content
366 };
367
368 if let Some(progress) = progress {
369 Progress::progress().remove_load(progress);
370 }
371 Ok(content)
372}
373
374pub fn read_source<P: AsRef<Utf8Path>>(
377 fname: P,
378 options: &ParserOptions
379) -> Result<String, AssemblerError> {
380 let fname = fname.as_ref();
381
382 let (mut content, header_removed) = load_file(fname, options)?;
383 assert!(header_removed.is_none());
384
385 let content = content.make_contiguous();
386 Ok(String::from_utf8_lossy(content).into_owned())
389}
390
391#[cfg(all(feature = "chardetng", not(target_arch = "wasm32")))]
393pub fn handle_source_encoding(_fname: &str, content: &[u8]) -> Result<String, AssemblerError> {
394 let mut decoder = chardetng::EncodingDetector::new();
395 decoder.feed(content, true);
396 let encoding = decoder.guess(None, true);
397 let content = encoding.decode(content).0;
398
399 let content = content.into_owned();
400
401 Ok(content)
402}
403
404#[cfg(any(not(feature = "chardetng"), target_arch = "wasm32"))]
405pub fn handle_source_encoding(_fname: &str, _content: &[u8]) -> Result<String, AssemblerError> {
406 unimplemented!(
407 "i have deactivated this stuff to speed up everything. Let's consider each source is UTF8!"
408 )
409}
410
411