use crate::backend::Backend; use crate::ir::Program as IR; use crate::syntax::Program as Syntax; use codespan_reporting::{ diagnostic::Diagnostic, files::SimpleFiles, term::{self, Config}, }; use pretty::termcolor::{ColorChoice, StandardStream}; use target_lexicon::Triple; /// A high-level compiler for NGR programs. /// /// This object can be built once, and then re-used many times to build multiple /// files. For most users, the [`Default`] implementation should be sufficient; /// it will use `stderr` for warnings and errors, with default colors based on /// what we discover from the terminal. For those who want to provide alternate /// outputs, though, the `Compiler::new` constructor is available. pub struct Compiler { file_database: SimpleFiles, console: StandardStream, console_config: Config, } impl Default for Compiler { fn default() -> Self { let console = StandardStream::stderr(ColorChoice::Auto); Compiler::new(console, Config::default()) } } impl Compiler { /// Create a new compiler object. /// /// This object can be re-used to compile as many files as you like. /// Use this function if you want to configure your output console and/or /// its configuration in some custom way. Alternatively, you can use the /// `Default` implementation, which will emit information to `stderr` with /// a reasonable default configuration. pub fn new(console: StandardStream, console_config: Config) -> Self { Compiler { file_database: SimpleFiles::new(), console, console_config, } } /// Compile the given file, returning the object file as a vector of bytes. /// /// This function may create output, via the console configured with this /// `Compiler` object. If the compilation fails for any reason, will return /// `None`. pub fn compile>(&mut self, input_file: P) -> Option> { match self.compile_internal(input_file.as_ref()) { Ok(x) => x, Err(e) => { self.emit(e.into()); None } } } /// This is the actual meat of the compilation chain; we hide it from the user /// because the type is kind of unpleasant. /// /// The weird error type comes from the fact that we can run into three types /// of result: /// /// * Fundamental errors, like an incorrectly formatted file or some /// oddity with IO. These return `Err`. /// * Validation errors, where we reject the program due to something /// semantically wrong with them. These return `Ok(None)`. /// * Success! In this case, we return `Ok(Some(...))`, where the bytes /// returned is the contents of the compiled object file. /// fn compile_internal(&mut self, input_file: &str) -> Result>, CompilerError> { // Try to parse the file into our syntax AST. If we fail, emit the error // and then immediately return `None`. let syntax = Syntax::parse_file(&mut self.file_database, input_file)?; // Now validate the user's syntax AST. This can possibly find errors and/or // create warnings. We can continue if we only get warnings, but need to stop // if we get any errors. let (mut errors, mut warnings) = syntax.validate(); let stop = !errors.is_empty(); let messages = errors .drain(..) .map(Into::into) .chain(warnings.drain(..).map(Into::into)); // emit all the messages we receive; warnings *and* errors for message in messages { self.emit(message); } // we got errors, so just stop right now. perhaps oddly, this is Ok(None); // we've already said all we're going to say in the messags above, so there's // no need to provide another `Err` result. if stop { return Ok(None); } // Now that we've validated it, turn it into IR. let ir = IR::from(syntax); // Finally, send all this to Cranelift for conversion into an object file. let mut backend = Backend::object_file(Triple::host())?; backend.compile_function("gogogo", ir)?; Ok(Some(backend.bytes()?)) } /// Emit a diagnostic. /// /// This is just a really handy shorthand we use elsewhere in the object, because /// there's a lot of boilerplate we'd like to skip. fn emit(&mut self, diagnostic: Diagnostic) { term::emit( &mut self.console.lock(), &self.console_config, &self.file_database, &diagnostic, ) .expect("codespan reporting term::emit works"); } } // This is just a handy type that we can convert things into; it's not // exposed outside this module, and doesn't actually do much of interest. #[derive(Debug, thiserror::Error)] enum CompilerError { #[error(transparent)] Backend(#[from] crate::backend::BackendError), #[error(transparent)] ParserError(#[from] crate::syntax::ParserError), #[error(transparent)] IoError(#[from] std::io::Error), #[error(transparent)] WriteError(#[from] cranelift_object::object::write::Error), } // Since we're going to use codespan to report pretty much all errors, // this just passes through most of the errors, or makes simple versions // of `Diagnostic` for those that we don't have existing `From`s. impl From for Diagnostic { fn from(value: CompilerError) -> Self { match value { CompilerError::Backend(be) => be.into(), CompilerError::ParserError(pe) => (&pe).into(), CompilerError::IoError(e) => { Diagnostic::error().with_message(format!("IO error: {}", e)) } CompilerError::WriteError(e) => { Diagnostic::error().with_message(format!("Module write error: {}", e)) } } } }