1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

use either::Either;

use super::embedded::EmbeddedFiles;
use super::Env;
use crate::error::AssemblerError;
use crate::preamble::ParserOptions;

use crate::progress::Progress;

type Fname<'a, 'b> = either::Either<&'a Path, (&'a str, &'b Env)>;

pub fn get_filename(
    fname: &str,
    options: &ParserOptions,
    env: Option<&Env>
) -> Result<PathBuf, AssemblerError> {
    options.get_path_for(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)
                }
            }
        }
    })
}

/// Load a file
/// - if path is provided, this is the file name used
/// - if a string is provided, there is a search of appropriate filename
pub fn load_binary(fname: Fname, options: &ParserOptions) -> Result<Vec<u8>, AssemblerError> {
    // Retreive fname
    let fname = match &fname {
        either::Either::Right((p, env)) => get_filename(p, options, Some(env))?,
        either::Either::Left(p) => p.into()
    };

    let fname_repr = fname.to_str().unwrap();

    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://") {
        // handle inner file
        EmbeddedFiles::get(fname_repr)
            .ok_or(AssemblerError::IOError {
                msg: format!("Unable to open {:?}; it is not embedded.", fname_repr)
            })?
            .data
            .to_vec()
    }
    else {
        // handle real file
        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.to_string())
            }
        })?;
        content
    };

    if let Some(progress) = progress {
        Progress::progress().remove_load(progress);
    }
    Ok(content)
}

/// Read the content of the source file.
/// Uses the context to obtain the appropriate file other the included directories
pub fn read_source<P: AsRef<Path>>(
    fname: P,
    options: &ParserOptions
) -> Result<String, AssemblerError> {
    let fname = fname.as_ref();

    let content = load_binary(Either::Left(fname), options)?;
    handle_source_encoding(fname.to_str().unwrap(), &content)
}

// Never fail
#[cfg(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(target_arch = "wasm32")]
pub fn handle_source_encoding(_fname: &str, content: &[u8]) -> Result<String, AssemblerError> {
    todo!()
}