Skip to main content

geam_stdlib/
io.rs

1mod function;
2
3use crate::{Component, GleamStdlibHostProfile, GleamStdlibRunState};
4use crate::{HostProviderModule, HostRegistrationError};
5use ecow::EcoString;
6use geam_core::provider::Call;
7
8/// A caller-owned destination for official Gleam standard-library IO events.
9pub trait IoSink {
10    /// Receives one owned standard-library IO event.
11    fn emit(&mut self, output: IoOutput);
12}
13
14/// One owned standard-library IO event.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct IoOutput {
17    stream: IoStream,
18    text: EcoString,
19}
20
21/// The standard stream selected by a Gleam IO operation.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum IoStream {
24    /// Standard output.
25    Stdout,
26    /// Standard error.
27    Stderr,
28}
29
30impl IoOutput {
31    pub(super) fn new(stream: IoStream, text: EcoString) -> Self {
32        Self { stream, text }
33    }
34
35    /// Returns the selected standard stream.
36    pub fn stream(&self) -> IoStream {
37        self.stream
38    }
39
40    /// Returns the exact text emitted by the Gleam IO operation.
41    pub fn text(&self) -> &EcoString {
42        &self.text
43    }
44}
45
46impl IoSink for Vec<IoOutput> {
47    fn emit(&mut self, output: IoOutput) {
48        self.push(output);
49    }
50}
51
52#[geam_macros::module(
53    path = "gleam/io",
54    crate_path = geam_core,
55    profile = crate::GleamStdlibHostProfile,
56    component = crate::Component<Profile::Io>,
57)]
58mod provider {
59    use super::{Call, EcoString, GleamStdlibRunState, function};
60
61    #[geam_macros::function(profile = Profile)]
62    fn print(
63        #[geam_macros::call] call: &mut Call<GleamStdlibRunState<Profile::Io>>,
64        text: EcoString,
65    ) -> () {
66        function::print(call.state_mut().io_sink(), text)
67    }
68
69    #[geam_macros::function(profile = Profile)]
70    fn print_error(
71        #[geam_macros::call] call: &mut Call<GleamStdlibRunState<Profile::Io>>,
72        text: EcoString,
73    ) -> () {
74        function::print_error(call.state_mut().io_sink(), text)
75    }
76
77    #[geam_macros::function(profile = Profile)]
78    fn println(
79        #[geam_macros::call] call: &mut Call<GleamStdlibRunState<Profile::Io>>,
80        text: EcoString,
81    ) -> () {
82        function::println(call.state_mut().io_sink(), text)
83    }
84
85    #[geam_macros::function(profile = Profile)]
86    fn println_error(
87        #[geam_macros::call] call: &mut Call<GleamStdlibRunState<Profile::Io>>,
88        text: EcoString,
89    ) -> () {
90        function::println_error(call.state_mut().io_sink(), text)
91    }
92}
93
94pub(super) fn host_provider<Profile>() -> Result<HostProviderModule<Profile>, HostRegistrationError>
95where
96    Profile: GleamStdlibHostProfile,
97{
98    provider::__geam_module::<Profile>()
99}
100
101#[cfg(test)]
102mod tests {
103    use super::{IoOutput, IoSink, IoStream, host_provider};
104    use crate::GleamStdlibProfile;
105
106    #[test]
107    fn output_preserves_owned_stream_and_text() {
108        let output = IoOutput::new(IoStream::Stderr, "message".into());
109
110        assert_eq!(output.stream(), IoStream::Stderr);
111        assert_eq!(output.text(), "message");
112        assert_eq!(output.clone(), output);
113    }
114
115    #[test]
116    fn vector_sink_collects_outputs_in_order() {
117        let mut outputs = Vec::new();
118        outputs.emit(IoOutput::new(IoStream::Stdout, "first".into()));
119        outputs.emit(IoOutput::new(IoStream::Stderr, "second".into()));
120
121        assert_eq!(
122            outputs
123                .iter()
124                .map(|output| (output.stream(), output.text().as_str()))
125                .collect::<Vec<_>>(),
126            [(IoStream::Stdout, "first"), (IoStream::Stderr, "second")],
127        );
128    }
129
130    #[test]
131    fn registers_the_exact_official_io_provider_inventory() {
132        let provider =
133            host_provider::<GleamStdlibProfile>().expect("official IO provider should register");
134        let functions = provider.functions().collect::<Vec<_>>();
135
136        assert_eq!(provider.package(), "gleam_stdlib");
137        assert_eq!(provider.module(), "gleam/io");
138        assert_eq!(
139            functions
140                .iter()
141                .map(|function| function.name().as_str())
142                .collect::<Vec<_>>(),
143            ["print", "print_error", "println", "println_error"],
144        );
145        for function in functions {
146            assert!(function.scheme().parameters().is_empty());
147            assert_eq!(
148                function.type_().argument_types(),
149                [crate::ValueType::String],
150            );
151            assert_eq!(function.type_().return_(), &crate::ValueType::Nil);
152        }
153    }
154}