📜 Add better documentation across the compiler. (#3)
These changes pay particular attention to API endpoints, to try to ensure that any rustdocs generated are detailed and sensible. A good next step, eventually, might be to include doctest examples, as well. For the moment, it's not clear that they would provide a lot of value, though. In addition, this does a couple refactors to simplify the code base in ways that make things clearer or, at least, briefer.
This commit is contained in:
@@ -1,17 +1,7 @@
|
||||
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)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct CommandLineArguments {
|
||||
@@ -23,76 +13,14 @@ struct CommandLineArguments {
|
||||
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() {
|
||||
let mut file_database = SimpleFiles::new();
|
||||
let args = CommandLineArguments::parse();
|
||||
let mut compiler = ngr::Compiler::default();
|
||||
|
||||
match compile(&mut file_database) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
let output_file = args.output.unwrap_or("output.o".to_string());
|
||||
|
||||
term::emit(
|
||||
&mut writer.lock(),
|
||||
&config,
|
||||
&file_database,
|
||||
&Diagnostic::from(e),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(bytes) = compiler.compile(&args.file) {
|
||||
std::fs::write(&output_file, bytes)
|
||||
.unwrap_or_else(|x| eprintln!("Could not write to file {}: {}", output_file, x));
|
||||
}
|
||||
}
|
||||
|
||||
135
src/bin/ngri.rs
135
src/bin/ngri.rs
@@ -1,130 +1,11 @@
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use codespan_reporting::files::SimpleFiles;
|
||||
use codespan_reporting::term::{self, Config};
|
||||
use cranelift_jit::JITModule;
|
||||
use cranelift_module::ModuleError;
|
||||
use ngr::backend::{Backend, BackendError};
|
||||
use ngr::ir::Program as IR;
|
||||
use ngr::syntax::{Location, ParserError, Statement};
|
||||
use pretty::termcolor::{ColorChoice, StandardStream, WriteColor};
|
||||
use ngr::backend::BackendError;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::DefaultEditor;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct RunLoop<'a> {
|
||||
file_database: SimpleFiles<&'a str, String>,
|
||||
jitter: Backend<JITModule>,
|
||||
variable_binding_sites: HashMap<String, Location>,
|
||||
gensym_index: usize,
|
||||
writer: &'a mut dyn WriteColor,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum REPLError {
|
||||
#[error("Error parsing statement: {0}")]
|
||||
Parser(#[from] ParserError),
|
||||
#[error("JIT error: {0}")]
|
||||
JIT(#[from] BackendError),
|
||||
#[error("Internal cranelift error: {0}")]
|
||||
Cranelift(#[from] ModuleError),
|
||||
#[error(transparent)]
|
||||
Reporting(#[from] codespan_reporting::files::Error),
|
||||
}
|
||||
|
||||
impl From<REPLError> for Diagnostic<usize> {
|
||||
fn from(value: REPLError) -> Self {
|
||||
match value {
|
||||
REPLError::Parser(err) => Diagnostic::from(&err),
|
||||
REPLError::JIT(err) => Diagnostic::from(err),
|
||||
REPLError::Cranelift(err) => Diagnostic::bug().with_message(format!("{}", err)),
|
||||
REPLError::Reporting(err) => Diagnostic::bug().with_message(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RunLoop<'a> {
|
||||
pub fn new(writer: &'a mut dyn WriteColor, config: Config) -> Result<Self, BackendError> {
|
||||
Ok(RunLoop {
|
||||
file_database: SimpleFiles::new(),
|
||||
jitter: Backend::jit(None)?,
|
||||
variable_binding_sites: HashMap::new(),
|
||||
gensym_index: 1,
|
||||
writer,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
fn emit_diagnostic(
|
||||
&mut self,
|
||||
diagnostic: Diagnostic<usize>,
|
||||
) -> Result<(), codespan_reporting::files::Error> {
|
||||
term::emit(self.writer, &self.config, &self.file_database, &diagnostic)
|
||||
}
|
||||
|
||||
fn process_input(&mut self, line_no: usize, command: String) {
|
||||
if let Err(err) = self.process(line_no, command) {
|
||||
if let Err(e) = self.emit_diagnostic(Diagnostic::from(err)) {
|
||||
eprintln!(
|
||||
"WOAH! System having trouble printing error messages. This is very bad. ({})",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, line_no: usize, command: String) -> Result<(), REPLError> {
|
||||
let entry = self.file_database.add("entry", command);
|
||||
let source = self
|
||||
.file_database
|
||||
.get(entry)
|
||||
.expect("entry exists")
|
||||
.source();
|
||||
let syntax = Statement::parse(entry, source)?;
|
||||
|
||||
// if this is a variable binding, and we've never defined this variable before,
|
||||
// we should tell cranelift about it. this is optimistic; if we fail to compile,
|
||||
// then we won't use this definition until someone tries again.
|
||||
if let Statement::Binding(_, ref name, _) = syntax {
|
||||
if !self.variable_binding_sites.contains_key(name.as_str()) {
|
||||
self.jitter.define_string(name)?;
|
||||
self.jitter.define_variable(name.clone())?;
|
||||
}
|
||||
};
|
||||
|
||||
let (mut errors, mut warnings) = syntax.validate(&mut self.variable_binding_sites);
|
||||
let stop = !errors.is_empty();
|
||||
let messages = errors
|
||||
.drain(..)
|
||||
.map(Into::into)
|
||||
.chain(warnings.drain(..).map(Into::into));
|
||||
|
||||
for message in messages {
|
||||
self.emit_diagnostic(message)?;
|
||||
}
|
||||
|
||||
if stop {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ir = IR::from(syntax.simplify(&mut self.gensym_index));
|
||||
let name = format!("line{}", line_no);
|
||||
let function_id = self.jitter.compile_function(&name, ir)?;
|
||||
self.jitter.module.finalize_definitions()?;
|
||||
let compiled_bytes = self.jitter.bytes(function_id);
|
||||
let compiled_function = unsafe { std::mem::transmute::<_, fn() -> ()>(compiled_bytes) };
|
||||
compiled_function();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), BackendError> {
|
||||
let mut editor = DefaultEditor::new().expect("rustyline works");
|
||||
let mut line_no = 0;
|
||||
let mut writer = StandardStream::stdout(ColorChoice::Auto);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
let mut state = RunLoop::new(&mut writer, config)?;
|
||||
let mut state = ngr::REPL::default();
|
||||
|
||||
println!("No Good Reason, the Interpreter!");
|
||||
loop {
|
||||
@@ -135,18 +16,30 @@ fn main() -> Result<(), BackendError> {
|
||||
":quit" => break,
|
||||
_ => state.process_input(line_no, command),
|
||||
},
|
||||
|
||||
// it's not clear to me what this could be, but OK
|
||||
Err(ReadlineError::Io(e)) => {
|
||||
eprintln!("IO error: {}", e);
|
||||
break;
|
||||
}
|
||||
|
||||
// Control-D and Control-C
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(ReadlineError::Interrupted) => break,
|
||||
|
||||
// For some reason this doesn't exist on Windows. I also don't quite know
|
||||
// what would cause this, but ...
|
||||
#[cfg(not(windows))]
|
||||
Err(ReadlineError::Errno(e)) => {
|
||||
eprintln!("Unknown syscall error: {}", e);
|
||||
break;
|
||||
}
|
||||
|
||||
// We don't actually do any reflow-ing if we change the terminal size,
|
||||
// so we can just ignore this.
|
||||
Err(ReadlineError::WindowResized) => continue,
|
||||
|
||||
// Why on earth are there so many error types?
|
||||
Err(e) => {
|
||||
eprintln!("Unknown internal error: {}", e);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user