Pull some of the REPL implementation out into the library, rather than the binary.
This commit is contained in:
123
src/bin/ngri.rs
123
src/bin/ngri.rs
@@ -1,130 +1,11 @@
|
|||||||
use codespan_reporting::diagnostic::Diagnostic;
|
use ngr::backend::BackendError;
|
||||||
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::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
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> {
|
fn main() -> Result<(), BackendError> {
|
||||||
let mut editor = DefaultEditor::new().expect("rustyline works");
|
let mut editor = DefaultEditor::new().expect("rustyline works");
|
||||||
let mut line_no = 0;
|
let mut line_no = 0;
|
||||||
let mut writer = StandardStream::stdout(ColorChoice::Auto);
|
let mut state = ngr::REPL::default();
|
||||||
let config = codespan_reporting::term::Config::default();
|
|
||||||
let mut state = RunLoop::new(&mut writer, config)?;
|
|
||||||
|
|
||||||
println!("No Good Reason, the Interpreter!");
|
println!("No Good Reason, the Interpreter!");
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ pub mod backend;
|
|||||||
mod compiler;
|
mod compiler;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod ir;
|
pub mod ir;
|
||||||
|
mod repl;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
pub use crate::compiler::Compiler;
|
pub use crate::compiler::Compiler;
|
||||||
|
pub use crate::repl::REPL;
|
||||||
|
|||||||
130
src/repl.rs
Normal file
130
src/repl.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use crate::backend::{Backend, BackendError};
|
||||||
|
use crate::ir::Program as IR;
|
||||||
|
use crate::syntax::{Location, ParserError, Statement};
|
||||||
|
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 pretty::termcolor::{ColorChoice, StandardStream};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct REPL {
|
||||||
|
file_database: SimpleFiles<String, String>,
|
||||||
|
jitter: Backend<JITModule>,
|
||||||
|
variable_binding_sites: HashMap<String, Location>,
|
||||||
|
gensym_index: usize,
|
||||||
|
console: StandardStream,
|
||||||
|
console_config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for REPL {
|
||||||
|
fn default() -> Self {
|
||||||
|
let console = StandardStream::stdout(ColorChoice::Auto);
|
||||||
|
REPL::new(console, Config::default()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 REPL {
|
||||||
|
pub fn new(console: StandardStream, console_config: Config) -> Result<Self, BackendError> {
|
||||||
|
Ok(REPL {
|
||||||
|
file_database: SimpleFiles::new(),
|
||||||
|
jitter: Backend::jit(None)?,
|
||||||
|
variable_binding_sites: HashMap::new(),
|
||||||
|
gensym_index: 1,
|
||||||
|
console,
|
||||||
|
console_config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_diagnostic(
|
||||||
|
&mut self,
|
||||||
|
diagnostic: Diagnostic<usize>,
|
||||||
|
) -> Result<(), codespan_reporting::files::Error> {
|
||||||
|
term::emit(
|
||||||
|
&mut self.console,
|
||||||
|
&self.console_config,
|
||||||
|
&self.file_database,
|
||||||
|
&diagnostic,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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".to_string(), 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user