use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::thread;
use codespan_reporting::diagnostic::Diagnostic;
use arret_syntax::datum::DataStr;
use arret_syntax::span::FileId;
use crate::context;
use crate::context::ModuleId;
use crate::hir;
use crate::hir::scope::Scope;
use crate::reporting::{diagnostic_for_syntax_error, errors_to_diagnostics, new_primary_label};
use crate::ty;
use crate::CompileCtx;
use crate::mir::eval_hir::EvalHirCtx;
use crate::mir::Value;
use crate::typeck::infer::{infer_module, infer_repl_expr};
#[derive(Clone, Copy)]
pub enum EvalKind {
Type,
Value,
}
#[derive(Debug, PartialEq)]
pub struct EvaledExprValue {
pub type_str: String,
pub value_str: String,
pub type_is_literal: bool,
}
#[derive(Debug, PartialEq)]
pub enum EvaledLine {
EmptyInput,
Defs(Vec<DataStr>),
ExprType(String),
ExprValue(EvaledExprValue),
}
struct ReplEngine<'ccx> {
root_scope: Scope<'static>,
ccx: &'ccx CompileCtx,
inferred_module_vars: HashMap<context::ModuleId, Arc<HashMap<hir::LocalId, ty::Ref<ty::Poly>>>>,
seen_modules: HashSet<context::ModuleId>,
ehx: EvalHirCtx,
}
impl<'ccx> ReplEngine<'ccx> {
fn new(ccx: &'ccx CompileCtx) -> Self {
Self {
root_scope: Scope::root(),
ccx,
seen_modules: HashSet::new(),
inferred_module_vars: HashMap::new(),
ehx: EvalHirCtx::new(ccx.enable_optimisations()),
}
}
fn bound_names(&self) -> Vec<DataStr> {
self.root_scope
.bound_idents()
.filter_map(move |ident| {
if ident.ns_id() == Scope::root_ns_id() {
Some(ident.name().clone())
} else {
None
}
})
.collect()
}
fn visit_module_tree(
&mut self,
root_module: &Arc<context::Module>,
) -> Result<(), Vec<Diagnostic<FileId>>> {
if self.seen_modules.contains(&root_module.module_id) {
return Ok(());
}
self.seen_modules.insert(root_module.module_id);
for import in root_module.imports.values() {
self.visit_module_tree(import)?;
}
self.inferred_module_vars
.insert(root_module.module_id, root_module.inferred_locals.clone());
self.ehx
.visit_module_defs(root_module.module_id, &root_module.defs)?;
Ok(())
}
fn eval_line(
&mut self,
input: String,
kind: EvalKind,
) -> Result<EvaledLine, Vec<Diagnostic<FileId>>> {
use std::io::Write;
use crate::hir::lowering::LoweredReplDatum;
let source_file = self.ccx.source_loader().load_string("repl".into(), input);
let input_data = source_file
.parsed()
.map_err(|err| vec![diagnostic_for_syntax_error(&err)])?;
let input_datum = match input_data {
[] => {
return Ok(EvaledLine::EmptyInput);
}
[input_datum] => input_datum,
_ => {
let extra_span = input_data[1].span();
return Err(vec![Diagnostic::error()
.with_message("unexpected trailing datum")
.with_labels(vec![new_primary_label(
extra_span,
"trailing datum",
)])]);
}
};
let module_id = ModuleId::alloc();
let mut child_scope = Scope::child(&self.root_scope);
let lowered_repl_datum =
hir::lowering::lower_repl_datum(self.ccx, &mut child_scope, input_datum)
.map_err(errors_to_diagnostics)?;
let exported_bindings = child_scope.into_exported_bindings();
self.root_scope
.import_bindings(exported_bindings, module_id);
match lowered_repl_datum {
LoweredReplDatum::Import(modules) => {
for module in modules.values() {
self.visit_module_tree(module)?;
}
Ok(EvaledLine::Defs(self.bound_names()))
}
LoweredReplDatum::EvaluableDef(def) => {
let inferred_module = infer_module(&self.inferred_module_vars, vec![def])
.map_err(errors_to_diagnostics)?;
self.inferred_module_vars
.insert(module_id, Arc::new(inferred_module.inferred_locals));
self.ehx
.consume_module_defs(module_id, inferred_module.defs)?;
Ok(EvaledLine::Defs(self.bound_names()))
}
LoweredReplDatum::NonEvaluableDef => {
Ok(EvaledLine::Defs(self.bound_names()))
}
LoweredReplDatum::Expr(decl_expr) => {
let node = infer_repl_expr(&self.inferred_module_vars, decl_expr)?;
let type_str = hir::str_for_ty_ref(node.result_ty());
match kind {
EvalKind::Type => Ok(EvaledLine::ExprType(type_str)),
EvalKind::Value => {
use crate::mir::eval_hir::FunCtx;
use arret_runtime_syntax::writer;
use std::str;
let type_is_literal = ty::props::is_literal(node.result_ty());
let mut fcx = FunCtx::new(None);
let value = self
.ehx
.consume_expr(&mut fcx, &mut None, node.into_expr())?;
let boxed = self
.ehx
.value_to_const(&value)
.expect("Received register from MIR evaluation");
let mut output_buf: Vec<u8> = vec![];
writer::write_boxed(&mut output_buf, &self.ehx, boxed).unwrap();
match value {
Value::ArretFun(arret_fun) => {
if let Some(source_name) = arret_fun.source_name() {
write!(&mut output_buf, "/{}", source_name).unwrap();
}
}
Value::RustFun(rust_fun) => {
write!(&mut output_buf, "/{}", rust_fun.symbol()).unwrap();
}
_ => {}
}
let value_str = str::from_utf8(output_buf.as_slice()).unwrap().to_owned();
Ok(EvaledLine::ExprValue(EvaledExprValue {
type_str,
value_str,
type_is_literal,
}))
}
}
}
}
}
}
pub struct ReplCtx {
send_line: crossbeam_channel::Sender<(String, EvalKind)>,
receive_result: crossbeam_channel::Receiver<Result<EvaledLine, Vec<Diagnostic<FileId>>>>,
}
#[derive(Debug)]
pub struct EngineDisconnected;
impl ReplCtx {
pub fn new(ccx: Arc<CompileCtx>) -> Self {
let (send_line, receive_line) = crossbeam_channel::unbounded();
let (send_result, receive_result) = crossbeam_channel::unbounded();
thread::spawn(move || {
let mut engine = ReplEngine::new(&ccx);
for (input, kind) in receive_line.iter() {
let result = engine.eval_line(input, kind);
send_result.send(result).unwrap();
if engine.ehx.should_collect() {
engine.ehx.collect_garbage();
}
}
});
Self {
send_line,
receive_result,
}
}
pub fn send_line(&self, input: String, kind: EvalKind) -> Result<(), EngineDisconnected> {
self.send_line
.send((input, kind))
.map_err(|_| EngineDisconnected)
}
pub fn receive_result(&self) -> Result<EvaledLine, Vec<Diagnostic<FileId>>> {
self.receive_result.recv().unwrap()
}
}
#[cfg(test)]
mod test {
use super::*;
fn eval_line_sync(
rcx: &mut ReplCtx,
input: String,
kind: EvalKind,
) -> Result<EvaledLine, Vec<Diagnostic<FileId>>> {
rcx.send_line(input, kind).unwrap();
rcx.receive_result()
}
fn assert_defs(rcx: &mut ReplCtx, line: &'static str) {
match eval_line_sync(rcx, line.to_owned(), EvalKind::Value).unwrap() {
EvaledLine::Defs(_) => {}
other => {
panic!("Expected defs, got {:?}", other);
}
}
}
fn assert_empty(rcx: &mut ReplCtx, line: &'static str) {
assert_eq!(
EvaledLine::EmptyInput,
eval_line_sync(rcx, line.to_owned(), EvalKind::Value).unwrap()
);
}
fn assert_expr(
rcx: &mut ReplCtx,
expected_value: &'static str,
expected_type: &'static str,
line: &'static str,
) {
assert_eq!(
EvaledLine::ExprType(expected_type.to_owned()),
eval_line_sync(rcx, line.to_owned(), EvalKind::Type).unwrap()
);
match eval_line_sync(rcx, line.into(), EvalKind::Value).unwrap() {
EvaledLine::ExprValue(EvaledExprValue {
value_str,
type_str,
..
}) => {
assert_eq!(value_str, expected_value.to_owned());
assert_eq!(type_str, expected_type.to_owned());
}
other => {
panic!("unexpected REPL result: {:?}", other);
}
}
}
#[test]
fn basic_session() {
use crate::codegen::test::initialise_test_llvm;
use crate::PackagePaths;
initialise_test_llvm();
let ccx = Arc::new(CompileCtx::new(PackagePaths::test_paths(None), true));
let mut rcx = ReplCtx::new(ccx);
assert_empty(&mut rcx, " ");
assert_empty(&mut rcx, "; COMMENT!");
assert_expr(&mut rcx, "1", "Int", "1");
eval_line_sync(
&mut rcx,
"(import [stdlib base])".to_owned(),
EvalKind::Value,
)
.expect(
"unable to load stdlib library; you may need to `cargo build` before running tests",
);
assert_expr(&mut rcx, "true", "true", "(int? 5)");
assert_defs(&mut rcx, "(def x 'first)");
assert_defs(&mut rcx, "(def x 'second)");
assert_expr(&mut rcx, "second", "'second", "x");
assert_expr(&mut rcx, "baz", "'baz", "(do 'foo 'bar 'baz)");
assert_defs(
&mut rcx,
"(def return-constant (fn #{T} ([x T]) (fn () -> T x)))",
);
assert_defs(&mut rcx, "(def return-one (return-constant 1))");
assert_defs(&mut rcx, "(def return-two (return-constant 'two))");
assert_expr(&mut rcx, "1", "Int", "(return-one)");
assert_expr(&mut rcx, "two", "'two", "(return-two)");
}
}