use sim_codec::{Input, encode_with_codec};
use sim_kernel::{
Cx, EncodeOptions, Expr, ReadPolicy, Result, Symbol, Value, eval_fabric_capability,
};
use crate::{Connection, ensure_installed_codec};
use super::spec::DriverSpec;
#[derive(Clone)]
pub enum ReplOutput {
Driver,
String,
}
pub struct ReplOptions {
pub connection: Connection,
pub codec: Symbol,
pub prompt: String,
pub driver: DriverSpec,
pub input: Option<String>,
pub output: ReplOutput,
}
pub fn run_repl(cx: &mut Cx, options: ReplOptions) -> Result<Value> {
cx.require(&eval_fabric_capability())?;
for capability in options.driver.required_capabilities() {
cx.require(&capability)?;
}
ensure_installed_codec(cx, &options.codec)?;
if let Some(input) = options.input {
let rendered = run_once(cx, &options.connection, &options.codec, &input)?;
return match options.output {
ReplOutput::String => cx.factory().string(rendered),
ReplOutput::Driver => cx.factory().nil(),
};
}
let mut driver = options.driver.create_driver(cx)?;
while let Some(input) = driver.read_line(cx, &options.prompt)? {
if input.trim().is_empty() {
continue;
}
match run_once(cx, &options.connection, &options.codec, &input) {
Ok(rendered) => {
driver.write_output(cx, &(rendered + "\n"))?;
}
Err(err) => {
driver.write_output(cx, &format!("error: {err}\n"))?;
}
}
}
cx.factory().nil()
}
fn run_once(cx: &mut Cx, connection: &Connection, codec: &Symbol, input: &str) -> Result<String> {
let expr = lower_repl_expr(sim_codec::decode_with_codec(
cx,
codec,
Input::Text(input.to_owned()),
ReadPolicy::default(),
)?);
let value = connection.request(cx, expr, None, Vec::new())?;
render_value(cx, codec, &value)
}
pub(crate) fn lower_repl_expr(expr: Expr) -> Expr {
match expr {
Expr::List(items) => {
let mut items = items.into_iter();
let Some(operator) = items.next() else {
return Expr::List(Vec::new());
};
Expr::Call {
operator: Box::new(lower_repl_operator(operator)),
args: items.map(lower_repl_expr).collect(),
}
}
Expr::Vector(items) => Expr::Vector(items.into_iter().map(lower_repl_expr).collect()),
Expr::Map(entries) => Expr::Map(
entries
.into_iter()
.map(|(key, value)| (key, lower_repl_expr(value)))
.collect(),
),
Expr::Set(items) => Expr::Set(items.into_iter().map(lower_repl_expr).collect()),
Expr::Block(items) => Expr::Block(items.into_iter().map(lower_repl_expr).collect()),
Expr::Annotated { expr, annotations } => Expr::Annotated {
expr: Box::new(lower_repl_expr(*expr)),
annotations,
},
other => other,
}
}
fn lower_repl_operator(expr: Expr) -> Expr {
match expr {
Expr::Symbol(symbol) => Expr::Symbol(match symbol.name.as_ref() {
"+" if symbol.namespace.is_none() => Symbol::qualified("math", "add"),
"-" if symbol.namespace.is_none() => Symbol::qualified("math", "sub"),
"*" if symbol.namespace.is_none() => Symbol::qualified("math", "mul"),
"/" if symbol.namespace.is_none() => Symbol::qualified("math", "div"),
_ => symbol,
}),
other => lower_repl_expr(other),
}
}
fn render_value(cx: &mut Cx, codec: &Symbol, value: &Value) -> Result<String> {
let expr = value.object().as_expr(cx)?;
encode_with_codec(cx, codec, &expr, EncodeOptions::default())?.into_text()
}