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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
pub use backend::Rom;
pub use codebase::StdFileSystem;
pub use diagnostics::TerminalOutput;

use codebase::CodebaseError;
use diagnostics::mk_diagnostic;

mod backend;
mod codebase;
mod diagnostics;
mod expr;
mod frontend;
mod instruction;
mod span;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Width {
    Byte,
    Word,
}

impl Width {
    fn len(self) -> i32 {
        match self {
            Width::Byte => 1,
            Width::Word => 2,
        }
    }
}

pub struct Config<'a> {
    pub input: &'a mut dyn codebase::FileSystem,
    pub output: &'a mut dyn diagnostics::DiagnosticsOutput,
}

pub fn assemble<'a>(name: &str, config: &mut Config<'a>) -> Option<Rom> {
    let result = try_assemble(name, config);
    match result {
        Ok(rom) => Some(rom),
        Err(error) => {
            config.output.emit(mk_diagnostic(name, &error.into()));
            None
        }
    }
}

fn try_assemble<'a>(name: &str, config: &mut Config<'a>) -> Result<Rom, CodebaseError> {
    let codebase = codebase::FileCodebase::new(config.input);
    let mut diagnostics = diagnostics::OutputForwarder {
        output: config.output,
        codebase: &codebase.cache,
    };
    let object = frontend::analyze_file(
        name.to_string(),
        &codebase,
        span::SimpleTokenTracker {},
        backend::ObjectBuilder::new(),
        &mut diagnostics,
    )?;
    Ok(backend::link(object, &mut diagnostics).into_rom())
}

#[cfg(test)]
mod tests {
    use super::*;
    use diagnostics::Diagnostic;
    use std::collections::HashMap;
    use std::io;

    struct MockFileSystem {
        files: HashMap<String, Vec<u8>>,
    }

    impl MockFileSystem {
        fn new() -> MockFileSystem {
            MockFileSystem {
                files: HashMap::new(),
            }
        }

        fn add(&mut self, name: impl Into<String>, data: &[u8]) {
            self.files.insert(name.into(), data.into());
        }
    }

    impl codebase::FileSystem for MockFileSystem {
        fn read_file(&self, filename: &str) -> io::Result<Vec<u8>> {
            self.files
                .get(filename)
                .cloned()
                .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "file does not exist"))
        }
    }

    struct MockDiagnosticOutput {
        diagnostics: Vec<Diagnostic<String>>,
    }

    impl MockDiagnosticOutput {
        fn new() -> MockDiagnosticOutput {
            MockDiagnosticOutput {
                diagnostics: Vec::new(),
            }
        }
    }

    impl diagnostics::DiagnosticsOutput for MockDiagnosticOutput {
        fn emit(&mut self, diagnostic: Diagnostic<String>) {
            self.diagnostics.push(diagnostic)
        }
    }

    #[test]
    fn invalid_utf8() {
        let path = "/my/file";
        let mut fs = MockFileSystem::new();
        fs.add(path, &[0x5a, 0x0a, 0xf6, 0xa6]);
        let mut output = MockDiagnosticOutput::new();
        {
            let mut config = Config {
                input: &mut fs,
                output: &mut output,
            };
            assemble(path, &mut config);
        }
        assert_eq!(
            output.diagnostics,
            [Diagnostic {
                file: path.to_string(),
                message: "file contains invalid UTF-8".to_string(),
                location: None
            }]
        )
    }

    #[test]
    fn nonexistent_file() {
        let path = "/my/file";
        let mut fs = MockFileSystem::new();
        let mut output = MockDiagnosticOutput::new();
        {
            let mut config = Config {
                input: &mut fs,
                output: &mut output,
            };
            assemble(path, &mut config);
        }
        assert_eq!(
            output.diagnostics,
            [Diagnostic {
                file: path.to_string(),
                message: "file does not exist".to_string(),
                location: None
            }]
        )
    }
}