1#![allow(unknown_lints, uncommon_codepoints)]
2#![allow(unused_imports)]
3
4#![cfg_attr(feature = "nightly", feature(test))]
5
6#![cfg_attr(feature = "re", feature(try_trait_v2))]
7#![cfg_attr(feature = "re", feature(never_type))]
8
9extern crate libc;
10#[cfg(feature = "crossterm")] extern crate crossterm;
11
12#[cfg(feature = "memchr")] use memchr::{memchr, memrchr};
13use core::any::Any;
14use core::arch::asm;
15use core::cell::UnsafeCell;
16use core::str::from_utf8_unchecked;
17use core::sync::atomic::{AtomicI8, AtomicUsize, Ordering};
18use std::fmt::{self, Write};
19use std::fs;
20use std::hint::spin_loop;
21use std::io::{self, Read};
22use std::mem::MaybeUninit;
23use std::ops::{Deref, DerefMut};
24use std::os::raw::c_int;
25use std::path::Path;
26use std::process::{Command, Stdio};
27use std::sync::Mutex;
28use std::thread;
29use std::time::{Duration, SystemTime, UNIX_EPOCH};
30
31#[inline] pub fn b2s (b: &[u8]) -> &str {unsafe {std::str::from_utf8_unchecked (b)}}
32
33pub fn filename<'a> (path: &'a str) -> &'a str {
37 let name = match path.rfind (|ch| ch == '/' || ch == '\\') {
42 Some (ofs) => &path[ofs+1..],
43 None => path};
44
45 if name.ends_with (".rs") {&name[0 .. name.len() - 3]} else {name}}
46
47#[macro_export] macro_rules! try_s {
54 ($e: expr) => {match $e {
55 Ok (ok) => ok,
56 Err (err) => {return Err (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err));}}}}
57
58#[macro_export] macro_rules! try_f {
60 ($e: expr) => {match $e {
61 Ok (ok) => ok,
62 Err (err) => {return Err (From::from (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err)));}}}}
63
64#[macro_export] macro_rules! try_sp {
66 ($e: expr) => {match $e {&Ok (ref ok) => ok,
67 &Err (ref err) => {return Err (From::from (format! ("{}:{}] {:?}", $crate::filename (file!()), line!(), err)));}}}}
68
69#[macro_export] macro_rules! try_fu {
81 ($e: expr) => {match $e {
82 Ok (ok) => ok,
83 Err (err) => {return Box::new (futures01::future::err (From::from (err)))}}}}
84
85#[macro_export] macro_rules! try_fus {
93 ($e: expr) => {match $e {
94 Ok (ok) => ok,
95 Err (err) => {return Box::new (futures01::future::err (ERRL! ("{}", err)))}}}}
96
97#[macro_export] macro_rules! ERRL {
99 ($format: expr, $($args: tt)+) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!(), $($args)+)};
100 ($format: expr) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!())}}
101
102#[macro_export] macro_rules! ERR {
106 ($format: expr, $($args: tt)+) => {Err ($crate::ERRL! ($format, $($args)+))};
107 ($format: expr) => {Err ($crate::ERRL! ($format))}}
108
109#[cfg(feature = "base62")]
110pub mod base62;
111
112#[cfg(feature = "base62j")]
113pub mod base62j;
114
115#[cfg(all(feature = "base91", feature = "nightly"))]
116pub mod base91;
117
118#[cfg(feature = "re")]
119pub mod re;
120
121#[cfg(feature = "lines")]
122pub mod lines;
123
124#[cfg(feature = "link")]
125pub mod link;
126
127#[cfg(all(feature = "re"))]
128pub mod aarc;
129
130#[cfg(all(feature = "crossterm", not(target_arch = "wasm32")))]
133fn isatty (fd: c_int) -> c_int {unsafe {libc::isatty (fd)}}
134
135#[cfg(all(feature = "crossterm", target_arch = "wasm32"))]
136fn isatty (_fd: c_int) -> c_int {0}
137
138#[cfg(feature = "crossterm")]
139static mut STATUS_LINE: IniMutex<String> = IniMutex::none();
140
141#[cfg(feature = "crossterm")]
142pub struct IsTty {pub is_tty: AtomicI8}
143#[cfg(feature = "crossterm")]
144impl core::ops::Deref for IsTty {
145 type Target = bool;
146 fn deref (&self) -> &Self::Target {
147 let mut is_tty = self.is_tty.load (Ordering::Relaxed);
148 if is_tty == 0 {
149 is_tty = if isatty (1) != 0 {1} else {-1};
151 self.is_tty.store (is_tty, Ordering::Relaxed)}
152 if is_tty == 1 {&true} else {&false}}}
153
154#[cfg(feature = "crossterm")]
156pub static ISATTY: IsTty = IsTty {is_tty: AtomicI8::new (0)};
157
158#[cfg(feature = "crossterm")]
164pub static STATUS_LINE_LM: AtomicUsize = AtomicUsize::new (0);
165
166#[cfg(feature = "crossterm")]
168fn delete_line (stdout: &mut io::Stdout) {
169 use crossterm::{terminal, QueueableCommand};
170
171 let _ = stdout.queue (terminal::Clear (terminal::ClearType::UntilNewLine));}
172
173 #[cfg(all(feature = "crossterm"))]
198pub fn status_line (file: &str, line: u32, status: &str) {
199 use crossterm::{QueueableCommand, cursor};
200 use io::{stdout, Write};
201 use std::collections::hash_map::DefaultHasher;
202 use std::hash::Hasher;
203
204 #[allow(static_mut_refs)]
205 if let Ok (mut status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
206 let mut stdout = stdout();
207 let old_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
208 status_line.clear();
209 use std::fmt::Write;
210 let _ = write! (&mut *status_line, "{}:{}] {}", filename (file), line, status);
211 let new_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
212 if old_hash != new_hash {
213 STATUS_LINE_LM.s (now_ms() as usize);
214
215 match crossterm::terminal::size() {
217 Ok ((w, _)) if status_line.chars().count() >= w as usize => {
218 let mut tmp = String::with_capacity (w as usize - 1);
219 for ch in status_line.chars().take (w as usize - 1) {tmp.push (ch)}
220 let _ = stdout.write (tmp.as_bytes());},
221 _ => {let _ = stdout.write (status_line.as_bytes());}};
222
223 delete_line (&mut stdout);
224 let _ = stdout.queue (cursor::MoveToColumn (0));
225 let _ = stdout.flush();}}}
226
227#[cfg(feature = "crossterm")]
229pub fn status_line_clear() -> String {
230 use io::{stdout, Write};
231 let mut ret = String::new();
232 #[allow(static_mut_refs)]
233 if let Ok (mut status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
234 if *ISATTY && !status_line.is_empty() {
235 let mut stdout = stdout();
236 STATUS_LINE_LM.s (now_ms() as usize);
237 core::mem::swap (&mut ret, &mut status_line);
238 delete_line (&mut stdout);
239 let _ = stdout.flush();}}
240 ret}
241
242#[cfg(feature = "crossterm")]
246pub fn with_status_line (code: &dyn Fn()) {
247 use crossterm::{QueueableCommand, cursor};
248 use io::{stdout, Write};
249
250 #[allow(static_mut_refs)]
251 if let Ok (status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
252 if !*ISATTY || status_line.is_empty() {
253 code()
254 } else {
255 let mut stdout = stdout();
256 delete_line (&mut stdout);
257 let _ = stdout.flush(); code();
259 let _ = stdout.write (status_line.as_bytes());
261 let _ = stdout.queue (cursor::MoveToColumn (0));
262 let _ = stdout.flush();}}}
263
264#[cfg(feature = "crossterm")]
265#[test] fn test_status_line() {
266 with_status_line (&|| println! ("hello world"));}
267
268#[cfg(all(feature = "inlinable_string", feature = "fomat-macros"))]
269pub fn short_log_time (ms: u64) -> inlinable_string::InlinableString {
270 use fomat_macros::wite;
271 let iso = ms2iso8601 (ms as i64);
272 ifomat! ((&iso[8..10]) ' ' (&iso[11..19]))}
273
274#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
276pub static INDENT: IniMutex<(inlinable_string::InlinableString, i8)> = IniMutex::none();
277
278#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
279#[macro_export] macro_rules! log {
280
281 ($on: literal, $($args: tt)+) => { if $on == 2 {
283 log! ($($args)+)
284 } else if $on == 1 { let i1 = $crate::INDENT.spin_default().1;
286 if 0 <= i1 {log! ($($args)+)}}};
287
288 (t $time: expr => $delay: expr, $($args: tt)+) => {{ static LL: core::sync::atomic::AtomicI64 = core::sync::atomic::AtomicI64::new (0);
290 let now = $time as i64;
291 let Δ = now - LL.load (core::sync::atomic::Ordering::Relaxed);
292 if $delay <= Δ {
293 LL.store (now, core::sync::atomic::Ordering::Relaxed);
294 log! ($($args)+)}}};
295
296 (q $command: expr, $($args: tt)+) => {{
297 $crate::with_status_line (&|| {
298 use crossterm::QueueableCommand;
299 use fomat_macros::{wite, fomat};
300 use std::io::Write;
301 let tty = *$crate::ISATTY;
302 let i0 = $crate::INDENT.spin_default().0.clone();
303 let mut stdout = std::io::stdout();
304 if tty {let _ = stdout.queue ($command);}
305 let _ = wite! (&mut stdout,
306 ($crate::short_log_time ($crate::now_ms())) ' '
307 (i0)
308 ($crate::filename (file!())) ':' (line!()) "] "
309 $($args)+ '\n');
310 if tty {let _ = stdout.queue (crossterm::style::ResetColor);}
311 let _ = stdout.flush();})}};
312
313 (c $color: expr, $($args: tt)+) => {
315 log! (q crossterm::style::SetForegroundColor ($color), $($args)+)};
316
317 (a $ansi: expr, $($args: tt)+) => {
320 log! (q crossterm::style::SetForegroundColor (
321 crossterm::style::Color::AnsiValue ($ansi)), $($args)+)};
322
323 ($($args: tt)+) => {{
324 $crate::with_status_line (&|| {
325 use fomat_macros::{pintln, fomat};
326 let i0 = $crate::INDENT.spin_default().0.clone();
327 pintln! (
328 ($crate::short_log_time ($crate::now_ms())) ' '
329 (i0)
330 ($crate::filename (file!())) ':' (line!()) "] "
331 $($args)+);})}};}
332
333#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
334#[test] fn test_log() {
335 log! ([= 2 + 2])}
336
337#[macro_export] macro_rules! gstring {($array: ident, $code: block) => {{
355 let end = {
356 let mut $array = ::std::io::Cursor::new (&mut $array[..]);
357 let $array = &mut $array;
358 $code;
359 $array.position() as usize};
360 let s = unsafe {::core::str::from_utf8_unchecked (&$array[0..end])};
361 s}}}
362
363#[cfg(feature = "fomat-macros")]
370#[macro_export] macro_rules! ifomat {
371 ($($args: tt)+) => ({
372 use inlinable_string::{InlinableString, StringExt};
373 use std::fmt::Write as FmtWrite;
374 let mut is = InlinableString::new();
375 wite! (&mut is, $($args)+) .expect ("!wite");
376 is})}
377
378pub const fn civil_from_days (mut z: i32) -> (i32, u32, u32) {
382 z += 719468;
383 let era = if 0 <= z {z} else {z - 146096} / 146097;
384 let doe = (z - era * 146097) as u32; let yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; let y = yoe as i32 + era * 400;
387 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = (mp as i32 + if (mp as i32) < 10 {3} else {-9}) as u32; (y + if m <= 2 {1} else {0}, m, d)}
392
393pub const fn days_from_civil (mut y: i32, m: u32, d: u32) -> i32 {
395 y -= if m <= 2 {1} else {0};
396 let era = if 0 <= y {y} else {y - 399} / 400;
397 let yoe = (y - era * 400) as u32; let doy = (153 * (m as i32 + (if 2 < m {-3} else {9})) + 2) as u32 / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; era * 146097 + doe as i32 - 719468}
401
402pub const fn ymd2dow (mut y: i32, m: i32, mut d: i32) -> i32 {
404 if m < 3 {y -= 1} else {d -= 2}
405 (23*m/9 + d + 4 + y/4 - y/100 + y/400 + y) % 7}
406
407#[cfg(feature = "chrono")]
409pub fn ldt2ics (dt: &chrono::DateTime<chrono::Local>) -> i64 {
410 use chrono::{Datelike, Timelike};
411 let y = dt.year() as i64;
412 let m = dt.month() as i64;
413 let d = dt.day() as i64;
414 let ti = dt.time();
415 let h = ti.hour() as i64;
416 let min = ti.minute() as i64;
417 let s = ti.second() as i64;
418 let cs = ti.nanosecond() as i64 / 10000000;
419 y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
420
421#[cfg(feature = "inlinable_string")]
423pub fn ms2iso8601 (ms: i64) -> inlinable_string::InlinableString {
424 use inlinable_string::{InlinableString, StringExt};
425 use std::fmt::Write as FmtWrite;
426 let day = (ms / 1000 / 86400) as i32;
427 let h = ((ms / 1000) % 86400) / 3600;
428 let min = ((ms / 1000) % 3600) / 60;
429 let s = (ms / 1000) % 60;
430 let ms = ms % 1000;
431 let (y, m, d) = civil_from_days (day);
432 let mut is = inlinable_string::InlinableString::new();
433 write! (&mut is, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", y, m, d, h, min, s, ms) .expect ("!write!");
435 is}
436
437pub const fn ms2ics (ms: i64) -> i64 {
439 let day = (ms / 1000 / 86400) as i32;
440 let h = ((ms / 1000) % 86400) / 3600;
441 let min = ((ms / 1000) % 3600) / 60;
442 let s = (ms / 1000) % 60;
443 let cs = ms % 1000 / 10;
444 let (y, m, d) = civil_from_days (day);
445 let y = y as i64; let m = m as i64; let d = d as i64;
446 y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
447
448pub const fn ics2ms (ics: i64) -> i64 {
450 let day = days_from_civil (
451 (ics / 1000000000000 + 2000) as i32,
452 (ics / 10000000000 % 100) as u32,
453 (ics / 100000000 % 100) as u32) as i64;
454 let tm_hour = (ics / 1000000 % 100) as i64;
455 let tm_min = (ics / 10000 % 100) as i64;
456 let tm_sec = (ics / 100 % 100) as i64;
457 let ms = (ics % 100 * 10) as i64;
458 ms + tm_sec * 1000 + tm_min * 60000 + tm_hour * 3600000 + day * 86400000}
459
460#[cfg(all(feature = "chrono", feature = "re"))]
462pub fn ics2ldt (ims: i64) -> re::Re<chrono::DateTime<chrono::Local>> {
463 use chrono::{TimeZone, Timelike};
464 let dt = chrono::Local.with_ymd_and_hms (
465 (ims / 1000000000000 + 2000) as i32,
466 (ims / 10000000000 % 100) as u32,
467 (ims / 100000000 % 100) as u32,
468 (ims / 1000000 % 100) as u32,
469 (ims / 10000 % 100) as u32,
470 (ims / 100 % 100) as u32) .earliest()?;
471 let dt = dt.with_nanosecond ((ims % 100) as u32 * 10000000)?;
472 re::Re::Ok (dt)}
473
474#[cfg(all(feature = "chrono", feature = "re"))]
476pub fn ics2ndt (ims: i64) -> re::Re<chrono::NaiveDateTime> {
477 use chrono::{TimeZone, Timelike};
478 let dt = chrono::NaiveDateTime::new (
479 chrono::NaiveDate::from_ymd_opt (
480 (ims / 1000000000000 + 2000) as i32,
481 (ims / 10000000000 % 100) as u32,
482 (ims / 100000000 % 100) as u32)?,
483 chrono::NaiveTime::from_hms_nano_opt (
484 (ims / 1000000 % 100) as u32,
485 (ims / 10000 % 100) as u32,
486 (ims / 100 % 100) as u32,
487 (ims % 100) as u32 * 10000000)?);
488 re::Re::Ok (dt)}
489
490#[cfg(feature = "fomat-macros")]
493#[macro_export] macro_rules! iso8601z {
494 ($date_or_time: expr) => {{
495 let sufff = ($date_or_time.len() as i32 - 10) .max (0) as usize;
496 ifomat! (($date_or_time) if sufff < 10 {(&"T00:00:00Z"[sufff..])})}}}
497
498#[cfg(feature = "fomat-macros")]
500#[macro_export] macro_rules! iso8601toL {($short: expr) => {
501 Local.from_local_datetime (&(DateTime::parse_from_rfc3339 (&iso8601z! ($short))?) .naive_utc()) .earliest()?}}
502
503pub fn iso8601ics (iso: &[u8]) -> i64 {
506 let mut ics: [u8; 15] = *b"000000000000000";
507 if 4 <= iso.len() {
508 ics[0] = iso[1]; ics[1] = iso[2]; ics[2] = iso[3];
509 if 7 <= iso.len() && iso[4] == b'-' {
510 ics[3] = iso[5]; ics[4] = iso[6];
511 if 10 <= iso.len() && iso[7] == b'-' {
512 ics[5] = iso[8]; ics[6] = iso[9];
513 if 13 <= iso.len() && (iso[10] == b'T' || iso[10] == b' ') {
514 ics[7] = iso[11]; ics[8] = iso[12];
515 if 16 <= iso.len() && iso[13] == b':' {
516 ics[9] = iso[14]; ics[10] = iso[15];
517 if 19 <= iso.len() && iso[16] == b':' {
518 ics[11] = iso[17]; ics[12] = iso[18];
519 if 22 <= iso.len() && iso[19] == b'.' {
520 ics[13] = iso[20]; ics[14] = iso[21]}}}}}}}
521 match b2s (&ics) .parse() {Ok (k) => k, Err (_err) => 0}}
522
523#[cfg(all(test, feature = "nightly", feature = "chrono", feature = "inlinable_string", feature = "re"))]
524mod time_bench {
525 extern crate test;
526 use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike, Utc};
527 use crate::{civil_from_days, days_from_civil, ics2ldt, ics2ms, ics2ndt, iso8601ics, ldt2ics, ms2ics, ms2iso8601, now_ms, re::Re};
528 use fomat_macros::wite;
529 use inlinable_string::InlinableString;
530 use rand::{rngs::SmallRng, seq::index::sample, Rng, SeedableRng};
531 use test::black_box;
532
533 #[bench] fn duration (bm: &mut test::Bencher) {
534 assert! (946684800000 == days_from_civil (2000, 1, 1) as i64 * 86400 * 1000);
535 let duration = 118050;
536 assert! ("00:01:58.050" == &ms2iso8601 (946684800000 + duration) [11..23]);
537 assert! ( 15805 == ms2ics (946684800000 + duration) - 10100000000);
538 let mut ms = 0;
539 bm.iter (|| { let ics = ms2ics (946684800000 + ms as i64) - 10100000000;
541 let tm_min = (ics / 10000 % 100) as i64;
542 let tm_sec = (ics / 100 % 100) as i64;
543 let ims = (ics % 100 * 10) as i64;
544 let msʹ = ims + tm_sec * 1000 + tm_min * 60000;
545 assert! (ms == msʹ);
546 ms += 10;
547 if 3600 * 1000 <= ms {ms = 0}})}
548
549 #[bench] fn iso8601icsᵇ (bm: &mut test::Bencher) {
550 assert! (00000000000000 == iso8601ics (b""));
551 assert! (24000000000000 == iso8601ics (b"2024"));
552 assert! (24120000000000 == iso8601ics (b"2024-12"));
553 assert! (24121300000000 == iso8601ics (b"2024-12-13"));
554 assert! (24121321000000 == iso8601ics (b"2024-12-13T21"));
555 assert! (24121321000000 == iso8601ics (b"2024-12-13T21+03"));
556 assert! (24121314150000 == iso8601ics (b"2024-12-13T14:15"));
557 assert! (24121314150000 == iso8601ics (b"2024-12-13 14:15"));
558 assert! (24121314151600 == iso8601ics (b"2024-12-13T14:15:16"));
559 assert! (24121314151600 == iso8601ics (b"2024-12-13 14:15:16"));
560 assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.98"));
561 assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980"));
562 assert! (24121314151698 == iso8601ics (b"3024-12-13T14:15:16.980Z"));
563 assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980-03"));
564 assert! (999121314151698 == iso8601ics (b"2999-12-13T14:15:16.980-03"));
565 bm.iter (|| {
566 let ics = iso8601ics (b"4321-12-23T13:14:15.987");
567 assert! (black_box (ics) == 321122313141598, "{}", ics)})}
568
569 #[bench] fn ms2iso8601ᵇ (bm: &mut test::Bencher) {
570 let mut rng = SmallRng::seed_from_u64 (now_ms());
571 bm.iter (|| {
572 let it = ms2iso8601 (rng.random::<i64>().abs());
573 assert! (black_box (it) .ends_with ('Z'))})}
574
575 #[bench] fn chrono_iso8601 (bm: &mut test::Bencher) {
576 let dt = Utc::now();
577 bm.iter (|| {
578 let it = ifomat! ((dt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
579 assert! (it.ends_with ('Z'))})}
580
581 fn make_samples() -> Vec<(DateTime<Local>, InlinableString)> {
582 let mut rng = SmallRng::seed_from_u64 (now_ms());
583 let mut samples = Vec::with_capacity (65536);
584 while samples.len() < samples.capacity() {
585 let ms = rng.random::<i64>().abs() / 10 * 10;
586 if let Some (udt) = Utc.timestamp_millis_opt (ms) .earliest() {
587 let day = (ms / 1000 / 86400) as i32;
588 let (y, m, d) = civil_from_days (day);
589 assert! (udt.year() == y && udt.month() == m && udt.day() == d, "{:?}, y{}, m{}, d{}", udt, y, m, d);
590 assert! (days_from_civil (y, m, d) == day);
591 let cit = ifomat! ((udt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
592 let cit = if cit.starts_with ('+') {&cit[1..]} else {&cit};
593 let dit = ms2iso8601 (ms);
594 assert! (cit == dit, "{} <> {}", cit, dit);
595 if let Some (udt) = udt.with_year (2000 + udt.year() % 1000) {
596 let ms = udt.timestamp_millis();
597 let ics = ms2ics (ms);
598 let udtʹ = Utc.from_utc_datetime (&ics2ndt (ics) .unwrap());
599 assert! (udt == udtʹ, "{:?} <> {:?}", udt, udtʹ);
600 let msʹ = ics2ms (ics);
601 assert! (ms == msʹ, "{} <> {}", ms, msʹ)}}
602 if let Some (ldt) = Local.timestamp_millis_opt (ms) .earliest() {
603 if let Some (ldt) = ldt.with_year (2000 + ldt.year() % 1000) {
604 let sdt = ifomat! ((ldt.format ("%Y-%m-%dT%H:%M:%S%.3f")));
605 samples.push ((ldt, sdt))}}}
606 samples}
607
608 #[bench] fn iso8601tol (bm: &mut test::Bencher) {
609 let (samples, mut sx) = (make_samples(), 0);
610 bm.iter (|| {
611 let ics = iso8601ics (samples[sx].1.as_bytes());
612 let dt = ics2ldt (ics) .unwrap();
613 let odt = &samples[sx].0;
614 assert! (
615 dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
616 && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
617 && dt.nanosecond() == odt.nanosecond(),
618 "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
619 sx += 1; if samples.len() <= sx {sx = 0}})}
620
621 #[bench] fn iso8601ton (bm: &mut test::Bencher) {
622 let (samples, mut sx) = (make_samples(), 0);
623 bm.iter (|| {
624 let ics = iso8601ics (samples[sx].1.as_bytes());
625 let dt = ics2ndt (ics) .unwrap();
626 let odt = &samples[sx].0;
627 assert! (
628 dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
629 && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
630 && dt.nanosecond() == odt.nanosecond(),
631 "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
632 sx += 1; if samples.len() <= sx {sx = 0}})}
633
634 #[bench] fn chrono_from_str (bm: &mut test::Bencher) {bm.iter (|| {
635 let dt = NaiveDateTime::parse_from_str ("4321-12-23T13:14:15", "%Y-%m-%dT%H:%M:%S") .unwrap();
636 assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
637 && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
638
639 #[bench] fn chrono_from_rfc3339 (bm: &mut test::Bencher) {bm.iter (|| {
640 let dt = DateTime::parse_from_rfc3339 ("4321-12-23T13:14:15Z") .unwrap();
641 assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
642 && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
643
644 #[bench] fn iso8601tol_macro (bm: &mut test::Bencher) {bm.iter (|| {
645 fn f() -> Re<DateTime<Local>> {Re::Ok (iso8601toL! ("4321-12-23T13:14:15"))}
646 let dt = f().unwrap();
647 assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
648 && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
649
650 #[bench] fn iso8601_ics_ms (bm: &mut test::Bencher) {bm.iter (|| {
651 let ics = iso8601ics (black_box (b"4321-12-23T13:14:15"));
652 assert! (ics == 321122313141500);
653 assert_eq! (ics2ms (black_box (ics)), 11107286055000)})}}
655
656pub fn netstring (at: &[u8]) -> Result<(&[u8], &[u8]), String> {
663 let length_end = match at.iter().position (|&ch| ch < b'0' || ch > b'9') {Some (l) if l > 0 => l, _ => return ERR! ("No len.")};
664 match at.get (length_end) {Some (&ch) if ch == b':' => (), _ => return ERR! ("No colon.")};
665 let length = b2s (&at[0 .. length_end]);
666 let length: usize = try_s! (length.parse());
667 let bulk_pos = 0 + length_end + 1;
668 let bulk_end = bulk_pos + length;
669 match at.get (bulk_end) {Some (&ch) if ch == b',' => (), _ => return ERR! ("No comma.")}
670 Ok ((&at[bulk_pos .. bulk_end], &at[bulk_end + 1 ..]))}
671
672#[cfg(unix)]
674pub fn with_hostname (visitor: &mut dyn FnMut (&[u8])) -> Result<(), std::io::Error> {
675 use libc::{size_t, gethostname}; use std::ffi::CStr;
677
678 let mut buf = [0; 128];
679 let rc = unsafe {gethostname (buf.as_mut_ptr(), (buf.len() - 1) as size_t)};
680 if rc == 0 {
681 let cs = unsafe {CStr::from_ptr (buf.as_ptr())};
682 Ok (visitor (cs.to_bytes()))
683 } else {
684 Err (io::Error::last_os_error())}}
685
686#[cfg(unix)] #[test] fn test_hostname() {
687 let mut hostname = String::new();
688 with_hostname (&mut |bytes| hostname = String::from_utf8_lossy (bytes) .into_owned()) .unwrap();}
689
690pub fn slurp (path: &dyn AsRef<Path>) -> Vec<u8> {
695 let Ok (mut file) = fs::File::open (path) else {return Vec::new()};
696 let mut buf = Vec::new();
697 if let Err (_err) = file.read_to_end (&mut buf) {return Vec::new()}
699 buf}
700
701pub fn slurp_prog (command: &str) -> Result<String, String> {
707 let output = match Command::new ("dash") .arg ("-c") .arg (command) .output() {
708 Ok (output) => output,
709 Err (ref err) if err.kind() == io::ErrorKind::NotFound => { try_s! (Command::new ("sh") .arg ("-c") .arg (command) .output())},
711 Err (err) => return ERR! ("{}", err)};
712
713 let combined_output: String = if output.stderr.is_empty() {
714 try_s! (String::from_utf8 (output.stdout))
715 } else if output.stdout.is_empty() {
716 try_s! (String::from_utf8 (output.stderr))
717 } else {
718 let mut buf = String::with_capacity (output.stderr.len() + output.stdout.len());
719 buf.push_str (try_s! (std::str::from_utf8 (&output.stderr[..])));
720 buf.push_str (try_s! (std::str::from_utf8 (&output.stdout[..])));
721 buf};
722
723 if output.status.success() {Ok (combined_output)} else {Err (combined_output)}}
724
725#[test] fn test_slurp_prog() {
726 if cfg! (windows) {println! ("Skipping as dash might be missing on Windows sometimes"); return}
727 let foo = match slurp_prog ("echo foo") {Ok (foo) => foo, Err (err) => panic! ("{}", err)};
728 assert_eq! (foo.trim(), "foo");}
729
730pub fn cmd (cmd: &str) -> Result<(), String> {
732 println! ("$ {}", cmd);
733 let status = try_s! (Command::new ("dash") .arg ("-c") .arg (cmd) .stdout (Stdio::inherit()) .stderr (Stdio::inherit()) .status());
734 if !status.success() {Err (format! ("Command returned an error status: {}", status))} else {Ok(())}}
735
736pub fn any_to_str<'a> (message: &'a dyn Any) -> Option<&'a str> {
745 if let Some (message) = message.downcast_ref::<&str>() {return Some (message)}
746 if let Some (message) = message.downcast_ref::<String>() {return Some (&message[..])}
747 return None}
748
749pub fn duration_to_float (duration: Duration) -> f64 {
753 duration.as_secs() as f64 + ((duration.subsec_nanos() as f64) / 1000000000.0)}
754
755#[cfg(feature = "chrono")]
757pub fn dtl2float (dt: chrono::DateTime<chrono::Local>) -> f64 {
758 dt.timestamp() as f64 + ((dt.timestamp_subsec_nanos() as f64) / 1000000000.0)}
759
760pub fn ms2sec (ms: u64) -> f64 {
762 (ms / 1000) as f64 + ((ms % 1000) as f64 / 1000.0)}
763
764pub fn now_float() -> f64 {
768 let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
769 duration_to_float (now)}
770
771#[test] fn test_now_float() {
772 let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since") .as_secs();
773 let t1 = now_float();
774 assert_eq! (now, t1 as u64);
775 thread::sleep (Duration::from_millis (100));
776 let t2 = now_float();
777 let delta = t2 - t1;
778 assert! (delta >= 0.098 && delta <= 0.150, "delta: {}", delta);}
779
780pub fn duration_to_ms (duration: Duration) -> u64 {
782 duration.as_secs() * 1000 + (duration.subsec_nanos() / 1000000) as u64}
783
784pub fn now_ms() -> u64 {
786 let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
787 duration_to_ms (now)}
788
789#[test] fn test_now_ms() {
790 let t1 = now_ms();
791 thread::sleep (Duration::from_millis (100));
792 let t2 = now_ms();
793 let delta = t2 - t1;
794 assert! (delta >= 98 && delta <= 150, "delta: {}", delta);}
795
796pub fn last_modified_sec (path: &dyn AsRef<Path>) -> Result<f64, String> {
803 let meta = match path.as_ref().metadata() {
804 Ok (m) => m,
805 Err (ref err) if err.kind() == std::io::ErrorKind::NotFound => return Ok (0.),
806 Err (err) => return ERR! ("{}", err)};
807 let lm = try_s! (meta.modified());
808 let lm = duration_to_float (try_s! (lm.duration_since (UNIX_EPOCH)));
809 Ok (lm)}
810
811#[cfg(target_arch="x86_64")]
817pub fn rdtsc() -> u64 {unsafe {core::arch::x86_64::_rdtsc()}}
818
819#[cfg(not (target_arch="x86_64"))]
820pub fn rdtsc() -> u64 {SystemTime::now().duration_since (SystemTime::UNIX_EPOCH) .expect ("!now") .as_nanos() as u64}
821
822#[cfg(target_arch="x86_64")]
823#[test] fn test_rdtsc() {
824 assert! (rdtsc() != rdtsc())}
825
826pub struct FileLock<'a> {
830 pub lock_path: &'a dyn AsRef<Path>,
832 pub ttl_sec: f64,
834 pub file: std::fs::File}
836impl<'a> FileLock<'a> {
837 pub fn lock (lock_path: &'a dyn AsRef<Path>, ttl_sec: f64) -> Result<Option<FileLock<'a>>, String> {
847 let mut cycle = 0u8;
848 loop {
849 if cycle > 1 {break Ok (None)} cycle += 1;
851 let mut fo = std::fs::OpenOptions::new();
852 match fo.read (true) .write (true) .create_new (true) .open (lock_path.as_ref()) {
853 Ok (file) => break Ok (Some (FileLock {lock_path, ttl_sec, file})),
854 Err (ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => {
855 let lm = match last_modified_sec (lock_path) {
857 Ok (lm) => lm,
858 Err (ie) => break ERR! ("Error checking {:?}: {}", lock_path.as_ref(), ie)};
859 if lm == 0. {continue} if now_float() - lm > ttl_sec {
861 if let Err (err) = std::fs::remove_file (lock_path.as_ref()) {break ERR! ("Error removing {:?}: {}", lock_path.as_ref(), err)}
862 continue}
863 break Ok (None)},
864 Err (ie) => break ERR! ("Error creating {:?}: {}", lock_path.as_ref(), ie)}}}
865 #[cfg(target_os = "linux")]
868 pub fn touch (&self) -> Result<(), String> {
869 let ts = libc::timespec {tv_sec: 0, tv_nsec: libc::UTIME_NOW};
871 let times = [ts, ts];
872 use std::os::unix::io::AsRawFd;
873 let rc = unsafe {libc::futimens (self.file.as_raw_fd(), ×[0])};
874 if rc != 0 {
875 let err = std::io::Error::last_os_error();
876 return ERR! ("Can't touch {:?}: {}", self.lock_path.as_ref(), err)}
877 Ok(())}}
878impl<'a> Drop for FileLock<'a> {
879 fn drop (&mut self) {
880 let _ = std::fs::remove_file (self.lock_path);}}
881impl<'a> fmt::Debug for FileLock<'a> {
882 fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
883 write! (ft, "FileLock ({:?}, {})", self.lock_path.as_ref(), self.ttl_sec)}}
884
885#[derive(Debug)]
887pub struct ProcEn {
888 pub name: String,
889 pub path: std::path::PathBuf,
890 pub cmdline: Vec<String>}
892impl ProcEn {
893 pub fn pid (&self) -> Option<u32> {
894 if let Some (file_name) = self.path.file_name() {
895 if let Some (file_name) = file_name.to_str() {
896 if let Ok (pid) = file_name.parse() {
897 return Some (pid)}}}
898 None}}
899pub struct ProcIt {read_dir: std::fs::ReadDir}
910impl ProcIt {
911 pub fn new() -> ProcIt {
912 ProcIt {
913 read_dir: match Path::new ("/proc") .read_dir() {Ok (it) => it, Err (err) => panic! ("!proc: {}", err)}}}}
914impl Iterator for ProcIt {
915 type Item = ProcEn;
916 fn next (&mut self) -> Option<ProcEn> {
917 match self.read_dir.next() {
918 None => return None,
919 Some (Err (err)) => panic! ("ProcIt] !read_dir: {}", err),
920 Some (Ok (proc_en)) => {
921 let file_type = match proc_en.file_type() {
922 Ok (ft) => ft,
923 Err (err) => {
924 if matches! (err.kind(), io::ErrorKind::NotFound) {
925 return self.next()
926 } else {
927 panic! ("!file_type ({:?}): {}", proc_en.path(), err)}}};
928 if !file_type.is_dir() {return self.next()}
929 let name = proc_en.file_name();
930 let name = match name.to_str() {Some (name) => name, None => panic! ("ProcIt] !to_str")};
931 if !name.as_bytes().iter().all (|&b| b >= b'0' && b <= b'9') { return self.next()}
933 let path = proc_en.path();
934 let cmdline = String::from_utf8 (slurp (&path.join ("cmdline"))) .expect ("!from_utf8"); if cmdline.is_empty() {return self.next()}
936 Some (ProcEn {name: name.into(), path: path, cmdline: cmdline.split ('\0') .map (String::from) .collect()})}}}}
937
938pub static mut SPIN_OUT: u32 = 1234567;
939
940fn spin_yield() {
941 spin_loop();
942 thread::yield_now(); spin_loop()}
944
945pub struct IniMutex<T> {au: AtomicI8, vc: UnsafeCell<MaybeUninit<T>>}
946#[must_use = "if unused the Mutex will immediately unlock"]
947pub struct IniMutexGuard<'a, T> {lo: &'a IniMutex<T>}
948
949unsafe impl<T: Send> Send for IniMutex<T> {}
950unsafe impl<T: Send> Sync for IniMutex<T> {}
951
952struct ResetTo0<'a, T> (&'a IniMutex<T>, bool);
954impl<'a, T> Drop for ResetTo0<'a, T> {
955 fn drop (&mut self) {
956 if self.1 {self.0.au.store (0, Ordering::Relaxed)}}}
957
958impl<T> IniMutex<T> {
959 pub const fn none() -> IniMutex<T> {
960 IniMutex {
961 au: AtomicI8::new (0),
962 vc: UnsafeCell::new (MaybeUninit::uninit())}}
963
964 pub const fn new (init: T) -> IniMutex<T> {
965 IniMutex {
966 au: AtomicI8::new (1),
967 vc: UnsafeCell::new (MaybeUninit::new (init))}}
968
969 pub fn is_none (&self) -> bool {
971 0 == self.au.load (Ordering::Relaxed)}
972
973 pub fn is_some (&self) -> bool {
975 0 != self.au.load (Ordering::Relaxed)}
976
977 pub fn lock (&self) -> Result<IniMutexGuard<'_, T>, i8> {
979 match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
980 Ok (1) => Ok (IniMutexGuard {lo: self}),
981 Ok (lock) => Err (lock),
982 Err (au) => Err (au)}}
983
984 pub fn spin (&self) -> IniMutexGuard<'_, T> {
985 loop {
986 if let Ok (lock) = self.lock() {return lock}
987 spin_yield()}}
988
989 pub fn spinᵗ (&self, spins: u32) -> Result<IniMutexGuard<'_, T>, &'static str> {
991 for spin in 0..spins {
992 if let Ok (lock) = self.lock() {return Ok (lock)}
993 if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}}
994 Err ("spin-out")}
995
996 #[cfg(feature = "re")]
997 pub fn lock_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, LockInitErr> {
998 match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
999 Ok (1) => Ok (IniMutexGuard {lo: self}),
1000 Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
1001 Ok (0) => {
1002 let vc = unsafe {&mut *self.vc.get()};
1003 let mut reset = ResetTo0 (self, true);
1004 match init() {
1005 re::Re::Ok (vi) => {
1006 *vc = MaybeUninit::new (vi);
1007 reset.1 = false;
1008 Ok (IniMutexGuard {lo: self})},
1009 re::Re::Err (err) => {
1010 Err (LockInitErr::Init (err))}}},
1011 Ok (au) => Err (LockInitErr::Lock (au)),
1012 Err (au) => Err (LockInitErr::Lock (au))},
1013 Ok (au) => Err (LockInitErr::Lock (au)),
1014 Err (au) => Err (LockInitErr::Lock (au))}}
1015
1016 #[cfg(feature = "re")]
1017 pub fn spin_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, String> {
1018 loop {
1019 match self.lock_init (init) {
1020 Ok (lock) => break Ok (lock),
1021 Err (LockInitErr::Lock (_l)) => spin_yield(),
1022 Err (LockInitErr::Init (err)) => break Err (err)}}}
1023
1024 #[cfg(feature = "re")]
1026 pub fn spin_initᵗ (&self, spins: u32, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, String> {
1027 for spin in 0..spins {
1028 match self.lock_init (init) {
1029 Ok (lock) => return Ok (lock),
1030 Err (LockInitErr::Lock (_l)) => {
1031 if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}},
1032 Err (LockInitErr::Init (err)) => return Err (err)}}
1033 Err ("spin-out".to_string())}
1034
1035 pub fn evict (lock: IniMutexGuard<'_, T>) {unsafe {
1037 let vc = &mut *lock.lo.vc.get();
1038 let val = core::ptr::read (vc) .assume_init();
1039 lock.lo.au.store (0, Ordering::Release);
1040 core::mem::forget (lock);
1043 drop (val)}}}
1044
1045impl<T> Default for IniMutex<T> {
1046 fn default() -> Self {
1048 IniMutex::none()}}
1049
1050#[derive (Debug)]
1051pub enum LockInitErr {Lock (i8), Init (String)}
1052
1053impl fmt::Display for LockInitErr {
1054 fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1055 match *self {
1056 LockInitErr::Lock (au) => fm.write_fmt (format_args! ("{}", au)),
1057 LockInitErr::Init (ref err) => fm.write_str (err)}}}
1058
1059impl<T: Default> IniMutex<T> {
1060 pub fn lock_default (&self) -> Result<IniMutexGuard<'_, T>, i8> {
1061 match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
1062 Ok (1) => Ok (IniMutexGuard {lo: self}),
1063 Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
1064 Ok (0) => {
1065 let vc = unsafe {&mut *self.vc.get()};
1066 let mut reset = ResetTo0 (self, true);
1067 *vc = MaybeUninit::new (T::default());
1068 reset.1 = false;
1069 Ok (IniMutexGuard {lo: self})},
1070 Ok (au) => Err (au),
1071 Err (au) => Err (au)},
1072 Ok (au) => Err (au),
1073 Err (au) => Err (au)}}
1074
1075 pub fn spin_default (&self) -> IniMutexGuard<'_, T> {
1076 loop {
1077 if let Ok (lock) = self.lock_default() {return lock}
1078 spin_yield()}}
1079
1080 pub fn spin_defaultᵗ (&self, mut spins: u32) -> Result<IniMutexGuard<'_, T>, i8> {
1081 loop {
1082 match self.lock_default() {
1083 Ok (lock) => return Ok (lock),
1084 Err (err) => {
1085 if spins == 0 {return Err (err)}
1086 spins -= 1;
1087 if spins % 10 == 0 {spin_loop()} else {thread::yield_now()}}}}}}
1088
1089impl<T> Deref for IniMutexGuard<'_, T> {
1090 type Target = T;
1091 fn deref (&self) -> &T {
1092 let vc = unsafe {&mut *self.lo.vc.get()};
1093 unsafe {&*vc.as_ptr()}}}
1094
1095impl<T> DerefMut for IniMutexGuard<'_, T> {
1096 fn deref_mut (&mut self) -> &mut T {
1097 let vc = unsafe {&mut *self.lo.vc.get()};
1098 unsafe {&mut *vc.as_mut_ptr()}}}
1099
1100impl<T: fmt::Debug> fmt::Debug for IniMutexGuard<'_, T> {
1101 fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1102 let vc = unsafe {&mut *self.lo.vc.get()};
1103 unsafe {fmt::Debug::fmt (&*vc.as_ptr(), fm)}}}
1104
1105impl<T: fmt::Display> fmt::Display for IniMutexGuard<'_, T> {
1106 fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1107 let vc = unsafe {&mut *self.lo.vc.get()};
1108 unsafe {(*vc.as_ptr()) .fmt (fm)}}}
1109
1110impl<T> Drop for IniMutexGuard<'_, T> {
1111 fn drop (&mut self) {
1112 self.lo.au.store (1, Ordering::Release)}}
1113
1114impl<T> Drop for IniMutex<T> {
1115 fn drop (&mut self) {
1116 if self.au.load (Ordering::Acquire) != 0 {
1117 self.au.store (0, Ordering::Relaxed);
1118 let vc = unsafe {&mut *self.vc.get()};
1119 unsafe {vc.assume_init_drop()}}}}
1120
1121#[cfg(feature = "inlinable_string")]
1123pub static HOST: IniMutex<inlinable_string::InlinableString> = IniMutex::none();
1124
1125pub struct TSafe<T> (pub T);
1126unsafe impl<T> Send for TSafe<T> {}
1127unsafe impl<T> Sync for TSafe<T> {}
1128impl<T: Default> Default for TSafe<T> {fn default() -> Self {TSafe(T::default())}}
1129impl<T: Clone> Clone for TSafe<T> {fn clone (&self) -> Self {TSafe (self.0.clone())}}
1130impl<T: fmt::Debug> fmt::Debug for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1131impl<T: fmt::Display> fmt::Display for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1132
1133pub fn binprint (bin: &[u8], blank: u8) -> String {
1136 let mut bin: Vec<u8> = bin.into();
1137 for ch in bin.iter_mut() {if *ch < 0x20 || *ch >= 0x7F {*ch = blank}}
1138 unsafe {String::from_utf8_unchecked (bin)}}
1139
1140pub fn bits2bedstead (ch: u32) -> char {
1144 let ch =
1145 if 0b111111 < ch {0xEE00}
1146 else if 32 <= ch {0xEE40 + ch - 32}
1147 else if 0 < ch {0xEE00 + ch}
1148 else {0xEE00 + ch};
1149 unsafe {char::from_u32_unchecked (ch)}}
1150
1151pub fn bedstead2bits (ch: char) -> u32 {
1153 let ch = ch as u32;
1154 if 0xEE5F < ch {0} else if 0xEE40 <= ch {ch - 0xEE40 + 32}
1156 else if 0xEE00 <= ch {ch - 0xEE00}
1157 else {0}} #[test]
1160fn test_bedstead() {
1161 for bits in 0 ..= 0b111111 {
1162 let ch = bits2bedstead (bits);
1163 assert_eq! (bits, bedstead2bits (ch))}}
1164
1165pub fn round_to (decimals: u32, num: f32) -> f32 {
1166 let r = 10u32 .pow (decimals) as f32;
1167 (num * r) .round() / r}
1168
1169pub fn round8 (decimals: u32, num: f64) -> f64 {
1170 let r = 10u32 .pow (decimals) as f64;
1171 (num * r) .round() / r}
1172
1173#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1175pub struct OrdFloat (pub f64);
1176impl Eq for OrdFloat {}
1177impl Ord for OrdFloat {
1178 fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1179 self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1181use std::hash::{Hash, Hasher};
1182impl Hash for OrdFloat {
1183 fn hash<H: Hasher> (&self, state: &mut H) {
1184 self.0.to_bits().hash (state)}}
1185impl fmt::Display for OrdFloat {
1186 fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1187 self.0.fmt (fm)}}
1188
1189#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1191pub struct OrdF32 (pub f32);
1192impl Eq for OrdF32 {}
1193impl Ord for OrdF32 {
1194 fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1195 self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1197impl Hash for OrdF32 {
1198 fn hash<H: Hasher> (&self, state: &mut H) {
1199 self.0.to_bits().hash (state)}}
1200impl fmt::Display for OrdF32 {
1201 fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1202 self.0.fmt (fm)}}
1203
1204pub trait AtBool {
1228 fn l (&self) -> bool;
1230 fn s (&self, val: bool);}
1232impl AtBool for core::sync::atomic::AtomicBool {
1233 fn l (&self) -> bool {
1234 self.load (Ordering::Relaxed)}
1235 fn s (&self, val: bool) {
1236 self.store (val, Ordering::Relaxed)}}
1237
1238pub trait AtI8 {
1239 fn cas (&self, current: i8, new: i8) -> Result<i8, i8>;
1241 fn l (&self) -> i8;
1243 fn s (&self, val: i8);}
1245impl AtI8 for core::sync::atomic::AtomicI8 {
1246 fn cas (&self, current: i8, new: i8) -> Result<i8, i8> {
1247 self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1248 fn l (&self) -> i8 {
1249 self.load (Ordering::Relaxed)}
1250 fn s (&self, val: i8) {
1251 self.store (val, Ordering::Relaxed)}}
1252
1253pub trait AtI32 {
1254 fn cas (&self, current: i32, new: i32) -> Result<i32, i32>;
1256 fn l (&self) -> i32;
1258 fn s (&self, val: i32);}
1260impl AtI32 for core::sync::atomic::AtomicI32 {
1261 fn cas (&self, current: i32, new: i32) -> Result<i32, i32> {
1262 self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1263 fn l (&self) -> i32 {
1264 self.load (Ordering::Relaxed)}
1265 fn s (&self, val: i32) {
1266 self.store (val, Ordering::Relaxed)}}
1267
1268pub trait AtI64 {
1269 fn cas (&self, current: i64, new: i64) -> Result<i64, i64>;
1271 fn l (&self) -> i64;
1273 fn s (&self, val: i64);}
1275impl AtI64 for core::sync::atomic::AtomicI64 {
1276 fn cas (&self, current: i64, new: i64) -> Result<i64, i64> {
1277 self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1278 fn l (&self) -> i64 {
1279 self.load (Ordering::Relaxed)}
1280 fn s (&self, val: i64) {
1281 self.store (val, Ordering::Relaxed)}}
1282
1283pub trait AtU64 {
1284 fn cas (&self, current: u64, new: u64) -> Result<u64, u64>;
1286 fn l (&self) -> u64;
1288 fn s (&self, val: u64);}
1290impl AtU64 for core::sync::atomic::AtomicU64 {
1291 fn cas (&self, current: u64, new: u64) -> Result<u64, u64> {
1292 self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1293 fn l (&self) -> u64 {
1294 self.load (Ordering::Relaxed)}
1295 fn s (&self, val: u64) {
1296 self.store (val, Ordering::Relaxed)}}
1297
1298pub trait AtUsize {
1299 fn cas (&self, current: usize, new: usize) -> Result<usize, usize>;
1301 fn l (&self) -> usize;
1303 fn s (&self, val: usize);}
1305impl AtUsize for AtomicUsize {
1306 fn cas (&self, current: usize, new: usize) -> Result<usize, usize> {
1307 self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1308 fn l (&self) -> usize {
1309 self.load (Ordering::Relaxed)}
1310 fn s (&self, val: usize) {
1311 self.store (val, Ordering::Relaxed)}}
1312
1313#[cfg(feature = "memchr")]
1319pub struct LinesIt<'a> {
1320 pub lines: &'a [u8],
1321 pub head: usize,
1322 pub tail: usize}
1323
1324#[cfg(feature = "memchr")]
1325impl<'a> LinesIt<'a> {
1326 pub fn new (lines: &'a [u8]) -> LinesIt<'a> {
1327 let (mut head, mut tail) = (0, lines.len());
1328
1329 loop {
1330 if tail <= head {break}
1331 if lines[head] == b'\n' {head += 1; continue}
1332 break}
1333
1334 loop {
1335 if tail <= head {break}
1336 if lines[tail-1] == b'\n' {tail -= 1; continue}
1337 break}
1338
1339 LinesIt {lines, head, tail}}
1340
1341 pub fn heads_up (lines: &'a [u8], pos: usize) -> LinesIt<'a> {
1343 let len = lines.len();
1344 if len < pos {
1345 LinesIt {lines, head: len, tail: len}
1346 } else {
1347 LinesIt {lines,
1348 head: memrchr (b'\n', &lines[..pos]) .unwrap_or_default(),
1349 tail: len}}}}
1350
1351#[cfg(feature = "memchr")]
1352impl<'a> Iterator for LinesIt<'a> {
1353 type Item = &'a [u8];
1354 fn next (&mut self) -> Option<Self::Item> {
1355 loop {
1356 if self.tail <= self.head {return None}
1357 if self.lines[self.head] == b'\n' {self.head += 1; continue}
1358 break}
1359 if let Some (mut lf) = memchr (b'\n', &self.lines[self.head .. self.tail]) {
1360 lf += self.head;
1361 let line = &self.lines[self.head .. lf];
1362 self.head = lf + 1;
1363 Some (line)
1364 } else {
1365 let line = &self.lines[self.head .. self.tail];
1366 self.head = self.tail;
1367 Some (line)}}}
1368
1369#[cfg(feature = "memchr")]
1370impl<'a> DoubleEndedIterator for LinesIt<'a> {
1371 fn next_back (&mut self) -> Option<Self::Item> {
1372 loop {
1373 if self.tail <= self.head {return None}
1374 if self.lines[self.tail-1] == b'\n' {self.tail -= 1; continue}
1375 break}
1376 if let Some (mut lf) = memrchr (b'\n', &self.lines[self.head .. self.tail]) {
1377 lf += self.head;
1378 let line = &self.lines[lf + 1 .. self.tail];
1379 self.tail = lf;
1380 Some (line)
1381 } else {
1382 let line = &self.lines[self.head .. self.tail];
1383 self.tail = self.head;
1384 Some (line)}}}
1385
1386pub mod shuffled_iter {
1388 use crate::now_ms;
1398
1399 pub struct ShuffledIter {
1401 len: usize,
1402 mask: u64, counter: u64,
1404 emitted: u64,
1405 f: [u64; 3], x: [u64; 3]} impl ShuffledIter {
1411 pub fn with_seed (len: usize, seed: u64) -> ShuffledIter {
1412 let mask = if len <= 1 {0} else {
1413 let bits = 64 - ((len - 1) as u64) .leading_zeros();
1414 (1u64 << bits) - 1};
1415 let mut s = seed;
1418 let mut next = || -> u64 {
1419 s = s.wrapping_add (0x9e3779b97f4a7c15);
1420 let mut z = s;
1421 z = (z ^ (z >> 30)).wrapping_mul (0xbf58476d1ce4e5b9);
1422 z = (z ^ (z >> 27)).wrapping_mul (0x94d049bb133111eb);
1423 z ^ (z >> 31)};
1424 let f = [next() | 1, next() | 1, next() | 1]; let x = [next(), next(), next()];
1426 ShuffledIter {len, mask, counter: 0, emitted: 0, f, x}}
1427
1428 #[inline]
1437 fn mix (&self, val: u64) -> u64 {
1438 let mut v = val;
1439 for i in 0..3 {v = (v.wrapping_mul (self.f[i]) ^ self.x[i]) & self.mask}
1440 v}}
1441
1442 impl Iterator for ShuffledIter {
1443 type Item = usize;
1444
1445 #[inline]
1446 fn next (&mut self) -> Option<usize> {
1447 if self.len == 0 {return None}
1448 if self.emitted >= self.len as u64 {return None}
1449 loop {
1450 let candidate = self.mix (self.counter);
1451 self.counter += 1;
1452 if candidate < self.len as u64 {
1453 self.emitted += 1;
1454 return Some (candidate as usize)}}}}
1455
1456 pub fn shuffled<T> (seed: u64, v: &[T]) -> impl Iterator<Item = (usize, &T)> {
1458 let len = v.len();
1459 ShuffledIter::with_seed (len, seed) .map (move |ix| (ix, &v[ix]))}
1460
1461 #[test]
1462 fn every_index_visited () {
1463 for len in [0, 1, 2, 3, 7, 8, 15, 16, 100, 1000, 1024, 1025] {
1464 let mut seen = vec![false; len];
1465 let it = ShuffledIter::with_seed (len, now_ms());
1466 let mut count = 0usize;
1467 for ix in it {
1468 assert! (ix < len, "index {ix} out of bounds for len {len}");
1469 assert! (!seen[ix], "index {ix} visited twice (len {len})");
1470 seen[ix] = true;
1471 count += 1;}
1472 assert_eq! (count, len, "expected {len} items, got {count}");
1473 assert! (seen.iter().all (|&v| v || len == 0), "not every index visited (len {len})");}}
1474
1475 #[test]
1476 fn shuffled_vec_demo() {
1477 let data = vec!["alpha", "beta", "gamma", "delta", "epsilon"];
1478 let mut visited = vec![false; data.len()];
1479 for (ix, val) in shuffled (now_ms(), &data) {
1480 assert! (!visited[ix]);
1481 visited[ix] = true;
1482 assert_eq! (*val, data[ix]);}
1483 assert! (visited.iter().all (|&v| v));}
1484
1485 #[test]
1486 fn deterministic_with_same_seed() {
1487 let seed = 42u64;
1488 let a: Vec<usize> = ShuffledIter::with_seed (50, seed) .collect();
1489 let b: Vec<usize> = ShuffledIter::with_seed (50, seed) .collect();
1490 assert_eq! (a, b);}
1491
1492 #[test]
1493 fn likely_not_sequential() {
1494 let data: Vec<usize> = (0..100) .collect();
1495 let mut same_pos = 0;
1496 for (ix, &val) in shuffled (now_ms(), &data) {assert_eq! (ix, val)}
1497 let yielded_indices: Vec<usize> = shuffled (now_ms(), &data) .map (|p| p.0) .collect();
1498 for (i, &ix) in yielded_indices.iter().enumerate() {
1499 if i == ix {same_pos += 1}}
1500 assert! (same_pos < 9, "{}", same_pos)}}
1502
1503#[cfg(feature = "tpool")] pub mod tpool {
1505 use crate::any_to_str;
1521 use crate::aarc::{AArc, AArcErr, AReadGuard};
1522 use crate::re::Re;
1523 use fomat_macros::fomat;
1524 use inlinable_string::InlinableString;
1525 use std::collections::VecDeque;
1526 use std::hint::spin_loop;
1527 use std::panic::{catch_unwind, AssertUnwindSafe};
1528 use std::sync::atomic::{AtomicBool, AtomicI16, Ordering};
1529 use std::thread::{self, JoinHandle};
1530 use std::time::Duration;
1531 use parking_lot::{Condvar, Mutex};
1532
1533 #[derive (Default)]
1534 struct TPoolState {
1535 queue: VecDeque<Box<dyn FnOnce() -> Re<()> + Send + 'static>>,
1536 threads: Vec<(InlinableString, JoinHandle<Re<()>>)>,
1537 finalizers: Vec<(InlinableString, Box<dyn FnOnce() -> Re<()> + Send + 'static>)>}
1538
1539 #[derive (Default)]
1540 pub struct TPool {
1541 state: Mutex<TPoolState>,
1542 alarm: Condvar,
1543 running: AtomicI16,
1544 bye: AtomicBool}
1545
1546 unsafe impl Send for TPool {}
1547 unsafe impl Sync for TPool {}
1548
1549 pub trait Sponsor {
1550 fn sponsor (&self, tag: InlinableString) -> Re<bool>;}
1553
1554 impl Sponsor for AReadGuard<'_, TPool, TPool> {
1555 fn sponsor (&self, tag: InlinableString) -> Re<bool> {
1557 let mut state = self.state.lock();
1558 if state.threads.iter().any (|(tagʹ, _)| *tagʹ == tag) {return Re::Ok (false)}
1559 let tname = fomat! ("TP" (tag)); let pool = self.aarc();
1561 state.threads.push ((tag,
1562 thread::Builder::new().name (tname) .spawn (move || -> Re<()> {
1563 let po = pool.spin_rd()?; loop {
1565 let task = {
1566 let mut state = po.state.lock();
1567 match state.queue.pop_front() {
1568 Some (j) => {po.running.fetch_add (1, Ordering::Relaxed); j}
1569 None if po.bye.load (Ordering::Relaxed) => {break Re::Ok(())}
1571 None => {
1572 po.alarm.wait_for (&mut state, Duration::from_secs_f32 (0.31));
1573 if let Some (job) = state.queue.pop_front() {po.running.fetch_add (1, Ordering::Relaxed); job} else {continue}}}};
1574 let rc = catch_unwind (AssertUnwindSafe (task));
1575 let running = po.running.fetch_sub (1, Ordering::Relaxed);
1576 if running < 0 {log! (a 202, [=running])}
1577 match rc {
1578 Ok (Re::Ok(())) => {}
1579 Ok (Re::Err (err)) => {log! (a 1, (err))}
1580 Err (err) => {log! (a 1, [any_to_str (&*err)])}}}})?));
1581 Re::Ok (true)}}
1582
1583 impl TPool {
1584 pub fn post (&self, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<()> {
1586 let mut state = self.state.lock();
1588 if self.bye.load (Ordering::Relaxed) {return Re::Err ("TPool is stopping".into())}
1589 state.queue.push_back (task);
1590 self.alarm.notify_one();
1591 Re::Ok(())}
1592
1593 pub fn fin (&self, tag: InlinableString, finalizer: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> bool {
1596 let mut state = self.state.lock();
1597 if !tag.is_empty() && state.finalizers.iter().any (|(tagʹ, _)| *tagʹ == tag) {false}
1598 else {state.finalizers.push ((tag, finalizer)); true}}
1599
1600 pub fn jobsⁿ (&self) -> Re<usize> {
1602 let state = self.state.lock();
1603 Re::Ok (state.queue.len() + self.running.load (Ordering::Relaxed) .max (0) as usize)}
1604
1605 pub fn threadsⁿ (&self) -> usize {
1607 self.state.lock().threads.len()}
1608
1609 pub fn bye (&self) -> usize {
1611 self.bye.store (true, Ordering::Relaxed);
1612 self.alarm.notify_all()}
1613
1614 pub fn stop (&self) -> usize {
1617 let mut finalizers = Vec::new();
1618 let len = {
1619 let mut state = self.state.lock();
1620 let mut i = 0;
1621 while i < state.threads.len() {
1622 if state.threads[i].1.is_finished() {
1623 let (_tag, th) = state.threads.remove (i);
1624 match th.join() {
1625 Ok (Re::Ok(())) => {}
1626 Ok (Re::Err (err)) => {log! (a 1, (err))}
1627 Err (err) => {log! (a 1, [any_to_str (&*err)])}}
1628 } else {i += 1}}
1629 if state.threads.is_empty() {
1630 finalizers = std::mem::take (&mut state.finalizers)}
1631 state.threads.len()};
1632 for (tag, finalizer) in finalizers { let rc = catch_unwind (AssertUnwindSafe (finalizer));
1634 match rc {
1635 Ok (Re::Ok(())) => {}
1636 Ok (Re::Err (err)) => {log! (a 1, (tag) "] " (err))}
1637 Err (err) => {log! (a 1, (tag) "] " [any_to_str (&*err)])}}}
1638 len}}
1639
1640 pub static _TPOOL: AArc<TPool> = AArc::none();
1641
1642 pub fn tpool() -> Result<AReadGuard<'static, TPool, TPool>, AArcErr> {_TPOOL.spid()}
1644
1645 pub fn tpost (spin: i32, threads: u8, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<bool> {
1655 let pool = _TPOOL.spidʳ (spin)?;
1656 if pool.threadsⁿ() < threads as usize {
1657 task()?;
1658 Re::Ok (false)
1659 } else {
1660 pool.post (task)?;
1661 Re::Ok (true)}}
1662
1663 #[cfg(all(test, feature = "nightly", feature = "re"))]
1664 mod tests {
1665 use super::*;
1666 use crate::aarc::AArc;
1667 use crate::re::Re;
1668 use std::thread;
1669 use std::time::Duration;
1670
1671 #[test]
1672 fn jobs_n_gets_to_zero() {
1673 let pool_arc = AArc::<TPool>::new (TPool::default());
1674 let pool = pool_arc.spin_rd().unwrap();
1675
1676 pool.sponsor ("test_worker".into()) .unwrap();
1678
1679 assert_eq! (pool.jobsⁿ().unwrap(), 0);
1681
1682 pool.post (Box::new (|| {
1684 thread::sleep (Duration::from_millis (50));
1685 Re::Ok(())})) .unwrap();
1686
1687 assert_eq! (1, pool.jobsⁿ().unwrap());
1689
1690 let mut cleared = false;
1692 for _ in 0..90 {
1693 if pool.jobsⁿ().unwrap() == 0 {
1694 cleared = true;
1695 break;}
1696 thread::sleep (Duration::from_millis (10))}
1697
1698 assert! (cleared, "jobsⁿ did not reach 0 in time");
1700 assert_eq! (pool.jobsⁿ().unwrap(), 0);
1701
1702 pool.bye();
1703 while pool.stop() > 0 {
1704 thread::sleep (Duration::from_millis (10));}}
1705
1706 #[test]
1707 fn aarc_oneshot() {
1708 { let tpool = tpool().unwrap();
1709 if tpool.threadsⁿ() == 0 {
1710 tpool.sponsor ("oneshot".into()) .unwrap();
1711 assert_eq! (tpool.threadsⁿ(), 1)} }
1712
1713 let rx = AArc::<&'static str>::empty();
1714 let tx = rx.clone();
1715 tpost (9, 1, Box::new (move || {
1716 thread::sleep (Duration::from_millis (20));
1717 tx.spinˢ (1, "done")?;
1718 Re::Ok(())})) .unwrap();
1719 let val = rx.spinʷ (-1) .unwrap().take();
1720 assert_eq! (*val, "done")}
1721
1722 #[test]
1723 fn vs_system() {
1724 use std::time::Instant;
1725
1726 { let tpool = tpool().unwrap();
1727 if tpool.threadsⁿ() == 0 {
1728 tpool.sponsor ("vs_system".into()) .unwrap();
1729 assert_eq! (tpool.threadsⁿ(), 1)} }
1730
1731 let tpost_done = AArc::<u64>::empty();
1732 let tpost_done_c = tpost_done.clone();
1733 fn cps_tpost (count: u32, start: Instant, done: AArc<u64>) {
1734 if count == 123 {
1735 done.spin_set (start.elapsed().as_micros() as u64) .unwrap();
1736 return}
1737 assert! (tpost (9, 1, Box::new (move || {
1738 cps_tpost (count + 1, start, done);
1739 Re::Ok(())})) .unwrap())}
1740 cps_tpost (0, Instant::now(), tpost_done_c);
1741
1742 let thread_done = AArc::<u64>::empty();
1743 let thread_done_c = thread_done.clone();
1744 fn cps_thread (count: u32, start: Instant, done: AArc<u64>) {
1745 if count == 123 {
1746 done.spin_set (start.elapsed().as_micros() as u64) .unwrap();
1747 return}
1748 thread::spawn (move || cps_thread (count + 1, start, done));}
1749 cps_thread (0, Instant::now(), thread_done_c);
1750
1751 loop {
1752 if let (Ok (tpost_time), Ok (thread_time)) = (tpost_done.spin_rd(), thread_done.spin_rd()) {
1753 if true {print! ("tpost: {}µs, thread: {}µs ", *tpost_time, *thread_time)}
1755 assert! (*tpost_time < *thread_time);
1756 break}
1757 thread::sleep (Duration::from_millis (20))}}}}
1758
1759