📜 Add better documentation across the compiler. #3
@@ -1,17 +1,7 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use codespan_reporting::diagnostic::Diagnostic;
|
|
||||||
use codespan_reporting::files::SimpleFiles;
|
|
||||||
use codespan_reporting::term;
|
|
||||||
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
|
|
||||||
use cranelift_object::object;
|
|
||||||
|
|
||||||
use ngr::backend::Backend;
|
|
||||||
use ngr::backend::BackendError;
|
|
||||||
use ngr::ir::Program as IR;
|
|
||||||
use ngr::syntax::{ParserError, Program as Syntax};
|
|
||||||
use target_lexicon::Triple;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
|
/// Clap is great! Even though we don't have many command line arguments
|
||||||
|
/// yet, this is just really neat.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct CommandLineArguments {
|
struct CommandLineArguments {
|
||||||
@@ -23,76 +13,14 @@ struct CommandLineArguments {
|
|||||||
file: String,
|
file: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
enum MainError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Backend(#[from] BackendError),
|
|
||||||
#[error("Parser error")]
|
|
||||||
ParserError(#[from] ParserError),
|
|
||||||
#[error("IO error")]
|
|
||||||
IoError(#[from] std::io::Error),
|
|
||||||
#[error("write error")]
|
|
||||||
WriteError(#[from] object::write::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MainError> for Diagnostic<usize> {
|
|
||||||
fn from(value: MainError) -> Self {
|
|
||||||
match value {
|
|
||||||
MainError::Backend(be) => be.into(),
|
|
||||||
MainError::ParserError(pe) => (&pe).into(),
|
|
||||||
MainError::IoError(e) => Diagnostic::error().with_message(format!("IO error: {}", e)),
|
|
||||||
MainError::WriteError(e) => {
|
|
||||||
Diagnostic::error().with_message(format!("Module write error: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile(file_database: &mut SimpleFiles<String, String>) -> Result<(), MainError> {
|
|
||||||
let args = CommandLineArguments::parse();
|
|
||||||
|
|
||||||
let syntax = Syntax::parse_file(file_database, &args.file)?;
|
|
||||||
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));
|
|
||||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
|
||||||
let config = codespan_reporting::term::Config::default();
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
term::emit(&mut writer.lock(), &config, file_database, &message).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if stop {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let ir = IR::from(syntax.simplify());
|
|
||||||
let mut backend = Backend::object_file(Triple::host())?;
|
|
||||||
backend.compile_function("gogogo", ir)?;
|
|
||||||
let bytes = backend.bytes()?;
|
|
||||||
std::fs::write(args.output.unwrap_or_else(|| "output.o".to_string()), bytes)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut file_database = SimpleFiles::new();
|
let args = CommandLineArguments::parse();
|
||||||
|
let mut compiler = ngr::Compiler::default();
|
||||||
|
|
||||||
match compile(&mut file_database) {
|
let output_file = args.output.unwrap_or("output.o".to_string());
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => {
|
|
||||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
|
||||||
let config = codespan_reporting::term::Config::default();
|
|
||||||
|
|
||||||
term::emit(
|
if let Some(bytes) = compiler.compile(&args.file) {
|
||||||
&mut writer.lock(),
|
std::fs::write(&output_file, bytes)
|
||||||
&config,
|
.unwrap_or_else(|x| eprintln!("Could not write to file {}: {}", output_file, x));
|
||||||
&file_database,
|
|
||||||
&Diagnostic::from(e),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
136
src/compiler.rs
Normal file
136
src/compiler.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
pub struct Compiler {
|
||||||
|
file_database: SimpleFiles<String, String>,
|
||||||
|
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<P: AsRef<str>>(&mut self, input_file: P) -> Option<Vec<u8>> {
|
||||||
|
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.
|
||||||
|
fn compile_internal(&mut self, input_file: &str) -> Result<Option<Vec<u8>>, 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; first we simplify it, then
|
||||||
|
// we do the conversion.
|
||||||
|
let ir = IR::from(syntax.simplify());
|
||||||
|
|
||||||
|
// 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()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(&mut self, diagnostic: Diagnostic<usize>) {
|
||||||
|
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<CompilerError> for Diagnostic<usize> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
mod compiler;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod ir;
|
pub mod ir;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
|
pub use crate::compiler::Compiler;
|
||||||
|
|||||||
Reference in New Issue
Block a user