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(()) } }