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::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 {
34 history_file_path: PathBuf,
35 printer: Box<dyn ReplPrinter>,
36 editor: Editor<RibEdit, DefaultHistory>,
37 repl_state: ReplState,
38}
39
40impl RibRepl {
41 pub fn read_line(&mut self) -> rustyline::Result<String> {
42 self.editor.readline(">>> ".magenta().to_string().as_str())
43 }
44
45 pub async fn bootstrap(
46 history_file: Option<PathBuf>,
47 dependency_manager: Arc<dyn RibDependencyManager + Sync + Send>,
48 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
49 printer: Box<dyn ReplPrinter>,
50 component_details: Option<ComponentDetails>,
51 ) -> Result<RibRepl, ReplBootstrapError> {
52 let history_file_path = history_file.unwrap_or_else(get_default_history_file);
53
54 let rib_editor = RibEdit::init();
55
56 let mut rl = Editor::<RibEdit, DefaultHistory>::with_history(
57 Config::default(),
58 DefaultHistory::new(),
59 )
60 .unwrap();
61
62 rl.set_helper(Some(rib_editor));
63
64 if history_file_path.exists() {
65 if let Err(err) = rl.load_history(&history_file_path) {
66 return Err(ReplBootstrapError::ReplHistoryFileError(format!(
67 "Failed to load history: {}. Starting with an empty history.",
68 err
69 )));
70 }
71 }
72
73 let component_dependency = match component_details {
74 Some(ref details) => dependency_manager
75 .add_component(&details.source_path, details.component_name.clone())
76 .await
77 .map_err(ReplBootstrapError::ComponentLoadError),
78 None => {
79 let dependencies = dependency_manager.get_dependencies().await;
80
81 match dependencies {
82 Ok(dependencies) => {
83 let component_dependencies = dependencies.component_dependencies;
84
85 match &component_dependencies.len() {
86 0 => Err(ReplBootstrapError::NoComponentsFound),
87 1 => Ok(component_dependencies[0].clone()),
88 _ => Err(ReplBootstrapError::MultipleComponentsFound(
89 "multiple components detected. rib repl currently support only a single component".to_string(),
90 )),
91 }
92 }
93 Err(err) => Err(ReplBootstrapError::ComponentLoadError(format!(
94 "failed to register components: {}",
95 err
96 ))),
97 }
98 }
99 }?;
100
101 let rib_function_invoke = Arc::new(ReplRibFunctionInvoke::new(
102 component_dependency.clone(),
103 worker_function_invoke,
104 ));
105
106 let repl_state = ReplState::new(&component_dependency, rib_function_invoke);
107
108 Ok(RibRepl {
109 history_file_path,
110 printer,
111 editor: rl,
112 repl_state,
113 })
114 }
115
116 pub async fn process_command(&mut self, prompt: &str) -> Result<Option<RibResult>, RibError> {
117 if !prompt.is_empty() {
118 self.update_rib_text_in_session(prompt);
119
120 let _ = self.editor.add_history_entry(prompt);
124 let _ = self.editor.save_history(&self.history_file_path);
125
126 match compile_rib_script(&self.current_rib_program(), &mut self.repl_state) {
127 Ok(compilation) => {
128 let helper = self.editor.helper_mut().unwrap();
129
130 helper.update_progression(&compilation);
131
132 let result = eval(compilation.rib_byte_code, &mut self.repl_state).await;
134 match result {
135 Ok(result) => {
136 self.printer.print_rib_result(&result);
137 Ok(Some(result))
138 }
139 Err(err) => {
140 self.remove_rib_text_in_session();
141 self.printer.print_runtime_error(&err);
142 Err(RibError::InternalError(err))
143 }
144 }
145 }
146 Err(err) => {
147 self.remove_rib_text_in_session();
148 self.printer.print_rib_error(&err);
149 Err(RibError::InternalError(err.to_string()))
150 }
151 }
152 } else {
153 Ok(None)
154 }
155 }
156
157 pub async fn run(&mut self) {
158 loop {
159 let readline = self.editor.readline(">>> ".magenta().to_string().as_str());
160 match readline {
161 Ok(line) => {
162 let _ = self.process_command(&line).await;
163 }
164 Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => break,
165 Err(_) => continue,
166 }
167 }
168 }
169
170 fn update_rib_text_in_session(&mut self, rib_text: &str) {
171 self.repl_state.update_rib_text(rib_text);
172 }
173
174 fn remove_rib_text_in_session(&mut self) {
175 self.repl_state.pop_rib_text()
176 }
177
178 fn current_rib_program(&self) -> String {
179 self.repl_state.current_rib_program()
180 }
181}
182
183pub struct ComponentDetails {
184 pub component_name: String,
186 pub source_path: PathBuf,
188}
189
190async fn eval(rib_byte_code: RibByteCode, repl_state: &mut ReplState) -> Result<RibResult, String> {
191 repl_state
192 .interpreter()
193 .run(rib_byte_code)
194 .await
195 .map_err(|e| format!("Runtime error: {}", e))
196}
197
198fn get_default_history_file() -> PathBuf {
199 let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
200 path.push(".rib_history");
201 path
202}
203
204#[derive(Debug, Clone)]
205pub enum ReplBootstrapError {
206 MultipleComponentsFound(String),
212 NoComponentsFound,
213 ComponentLoadError(String),
214 ReplHistoryFileError(String),
215}
216
217pub struct ReplRibFunctionInvoke {
222 component_dependency: RibComponentMetadata,
223 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
224}
225
226impl ReplRibFunctionInvoke {
227 pub fn new(
228 component_dependency: RibComponentMetadata,
229 worker_function_invoke: Arc<dyn WorkerFunctionInvoke + Sync + Send>,
230 ) -> Self {
231 Self {
232 component_dependency,
233 worker_function_invoke,
234 }
235 }
236}
237
238#[async_trait]
239impl RibFunctionInvoke for ReplRibFunctionInvoke {
240 async fn invoke(
241 &self,
242 worker_name: Option<EvaluatedWorkerName>,
243 function_name: EvaluatedFqFn,
244 args: EvaluatedFnArgs,
245 ) -> Result<ValueAndType, String> {
246 let component_id = self.component_dependency.component_id;
247
248 self.worker_function_invoke
249 .invoke(component_id, worker_name, function_name, args)
250 .await
251 }
252}