1pub use async_trait;
2pub use futures;
3pub use futures::StreamExt;
4use std::pin::Pin;
5use std::sync::Arc;
6pub use tokio;
7
8pub type Stream<T> = Pin<Box<dyn futures::Stream<Item = T> + Send>>;
9
10pub fn runtime_error_code(message: &str) -> &'static str {
11 if message.contains("out of range for a List") {
12 "OR0010"
13 } else if message.contains("already borrowed") {
14 "OR0011"
15 } else if message.contains("not a char boundary") {
16 "OR0012"
17 } else if message.contains("attempt to divide by zero") {
18 "OR0013"
19 } else if message.contains("called `Option::unwrap() on a `None` value")
20 || message.contains("called `Option::unwrap()` on a `None` value")
21 {
22 "OR0014"
23 } else if message.contains("overflow") {
24 "OR0008"
25 } else {
26 "OR0099"
27 }
28}
29
30pub fn friendly_panic_message(message: &str) -> String {
31 if let Some(rest) = message.strip_prefix("byte index ") {
32 if let Some(index) = rest.split_whitespace().next() {
33 return format!("byte {index} is in the middle of a character");
34 }
35 }
36 if message.contains("attempt to divide by zero") {
37 return "integer division by zero".into();
38 }
39 if message.contains("called `Option::unwrap()` on a `None` value") {
40 return "tried to unwrap null; check the value before using it".into();
41 }
42 message.into()
43}
44
45pub fn install_panic_hook() {
46 std::panic::set_hook(Box::new(|panic| {
47 let message = panic
48 .payload()
49 .downcast_ref::<&str>()
50 .copied()
51 .or_else(|| panic.payload().downcast_ref::<String>().map(String::as_str))
52 .unwrap_or("runtime panic");
53 let code = runtime_error_code(message);
54 let message = friendly_panic_message(message);
55 if std::env::var_os("ORUST_BACKTRACE").is_some() {
56 eprintln!("ORust runtime error [{code}]: {message}");
57 if let Some(location) = panic.location() {
58 eprintln!(
59 "at {}:{}:{}",
60 location.file(),
61 location.line(),
62 location.column()
63 );
64 }
65 } else {
66 eprintln!("ORust runtime error [{code}]: {message}");
67 }
68 }));
69}
70
71pub fn display_option<T: std::fmt::Display>(value: &Option<T>) -> String {
72 match value {
73 Some(value) => value.to_string(),
74 None => "null".into(),
75 }
76}
77
78pub struct Channel<T> {
79 sender: tokio::sync::mpsc::Sender<T>,
80 receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<T>>>,
81}
82
83pub fn channel<T: Send + 'static>(capacity: usize) -> Channel<T> {
84 let (sender, receiver) = tokio::sync::mpsc::channel(capacity);
85 Channel {
86 sender,
87 receiver: Arc::new(tokio::sync::Mutex::new(receiver)),
88 }
89}
90
91impl<T: Send + 'static> Clone for Channel<T> {
92 fn clone(&self) -> Self {
93 Self {
94 sender: self.sender.clone(),
95 receiver: Arc::clone(&self.receiver),
96 }
97 }
98}
99
100impl<T: Send + 'static> Channel<T> {
101 pub async fn send(&self, value: T) -> Result<(), Error> {
102 self.sender
103 .send(value)
104 .await
105 .map_err(|_| Error::new("channel receiver was dropped"))
106 }
107
108 pub async fn recv(&self) -> Option<T> {
109 self.receiver.lock().await.recv().await
110 }
111}
112pub use tokio::time::{sleep, Duration};
113
114#[derive(Debug)]
115pub struct Error {
116 pub message: String,
117}
118impl Error {
119 pub fn new(message: impl Into<String>) -> Self {
120 Self {
121 message: message.into(),
122 }
123 }
124}
125
126impl<E> From<E> for Error
127where
128 E: std::error::Error + Send + Sync + 'static,
129{
130 fn from(error: E) -> Self {
131 Self::new(error.to_string())
132 }
133}
134
135pub struct Task<T>(tokio::task::JoinHandle<T>);
136impl<T> Task<T> {
137 pub async fn await_task(self) -> Result<T, Error> {
138 self.0.await.map_err(|error| Error::new(error.to_string()))
139 }
140}
141pub fn spawn<T: Send + 'static>(
142 future: impl std::future::Future<Output = T> + Send + 'static,
143) -> Task<T> {
144 Task(tokio::spawn(future))
145}
146
147pub fn millis(value: u64) -> Duration {
148 Duration::from_millis(value)
149}
150pub fn seconds(value: u64) -> Duration {
151 Duration::from_secs(value)
152}
153
154#[allow(non_upper_case_globals)]
155pub mod std_math {
156 pub const pi: f64 = std::f64::consts::PI;
157 pub const e: f64 = std::f64::consts::E;
158
159 pub fn sqrt(value: f64) -> f64 {
160 value.sqrt()
161 }
162 pub fn pow(value: f64, exponent: f64) -> f64 {
163 value.powf(exponent)
164 }
165 pub fn sin(value: f64) -> f64 {
166 value.sin()
167 }
168 pub fn cos(value: f64) -> f64 {
169 value.cos()
170 }
171 pub fn tan(value: f64) -> f64 {
172 value.tan()
173 }
174 pub fn log(value: f64) -> f64 {
175 value.ln()
176 }
177 pub fn exp(value: f64) -> f64 {
178 value.exp()
179 }
180 pub fn min(left: f64, right: f64) -> f64 {
181 left.min(right)
182 }
183 pub fn max(left: f64, right: f64) -> f64 {
184 left.max(right)
185 }
186 pub fn abs(value: f64) -> f64 {
187 value.abs()
188 }
189 pub fn floor(value: f64) -> f64 {
190 value.floor()
191 }
192 pub fn ceil(value: f64) -> f64 {
193 value.ceil()
194 }
195}
196
197pub mod std_env {
198 pub fn args() -> Vec<String> {
199 std::env::args().collect()
200 }
201
202 pub fn get(name: String) -> Option<String> {
203 std::env::var(name).ok()
204 }
205
206 pub fn set(name: String, value: String) {
207 std::env::set_var(name, value);
208 }
209
210 pub fn cwd() -> String {
211 std::env::current_dir()
212 .ok()
213 .and_then(|path| path.into_os_string().into_string().ok())
214 .unwrap_or_default()
215 }
216}
217
218pub mod std_io {
219 use super::Error;
220
221 pub struct File;
222
223 impl File {
224 pub fn exists(path: String) -> bool {
225 std::path::Path::new(&path).exists()
226 }
227
228 pub fn read_string(path: String) -> Result<String, Error> {
229 std::fs::read_to_string(path).map_err(|error| Error::new(error.to_string()))
230 }
231
232 pub fn write_string(path: String, text: String) -> Result<(), Error> {
233 std::fs::write(path, text).map_err(|error| Error::new(error.to_string()))
234 }
235
236 pub fn append_string(path: String, text: String) -> Result<(), Error> {
237 use std::io::Write;
238 let mut file = std::fs::OpenOptions::new()
239 .create(true)
240 .append(true)
241 .open(path)
242 .map_err(|error| Error::new(error.to_string()))?;
243 file.write_all(text.as_bytes())
244 .map_err(|error| Error::new(error.to_string()))
245 }
246 }
247
248 pub struct Path;
249
250 impl Path {
251 pub fn join(left: String, right: String) -> String {
252 std::path::Path::new(&left)
253 .join(right)
254 .to_string_lossy()
255 .into_owned()
256 }
257
258 pub fn parent(path: String) -> Option<String> {
259 std::path::Path::new(&path)
260 .parent()
261 .map(|value| value.to_string_lossy().into_owned())
262 }
263
264 pub fn file_name(path: String) -> Option<String> {
265 std::path::Path::new(&path)
266 .file_name()
267 .map(|value| value.to_string_lossy().into_owned())
268 }
269 }
270}
271
272pub fn checked_index(index: i64, length: usize) -> usize {
273 if index < 0 || index as usize >= length {
274 panic!(
275 "index {} is out of range for a List of length {}",
276 index, length
277 );
278 }
279 index as usize
280}
281
282#[cfg(test)]
283mod tests {
284 use super::{checked_index, friendly_panic_message, runtime_error_code};
285
286 #[test]
287 fn accepts_in_range_indices() {
288 assert_eq!(checked_index(1, 3), 1);
289 }
290
291 #[test]
292 #[should_panic(expected = "index -1 is out of range for a List of length 3")]
293 fn rejects_negative_indices() {
294 checked_index(-1, 3);
295 }
296
297 #[test]
298 #[should_panic(expected = "index 5 is out of range for a List of length 3")]
299 fn rejects_indices_at_or_above_length() {
300 checked_index(5, 3);
301 }
302
303 #[test]
304 fn assigns_stable_runtime_error_codes() {
305 assert_eq!(
306 runtime_error_code("index -1 is out of range for a List of length 3"),
307 "OR0010"
308 );
309 assert_eq!(
310 runtime_error_code("already borrowed: BorrowMutError"),
311 "OR0011"
312 );
313 assert_eq!(runtime_error_code("unexpected panic"), "OR0099");
314 }
315
316 #[test]
317 fn translates_string_boundary_panics() {
318 let raw = "byte index 3 is not a char boundary; it is inside 'é'";
319 assert_eq!(runtime_error_code(raw), "OR0012");
320 assert_eq!(
321 friendly_panic_message(raw),
322 "byte 3 is in the middle of a character"
323 );
324 }
325
326 #[test]
327 fn translates_common_numeric_and_option_panics() {
328 assert_eq!(runtime_error_code("attempt to divide by zero"), "OR0013");
329 assert_eq!(runtime_error_code("attempt to add with overflow"), "OR0008");
330 assert_eq!(
331 runtime_error_code("called `Option::unwrap()` on a `None` value"),
332 "OR0014"
333 );
334 }
335}