gbas 0.1.0

An assembler for Game Boy
Documentation
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
            }]
        )
    }
}