#![allow(non_camel_case_types)] #![allow(non_snake_case)] //! Lorem ipsum generator for the command-line programs. //! //! Port of `programs/lorem.c`. Output is byte-identical to the C generator //! for a given `(size, seed, first, fill)` call, including the trailing //! sentence/paragraph punctuation and the buffer-end fill. //! //! The C unit keeps its cursor and random root in file-scope statics and is //! documented as sequential-only. The port carries the same values in a //! per-call state struct instead: the word-distribution table depends only //! on constants, so building it per call yields the identical table the C //! code caches in a global. use std::ffi::c_void; use std::os::raw::{c_int, c_uint}; const WORDS: [&str; 255] = [ "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "dis", "lectus", "vestibulum", "mattis", "ullamcorper", "velit", "commodo", "a", "lacus", "arcu", "magnis", "parturient", "montes", "nascetur", "ridiculus", "mus", "mauris", "nulla", "malesuada", "pellentesque", "eget", "gravida", "in", "dictum", "non", "erat", "nam", "voluptat", "maecenas", "blandit", "aliquam", "etiam", "enim", "lobortis", "scelerisque", "fermentum", "dui", "faucibus", "ornare", "at", "elementum", "eu", "facilisis", "odio", "morbi", "quis", "eros", "donec", "ac", "orci", "purus", "turpis", "cursus", "leo", "vel", "porta", "consequat", "interdum", "varius", "vulputate", "aliquet", "pharetra", "nunc", "auctor", "urna", "id", "metus", "viverra", "nibh", "cras", "mi", "unde", "omnis", "iste", "natus", "error", "perspiciatis", "voluptatem", "accusantium", "doloremque", "laudantium", "totam", "rem", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "nemo", "ipsam", "quia", "voluptas", "aspernatur", "aut", "odit", "fugit", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "sequi", "nesciunt", "neque", "porro", "quisquam", "est", "dolorem", "adipisci", "numquam", "eius", "modi", "tempora", "incidunt", "magnam", "quaerat", "ad", "minima", "veniam", "nostrum", "ullam", "corporis", "suscipit", "laboriosam", "nisi", "aliquid", "ex", "ea", "commodi", "consequatur", "autem", "eum", "iure", "voluptate", "esse", "quam", "nihil", "molestiae", "illum", "fugiat", "quo", "pariatur", "vero", "accusamus", "iusto", "dignissimos", "ducimus", "blanditiis", "praesentium", "voluptatum", "deleniti", "atque", "corrupti", "quos", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "provident", "similique", "culpa", "officia", "deserunt", "mollitia", "animi", "laborum", "dolorum", "fuga", "harum", "quidem", "rerum", "facilis", "expedita", "distinctio", "libero", "tempore", "cum", "soluta", "nobis", "eligendi", "optio", "cumque", "impedit", "minus", "quod", "maxime", "placeat", "facere", "possimus", "assumenda", "repellendus", "temporibus", "quibusdam", "officiis", "debitis", "saepe", "eveniet", "voluptates", "repudiandae", "recusandae", "itaque", "earum", "hic", "tenetur", "sapiente", "delectus", "reiciendis", "cillum", "maiores", "alias", "perferendis", "doloribus", "asperiores", "repellat", "minim", "nostrud", "exercitation", "ullamco", "laboris", "aliquip", "duis", "aute", "irure", ]; /* simple 1-dimension distribution, based on word's length, favors small words */ const WEIGHTS: [u32; 6] = [0, 8, 6, 4, 3, 2]; const DISTRIB_SIZE_MAX: usize = 650; struct LoremState { ptr: *mut u8, nb_chars: usize, max_chars: usize, rand_root: u32, distrib: [u16; DISTRIB_SIZE_MAX], distrib_count: u32, } impl LoremState { fn new(buffer: *mut u8, size: usize, seed: u32) -> Self { let mut state = LoremState { ptr: buffer, nb_chars: 0, max_chars: size, rand_root: seed, distrib: [0; DISTRIB_SIZE_MAX], distrib_count: 0, }; let mut d = 0usize; for (w, word) in WORDS.iter().enumerate() { let len = word.len().min(WEIGHTS.len() - 1); for _ in 0..WEIGHTS[len] { state.distrib[d] = w as u16; d += 1; } } state.distrib_count = d as u32; debug_assert!(d <= DISTRIB_SIZE_MAX); state } fn rand(&mut self, range: u32) -> u32 { const PRIME1: u32 = 2654435761; const PRIME2: u32 = 2246822519; let mut rand32 = self.rand_root; rand32 = rand32.wrapping_mul(PRIME1); rand32 ^= PRIME2; rand32 = rand32.rotate_left(13); self.rand_root = rand32; (((rand32 as u64) * (range as u64)) >> 32) as u32 } /* The C `about()` adds two stateful draws in one expression; this build's * compiler evaluates them left to right, which the reference vectors pin. */ fn about(&mut self, target: u32) -> u32 { let a = self.rand(target); let b = self.rand(target); a + b + 1 } unsafe fn write_last_characters(&mut self) { let last_chars = self.max_chars - self.nb_chars; if last_chars == 0 { return; } unsafe { self.ptr.add(self.nb_chars).write(b'.') }; self.nb_chars += 1; if last_chars > 2 { unsafe { std::ptr::write_bytes(self.ptr.add(self.nb_chars), b' ', last_chars - 2); } } if last_chars > 1 { unsafe { self.ptr.add(self.max_chars - 1).write(b'\n') }; } self.nb_chars = self.max_chars; } unsafe fn generate_word(&mut self, word: &str, separator: &str, up_case: bool) { let len = word.len() + separator.len(); if self.nb_chars + len > self.max_chars { unsafe { self.write_last_characters() }; return; } unsafe { std::ptr::copy_nonoverlapping(word.as_ptr(), self.ptr.add(self.nb_chars), word.len()); } if up_case { let first = unsafe { self.ptr.add(self.nb_chars) }; unsafe { first.write(first.read().wrapping_sub(b'a' - b'A')) }; } self.nb_chars += word.len(); unsafe { std::ptr::copy_nonoverlapping( separator.as_ptr(), self.ptr.add(self.nb_chars), separator.len(), ); } self.nb_chars += separator.len(); } unsafe fn generate_sentence(&mut self, nb_words: u32) { let comma_pos = self.about(9); let comma2 = comma_pos + self.about(7); let qmark = self.rand(11) == 7; let end_sep = if qmark { "? " } else { ". " }; for i in 0..nb_words { let word_id = self.distrib[self.rand(self.distrib_count) as usize]; let word = WORDS[word_id as usize]; let mut sep = " "; if i == comma_pos { sep = ", "; } if i == comma2 { sep = ", "; } if i == nb_words - 1 { sep = end_sep; } unsafe { self.generate_word(word, sep, i == 0) }; } } unsafe fn generate_paragraph(&mut self, nb_sentences: u32) { for _ in 0..nb_sentences { let words_per_sentence = self.about(11); unsafe { self.generate_sentence(words_per_sentence) }; } if self.nb_chars < self.max_chars { unsafe { self.ptr.add(self.nb_chars).write(b'\n') }; self.nb_chars += 1; } if self.nb_chars < self.max_chars { unsafe { self.ptr.add(self.nb_chars).write(b'\n') }; self.nb_chars += 1; } } /* It's "common" for lorem ipsum generators to start with the same first * pre-defined sentence */ unsafe fn generate_first_sentence(&mut self) { for (i, word) in WORDS[..18].iter().enumerate() { let separator = if i == 4 || i == 7 { ", " } else { " " }; unsafe { self.generate_word(word, separator, i == 0) }; } unsafe { self.generate_word(WORDS[18], ". ", false) }; } } /// Rust implementation of the public `LOREM_genBlock()` ABI. /// /// # Safety /// `buffer` must be valid for `size` writable bytes. #[no_mangle] pub unsafe extern "C" fn LOREM_genBlock( buffer: *mut c_void, size: usize, seed: c_uint, first: c_int, fill: c_int, ) -> usize { debug_assert!(size < i32::MAX as usize); let mut state = LoremState::new(buffer.cast::(), size, seed); if first != 0 { unsafe { state.generate_first_sentence() }; } while state.nb_chars < state.max_chars { let sentences_per_paragraph = state.about(7); unsafe { state.generate_paragraph(sentences_per_paragraph) }; if fill == 0 { break; /* only generate one paragraph in not-fill mode */ } } state.nb_chars } /// Rust implementation of the public `LOREM_genBuffer()` ABI. /// /// # Safety /// `buffer` must be valid for `size` writable bytes. #[no_mangle] pub unsafe extern "C" fn LOREM_genBuffer(buffer: *mut c_void, size: usize, seed: c_uint) { unsafe { LOREM_genBlock(buffer, size, seed, 1, 1) }; } #[cfg(test)] mod tests { use super::*; /* Reference vectors produced by the original C implementation. */ #[test] fn matches_c_reference_for_the_first_sentence() { let mut buf = [0u8; 96]; let n = unsafe { LOREM_genBlock(buf.as_mut_ptr().cast(), buf.len(), 0, 1, 1) }; assert_eq!(n, 96); assert_eq!( &buf[..], b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do \ eiusmod tempor incididunt ut . \n" .as_slice() ); } #[test] fn matches_c_reference_for_a_seeded_paragraph() { let mut buf = [0u8; 96]; let n = unsafe { LOREM_genBlock(buf.as_mut_ptr().cast(), buf.len(), 4242, 0, 0) }; assert_eq!(n, 96); assert_eq!( &buf[..], b"Donec fuga veniam a temporibus tempor odio arcu delectus, velit. \ Sit magnam elementum ipsum. . \n" .as_slice() ); } #[test] fn distribution_table_matches_the_c_totals() { let mut probe = [0u8; 1]; /* Building the state exercises the distribution constructor. */ let state = LoremState::new(probe.as_mut_ptr(), probe.len(), 0); assert_eq!(state.distrib_count, 641); assert!((state.distrib_count as usize) <= DISTRIB_SIZE_MAX); /* Every entry must reference a real word. */ assert!(state.distrib[..state.distrib_count as usize] .iter() .all(|&w| (w as usize) < WORDS.len())); } #[test] fn zero_sized_generation_accepts_a_null_buffer() { for first in [0, 1] { for fill in [0, 1] { let generated = unsafe { LOREM_genBlock(std::ptr::null_mut(), 0, 123, first, fill) }; assert_eq!(generated, 0); } } unsafe { LOREM_genBuffer(std::ptr::null_mut(), 0, 123) }; } #[test] fn tiny_first_sentence_buffers_use_the_c_tail_fill() { for size in 0..=5 { let mut buffer = [0xa5u8; 5]; let generated = unsafe { LOREM_genBlock(buffer.as_mut_ptr().cast(), size, 0, 1, 1) }; let expected = match size { 0 => b"".as_slice(), 1 => b".".as_slice(), 2 => b".\n".as_slice(), 3 => b". \n".as_slice(), 4 => b". \n".as_slice(), 5 => b". \n".as_slice(), _ => unreachable!(), }; assert_eq!(generated, size); assert_eq!(&buffer[..size], expected); assert!(buffer[size..].iter().all(|&byte| byte == 0xa5)); } } #[test] fn nonzero_fill_values_continue_until_the_buffer_is_full() { let mut one = vec![0xa5u8; 4096]; let mut two = vec![0xa5u8; 4096]; let one_size = unsafe { LOREM_genBlock(one.as_mut_ptr().cast(), one.len(), 7, 0, 1) }; let two_size = unsafe { LOREM_genBlock(two.as_mut_ptr().cast(), two.len(), 7, 0, 2) }; assert_eq!(one_size, one.len()); assert_eq!(two_size, two.len()); assert_eq!(one, two); } #[test] fn gen_buffer_is_deterministic_per_seed() { let mut a = vec![0u8; 4096]; let mut b = vec![0u8; 4096]; unsafe { LOREM_genBuffer(a.as_mut_ptr().cast(), a.len(), 99) }; unsafe { LOREM_genBuffer(b.as_mut_ptr().cast(), b.len(), 99) }; assert_eq!(a, b); unsafe { LOREM_genBuffer(b.as_mut_ptr().cast(), b.len(), 100) }; assert_ne!(a, b); } }