From e648f3b31ba2345da9a771b4ff6673bc9149b7c7 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Sun, 16 Apr 2023 21:02:21 -0700 Subject: [PATCH] Pull some of the REPL implementation out into the library, rather than the binary. --- src/bin/ngri.rs | 123 +-------------------------------------------- src/lib.rs | 2 + src/repl.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 121 deletions(-) create mode 100644 src/repl.rs diff --git a/src/bin/ngri.rs b/src/bin/ngri.rs index b1d74de..433972a 100644 --- a/src/bin/ngri.rs +++ b/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, - variable_binding_sites: HashMap, - 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 for Diagnostic { - 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 { - 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, - ) -> 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 { diff --git a/src/lib.rs b/src/lib.rs index aea5c29..d0a4589 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ pub mod backend; mod compiler; pub mod eval; pub mod ir; +mod repl; pub mod syntax; pub use crate::compiler::Compiler; +pub use crate::repl::REPL; diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000..1270b84 --- /dev/null +++ b/src/repl.rs @@ -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, + jitter: Backend, + variable_binding_sites: HashMap, + 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 for Diagnostic { + 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 { + 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, + ) -> 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(()) + } +}