Skip to content

Commit deb673f

Browse files
authored
Merge pull request #162 from bytecodealliance/abk/io-p3
Implement `io` module for WASIp3
2 parents 2dea36e + 3d0ad61 commit deb673f

9 files changed

Lines changed: 885 additions & 44 deletions

File tree

‎examples/stdio.rs‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#![cfg_attr(not(target_os = "wasi"), no_main)]
2+
#![cfg(target_os = "wasi")]
3+
4+
//! Demonstrates async line-oriented stdin, stdout, stderr, and flushing.
5+
6+
use anyhow::Result;
7+
use wstd::io::{AsyncRead, AsyncWrite};
8+
9+
struct LineReader<R> {
10+
reader: R,
11+
buffer: Vec<u8>,
12+
}
13+
14+
impl<R: AsyncRead> LineReader<R> {
15+
fn new(reader: R) -> Self {
16+
Self {
17+
reader,
18+
buffer: Vec::new(),
19+
}
20+
}
21+
22+
async fn read_line(&mut self) -> std::io::Result<Option<Vec<u8>>> {
23+
loop {
24+
if let Some(newline) = self.buffer.iter().position(|byte| *byte == b'\n') {
25+
return Ok(Some(self.buffer.drain(..=newline).collect()));
26+
}
27+
28+
let mut chunk = [0; 1024];
29+
match self.reader.read(&mut chunk).await? {
30+
0 if self.buffer.is_empty() => return Ok(None),
31+
0 => return Ok(Some(std::mem::take(&mut self.buffer))),
32+
len => {
33+
self.buffer.extend_from_slice(&chunk[..len]);
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
#[wstd::main]
41+
async fn main() -> Result<()> {
42+
let mut stdin = LineReader::new(wstd::io::stdin());
43+
let mut stdout = wstd::io::stdout();
44+
let mut stderr = wstd::io::stderr();
45+
46+
let mut stdin_error = None;
47+
while let Some(line) = match stdin.read_line().await {
48+
Ok(Some(line)) => Some(line),
49+
Ok(None) => {
50+
stdin_error = Some(std::io::ErrorKind::UnexpectedEof.into());
51+
None
52+
}
53+
Err(error) => {
54+
stdin_error = Some(error);
55+
None
56+
}
57+
} {
58+
stdout.write_all(b"stdout: ").await.unwrap();
59+
stdout.write_all(&line).await.unwrap();
60+
stdout.flush().await.unwrap();
61+
62+
stderr.write_all(b"stderr: ").await.unwrap();
63+
stderr.write_all(&line).await.unwrap();
64+
stderr.flush().await.unwrap();
65+
}
66+
let stdin_error = stdin_error.expect("the loop only exits after a stdin error");
67+
68+
let stdout_result = match stdout.write_all(b"stdin closed\n").await {
69+
Ok(()) => stdout.flush().await,
70+
Err(error) => Err(error),
71+
};
72+
73+
stderr
74+
.write_all(format!("stdin error: {:?}\n", stdin_error.kind()).as_bytes())
75+
.await?;
76+
if let Err(error) = stdout_result {
77+
stderr
78+
.write_all(format!("stdout error: {:?}\n", error.kind()).as_bytes())
79+
.await?;
80+
}
81+
stderr.flush().await?;
82+
83+
Ok(())
84+
}

‎src/io/mod.rs‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,36 @@ mod cursor;
55
mod empty;
66
mod read;
77
mod seek;
8-
mod stdio;
9-
mod streams;
8+
9+
mod streams {
10+
mod sys {
11+
#[cfg(target_env = "p2")]
12+
pub(super) mod p2;
13+
#[cfg(target_env = "p3")]
14+
pub(super) mod p3;
15+
}
16+
#[cfg(target_env = "p2")]
17+
pub use sys::p2::*;
18+
#[cfg(target_env = "p3")]
19+
pub use sys::p3::*;
20+
}
21+
22+
mod stdio {
23+
mod sys {
24+
#[cfg(target_env = "p2")]
25+
pub(super) mod p2;
26+
#[cfg(target_env = "p3")]
27+
pub(super) mod p3;
28+
}
29+
#[cfg(target_env = "p2")]
30+
pub use sys::p2::*;
31+
#[cfg(target_env = "p3")]
32+
pub use sys::p3::*;
33+
}
34+
1035
mod write;
1136

37+
#[cfg(target_env = "p2")]
1238
pub use crate::runtime::AsyncPollable;
1339
pub use copy::*;
1440
pub use cursor::*;
@@ -26,3 +52,37 @@ pub use std::io::Error;
2652
/// A specialized Result type for I/O operations.
2753
///
2854
pub use std::io::Result;
55+
56+
#[cfg(test)]
57+
mod test {
58+
use super::{AsyncWrite, stderr, stdout};
59+
use crate::runtime::block_on;
60+
61+
#[test]
62+
// No internal predicate. Run test with --nocapture and inspect output manually.
63+
fn stdout_println_hello_world() {
64+
block_on(async {
65+
let mut stdout = stdout();
66+
let term = if stdout.is_terminal() { "is" } else { "is not" };
67+
stdout
68+
.write_all(format!("hello, world! stdout {term} a terminal\n",).as_bytes())
69+
.await
70+
.unwrap();
71+
stdout.flush().await.unwrap();
72+
})
73+
}
74+
75+
#[test]
76+
// No internal predicate. Run test with --nocapture and inspect output manually.
77+
fn stderr_println_hello_world() {
78+
block_on(async {
79+
let mut stderr = stderr();
80+
let term = if stderr.is_terminal() { "is" } else { "is not" };
81+
stderr
82+
.write_all(format!("hello, world! stderr {term} a terminal\n",).as_bytes())
83+
.await
84+
.unwrap();
85+
stderr.flush().await.unwrap();
86+
})
87+
}
88+
}

src/io/stdio.rs renamed to src/io/stdio/sys/p2.rs

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use super::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result};
1+
use crate::io::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result};
22
use std::cell::LazyCell;
3-
use wasip2::cli::terminal_input::TerminalInput;
4-
use wasip2::cli::terminal_output::TerminalOutput;
3+
4+
use wasip2::cli::{terminal_input::TerminalInput, terminal_output::TerminalOutput};
55

66
/// Use the program's stdin as an `AsyncInputStream`.
77
#[derive(Debug)]
@@ -34,10 +34,10 @@ impl Stdin {
3434
impl AsyncRead for Stdin {
3535
#[inline]
3636
async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
37-
self.stream.read(buf).await
37+
let read = self.stream.read(buf).await?;
38+
Ok(read)
3839
}
3940

40-
#[inline]
4141
async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
4242
self.stream.read_to_end(buf).await
4343
}
@@ -147,33 +147,3 @@ impl AsyncWrite for Stderr {
147147
self.stream.as_async_output_stream()
148148
}
149149
}
150-
151-
#[cfg(test)]
152-
mod test {
153-
use crate::io::AsyncWrite;
154-
use crate::runtime::block_on;
155-
#[test]
156-
// No internal predicate. Run test with --nocapture and inspect output manually.
157-
fn stdout_println_hello_world() {
158-
block_on(async {
159-
let mut stdout = super::stdout();
160-
let term = if stdout.is_terminal() { "is" } else { "is not" };
161-
stdout
162-
.write_all(format!("hello, world! stdout {term} a terminal\n",).as_bytes())
163-
.await
164-
.unwrap();
165-
})
166-
}
167-
#[test]
168-
// No internal predicate. Run test with --nocapture and inspect output manually.
169-
fn stderr_println_hello_world() {
170-
block_on(async {
171-
let mut stdout = super::stdout();
172-
let term = if stdout.is_terminal() { "is" } else { "is not" };
173-
stdout
174-
.write_all(format!("hello, world! stderr {term} a terminal\n",).as_bytes())
175-
.await
176-
.unwrap();
177-
})
178-
}
179-
}

0 commit comments

Comments
 (0)