Skip to main content

sim_lib_repl/
entrypoint.rs

1use std::io;
2use std::sync::Arc;
3
4use sim_run_core::cli_main_entrypoint_symbol;
5use sim_kernel::{
6    AbiVersion, Args, Callable, Cx, Error, Export, Expr, Lib, LibManifest, LibTarget, Linker,
7    LoadCx, Object, ObjectCompat, Result, Symbol, Value, Version,
8};
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: expr_kind(&other),
94        }),
95    }
96}
97
98fn default_codec() -> Symbol {
99    Symbol::qualified("codec", "lisp")
100}
101
102fn expr_kind(expr: &Expr) -> &'static str {
103    match expr {
104        Expr::Nil => "nil",
105        Expr::Bool(_) => "bool",
106        Expr::Number(_) => "number",
107        Expr::String(_) => "string",
108        Expr::Bytes(_) => "bytes",
109        Expr::Symbol(_) => "symbol",
110        Expr::Vector(_) => "vector",
111        Expr::List(_) => "list",
112        Expr::Map(_) => "map",
113        Expr::Set(_) => "set",
114        Expr::Call { .. } => "call",
115        Expr::Infix { .. } => "infix",
116        Expr::Prefix { .. } => "prefix",
117        Expr::Postfix { .. } => "postfix",
118        Expr::Block(_) => "block",
119        Expr::Quote { .. } => "quote",
120        Expr::Annotated { .. } => "annotated",
121        Expr::Local(_) => "local",
122        Expr::Extension { .. } => "extension",
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use sim::kernel::Lib;
129
130    use super::{ReplLib, repl_entrypoint_symbol};
131
132    #[test]
133    fn repl_lib_exports_cli_main_repl() {
134        let lib = ReplLib::new();
135        let manifest = lib.manifest();
136
137        assert!(manifest.exports.iter().any(|export| matches!(
138            export,
139            sim::kernel::Export::Function { symbol, .. } if symbol == &repl_entrypoint_symbol()
140        )));
141    }
142}