Skip to main content

geam_stdlib/
lib.rs

1pub(crate) use geam_core::{
2    BitArrayValue, HostCall, HostComponentProfile, HostConstruction, HostExternal,
3    HostExternalType, HostFailure, HostProfile, HostProvider, HostProviderComponent,
4    HostProviderComponentRegistration, HostProviderModule, HostRegistrationError, HostType,
5    HostTypeIndex0, HostTypeIndexNext, HostTypeList, HostTypeListEnd,
6};
7#[cfg(test)]
8pub(crate) use geam_core::{
9    ExecutionError, HostCallCompletion, HostCallError, HostExternalBinding, HostExternalEquality,
10    HostExternalHashing, HostExternalInspection, HostExternalSchema, HostExternalStorage,
11    HostExternalStore, HostExternalTypeSchema, HostModule, HostProviderSet, HostedExecution,
12    ModuleSource, PackageSource, PanicKind, PanicMessage, Value, ValueType,
13    compile_typed_host_program, plan_host_program,
14};
15use std::marker::PhantomData;
16
17mod bit_array;
18mod dict;
19mod dynamic;
20mod dynamic_decode;
21mod float;
22mod int;
23mod io;
24mod result;
25mod run_state;
26mod string;
27mod string_tree;
28mod uri;
29
30pub use io::{IoOutput, IoSink, IoStream};
31pub use run_state::{GleamStdlibRunState, GleamStdlibRunStateError};
32
33/// Narrow implementation contract used by sibling official provider packages.
34#[doc(hidden)]
35pub mod provider_support {
36    pub use crate::dict::{DictExternalStorage, DictOf, DictSchema, create_dynamic_dict};
37    pub use crate::dynamic::{
38        Dynamic, DynamicExternalStorage, DynamicSchema, create_value as create_dynamic_value,
39    };
40    pub use crate::dynamic_decode::DynamicDecodeErrorValue;
41    pub use crate::result::{GleamError, GleamOk, GleamResult};
42    pub use crate::string_tree::{
43        StoredStringTree, StringTree, StringTreeExternalStorage, StringTreePayload,
44        StringTreeSchema,
45    };
46}
47
48/// A host profile that exposes state and storage for the official Gleam standard library.
49pub trait GleamStdlibHostProfile: HostComponentProfile<Component<Self::Io>> {
50    /// The concrete caller-owned sink used by official Gleam IO functions.
51    type Io: IoSink + 'static;
52}
53
54/// External value stores used by the official Gleam standard library providers.
55#[derive(Default)]
56pub struct GleamStdlibStores {
57    dict: dict::Stores,
58    dynamic: dynamic::Stores,
59    string_tree: string_tree::Stores,
60}
61
62/// The statically composed provider component for the official Gleam standard library.
63#[derive(Debug, Clone, Copy)]
64pub struct Component<Io = Vec<IoOutput>>(PhantomData<fn() -> Io>);
65
66impl<Io> HostProviderComponent for Component<Io>
67where
68    Io: IoSink + 'static,
69{
70    const ID: &'static str = "gleam_stdlib";
71    type Stores = GleamStdlibStores;
72    type RunState = GleamStdlibRunState<Io>;
73}
74
75impl<Io> geam_core::__macro_support::ProviderPackage for Component<Io>
76where
77    Io: IoSink + 'static,
78{
79    const PACKAGE: &'static str = "gleam_stdlib";
80}
81
82/// The default profile for using only the official Gleam standard library providers.
83#[derive(Debug, Clone, Copy)]
84pub struct GleamStdlibProfile;
85
86impl HostProfile for GleamStdlibProfile {
87    type RunState = GleamStdlibRunState;
88    type ExternalStores = GleamStdlibStores;
89}
90
91impl HostComponentProfile<Component> for GleamStdlibProfile {
92    fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
93        stores
94    }
95
96    fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState {
97        state
98    }
99}
100
101impl GleamStdlibHostProfile for GleamStdlibProfile {
102    type Io = Vec<IoOutput>;
103}
104
105/// Registers the Rust providers for the official Gleam standard library.
106pub fn host_providers<Profile>() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>
107where
108    Profile: GleamStdlibHostProfile,
109{
110    <Component<Profile::Io> as HostProviderComponentRegistration<Profile>>::providers()
111}
112
113impl<Profile, Io> HostProviderComponentRegistration<Profile> for Component<Io>
114where
115    Profile: GleamStdlibHostProfile<Io = Io>,
116    Io: IoSink + 'static,
117{
118    fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError> {
119        register_host_providers::<Profile>()
120    }
121}
122
123pub(crate) fn stdlib_stores<Profile>(stores: &Profile::ExternalStores) -> &GleamStdlibStores
124where
125    Profile: GleamStdlibHostProfile,
126{
127    <Profile as HostComponentProfile<Component<Profile::Io>>>::component_stores(stores)
128}
129
130fn register_host_providers<Profile>()
131-> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>
132where
133    Profile: GleamStdlibHostProfile,
134{
135    let registrations: [ProviderRegistration<Profile>; 10] = [
136        dict::host_provider::<Profile>,
137        dynamic::host_provider::<Profile>,
138        float::host_provider::<Profile>,
139        int::host_provider::<Profile>,
140        string_tree::host_provider::<Profile>,
141        string::host_provider::<Profile>,
142        bit_array::host_provider::<Profile>,
143        dynamic_decode::host_provider::<Profile>,
144        io::host_provider::<Profile>,
145        uri::host_provider::<Profile>,
146    ];
147
148    registrations
149        .into_iter()
150        .map(|register| register())
151        .collect()
152}
153
154type ProviderRegistration<Profile> =
155    fn() -> Result<HostProviderModule<Profile>, HostRegistrationError>;
156
157#[cfg(test)]
158mod tests {
159    use super::{
160        Component, GleamStdlibHostProfile, GleamStdlibProfile, GleamStdlibRunState,
161        GleamStdlibStores, IoOutput, IoSink, IoStream, host_providers, stdlib_stores,
162    };
163    use crate::{
164        HostComponentProfile, HostProfile, HostProviderComponent, HostProviderComponentRegistration,
165    };
166
167    struct CustomProfile;
168
169    #[derive(Default)]
170    struct CustomStores {
171        stdlib: GleamStdlibStores,
172    }
173
174    struct CustomRunState {
175        stdlib: GleamStdlibRunState<RecordingSink>,
176    }
177
178    #[derive(Default)]
179    struct RecordingSink {
180        outputs: Vec<IoOutput>,
181    }
182
183    impl IoSink for RecordingSink {
184        fn emit(&mut self, output: IoOutput) {
185            self.outputs.push(output);
186        }
187    }
188
189    impl HostProfile for CustomProfile {
190        type RunState = CustomRunState;
191        type ExternalStores = CustomStores;
192    }
193
194    impl HostComponentProfile<Component<RecordingSink>> for CustomProfile {
195        fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
196            &stores.stdlib
197        }
198
199        fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState<RecordingSink> {
200            &mut state.stdlib
201        }
202    }
203
204    impl GleamStdlibHostProfile for CustomProfile {
205        type Io = RecordingSink;
206    }
207
208    #[test]
209    fn registers_providers_in_dependency_first_module_order() {
210        assert_eq!(<Component as HostProviderComponent>::ID, "gleam_stdlib");
211        let providers =
212            <Component as HostProviderComponentRegistration<GleamStdlibProfile>>::providers()
213                .expect("stdlib component should register");
214        let facade = host_providers::<GleamStdlibProfile>()
215            .expect("official stdlib provider facade should register");
216        assert_eq!(
217            facade
218                .iter()
219                .map(|provider| provider.module().as_str())
220                .collect::<Vec<_>>(),
221            providers
222                .iter()
223                .map(|provider| provider.module().as_str())
224                .collect::<Vec<_>>(),
225        );
226
227        assert_eq!(
228            providers
229                .iter()
230                .map(|provider| provider.module().as_str())
231                .collect::<Vec<_>>(),
232            [
233                "gleam/dict",
234                "gleam/dynamic",
235                "gleam/float",
236                "gleam/int",
237                "gleam/string_tree",
238                "gleam/string",
239                "gleam/bit_array",
240                "gleam/dynamic/decode",
241                "gleam/io",
242                "gleam/uri",
243            ],
244        );
245        let provider = &providers[0];
246        assert_eq!(provider.package(), "gleam_stdlib");
247        assert_eq!(provider.module(), "gleam/dict");
248        assert_eq!(
249            provider
250                .external_types()
251                .map(|schema| {
252                    (
253                        schema.package().as_str(),
254                        schema.module().as_str(),
255                        schema.name().as_str(),
256                        schema.parameter_count(),
257                    )
258                })
259                .collect::<Vec<_>>(),
260            [
261                ("gleam_stdlib", "gleam/dict", "Dict", 2),
262                ("gleam_stdlib", "gleam/dict", "TransientDict", 2),
263            ],
264        );
265        assert_eq!(
266            provider
267                .functions()
268                .map(|function| function.name().as_str())
269                .collect::<Vec<_>>(),
270            [
271                "to_transient",
272                "from_transient",
273                "size",
274                "do_has_key",
275                "new",
276                "get",
277                "do_insert",
278                "transient_insert",
279                "do_map_values",
280                "transient_delete",
281                "do_fold",
282                "transient_update_with",
283            ],
284        );
285    }
286
287    #[test]
288    fn custom_profiles_project_stdlib_stores_state_and_io() {
289        let default_stores = GleamStdlibStores::default();
290        let stores = CustomStores::default();
291        let mut default_state = GleamStdlibRunState::from_seed([1; 32]);
292        let mut state = CustomRunState {
293            stdlib: GleamStdlibRunState::from_seed_with_io([2; 32], RecordingSink::default()),
294        };
295
296        assert!(std::ptr::eq(
297            stdlib_stores::<GleamStdlibProfile>(&default_stores),
298            &default_stores,
299        ));
300        assert!(std::ptr::eq(
301            stdlib_stores::<CustomProfile>(&stores),
302            &stores.stdlib,
303        ));
304        let default_state_pointer = &mut default_state as *mut GleamStdlibRunState;
305        assert!(std::ptr::eq(
306            <GleamStdlibProfile as HostComponentProfile<Component>>::component_state(
307                &mut default_state,
308            ),
309            default_state_pointer,
310        ));
311        let state_pointer = &mut state.stdlib as *mut GleamStdlibRunState<RecordingSink>;
312        assert!(std::ptr::eq(
313            <CustomProfile as HostComponentProfile<Component<RecordingSink>>>::component_state(
314                &mut state,
315            ),
316            state_pointer,
317        ));
318
319        let default_io = <GleamStdlibProfile as HostComponentProfile<Component>>::component_state(
320            &mut default_state,
321        )
322        .io_sink();
323        default_io.emit(IoOutput::new(IoStream::Stdout, "default".into()));
324        assert_eq!(default_state.io_outputs()[0].text(), "default");
325
326        let custom_io =
327            <CustomProfile as HostComponentProfile<Component<RecordingSink>>>::component_state(
328                &mut state,
329            )
330            .io_sink();
331        custom_io.emit(IoOutput::new(IoStream::Stderr, "custom".into()));
332        assert_eq!(state.stdlib.io_sink().outputs[0].stream(), IoStream::Stderr);
333        assert_eq!(state.stdlib.io_sink().outputs[0].text(), "custom");
334    }
335}