🤷 The initial version of the compiler, both static and JIT.
This implements a full compiler, with both static compilation and JIT support, for the world's simplest and silliest programming language. You can do math, and print variables. That's it. On the bright side, it implements every part of the compiler, from the lexer and parser; through analysis and simplification; and into a reasonable code generator. This should be a good jumping off point for adding more advanced features. Tests, including proptests, are included to help avoid regressions.
This commit is contained in:
98
src/bin/ngrc.rs
Normal file
98
src/bin/ngrc.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
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;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct CommandLineArguments {
|
||||
/// Optional output file name
|
||||
#[clap(short, long)]
|
||||
output: Option<String>,
|
||||
|
||||
/// The file to parse
|
||||
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();
|
||||
|
||||
match compile(&mut file_database) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
|
||||
term::emit(
|
||||
&mut writer.lock(),
|
||||
&config,
|
||||
&file_database,
|
||||
&Diagnostic::from(e),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/bin/ngri.rs
Normal file
157
src/bin/ngri.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
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 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()?,
|
||||
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)?;
|
||||
|
||||
println!("No Good Reason, the Interpreter!");
|
||||
loop {
|
||||
line_no += 1;
|
||||
match editor.readline("> ") {
|
||||
Ok(command) => match command.trim() {
|
||||
"" => continue,
|
||||
":quit" => break,
|
||||
_ => state.process_input(line_no, command),
|
||||
},
|
||||
Err(ReadlineError::Io(e)) => {
|
||||
eprintln!("IO error: {}", e);
|
||||
break;
|
||||
}
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(ReadlineError::Interrupted) => break,
|
||||
Err(ReadlineError::Errno(e)) => {
|
||||
eprintln!("Unknown syscall error: {}", e);
|
||||
break;
|
||||
}
|
||||
Err(ReadlineError::WindowResized) => continue,
|
||||
Err(e) => {
|
||||
eprintln!("Unknown internal error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user