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: {}. Starting with an empty history.",
89 err
90 )));
91 }
92 }
93
94 let component_dependencies = match config.component_source {
95 Some(ref details) => {
96 let component_dependency = config
97 .dependency_manager
98 .add_component(&details.source_path, details.component_name.clone())
99 .await
100 .map_err(|err| ReplBootstrapError::ComponentLoadError(err.to_string()))?;
101
102 Ok(vec![component_dependency])
103 }
104 None => {
105 let dependencies = config.dependency_manager.get_dependencies().await;
106
107 match dependencies {
108 Ok(dependencies) => {
109 let component_dependencies = dependencies.component_dependencies;
110
111 if component_dependencies.is_empty() {
112 return Err(ReplBootstrapError::NoComponentsFound);
113 }
114
115 Ok(component_dependencies)
116 }
117 Err(err) => Err(ReplBootstrapError::ComponentLoadError(format!(
118 "failed to register components: {}",
119 err
120 ))),
121 }
122 }
123 };
124
125 let repl_state = ReplState::new(
128 config.worker_function_invoke,
129 RibCompiler::new(RibCompilerConfig::new(component_dependencies?, vec![])),
130 history_file_path.clone(),
131 );
132
133 Ok(RibRepl {
134 printer: config
135 .printer
136 .unwrap_or_else(|| Box::new(DefaultReplResultPrinter)),
137 editor: rl,
138 repl_state: Arc::new(repl_state),
139 prompt: config
140 .prompt
141 .unwrap_or_else(|| ">>> ".truecolor(192, 192, 192).to_string()),
142 command_registry,
143 })
144 }
145
146 pub fn read_line(&mut self) -> rustyline::Result<String> {
151 self.editor.readline(&self.prompt)
152 }
153
154 pub async fn execute(
160 &mut self,
161 script_or_command: &str,
162 ) -> Result<Option<RibResult>, RibExecutionError> {
163 let script_or_command = CommandOrExpr::from_str(script_or_command, &self.command_registry)
164 .map_err(RibExecutionError::Custom)?;
165
166 match script_or_command {
167 CommandOrExpr::Command { args, executor } => {
168 let mut repl_context =
169 ReplContext::new(self.printer.as_ref(), &self.repl_state, &mut self.editor);
170
171 executor.run(args.as_str(), &mut repl_context);
172
173 Ok(None)
174 }
175 CommandOrExpr::RawExpr(script) => {
176 if !script.is_empty() {
178 let rib = script.strip_suffix(";").unwrap_or(script.as_str()).trim();
179
180 self.repl_state.update_rib(rib);
181
182 let _ = self.editor.add_history_entry(rib);
186 let _ = self
187 .editor
188 .save_history(self.repl_state.history_file_path());
189
190 match compile_rib_script(&self.current_rib_program(), self.repl_state.clone()) {
191 Ok(compiler_output) => {
192 let rib_edit = self.editor.helper_mut().unwrap();
193
194 rib_edit.update_progression(&compiler_output);
195
196 let result =
197 eval(compiler_output.rib_byte_code, &self.repl_state).await;
198
199 match result {
200 Ok(result) => Ok(Some(result)),
201 Err(err) => {
202 self.repl_state.remove_last_rib_expression();
203
204 Err(RibExecutionError::RibRuntimeError(err))
205 }
206 }
207 }
208 Err(err) => {
209 self.repl_state.remove_last_rib_expression();
210
211 Err(RibExecutionError::RibCompilationError(err))
212 }
213 }
214 } else {
215 Ok(None)
216 }
217 }
218 }
219 }
220
221 pub async fn run(&mut self) {
227 loop {
228 let readline = self.read_line();
229 match readline {
230 Ok(rib) => {
231 let result = self.execute(rib.as_str()).await;
232
233 match result {
234 Ok(Some(result)) => {
235 self.printer.print_rib_result(&result);
236 }
237
238 Ok(None) => {}
239
240 Err(err) => match err {
241 RibExecutionError::RibRuntimeError(runtime_error) => {
242 self.printer.print_rib_runtime_error(&runtime_error);
243 }
244 RibExecutionError::RibCompilationError(runtime_error) => {
245 self.printer.print_rib_compilation_error(&runtime_error);
246 }
247 RibExecutionError::Custom(custom_error) => {
248 self.printer.print_custom_error(&custom_error);
249 }
250 },
251 }
252 }
253 Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => break,
254 Err(_) => continue,
255 }
256 }
257 }
258
259 fn current_rib_program(&self) -> String {
260 self.repl_state.current_rib_program()
261 }
262}
263
264enum CommandOrExpr {
265 Command {
266 args: String,
267 executor: Arc<dyn UntypedCommand>,
268 },
269 RawExpr(String),
270}
271
272impl CommandOrExpr {
273 pub fn from_str(input: &str, command_registry: &CommandRegistry) -> Result<Self, String> {
274 if input.starts_with(":") {
275 let repl_input = input.split_whitespace().collect::<Vec<&str>>();
276
277 let command_name = repl_input
278 .first()
279 .map(|x| x.strip_prefix(":").unwrap_or(x).trim())
280 .ok_or("Expecting a command name after `:`".to_string())?;
281
282 let input_args = repl_input[1..].join(" ");
283
284 let command = command_registry
285 .get_command(command_name)
286 .map(|command| CommandOrExpr::Command {
287 args: input_args,
288 executor: command,
289 })
290 .ok_or_else(|| format!("Command '{}' not found", command_name))?;
291
292 Ok(command)
293 } else {
294 Ok(CommandOrExpr::RawExpr(input.trim().to_string()))
295 }
296 }
297}
298
299pub struct ComponentSource {
304 pub component_name: String,
306
307 pub source_path: PathBuf,
309}
310
311fn get_default_history_file() -> PathBuf {
312 let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
313 path.push(".rib_history");
314 path
315}