1mod function;
2
3use crate::{Component, GleamStdlibHostProfile, GleamStdlibRunState};
4use crate::{HostProviderModule, HostRegistrationError};
5use ecow::EcoString;
6use geam_core::provider::Call;
7
8pub trait IoSink {
10 fn emit(&mut self, output: IoOutput);
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct IoOutput {
17 stream: IoStream,
18 text: EcoString,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum IoStream {
24 Stdout,
26 Stderr,
28}
29
30impl IoOutput {
31 pub(super) fn new(stream: IoStream, text: EcoString) -> Self {
32 Self { stream, text }
33 }
34
35 pub fn stream(&self) -> IoStream {
37 self.stream
38 }
39
40 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}