sim_lib_repl/
entrypoint.rs1use std::io;
2use std::sync::Arc;
3
4use sim_kernel::{
5 AbiVersion, Args, Callable, Cx, Error, Export, Expr, Lib, LibManifest, LibTarget, Linker,
6 LoadCx, Object, ObjectCompat, Result, Symbol, Value, Version,
7};
8use sim_run_core::cli_main_entrypoint_symbol;
9
10use crate::run_repl_lines;
11
12pub fn repl_entrypoint_symbol() -> Symbol {
14 cli_main_entrypoint_symbol("repl")
15}
16
17#[derive(Clone, Debug, Default)]
19pub struct ReplLib;
20
21impl ReplLib {
22 pub fn new() -> Self {
24 Self
25 }
26}
27
28impl Lib for ReplLib {
29 fn manifest(&self) -> LibManifest {
30 LibManifest {
31 id: Symbol::qualified("lib", "repl"),
32 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
33 abi: AbiVersion { major: 0, minor: 1 },
34 target: LibTarget::HostRegistered,
35 requires: Vec::new(),
36 capabilities: Vec::new(),
37 exports: vec![Export::Function {
38 symbol: repl_entrypoint_symbol(),
39 function_id: None,
40 }],
41 }
42 }
43
44 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
45 let entrypoint = cx.factory().opaque(Arc::new(ReplEntrypoint))?;
46 linker.function_value(repl_entrypoint_symbol(), entrypoint)?;
47 Ok(())
48 }
49}
50
51#[derive(Clone)]
52struct ReplEntrypoint;
53
54impl Object for ReplEntrypoint {
55 fn display(&self, _cx: &mut Cx) -> Result<String> {
56 Ok("cli/main/repl".to_owned())
57 }
58
59 fn as_any(&self) -> &dyn std::any::Any {
60 self
61 }
62}
63
64impl ObjectCompat for ReplEntrypoint {
65 fn as_callable(&self) -> Option<&dyn Callable> {
66 Some(self)
67 }
68}
69
70impl Callable for ReplEntrypoint {
71 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
72 let codec = match args.values().first() {
73 Some(envelope) => envelope_codec(cx, envelope)?,
74 None => default_codec(),
75 };
76 let stdin = io::stdin();
77 let mut stdout = io::stdout();
78 run_repl_lines(cx, &codec, stdin.lock(), &mut stdout).map_err(Error::Eval)?;
79 cx.factory().bool(true)
80 }
81}
82
83fn envelope_codec(cx: &mut Cx, envelope: &Value) -> Result<Symbol> {
84 let Some(table) = envelope.object().as_table_impl() else {
85 return Err(Error::Eval("CLI envelope is not a table".to_owned()));
86 };
87 let value = table.get(cx, Symbol::new("codec"))?;
88 match value.object().as_expr(cx)? {
89 Expr::Symbol(symbol) => Ok(symbol),
90 Expr::Nil => Ok(default_codec()),
91 other => Err(Error::TypeMismatch {
92 expected: "codec symbol",
93 found: sim_value::kind::expr_kind(&other),
94 }),
95 }
96}
97
98fn default_codec() -> Symbol {
99 Symbol::qualified("codec", "lisp")
100}
101
102#[cfg(test)]
103mod tests {
104 use sim::kernel::Lib;
105
106 use super::{ReplLib, repl_entrypoint_symbol};
107
108 #[test]
109 fn repl_lib_exports_cli_main_repl() {
110 let lib = ReplLib::new();
111 let manifest = lib.manifest();
112
113 assert!(manifest.exports.iter().any(|export| matches!(
114 export,
115 sim::kernel::Export::Function { symbol, .. } if symbol == &repl_entrypoint_symbol()
116 )));
117 }
118}