const_str/__ctfe/
squish.rs1#![allow(unsafe_code)]
2
3use crate::__ctfe::StrBuf;
4
5pub struct Squish<T>(pub T);
6
7impl Squish<&'_ str> {
8 pub const fn output_len(&self) -> usize {
9 let mut len = 0;
10
11 macro_rules! push {
12 ($x: expr) => {
13 len += 1;
14 };
15 }
16
17 let bytes = self.0.as_bytes();
18 let mut i = 0;
19 while i < bytes.len() {
20 let x = bytes[i];
21
22 if x.is_ascii_whitespace() {
23 let mut j = i + 1;
24 while j < bytes.len() {
25 if bytes[j].is_ascii_whitespace() {
26 j += 1;
27 } else {
28 break;
29 }
30 }
31 if !(i == 0 || j == bytes.len()) {
32 push!(b' ');
33 }
34 i = j;
35 continue;
36 }
37
38 push!(x);
39 i += 1;
40 }
41
42 len
43 }
44
45 pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
46 let mut buf = [0; N];
47 let mut pos = 0;
48
49 macro_rules! push {
50 ($x: expr) => {
51 buf[pos] = $x;
52 pos += 1;
53 };
54 }
55
56 let bytes = self.0.as_bytes();
57 let mut i = 0;
58 while i < bytes.len() {
59 let x = bytes[i];
60
61 if x.is_ascii_whitespace() {
62 let mut j = i + 1;
63 while j < bytes.len() {
64 if bytes[j].is_ascii_whitespace() {
65 j += 1;
66 } else {
67 break;
68 }
69 }
70 if !(i == 0 || j == bytes.len()) {
71 push!(b' ');
72 }
73 i = j;
74 continue;
75 }
76
77 push!(x);
78 i += 1;
79 }
80
81 assert!(pos == N);
82 unsafe { StrBuf::new_unchecked(buf) }
83 }
84}
85
86#[macro_export]
109macro_rules! squish {
110 ($s:expr) => {{
111 const INPUT: &str = $s;
112 const N: usize = $crate::__ctfe::Squish(INPUT).output_len();
113 const OUTPUT: $crate::__ctfe::StrBuf<N> = $crate::__ctfe::Squish(INPUT).const_eval();
114 OUTPUT.as_str()
115 }};
116}
117
118#[cfg(test)]
119mod tessts {
120 fn join<'a>(iter: impl IntoIterator<Item = &'a str>, sep: &str) -> String {
121 let mut ans = String::new();
122 let mut iter = iter.into_iter();
123 match iter.next() {
124 None => return ans,
125 Some(first) => ans.push_str(first),
126 }
127 for part in iter {
128 ans.push_str(sep);
129 ans.push_str(part);
130 }
131 ans
132 }
133
134 fn std_squish(input: &str) -> String {
135 join(input.split_ascii_whitespace(), " ")
136 }
137
138 #[test]
139 fn test_squish() {
140 macro_rules! testcase {
141 ($s:expr) => {{
142 const OUTPUT: &str = squish!($s);
143 let expected = std_squish($s);
144 assert_eq!(OUTPUT, expected);
145 }};
146 }
147
148 testcase!("");
149 testcase!(" ");
150 testcase!(" t");
151 testcase!("t ");
152 testcase!(" t ");
153 testcase!(" t t");
154
155 testcase!(" SQUISH \t THAT \t CAT ");
156
157 testcase!(
158 "
159 All you need to know is to \t
160 SQUISH THAT CAT! \
161 "
162 );
163
164 testcase!(concat!("We\n", "always\n", "SQUISH\n", "THAT\n", "CAT."));
165
166 testcase!(
167 "SELECT
168 name,
169 created_at,
170 updated_at
171 FROM users
172 WHERE id = ?"
173 );
174 }
175
176 #[test]
177 fn test_squish_runtime() {
178 use super::*;
179
180 let squish1 = Squish(" hello world ");
182 assert_eq!(squish1.output_len(), 11);
183 let buf1: StrBuf<11> = squish1.const_eval();
184 assert_eq!(buf1.as_str(), "hello world");
185
186 let squish2 = Squish("\t\n test \r\n");
187 assert_eq!(squish2.output_len(), 4);
188 let buf2: StrBuf<4> = squish2.const_eval();
189 assert_eq!(buf2.as_str(), "test");
190
191 let squish_empty = Squish(" ");
192 let len_empty = squish_empty.output_len();
193 assert_eq!(len_empty, 0);
194
195 let squish_single = Squish("word");
196 assert_eq!(squish_single.output_len(), 4);
197 let buf_single: StrBuf<4> = squish_single.const_eval();
198 assert_eq!(buf_single.as_str(), "word");
199
200 let squish_multi = Squish(" a b c ");
201 assert_eq!(squish_multi.output_len(), 5);
202 let buf_multi: StrBuf<5> = squish_multi.const_eval();
203 assert_eq!(buf_multi.as_str(), "a b c");
204 }
205}