golem_rib_repl/
rib_repl.rs1use crate::compiler::compile_rib_script;
16use crate::dependency_manager::{RibComponentMetadata, RibDependencyManager};
17use crate::invoke::WorkerFunctionInvoke;
18use crate::repl_printer::{DefaultReplResultPrinter, ReplPrinter};
19use crate::repl_state::ReplState;
20use crate::rib_edit::RibEdit;
21use async_trait::async_trait;
22use colored::Colorize;
23use golem_wasm_rpc::ValueAndType;
24use rib::RibResult;
25use rib::{EvaluatedFnArgs, EvaluatedFqFn, EvaluatedWorkerName, RibByteCode};
26use rib::{RibError, RibFunctionInvoke};
27use rustyline::error::ReadlineError;
28use rustyline::history::DefaultHistory;
29use rustyline::{Config, Editor};
30use std::path::PathBuf;
31use std::sync::Arc;
32
33pub struct RibRepl {
35 history_file_path: PathBuf,
36 printer: Box<dyn ReplPrinter>,
37 editor: Editor<RibEdit, DefaultHistory>,
38 repl_state: ReplState,
39}
40
41impl RibRepl {
42 pub async fn bootstrap(
59 history_file: Option<PathBuf>,
60 dependency_manager: Arc<dyn RibDependencyManager + Sync + Send>,
61 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
62 printer: Option<Box<dyn ReplPrinter>>,
63 component_source: Option<ComponentSource>,
64 ) -> Result<RibRepl, ReplBootstrapError> {
65 let history_file_path = history_file.unwrap_or_else(get_default_history_file);
66
67 let rib_editor = RibEdit::init();
68
69 let mut rl = Editor::<RibEdit, DefaultHistory>::with_history(
70 Config::default(),
71 DefaultHistory::new(),
72 )
73 .unwrap();
74
75 rl.set_helper(Some(rib_editor));
76
77 if history_file_path.exists() {
78 if let Err(err) = rl.load_history(&history_file_path) {
79 return Err(ReplBootstrapError::ReplHistoryFileError(format!(
80 "Failed to load history: {}. Starting with an empty history.",
81 err
82 )));
83 }
84 }
85
86 let component_dependency = match component_source {
87 Some(ref details) => dependency_manager
88 .add_component(&details.source_path, details.component_name.clone())
89 .await
90 .map_err(ReplBootstrapError::ComponentLoadError),
91 None => {
92 let dependencies = dependency_manager.get_dependencies().await;
93
94 match dependencies {
95 Ok(dependencies) => {
96 let component_dependencies = dependencies.component_dependencies;
97
98 match &component_dependencies.len() {
99 0 => Err(ReplBootstrapError::NoComponentsFound),
100 1 => Ok(component_dependencies[0].clone()),
101 _ => Err(ReplBootstrapError::MultipleComponentsFound(
102 "multiple components detected. rib repl currently support only a single component".to_string(),
103 )),
104 }
105 }
106 Err(err) => Err(ReplBootstrapError::ComponentLoadError(format!(
107 "failed to register components: {}",
108 err
109 ))),
110 }
111 }
112 }?;
113
114 let rib_function_invoke = Arc::new(ReplRibFunctionInvoke::new(
115 component_dependency.clone(),
116 worker_function_invoke,
117 ));
118
119 let repl_state = ReplState::new(&component_dependency, rib_function_invoke);
120
121 Ok(RibRepl {
122 history_file_path,
123 printer: printer.unwrap_or_else(|| Box::new(DefaultReplResultPrinter)),
124 editor: rl,
125 repl_state,
126 })
127 }
128
129 pub fn read_line(&mut self) -> rustyline::Result<String> {
134 self.editor.readline(">>> ".magenta().to_string().as_str())
135 }
136
137 pub async fn execute_rib(&mut self, rib: &str) -> Result<Option<RibResult>, RibError> {
143 if !rib.is_empty() {
144 self.update_rib(rib);
145
146 let _ = self.editor.add_history_entry(rib);
150 let _ = self.editor.save_history(&self.history_file_path);
151
152 match compile_rib_script(&self.current_rib_program(), &mut self.repl_state) {
153 Ok(compilation) => {
154 let helper = self.editor.helper_mut().unwrap();
155
156 helper.update_progression(&compilation);
157
158 let result = eval(compilation.rib_byte_code, &mut self.repl_state).await;
160 match result {
161 Ok(result) => {
162 self.printer.print_rib_result(&result);
163 Ok(Some(result))
164 }
165 Err(err) => {
166 self.remove_rib_text_in_session();
167 self.printer.print_runtime_error(&err);
168 Err(RibError::InternalError(err))
169 }
170 }
171 }
172 Err(err) => {
173 self.remove_rib_text_in_session();
174 self.printer.print_rib_error(&err);
175 Err(RibError::InternalError(err.to_string()))
176 }
177 }
178 } else {
179 Ok(None)
180 }
181 }
182
183 pub fn update_component_dependency(&mut self, dependency: RibComponentMetadata) {
191 self.repl_state.update_dependency(dependency);
192 }
193
194 pub async fn run(&mut self) {
200 loop {
201 let readline = self.editor.readline(">>> ".magenta().to_string().as_str());
202 match readline {
203 Ok(rib) => {
204 let _ = self.execute_rib(&rib).await;
205 }
206 Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => break,
207 Err(_) => continue,
208 }
209 }
210 }
211
212 fn update_rib(&mut self, rib_text: &str) {
213 self.repl_state.update_rib(rib_text);
214 }
215
216 fn remove_rib_text_in_session(&mut self) {
217 self.repl_state.pop_rib_text()
218 }
219
220 fn current_rib_program(&self) -> String {
221 self.repl_state.current_rib_program()
222 }
223}
224
225pub struct ComponentSource {
230 pub component_name: String,
232
233 pub source_path: PathBuf,
235}
236
237async fn eval(rib_byte_code: RibByteCode, repl_state: &mut ReplState) -> Result<RibResult, String> {
238 repl_state
239 .interpreter()
240 .run(rib_byte_code)
241 .await
242 .map_err(|e| format!("Runtime error: {}", e))
243}
244
245fn get_default_history_file() -> PathBuf {
246 let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
247 path.push(".rib_history");
248 path
249}
250
251#[derive(Debug, Clone)]
253pub enum ReplBootstrapError {
254 MultipleComponentsFound(String),
262
263 NoComponentsFound,
265
266 ComponentLoadError(String),
268
269 ReplHistoryFileError(String),
271}
272
273struct ReplRibFunctionInvoke {
279 component_dependency: RibComponentMetadata,
280 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
281}
282
283impl ReplRibFunctionInvoke {
284 fn new(
285 component_dependency: RibComponentMetadata,
286 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
287 ) -> Self {
288 Self {
289 component_dependency,
290 worker_function_invoke,
291 }
292 }
293}
294
295#[async_trait]
296impl RibFunctionInvoke for ReplRibFunctionInvoke {
297 async fn invoke(
298 &self,
299 worker_name: Option<EvaluatedWorkerName>,
300 function_name: EvaluatedFqFn,
301 args: EvaluatedFnArgs,
302 ) -> Result<ValueAndType, String> {
303 let component_id = self.component_dependency.component_id;
304
305 self.worker_function_invoke
306 .invoke(component_id, worker_name, function_name, args)
307 .await
308 }
309}