tartan_uefi/
io.rs

1//! [`SimpleTextOutput`]-based [`Write`] implementation to support formatting macros.
2
3use super::proto::SimpleTextOutput;
4use super::{Result, Status};
5use alloc::boxed::Box;
6use alloc::vec::Vec;
7use core::fmt::Write;
8
9#[macro_export]
10/// Write formatted data to an [`OutputStream`] and return the last result, rather than a
11/// dumb [`core::fmt::Error`].
12macro_rules! writeln_result {
13    [$out:ident, $($args:expr),* $(,)?] => {
14        match writeln!($out, $($args),*) {
15            _ => $out.last_result
16        }
17    }
18}
19
20#[derive(Clone, Copy)]
21pub struct OutputStream<'a> {
22    out: &'a SimpleTextOutput,
23    pub last_result: Result,
24}
25
26impl<'a> OutputStream<'a> {
27    pub fn new(out: &'a SimpleTextOutput) -> Self {
28        OutputStream { out, last_result: Ok(Status::Success) }
29    }
30}
31
32impl Write for OutputStream<'_> {
33    fn write_str(&mut self, s: &str) -> core::fmt::Result {
34        // TODO: Horribly inefficient, but simplest from a stack allocation perspective
35        for c in s.chars() {
36            if let e @ Err(_) = self.write_char(c) {
37                return e;
38            }
39        }
40        Ok(())
41    }
42
43    fn write_char(&mut self, c: char) -> core::fmt::Result {
44        // Automatically translate LF to CRLF. Internally-used panic strings can contain
45        // line breaks (e.g., assert_eq!), and this ensures they are formatted correctly.
46        if c == '\n' {
47            self.write_char('\r')?;
48        }
49
50        // Two for UTF-16 code point (possibly a surrogate pair). One for null terminator.
51        let mut buffer = [0_u16; 3];
52        c.encode_utf16(&mut buffer);
53        let out = &self.out;
54        unsafe {
55            self.last_result = (out.output_string)(out, buffer.as_ptr()).into_result();
56        }
57        match self.last_result {
58            Ok(_) => Ok(()),
59            Err(_) => Err(core::fmt::Error),
60        }
61    }
62}
63
64
65pub struct Logger<'a>(pub Option<OutputStream<'a>>);
66
67impl log::Log for Logger<'_> {
68    fn log(&self, record: &log::Record) {
69        if let Some(out) = self.0 {
70            writeln!(
71                out.clone(),
72                "{} [{}] {}",
73                record.level(),
74                record.module_path().unwrap_or("<unknown>"),
75                record.args(),
76            )
77            .unwrap();
78        }
79    }
80
81    fn enabled(&self, _: &log::Metadata) -> bool {
82        true
83    }
84
85    fn flush(&self) {}
86}
87
88
89/// Convert a Rust string to a buffer containing a null-terminated UTF-16 string. Requires
90/// dynamic memory allocation.
91pub fn encode_c_utf16(string: &str) -> Box<[u16]> {
92    // Encoded length of each character plus 1 byte for null terminator
93    let encoded_length = 1 + string.chars().map(char::len_utf16).sum::<usize>();
94    let mut encoded = Vec::with_capacity(encoded_length);
95    encoded.extend(string.encode_utf16());
96    encoded.push(0);
97    encoded.into_boxed_slice()
98}
99
100
101#[cfg(test)]
102mod test {
103    use super::*;
104
105    #[test]
106    fn test_encode_c_utf16() {
107        assert_eq!(*encode_c_utf16(""), [0x0]);
108        assert_eq!(*encode_c_utf16("z"), [0x7a, 0x0]);
109        assert_eq!(*encode_c_utf16("\0z"), [0x0, 0x7a, 0x0]);
110
111        assert_eq!(*encode_c_utf16("Eat up 🍔!"), [
112            0x45, 0x61, 0x74, 0x20, 0x75, 0x70, 0x20, 0xd83c, 0xdf54, 0x21, 0x0,
113        ]);
114    }
115}