1use std::path::PathBuf;
2
3use sim_kernel::{CapabilityName, Error, Expr, Result, Symbol};
4
5use crate::{keyword, line_driver_factories};
6
7use super::drivers::{BufferDriver, ExternalDriver, StdioLineDriver};
8
9const AGENT_DRIVE_CAPABILITY: &str = "agent-drive";
10
11pub trait LineDriver: Send + Sync {
13 fn read_line(&mut self, cx: &mut sim_kernel::Cx, prompt: &str) -> Result<Option<String>>;
15 fn write_output(&mut self, cx: &mut sim_kernel::Cx, output: &str) -> Result<()>;
17 fn supports_multiline(&self) -> bool {
19 false
20 }
21 fn capabilities(&self) -> &[CapabilityName] {
23 &[]
24 }
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum DriverSpec {
31 Line,
33 Multiline,
35 External {
37 cmd: String,
39 },
40 Buffer {
42 path: PathBuf,
44 on: Symbol,
46 },
47 Unavailable {
50 spec: Expr,
52 },
53}
54
55impl DriverSpec {
56 pub fn from_expr(expr: &Expr) -> Result<Self> {
59 match expr {
60 Expr::Symbol(symbol) if symbol.name.as_ref() == "line" => Ok(Self::Line),
61 Expr::Symbol(symbol) if symbol.name.as_ref() == "multiline" => Ok(Self::Multiline),
62 Expr::Symbol(symbol)
63 if symbol.name.as_ref() == "readline"
64 || symbol.name.as_ref() == "browser"
65 || symbol.name.as_ref() == "agent" =>
66 {
67 Ok(Self::Unavailable { spec: expr.clone() })
68 }
69 Expr::List(items) | Expr::Vector(items) => Self::from_items(items),
70 _ => Err(Error::Eval(
71 "server/repl :driver expects a driver symbol or spec list".to_owned(),
72 )),
73 }
74 }
75
76 fn from_items(items: &[Expr]) -> Result<Self> {
77 let Some(Expr::Symbol(kind)) = items.first() else {
78 return Err(Error::Eval(
79 "server/repl :driver list must start with a symbol".to_owned(),
80 ));
81 };
82 match kind.name.as_ref() {
83 "external" => {
84 let cmd = find_string_option(
85 items,
86 "cmd",
87 "external driver requires :cmd",
88 "external :cmd expects a string",
89 )?;
90 Ok(Self::External { cmd })
91 }
92 "buffer" => {
93 let path = PathBuf::from(find_string_option(
94 items,
95 "path",
96 "buffer driver requires :path",
97 "buffer :path expects a string",
98 )?);
99 let on = find_symbol_option(items, "on", "buffer :on expects a symbol")?
100 .unwrap_or_else(|| Symbol::new("save"));
101 Ok(Self::Buffer { path, on })
102 }
103 "readline" | "browser" | "agent" => Ok(Self::Unavailable {
104 spec: Expr::List(items.to_vec()),
105 }),
106 other => Err(Error::Eval(format!(
107 "server/repl: unknown driver kind {other}"
108 ))),
109 }
110 }
111
112 pub fn as_expr(&self) -> Expr {
115 match self {
116 Self::Line => Expr::Symbol(Symbol::new("line")),
117 Self::Multiline => Expr::Symbol(Symbol::new("multiline")),
118 Self::External { cmd } => Expr::List(vec![
119 Expr::Symbol(Symbol::new("external")),
120 Expr::Symbol(Symbol::new(":cmd")),
121 Expr::String(cmd.clone()),
122 ]),
123 Self::Buffer { path, on } => Expr::List(vec![
124 Expr::Symbol(Symbol::new("buffer")),
125 Expr::Symbol(Symbol::new(":path")),
126 Expr::String(path.display().to_string()),
127 Expr::Symbol(Symbol::new(":on")),
128 Expr::Quote {
129 mode: sim_kernel::QuoteMode::Quote,
130 expr: Box::new(Expr::Symbol(on.clone())),
131 },
132 ]),
133 Self::Unavailable { spec } => spec.clone(),
134 }
135 }
136
137 pub fn create_driver(&self, cx: &mut sim_kernel::Cx) -> Result<Box<dyn LineDriver>> {
140 match self {
141 Self::Line => Ok(Box::new(StdioLineDriver::new(false))),
142 Self::Multiline => Ok(Box::new(StdioLineDriver::new(true))),
143 Self::External { cmd } => Ok(Box::new(ExternalDriver::new(cmd.clone()))),
144 Self::Buffer { path, on } => {
145 if on.name.as_ref() != "save" {
146 return Err(Error::Eval(format!(
147 "buffer driver does not support :on {} yet",
148 on
149 )));
150 }
151 Ok(Box::new(BufferDriver::new(path.clone())))
152 }
153 Self::Unavailable { spec } => {
154 if let Some(driver) = lookup_registered_driver(cx, spec)? {
155 return Ok(driver);
156 }
157 Err(unavailable_driver_error(spec))
158 }
159 }
160 }
161
162 pub fn required_capabilities(&self) -> Vec<CapabilityName> {
164 match self {
165 Self::Unavailable { spec } => unavailable_driver_capabilities(spec),
166 _ => Vec::new(),
167 }
168 }
169}
170
171fn find_string_option(
172 items: &[Expr],
173 name: &str,
174 missing: &'static str,
175 wrong: &'static str,
176) -> Result<String> {
177 let mut iter = items.iter().skip(1);
178 while let Some(key_expr) = iter.next() {
179 let Some(value) = iter.next() else {
180 return Err(Error::Eval(
181 "driver options must be key/value pairs".to_owned(),
182 ));
183 };
184 if keyword(key_expr)? == name {
185 return match value {
186 Expr::String(text) => Ok(text.clone()),
187 _ => Err(Error::Eval(wrong.to_owned())),
188 };
189 }
190 }
191 Err(Error::Eval(missing.to_owned()))
192}
193
194fn find_symbol_option(items: &[Expr], name: &str, wrong: &'static str) -> Result<Option<Symbol>> {
195 let mut iter = items.iter().skip(1);
196 while let Some(key_expr) = iter.next() {
197 let Some(value) = iter.next() else {
198 return Err(Error::Eval(
199 "driver options must be key/value pairs".to_owned(),
200 ));
201 };
202 if keyword(key_expr)? == name {
203 return match value {
204 Expr::Symbol(symbol) => Ok(Some(symbol.clone())),
205 Expr::Quote { expr, .. } => match expr.as_ref() {
206 Expr::Symbol(symbol) => Ok(Some(symbol.clone())),
207 _ => Err(Error::Eval(wrong.to_owned())),
208 },
209 _ => Err(Error::Eval(wrong.to_owned())),
210 };
211 }
212 }
213 Ok(None)
214}
215
216fn unavailable_driver_capabilities(spec: &Expr) -> Vec<CapabilityName> {
217 match spec {
218 Expr::Symbol(symbol) if symbol.name.as_ref() == "agent" => {
219 vec![CapabilityName::new(AGENT_DRIVE_CAPABILITY)]
220 }
221 Expr::List(items) | Expr::Vector(items) if matches!(items.first(), Some(Expr::Symbol(symbol)) if symbol.name.as_ref() == "agent") =>
222 {
223 vec![CapabilityName::new(AGENT_DRIVE_CAPABILITY)]
224 }
225 _ => Vec::new(),
226 }
227}
228
229fn lookup_registered_driver(
230 cx: &mut sim_kernel::Cx,
231 spec: &Expr,
232) -> Result<Option<Box<dyn LineDriver>>> {
233 let Some(name) = unavailable_driver_name(spec) else {
234 return Ok(None);
235 };
236 let factory = {
237 let factories = line_driver_factories()
238 .lock()
239 .map_err(|_| Error::HostError("line driver registry mutex poisoned".to_owned()))?;
240 factories.get(name).copied()
241 };
242 let Some(factory) = factory else {
243 return Ok(None);
244 };
245 factory(cx, spec)
246}
247
248fn unavailable_driver_name(spec: &Expr) -> Option<&str> {
249 match spec {
250 Expr::Symbol(symbol) => Some(symbol.name.as_ref()),
251 Expr::List(items) | Expr::Vector(items) => match items.first() {
252 Some(Expr::Symbol(symbol)) => Some(symbol.name.as_ref()),
253 _ => None,
254 },
255 _ => None,
256 }
257}
258
259fn unavailable_driver_error(spec: &Expr) -> Error {
260 match unavailable_driver_name(spec) {
261 Some("browser") => Error::Eval("browser driver not available".to_owned()),
262 Some("readline") => Error::Eval("readline driver not available".to_owned()),
263 _ => Error::Eval("driver not available".to_owned()),
264 }
265}