sim_lib_repl/
entrypoint.rs1use 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
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: 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}