golem_rib_repl/
rib_repl.rs1use crate::compiler::compile_rib_script;
16use crate::dependency_manager::RibDependencyManager;
17use crate::eval::eval;
18use crate::invoke::WorkerFunctionInvoke;
19use crate::repl_printer::{DefaultReplResultPrinter, ReplPrinter};
20use crate::repl_state::ReplState;
21use crate::rib_context::ReplContext;
22use crate::rib_edit::RibEdit;
23use crate::{CommandRegistry, ReplBootstrapError, RibExecutionError, UntypedCommand};
24use colored::Colorize;
25use rib::{RibCompiler, RibCompilerConfig, RibResult};
26use rustyline::error::ReadlineError;
27use rustyline::history::DefaultHistory;
28use rustyline::{Config, Editor};
29use std::path::PathBuf;
30use std::sync::Arc;
31
32pub struct RibReplConfig {
46 pub history_file: Option<PathBuf>,
47 pub dependency_manager: Arc<dyn RibDependencyManager + Sync + Send>,
48 pub worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
49 pub printer: Option<Box<dyn ReplPrinter>>,
50 pub component_source: Option<ComponentSource>,
51 pub prompt: Option<String>,
52 pub command_registry: Option<CommandRegistry>,
53}
54
55pub struct RibRepl {
57 printer: Box<dyn ReplPrinter>,
58 editor: Editor<RibEdit, DefaultHistory>,
59 repl_state: Arc<ReplState>,
60 prompt: String,
61 command_registry: CommandRegistry,
62}
63
64impl RibRepl {
65 pub async fn bootstrap(config: RibReplConfig) -> Result<RibRepl, ReplBootstrapError> {
69 let history_file_path = config.history_file.unwrap_or_else(get_default_history_file);
70
71 let mut command_registry = CommandRegistry::built_in();
72 let external_commands = config.command_registry.unwrap_or_default();
73 command_registry.merge(external_commands);
74
75 let helper = RibEdit::init(&command_registry);
76
77 let mut rl = Editor::<RibEdit, DefaultHistory>::with_history(
78 Config::default(),
79 DefaultHistory::new(),
80 )
81 .unwrap();
82
83 rl.set_helper(Some(helper));
84
85 if history_file_path.exists() {
86 if let Err(err) = rl.load_history(&history_file_path) {
87 return Err(ReplBootstrapError::ReplHistoryFileError(format!(
88 "Failed to load history: {err}. Starting with an empty history."
89 )));
90 }
91 }
92
93 let component_dependencies = match config.component_source {
94 Some(ref details) => {
95 let component_dependency = config
96 .dependency_manager
97 .add_component(&details.source_path, details.component_name.clone())
98 .await
99 .map_err(|err| ReplBootstrapError::ComponentLoadError(err.to_string()))?;
100
101 Ok(vec![component_dependency])
102 }
103 None => {
104 let dependencies = config.dependency_manager.get_dependencies().await;
105
106 match dependencies {
107 Ok(dependencies) => {
108 let component_dependencies = dependencies.component_dependencies;
109
110 if component_dependencies.is_empty() {
111 return Err(ReplBootstrapError::NoComponentsFound);
112 }
113
114 Ok(component_dependencies)
115 }
116 Err(err) => Err(ReplBootstrapError::ComponentLoadError(format!(
117 "failed to register components: {err}"
118 ))),
119 }
120 }
121 };
122
123 let repl_state = ReplState::new(
126 config.worker_function_invoke,
127 RibCompiler::new(RibCompilerConfig::new(component_dependencies?, vec![])),
128 history_file_path.clone(),
129 );
130
131 Ok(RibRepl {
132 printer: config
133 .printer
134 .unwrap_or_else(|| Box::new(DefaultReplResultPrinter)),
135 editor: rl,
136 repl_state: Arc::new(repl_state),
137 prompt: config
138 .prompt
139 .unwrap_or_else(|| ">>> ".truecolor(192, 192, 192).to_string()),
140 command_registry,
141 })
142 }
143
144 pub fn read_line(&mut self) -> rustyline::Result<String> {
149 self.editor.readline(&self.prompt)
150 }
151
152 pub async fn execute(
158 &mut self,
159 script_or_command: &str,
160 ) -> Result<Option<RibResult>, RibExecutionError> {
161 let script_or_command = CommandOrExpr::from_str(script_or_command, &self.command_registry)
162 .map_err(RibExecutionError::Custom)?;
163
164 match script_or_command {
165 CommandOrExpr::Command { args, executor } => {
166 let mut repl_context =
167 ReplContext::new(self.printer.as_ref(), &self.repl_state, &mut self.editor);
168
169 executor.run(args.as_str(), &mut repl_context);
170
171 Ok(None)
172 }
173 CommandOrExpr::RawExpr(script) => {
174 if !script.is_empty() {
176 let rib = script.strip_suffix(";").unwrap_or(script.as_str()).trim();
177
178 self.repl_state.update_rib(rib);
179
180 let _ = self.editor.add_history_entry(rib);
184 let _ = self
185 .editor
186 .save_history(self.repl_state.history_file_path());
187
188 match compile_rib_script(&self.current_rib_program(), self.repl_state.clone()) {
189 Ok(compiler_output) => {
190 let rib_edit = self.editor.helper_mut().unwrap();
191
192 rib_edit.update_progression(&compiler_output);
193
194 let result =
195 eval(compiler_output.rib_byte_code, &self.repl_state).await;
196
197 match result {
198 Ok(result) => Ok(Some(result)),
199 Err(err) => {
200 self.repl_state.remove_last_rib_expression();
201
202 Err(RibExecutionError::RibRuntimeError(err))
203 }
204 }
205 }
206 Err(err) => {
207 self.repl_state.remove_last_rib_expression();
208
209 Err(RibExecutionError::RibCompilationError(err))
210 }
211 }
212 } else {
213 Ok(None)
214 }
215 }
216 }
217 }
218
219 pub async fn run(&mut self) {
225 loop {
226 let readline = self.read_line();
227 match readline {
228 Ok(rib) => {
229 let result = self.execute(rib.as_str()).await;
230
231 match result {
232 Ok(Some(result)) => {
233 self.printer.print_rib_result(&result);
234 }
235
236 Ok(None) => {}
237
238 Err(err) => match err {
239 RibExecutionError::RibRuntimeError(runtime_error) => {
240 self.printer.print_rib_runtime_error(&runtime_error);
241 }
242 RibExecutionError::RibCompilationError(runtime_error) => {
243 self.printer.print_rib_compilation_error(&runtime_error);
244 }
245 RibExecutionError::Custom(custom_error) => {
246 self.printer.print_custom_error(&custom_error);
247 }
248 },
249 }
250 }
251 Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => break,
252 Err(_) => continue,
253 }
254 }
255 }
256
257 fn current_rib_program(&self) -> String {
258 self.repl_state.current_rib_program()
259 }
260}
261
262enum CommandOrExpr {
263 Command {
264 args: String,
265 executor: Arc<dyn UntypedCommand>,
266 },
267 RawExpr(String),
268}
269
270impl CommandOrExpr {
271 pub fn from_str(input: &str, command_registry: &CommandRegistry) -> Result<Self, String> {
272 if input.starts_with(":") {
273 let repl_input = input.split_whitespace().collect::<Vec<&str>>();
274
275 let command_name = repl_input
276 .first()
277 .map(|x| x.strip_prefix(":").unwrap_or(x).trim())
278 .ok_or("Expecting a command name after `:`".to_string())?;
279
280 let input_args = repl_input[1..].join(" ");
281
282 let command = command_registry
283 .get_command(command_name)
284 .map(|command| CommandOrExpr::Command {
285 args: input_args,
286 executor: command,
287 })
288 .ok_or_else(|| format!("Command '{command_name}' not found"))?;
289
290 Ok(command)
291 } else {
292 Ok(CommandOrExpr::RawExpr(input.trim().to_string()))
293 }
294 }
295}
296
297pub struct ComponentSource {
302 pub component_name: String,
304
305 pub source_path: PathBuf,
307}
308
309fn get_default_history_file() -> PathBuf {
310 let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
311 path.push(".rib_history");
312 path
313}