//#[inline] fn reverse(slice: &mut [T]) { match slice { [ref mut a, ref mut rest @ .., ref mut b] => { std::mem::swap(a, b); reverse(rest) }, [] | [_] => (), } } /// This actually works! And is so easily parallelisable with something like rayon, or asyncing by spawning/creating the tail-call into a new task, then either waiting them concurrently or in parallen (spawn or created future from just calling the function without awaiting it) #[allow(dead_code)] fn binsearch<'a, V: ?Sized, T: PartialEq + 'a>(slice: &'a [T], find: &V) -> Option<&'a T> { match slice { [ref a, pivot @ .., ref b] => { match (a==find, b==find) { (true, _) => Some(a), (_, true) => Some(b), _ => binsearch(pivot, find), } }, [ref a] if a == find => Some(a), _ => None, } } trait Input: AsRef<[u8]> + std::fmt::Debug{} impl Input for T where T: AsRef<[u8]> + std::fmt::Debug{} fn collect_input() -> Box + 'static> { use std::{ ffi::{OsStr, OsString}, os::unix::ffi::*, }; //#[derive(Debug)] enum MaybeUTF8 { UTF8(String), Raw(OsString), Static(&'static [u8]), } impl std::fmt::Debug for MaybeUTF8 { // Custom Debug impl to ensure output is identical when `output-quoted` is enabled. #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let dbg: &dyn std::fmt::Debug = match self { Self::UTF8(string) => string, Self::Raw(raw) => raw, Self::Static(&[]) => return f.write_str("\"\""), Self::Static(bytes) => return std::fmt::Debug::fmt(OsStr::from_bytes(bytes), f), }; std::fmt::Debug::fmt(dbg, f) } } impl AsRef<[u8]> for MaybeUTF8 { #[inline] fn as_ref(&self) -> &[u8] { match self { Self::UTF8(string) => string.as_bytes(), Self::Raw(raw) => raw.as_bytes(), Self::Static(bytes) => bytes, } } } impl From for MaybeUTF8 { #[inline(always)] fn from(from: String) -> Self { Self::UTF8(from) } } impl From for MaybeUTF8 { #[inline(always)] fn from(from: OsString) -> Self { Self::Raw(from) } } impl MaybeUTF8 { #[inline(always)] pub const fn from_static_bytes(bytes: &'static [u8]) -> Self { Self::Static(bytes) } #[inline(always)] pub fn from_raw_bytes(bytes: &[u8]) -> Self { Self::Raw(OsStr::from_bytes(bytes).to_os_string()) } #[inline(always)] pub fn from_raw_vec(vec: Vec) -> Self { Self::Raw(OsString::from_vec(vec)) } } if std::env::args_os().len() <= 1 { use std::io::{ self, BufRead, }; // No args, collect stdin lines if !cfg!(feature="byte-strings") { // Collect utf8 string lines Box::new(io::stdin() .lock() .lines() .filter_map(Result::ok) .map(MaybeUTF8::from)) } else { // Collect arbitrary byte strings struct OsLineReader<'a>(io::StdinLock<'a>, Vec); impl<'a> Iterator for OsLineReader<'a> { type Item = MaybeUTF8; fn next(&mut self) -> Option { Some(match handle_fmt_err_or(self.0.read_until(b'\n', &mut self.1), || 0) { 0 => return None, 1 if self.1[0] == b'\n' => MaybeUTF8::from_static_bytes(&[]), read_sz => { let line = if self.1[read_sz-1] == b'\n' { MaybeUTF8::from_raw_bytes(&self.1[..(read_sz-1)]) } else { MaybeUTF8::from_raw_vec(self.1.clone()) }; debug_assert_ne!(line.as_ref().iter().last().copied(), Some(b'\n'), "Deliminator still in output"); self.1.clear(); line }, }) } } Box::new(OsLineReader(io::stdin().lock(), // Acquire the lock until the iterator is consumed (like all other paths in this function) Vec::with_capacity(4096))) // Buffer of size 4k } } else { // Has arguments, return them if cfg!(feature="byte-strings") { Box::new(std::env::args_os().skip(1).map(MaybeUTF8::from)) } else if cfg!(feature="ignore-invalid-args") { Box::new(std::env::args_os().skip(1).filter_map(|os| os.into_string().ok()).map(MaybeUTF8::from)) } else { Box::new(std::env::args_os().skip(1).map(|os| os.to_string_lossy().into_owned().into())) } } } #[allow(dead_code)] #[cfg_attr(feature="ignore-output-errors", inline)] fn handle_fmt_err_or(res: std::io::Result, or: F) -> T where F: FnOnce() -> T { #[cfg(not(feature="ignore-output-errors"))] { match res { Ok(v) => return v, Err(e) => eprintln!("[!] failed to write line: {e}"), } return or(); } #[cfg(feature="ignore-output-errors")] res.unwrap_or_else(|_| or()) } #[allow(dead_code)] #[cfg_attr(feature="ignore-output-errors", inline(always))] fn handle_fmt_err(res: std::io::Result) { #[cfg(not(feature="ignore-output-errors"))] if let Err(e) = res { eprintln!("[!] failed to write line: {e}"); } let _ = res; } fn main() { let mut args: Vec<_> = collect_input().collect(); reverse(&mut args[..]); //eprintln!("{:?}", binsearch(&args[..], "1")); // It works! #[cfg(feature="output-lines")] { #[allow(unused_imports)] use std::io::{ Write, BufWriter, }; #[cfg(feature="buffer-output")] let mut out = BufWriter::new(std::io::stdout().lock()); #[cfg(not(feature="buffer-output"))] let mut out = std::io::stdout().lock(); for x in args.iter() { handle_fmt_err({ if cfg!(feature="output-quoted") { //XXX: This doesn't flush, right? It shouldn't, but maybe we should test it? writeln!(&mut out, "{:?}", x) } else { //writeln!(&mut out, "{}", x) out.write(x.as_ref()) .and_then(|_| out.write(b"\n")) .map(|_| {}) } }); } //#[cfg(feature="buffer-output")] handle_fmt_err(out.flush()); //XXX: Do we need to flush when not buffering output? Does it matter since buffering output will be enabled by default and should almost always be enabled? } #[cfg(not(feature="output-lines"))] println!("{:?}", args); }