golem_rib_repl/
repl_printer.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Golem Source License v1.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::ReplBootstrapError;
16use colored::Colorize;
17use golem_wasm_ast::analysis::analysed_type::{record, str, u64};
18use golem_wasm_ast::analysis::{AnalysedResourceMode, AnalysedType, NameTypePair, TypeHandle};
19use golem_wasm_rpc::{Value, ValueAndType};
20use rib::*;
21use std::collections::BTreeMap;
22use std::fmt::Display;
23
24pub trait ReplPrinter {
25    fn print_bootstrap_error(&self, error: &ReplBootstrapError) {
26        print_bootstrap_error(error);
27    }
28
29    fn print_clap_parse_error(&self, error: &clap::Error) {
30        println!("{}", error.to_string().red());
31    }
32
33    fn print_custom_error(&self, error: &str) {
34        println!("{} {}", "[message]".red(), error.red());
35    }
36
37    fn print_custom_message(&self, message: &str) {
38        println!("{} {}", "[message]".yellow(), message.cyan());
39    }
40
41    fn print_components_and_exports(&self, exports: &ComponentDependencies) {
42        for (component_dependency_key, component) in &exports.dependencies {
43            let mut indent = Indent::new();
44
45            println!(
46                "{} {}",
47                "📦 Component:".bold().bright_yellow(),
48                component_dependency_key
49                    .component_name
50                    .bold()
51                    .truecolor(180, 180, 180)
52            );
53
54            indent.add();
55
56            if let Some(root_package) = &component_dependency_key.root_package_name {
57                println!(
58                    "{} {} {}",
59                    indent,
60                    "Root Package:".bold().bright_cyan(),
61                    root_package.bold().truecolor(180, 180, 180)
62                );
63
64                indent.add();
65            }
66
67            if let Some(root_interface) = &component_dependency_key.root_package_version {
68                println!(
69                    "{} {} {}",
70                    indent,
71                    "Root Package Version:".bold().bright_cyan(),
72                    root_interface.bold().truecolor(180, 180, 180)
73                );
74
75                indent.add();
76            }
77
78            print_function_dictionary(&mut indent, component)
79        }
80    }
81
82    fn print_rib_compilation_error(&self, error: &RibCompilationError) {
83        print_rib_compilation_error(error);
84    }
85
86    fn print_rib_result(&self, result: &RibResult) {
87        match result {
88            RibResult::Unit => {
89                println!("{}", "()".yellow());
90            }
91
92            RibResult::Val(value_and_type) => match &value_and_type.value {
93                Value::Handle { uri, resource_id } => {
94                    println!("{} {}", "[warn]".magenta(), "the syntax below to show the resource-handle value is only used for display purposes".to_string().white());
95
96                    println!();
97
98                    let resource = Value::Record(vec![
99                        Value::String(uri.to_string()),
100                        Value::U64(*resource_id),
101                    ]);
102
103                    let analysed_type = record(vec![
104                        NameTypePair {
105                            name: "uri".to_string(),
106                            typ: str(),
107                        },
108                        NameTypePair {
109                            name: "resource-id".to_string(),
110                            typ: u64(),
111                        },
112                    ]);
113
114                    let result = ValueAndType::new(resource, analysed_type);
115
116                    println!("{}", result.to_string().yellow());
117                }
118
119                _ => println!("{}", result.to_string().yellow()),
120            },
121        }
122    }
123
124    fn print_rib_runtime_error(&self, error: &RibRuntimeError) {
125        println!("{} {}", "[runtime error]".red(), error.to_string().white());
126    }
127
128    fn print_wasm_value_type(&self, analysed_type: &AnalysedType) {
129        match analysed_type {
130            AnalysedType::Handle(type_handle) => {
131                let text = display_for_resource_handle(type_handle);
132                println!("{} {}", "[warn]".magenta(), "the syntax below to show the resource-handle type is only used for display purposes".to_string().white());
133
134                println!();
135
136                println!("{}", text.yellow());
137            }
138
139            _ => println!(
140                "{}",
141                wasm_wave::wasm::DisplayType(analysed_type)
142                    .to_string()
143                    .yellow()
144            ),
145        }
146    }
147}
148
149#[derive(Clone)]
150pub struct DefaultReplResultPrinter;
151
152impl ReplPrinter for DefaultReplResultPrinter {}
153
154pub fn print_function_dictionary(indent: &mut Indent, dict: &FunctionDictionary) {
155    let mut output = String::new();
156
157    let mut hierarchy: BTreeMap<
158        Option<PackageName>,
159        BTreeMap<Option<InterfaceName>, HierarchyNode>,
160    > = BTreeMap::new();
161
162    for (name, ftype) in &dict.name_and_types {
163        match name {
164            FunctionName::Function(func) => {
165                let node = hierarchy
166                    .entry(func.package_name.clone())
167                    .or_default()
168                    .entry(func.interface_name.clone())
169                    .or_default();
170                node.functions.push((func.function_name.clone(), ftype));
171            }
172
173            FunctionName::ResourceConstructor(ctor) => {
174                let node = hierarchy
175                    .entry(ctor.package_name.clone())
176                    .or_default()
177                    .entry(ctor.interface_name.clone())
178                    .or_default();
179                node.resources
180                    .entry(ctor.resource_name.clone())
181                    .or_default()
182                    .constructor = Some(ftype);
183            }
184
185            FunctionName::ResourceMethod(method) => {
186                let node = hierarchy
187                    .entry(method.package_name.clone())
188                    .or_default()
189                    .entry(method.interface_name.clone())
190                    .or_default();
191                node.resources
192                    .entry(method.resource_name.clone())
193                    .or_default()
194                    .methods
195                    .push((method.method_name.clone(), ftype));
196            }
197            FunctionName::Variant(_) => {
198                continue;
199            }
200            FunctionName::Enum(_) => {
201                continue;
202            }
203        }
204    }
205
206    for (pkg, interfaces) in hierarchy {
207        match pkg {
208            Some(pkg) => {
209                output.push_str(&format!(
210                    "{} {} {}\n",
211                    indent,
212                    "📦 Package:".bold().bright_yellow(),
213                    format!("{}:{}", pkg.namespace, pkg.package_name)
214                        .bold()
215                        .truecolor(180, 180, 180)
216                ));
217
218                indent.add();
219            }
220            None => {
221                output.push_str(&format!("{}\n", "📦 Global Scope".bold().bright_yellow()));
222                indent.add();
223            }
224        }
225
226        for (iface, node) in interfaces {
227            if let Some(iface) = iface {
228                output.push_str(&format!(
229                    "{} {} {}\n",
230                    indent,
231                    "📄 Interface:".bold().bright_cyan(),
232                    iface.name.bold().truecolor(180, 180, 180)
233                ));
234                indent.add();
235            }
236
237            if !node.functions.is_empty() {
238                output.push_str(&format!(
239                    "{} {}\n",
240                    indent,
241                    "🔧 Functions:".bold().bright_green(),
242                ));
243                indent.add();
244            }
245
246            for (fname, ftype) in &node.functions {
247                output.push_str(&format!("{} {}\n", indent, fname.bright_magenta()));
248                indent.add();
249                output.push_str(&format!(
250                    "{} ↳ {}: {}\n",
251                    indent,
252                    "Args".blue(),
253                    format_type_list(&ftype.parameter_types).truecolor(180, 180, 180)
254                ));
255                output.push_str(&format!(
256                    "{} ↳ {}: {}\n",
257                    indent,
258                    "Returns".blue(),
259                    format_return_type(&ftype.return_type).truecolor(180, 180, 180)
260                ));
261                indent.remove();
262            }
263
264            for (res_name, res) in &node.resources {
265                output.push_str(&format!(
266                    "{} {} {}\n",
267                    indent,
268                    "🧩️ Resource:".bold().bright_yellow(),
269                    res_name.truecolor(180, 180, 180)
270                ));
271
272                indent.add();
273
274                if let Some(ftype) = res.constructor {
275                    output.push_str(&format!(
276                        "{} ↳ {}: {}\n",
277                        indent,
278                        "Args".blue(),
279                        format_type_list(&ftype.parameter_types).truecolor(180, 180, 180)
280                    ));
281
282                    indent.add();
283
284                    output.push_str(&format!(
285                        "{} {} \n",
286                        indent,
287                        "🔧 Methods:".bold().bright_green(),
288                    ));
289
290                    indent.add();
291
292                    for (mname, mtype) in &res.methods {
293                        output.push_str(&format!("{} {}\n", indent, mname.bright_magenta()));
294                        indent.add();
295
296                        let parameter_types = &mtype.parameter_types;
297
298                        let formatted = if !parameter_types.is_empty() {
299                            format_type_list(&parameter_types[1..]).truecolor(180, 180, 180)
300                        } else {
301                            format_type_list(&[]).truecolor(180, 180, 180)
302                        };
303
304                        output.push_str(&format!(
305                            "{} ↳ {}: {}\n",
306                            indent,
307                            "Args".blue(),
308                            formatted
309                        ));
310
311                        output.push_str(&format!(
312                            "{} ↳ {}: {}\n",
313                            indent,
314                            "Returns".blue(),
315                            format_return_type(&mtype.return_type).truecolor(180, 180, 180)
316                        ));
317
318                        indent.remove();
319                    }
320                }
321            }
322        }
323    }
324
325    println!("{output}");
326}
327
328fn format_type_list(types: &[InferredType]) -> String {
329    if types.is_empty() {
330        "()".to_string()
331    } else {
332        types
333            .iter()
334            .map(|t| wasm_wave::wasm::DisplayType(&AnalysedType::try_from(t).unwrap()).to_string())
335            .collect::<Vec<_>>()
336            .join(", ")
337    }
338}
339
340fn format_return_type(typ: &Option<InferredType>) -> String {
341    typ.as_ref()
342        .map(|t| wasm_wave::wasm::DisplayType(&AnalysedType::try_from(t).unwrap()).to_string())
343        .unwrap_or_else(|| "()".to_string())
344}
345
346#[derive(Default)]
347struct HierarchyNode<'a> {
348    functions: Vec<(String, &'a FunctionType)>,
349    resources: BTreeMap<String, ResourceNode<'a>>,
350}
351
352#[derive(Default)]
353struct ResourceNode<'a> {
354    constructor: Option<&'a FunctionType>,
355    methods: Vec<(String, &'a FunctionType)>,
356}
357
358fn print_rib_compilation_error(error: &RibCompilationError) {
359    match error {
360        RibCompilationError::RibStaticAnalysisError(msg) => {
361            println!("{} {}", "[rib static analysis error]".red(), msg.red());
362        }
363
364        RibCompilationError::UnsupportedGlobalInput {
365            invalid_global_inputs: found,
366            valid_global_inputs: expected,
367        } => {
368            println!(
369                "{} {} {}",
370                "[unsupported input]".red(),
371                "found:".yellow(),
372                found.join(", ").white()
373            );
374            println!(
375                "{} {} {}",
376                "[supported inputs]".green(),
377                "expected:".yellow(),
378                expected.join(", ").white()
379            );
380        }
381        RibCompilationError::RibTypeError(compilation_error) => {
382            let cause = &compilation_error.cause;
383            let position = &compilation_error.source_span;
384
385            println!("{}", "[compilation error]".red().bold());
386            println!("{} {}", "[position]".yellow(), position.start_column());
387            println!(
388                "{} {}",
389                "[expression]".yellow(),
390                compilation_error
391                    .expr
392                    .as_ref()
393                    .map(|x| x.to_string())
394                    .unwrap_or_default()
395                    .white()
396            );
397            println!("{} {}", "[cause]".yellow(), cause.bright_red().bold());
398
399            if !compilation_error.additional_error_details.is_empty() {
400                for detail in &compilation_error.additional_error_details {
401                    println!("{} {}", "[help]".yellow(), detail.cyan());
402                }
403            }
404
405            if !compilation_error.help_messages.is_empty() {
406                for message in &compilation_error.help_messages {
407                    println!("{} {}", "[help]".yellow(), message.cyan());
408                }
409            }
410        }
411        RibCompilationError::InvalidSyntax(script) => {
412            println!("{} {}", "[invalid script]".red(), script.white());
413        }
414        RibCompilationError::ByteCodeGenerationFail(error) => {
415            println!(
416                "{} {}",
417                "[internal bytecode generation error]".red(),
418                error.to_string().red()
419            );
420        }
421    }
422}
423
424fn print_bootstrap_error(error: &ReplBootstrapError) {
425    match error {
426        ReplBootstrapError::ReplHistoryFileError(msg) => {
427            println!("{} {}", "[warn]".yellow(), msg);
428        }
429        ReplBootstrapError::ComponentLoadError(msg) => {
430            println!("{} {}", "[error]".red(), msg);
431        }
432        ReplBootstrapError::MultipleComponentsFound(msg) => {
433            println!("{} {}", "[error]".red(), msg);
434            println!(
435                "{}",
436                "specify the component name when bootstrapping repl".yellow()
437            );
438        }
439        ReplBootstrapError::NoComponentsFound => {
440            println!(
441                "{} no components found in the repl context",
442                "[warn]".yellow()
443            );
444        }
445    }
446}
447
448// Only used for displaying since Wasm Wave is yet to support resource handle types
449fn display_for_resource_handle(type_handle: &TypeHandle) -> String {
450    let resource_id = &type_handle.resource_id.0;
451    let uri = &type_handle.mode;
452
453    let mode = match uri {
454        AnalysedResourceMode::Owned => "owned",
455        AnalysedResourceMode::Borrowed => "borrowed",
456    };
457
458    format!("handle<resource-id:{resource_id}, mode:{mode}>")
459}
460
461pub struct Indent {
462    level: usize,
463}
464
465impl Default for Indent {
466    fn default() -> Self {
467        Self::new()
468    }
469}
470
471impl Indent {
472    pub fn new() -> Self {
473        Self { level: 0 }
474    }
475
476    pub fn add(&mut self) {
477        self.level += 2;
478    }
479
480    pub fn remove(&mut self) {
481        if self.level >= 2 {
482            self.level -= 2;
483        }
484    }
485}
486
487impl Display for Indent {
488    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489        write!(f, "{}", " ".repeat(self.level))?;
490        Ok(())
491    }
492}