1#![cfg_attr(not(test), no_std)]
23#![warn(missing_docs)]
24
25#[cfg(feature = "std")]
26extern crate std;
27
28#[cfg(not(test))]
29use wasm_bindgen::prelude::wasm_bindgen;
30
31use core::{cmp, ptr, mem, fmt};
32
33#[cfg(not(test))]
34#[wasm_bindgen]
35extern "C" {
36 #[wasm_bindgen(js_namespace = console)]
37 fn error(s: &str);
38 #[wasm_bindgen(js_namespace = console)]
39 fn warn(s: &str);
40 #[wasm_bindgen(js_namespace = console)]
41 fn info(s: &str);
42 #[wasm_bindgen(js_namespace = console)]
43 fn debug(s: &str);
44}
45
46#[cfg(test)]
47fn error(_: &str) {
48}
49
50#[cfg(test)]
51fn warn(_: &str) {
52}
53
54#[cfg(test)]
55fn info(_: &str) {
56}
57
58#[cfg(test)]
59fn debug(_: &str) {
60}
61
62const BUFFER_CAPACITY: usize = 4096;
63
64#[derive(Debug, PartialEq, Eq, Copy, Clone)]
65pub enum ConsoleType {
67 Error,
69 Warn,
71 Info,
73 Debug,
75}
76
77pub struct Console {
83 typ: ConsoleType,
84 buffer: mem::MaybeUninit<[u8; BUFFER_CAPACITY]>,
85 len: usize,
86}
87
88impl Console {
89 pub const fn new(typ: ConsoleType) -> Self {
91 Self {
92 typ,
93 buffer: mem::MaybeUninit::uninit(),
94 len: 0,
95 }
96 }
97
98 #[inline(always)]
99 pub fn buffer(&self) -> &[u8] {
101 unsafe {
102 core::slice::from_raw_parts(self.buffer.as_ptr() as *const u8, self.len)
103 }
104 }
105
106 #[inline(always)]
107 fn as_mut_ptr(&mut self) -> *mut u8 {
108 self.buffer.as_mut_ptr() as _
109 }
110
111 #[inline(always)]
112 pub fn flush(&mut self) {
117 if self.len > 0 {
118 self.inner_flush();
119 }
120 }
121
122 fn inner_flush(&mut self) {
123 let text = unsafe {
124 core::str::from_utf8_unchecked(self.buffer())
125 };
126 match self.typ {
127 ConsoleType::Error => error(text),
128 ConsoleType::Warn => warn(text),
129 ConsoleType::Info => info(text),
130 ConsoleType::Debug => debug(text),
131 }
132
133 self.len = 0;
134 }
135
136 #[inline]
137 fn copy_data<'a>(&mut self, text: &'a [u8]) -> &'a [u8] {
138 let mut write_len = cmp::min(BUFFER_CAPACITY.saturating_sub(self.len), text.len());
139
140 #[inline(always)]
141 fn is_char_boundary(text: &[u8], idx: usize) -> bool {
142 if idx == 0 {
143 return true;
144 }
145
146 match text.get(idx) {
147 None => idx == text.len(),
148 Some(&byte) => (byte as i8) >= -0x40
149 }
150 }
151
152 #[inline(never)]
153 #[cold]
154 fn shift_by_char_boundary(text: &[u8], mut size: usize) -> usize {
155 while !is_char_boundary(text, size) {
156 size -= 1;
157 }
158 size
159 }
160
161 if !is_char_boundary(text, write_len) {
162 write_len = shift_by_char_boundary(text, write_len - 1);
164 }
165
166 unsafe {
167 ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().add(self.len), write_len);
168 }
169 self.len += write_len;
170 &text[write_len..]
171 }
172
173 pub fn write_data(&mut self, mut data: &[u8]) {
178 loop {
179 data = self.copy_data(data);
180
181 if data.is_empty() {
182 break;
183 } else {
184 self.flush();
185 }
186 }
187 }
188}
189
190impl fmt::Write for Console {
191 #[inline]
192 fn write_str(&mut self, text: &str) -> fmt::Result {
193 self.write_data(text.as_bytes());
194
195 Ok(())
196 }
197}
198
199#[cfg(feature = "std")]
200impl std::io::Write for Console {
201 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
202 self.write_data(buf);
203 Ok(buf.len())
204 }
205
206 #[inline(always)]
207 fn flush(&mut self) -> std::io::Result<()> {
208 self.flush();
209 Ok(())
210 }
211}
212
213impl Drop for Console {
214 #[inline]
215 fn drop(&mut self) {
216 self.flush();
217 }
218}
219
220#[macro_export]
221macro_rules! println {
223 () => {{
224 $crate::println!(" ");
225 }};
226 ($($arg:tt)*) => {{
227 use core::fmt::Write;
228 let mut writer = $crate::Console::new($crate::ConsoleType::Info);
229 let _ = write!(writer, $($arg)*);
230 drop(writer);
231 }}
232}
233
234#[macro_export]
235macro_rules! eprintln {
237 () => {{
238 $crate::println!(" ");
239 }};
240 ($($arg:tt)*) => {{
241 use core::fmt::Write;
242 let mut writer = $crate::Console::new($crate::ConsoleType::Error);
243 let _ = write!(writer, $($arg)*);
244 drop(writer);
245 }}
246}
247
248#[cfg(test)]
249mod tests {
250 use super::{Console, ConsoleType};
251 const DATA: &str = "1234567891";
252
253 #[test]
254 fn should_normal_write() {
255 let mut writer = Console::new(ConsoleType::Warn);
256
257 assert_eq!(writer.typ, ConsoleType::Warn);
258
259 let data = DATA.as_bytes();
260
261 writer.write_data(data);
262 assert_eq!(writer.len, data.len());
263 assert_eq!(writer.buffer(), data);
264
265 writer.write_data(b" ");
266 writer.write_data(data);
267 let expected = format!("{} {}", DATA, DATA);
268 assert_eq!(writer.len, expected.len());
269 assert_eq!(writer.buffer(), expected.as_bytes());
270 }
271
272 #[test]
273 fn should_handle_write_overflow() {
274 let mut writer = Console::new(ConsoleType::Warn);
275 let data = DATA.as_bytes();
276
277 for idx in 1..=409 {
279 writer.write_data(data);
280 assert_eq!(writer.len, data.len() * idx);
281 }
282
283 writer.write_data(data);
284 assert_eq!(writer.len, 4);
285 writer.flush();
286 assert_eq!(writer.len, 0);
287 }
288
289 #[test]
290 fn should_handle_write_overflow_outside_of_char_boundary() {
291 let mut writer = Console::new(ConsoleType::Warn);
292 let data = DATA.as_bytes();
293
294 for idx in 1..=409 {
295 writer.write_data(data);
296 assert_eq!(writer.len, data.len() * idx);
297 }
298
299 writer.write_data(b"1234");
300 assert_eq!(4094, writer.len);
301 let unicode = "ロリ";
302 writer.write_data(unicode.as_bytes());
303 assert_eq!(writer.len, unicode.len());
304 assert_eq!(writer.buffer(), unicode.as_bytes());
305 }
306}