Skip to main content

sim_lib_repl/
entrypoint.rs

1use 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
12/// Returns the function symbol exported for the bootloader handoff.
13pub fn repl_entrypoint_symbol() -> Symbol {
14    cli_main_entrypoint_symbol("repl")
15}
16
17/// Loadable REPL library.
18#[derive(Clone, Debug, Default)]
19pub struct ReplLib;
20
21impl ReplLib {
22    /// Creates a REPL library instance.
23    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}