Skip to main content

bop/
host.rs

1//! Ready-made [`BopHost`] building blocks for embedders.
2//!
3//! Most embedders only need two things beyond the default trait
4//! impl: a way to plug in a module resolver, and a way to capture
5//! `print` output. This module provides both as composable
6//! pieces so you can mix-and-match without re-implementing the
7//! whole trait from scratch.
8//!
9//! ```no_run
10//! use bop::host::{StringModuleHost, resolve_from_map};
11//! use bop::BopLimits;
12//!
13//! // Map of module-name → source, resolved in-process (no I/O).
14//! let mut host = StringModuleHost::new([
15//!     ("greetings", "fn hello() { print(\"hi\") }"),
16//! ]);
17//! bop::run("use greetings\nhello()", &mut host, &BopLimits::standard())
18//!     .unwrap();
19//! ```
20//!
21//! The helpers intentionally stay minimal — they don't own the
22//! terminal, they don't touch the filesystem, they don't parse
23//! environment variables. Embedders that want richer behaviour
24//! should implement [`BopHost`] directly or wrap these helpers.
25
26#[cfg(feature = "no_std")]
27use alloc::{borrow::ToOwned, string::String, vec::Vec};
28
29#[cfg(not(feature = "no_std"))]
30use std::collections::BTreeMap;
31#[cfg(feature = "no_std")]
32use alloc::collections::BTreeMap;
33
34use crate::error::BopError;
35use crate::value::Value;
36use crate::BopHost;
37
38/// Build a [`BopHost::resolve_module`] implementation from an
39/// in-memory table of `(module_path, source)` pairs.
40///
41/// The returned closure takes the same `&str` argument the
42/// `BopHost` trait passes in and returns the matching source
43/// wrapped in `Some(Ok(..))`, or `None` when the module name
44/// isn't in the table. Embedders with additional lookup logic
45/// (filesystem, HTTP, asset bundle) can layer this helper on top
46/// of their own fallback chain — see [`StringModuleHost`] for a
47/// minimal all-in-one example.
48pub fn resolve_from_map<I, K, V>(entries: I) -> impl Fn(&str) -> Option<Result<String, BopError>>
49where
50    I: IntoIterator<Item = (K, V)>,
51    K: Into<String>,
52    V: Into<String>,
53{
54    let map: BTreeMap<String, String> = entries
55        .into_iter()
56        .map(|(k, v)| (k.into(), v.into()))
57        .collect();
58    move |name: &str| map.get(name).map(|s| Ok(s.clone()))
59}
60
61/// A minimal [`BopHost`] that captures `print` output and serves
62/// modules from an in-memory string table.
63///
64/// Useful for tests, playgrounds, and embedders that want
65/// resolver-backed imports without writing a full `BopHost`
66/// from scratch. For embedders that only need module resolution
67/// (and have their own print handling), use [`resolve_from_map`]
68/// directly inside a custom trait impl instead.
69pub struct StringModuleHost {
70    /// Each `print(...)` invocation appends one entry. Leave
71    /// public so tests can assert on output without going
72    /// through an accessor.
73    pub prints: Vec<String>,
74    modules: BTreeMap<String, String>,
75}
76
77impl StringModuleHost {
78    /// Build a host preloaded with the given module map.
79    ///
80    /// The iterator yields `(name, source)` pairs. Later entries
81    /// with the same name overwrite earlier ones.
82    pub fn new<I, K, V>(modules: I) -> Self
83    where
84        I: IntoIterator<Item = (K, V)>,
85        K: Into<String>,
86        V: Into<String>,
87    {
88        Self {
89            prints: Vec::new(),
90            modules: modules
91                .into_iter()
92                .map(|(k, v)| (k.into(), v.into()))
93                .collect(),
94        }
95    }
96
97    /// Register (or replace) a single module after construction.
98    pub fn insert_module(&mut self, name: impl Into<String>, source: impl Into<String>) {
99        self.modules.insert(name.into(), source.into());
100    }
101
102    /// Join all accumulated prints with `\n` — convenient for
103    /// tests that want to assert on the combined output.
104    pub fn output(&self) -> String {
105        self.prints.join("\n")
106    }
107}
108
109impl BopHost for StringModuleHost {
110    fn call(&mut self, _name: &str, _args: &[Value], _line: u32) -> Option<Result<Value, BopError>> {
111        None
112    }
113
114    fn on_print(&mut self, message: &str) {
115        self.prints.push(message.to_owned());
116    }
117
118    fn resolve_module(&mut self, name: &str) -> Option<Result<String, BopError>> {
119        self.modules.get(name).map(|s| Ok(s.clone()))
120    }
121}