uiua/sys/
mod.rs

1#[cfg(feature = "native_sys")]
2pub(crate) mod native;
3
4use std::{
5    any::Any,
6    borrow::Cow,
7    fmt,
8    mem::take,
9    net::SocketAddr,
10    path::{Path, PathBuf},
11    sync::{Arc, LazyLock},
12    time::Duration,
13};
14
15#[cfg(feature = "image")]
16use image::DynamicImage;
17use parking_lot::Mutex;
18use time::UtcOffset;
19
20#[cfg(feature = "native_sys")]
21pub use self::native::*;
22use crate::{
23    Array, BigConstant, Boxed, FfiArg, FfiType, MetaPtr, Ops, Primitive, SysOp, Uiua,
24    UiuaErrorKind, UiuaResult, Value,
25    algorithm::{multi_output, validate_size},
26    cowslice::cowslice,
27    get_ops,
28};
29
30/// The text of Uiua's example module
31pub const EXAMPLE_UA: &str = "\
32# Uiua's example module
33
34Square ← ˙×
35Double ← ˙+
36Increment ← +1
37RangeDiff ↚ ⇡-
38Span ← +⟜RangeDiff
39Mac! ← /^0 [1 2 3 4 5]
40Foo ← 5
41Bar ← \"bar\"";
42
43/// The text of Uiua's example text file
44pub const EXAMPLE_TXT: &str = "\
45This is a simple text file for 
46use in example Uiua code ✨";
47
48/// Access the built-in `example.ua` file
49pub fn example_ua<T>(f: impl FnOnce(&mut String) -> T) -> T {
50    static S: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(EXAMPLE_UA.to_string()));
51    f(&mut S.lock())
52}
53
54/// Access the built-in `example.txt` file
55pub fn example_txt<T>(f: impl FnOnce(&mut String) -> T) -> T {
56    static S: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(EXAMPLE_TXT.to_string()));
57    f(&mut S.lock())
58}
59
60/// A handle to an IO stream
61///
62/// 0 is stdin, 1 is stdout, 2 is stderr.
63///
64/// Other handles can be used by files or sockets.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct Handle(pub u64);
67
68impl Handle {
69    const STDIN: Self = Self(0);
70    const STDOUT: Self = Self(1);
71    const STDERR: Self = Self(2);
72    /// The first handle that can be used by the user
73    pub const FIRST_UNRESERVED: Self = Self(3);
74}
75
76impl From<usize> for Handle {
77    fn from(n: usize) -> Self {
78        Self(n as u64)
79    }
80}
81
82impl Handle {
83    pub(crate) fn value(self, kind: HandleKind) -> Value {
84        let mut arr = Array::from(self.0 as f64);
85        arr.meta.handle_kind = Some(kind);
86        Boxed(arr.into()).into()
87    }
88}
89
90impl Value {
91    /// Attempt to convert the array to system handle
92    ///
93    /// The `requirement` parameter is used in error messages.
94    pub fn as_handle(
95        &self,
96        env: &Uiua,
97        requirement: impl Into<Option<&'static str>>,
98    ) -> UiuaResult<Handle> {
99        let requirement = requirement
100            .into()
101            .unwrap_or("Expected value to be a stream handle");
102        match self {
103            Value::Box(b) => {
104                if let Some(b) = b.as_scalar() {
105                    b.0.as_nat(env, requirement).map(|h| Handle(h as u64))
106                } else {
107                    Err(env.error(format!("{requirement}, but it is rank {}", b.rank())))
108                }
109            }
110            value => value.as_nat(env, requirement).map(|h| Handle(h as u64)),
111        }
112    }
113}
114
115/// The function type passed to `&rl`'s returned function
116#[cfg(not(target_arch = "wasm32"))]
117pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + Send + 'a>;
118/// The function type passed to `&rl`'s returned function
119#[cfg(target_arch = "wasm32")]
120pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + 'a>;
121
122/// The function type returned by `&rl`
123#[cfg(not(target_arch = "wasm32"))]
124pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + Send + 'a>;
125/// The function type returned by `&rl`
126#[cfg(target_arch = "wasm32")]
127pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + 'a>;
128
129/// The function type passed to `&ast`
130#[cfg(not(target_arch = "wasm32"))]
131pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>> + Send>;
132/// The function type passed to `&ast`
133#[cfg(target_arch = "wasm32")]
134pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>>>;
135
136/// The kind of a handle
137#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138#[allow(missing_docs)]
139pub enum HandleKind {
140    File(PathBuf),
141    TcpListener(SocketAddr),
142    TlsListener(SocketAddr),
143    TcpSocket(SocketAddr),
144    TlsSocket(SocketAddr),
145    UdpSocket(String),
146    ChildStdin(String),
147    ChildStdout(String),
148    ChildStderr(String),
149}
150
151impl fmt::Display for HandleKind {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self {
154            Self::File(path) => write!(f, "file {}", path.display()),
155            Self::TcpListener(addr) => write!(f, "tcp listener {addr}"),
156            Self::TlsListener(addr) => write!(f, "tls listener {addr}"),
157            Self::TcpSocket(addr) => write!(f, "tcp socket {addr}"),
158            Self::TlsSocket(addr) => write!(f, "tls socket {addr}"),
159            Self::UdpSocket(addr) => write!(f, "udp socket {addr}"),
160            Self::ChildStdin(com) => write!(f, "stdin {com}"),
161            Self::ChildStdout(com) => write!(f, "stdout {com}"),
162            Self::ChildStderr(com) => write!(f, "stderr {com}"),
163        }
164    }
165}
166
167/// Reference point and offset to seek a position in a stream with
168/// This is used instead of `std::io::SeekFrom` because the latter is more general than what Uiua uses, so using a more specific enum reduces checks elsewhere in the code.
169pub enum StreamSeek {
170    /// Seek a position forward from the start of the stream
171    Start(usize),
172    /// Seek a position backward from the end of the stream
173    End(usize),
174}
175
176impl From<StreamSeek> for std::io::SeekFrom {
177    fn from(value: StreamSeek) -> Self {
178        match value {
179            StreamSeek::Start(off) => std::io::SeekFrom::Start(off as u64),
180            StreamSeek::End(off) => std::io::SeekFrom::End(-(off as i64)),
181        }
182    }
183}
184
185#[cfg(feature = "image")]
186pub(crate) type WebcamImage = image::RgbImage;
187#[cfg(not(feature = "image"))]
188pub(crate) type WebcamImage = ();
189
190/// Trait for defining a system backend
191#[allow(unused_variables)]
192pub trait SysBackend: Any + Send + Sync + 'static {
193    /// Cast the backend to `&dyn Any`
194    fn any(&self) -> &dyn Any;
195    /// Cast the backend to `&mut dyn Any`
196    fn any_mut(&mut self) -> &mut dyn Any;
197    /// Save a color-formatted version of an error message for later printing
198    fn save_error_color(&self, message: String, colored: String) {}
199    /// Check whether output is enabled
200    fn output_enabled(&self) -> bool {
201        true
202    }
203    /// Set whether output should be enabled
204    ///
205    /// Returns the previous value.
206    ///
207    /// It is the trait implementor's responsibility to ensure that this value is respected.
208    fn set_output_enabled(&self, enabled: bool) -> bool {
209        true
210    }
211    /// Print a string (without a newline) to stdout
212    fn print_str_stdout(&self, s: &str) -> Result<(), String> {
213        Err("Printing to stdout is not supported in this environment".into())
214    }
215    /// Print a string (without a newline) to stderr
216    fn print_str_stderr(&self, s: &str) -> Result<(), String> {
217        Err("Printing to stderr is not supported in this environment".into())
218    }
219    /// Print a string that was create by `trace`
220    fn print_str_trace(&self, s: &str) {}
221    /// Show a value
222    fn show(&self, value: Value) -> Result<(), String> {
223        self.print_str_stdout(&format!("{}\n", value.show()))
224    }
225    /// Read a line from stdin
226    ///
227    /// Should return `Ok(None)` if EOF is reached.
228    fn scan_line_stdin(&self) -> Result<Option<String>, String> {
229        Err("Reading from stdin is not supported in this environment".into())
230    }
231    /// Read a number of bytes from stdin
232    ///
233    /// If `count` is `None`, read until EOF.
234    fn scan_stdin(&self, count: Option<usize>) -> Result<Vec<u8>, String> {
235        Err("Reading from stdin is not supported in this environment".into())
236    }
237    /// Read from stdin until a delimiter is reached
238    fn scan_until_stdin(&self, delim: &[u8]) -> Result<Vec<u8>, String> {
239        let mut buffer = Vec::new();
240        loop {
241            let bytes = self.scan_stdin(Some(1))?;
242            if bytes.is_empty() {
243                break;
244            }
245            buffer.extend_from_slice(&bytes);
246            if buffer.ends_with(delim) {
247                break;
248            }
249        }
250        Ok(buffer)
251    }
252    /// Set the terminal to raw mode
253    fn set_raw_mode(&self, raw_mode: bool) -> Result<(), String> {
254        Err("Setting raw mode is not supported in this environment".into())
255    }
256    /// Get the terminal raw mode
257    fn get_raw_mode(&self) -> Result<bool, String> {
258        Err("Getting raw mode is not supported in this environment".into())
259    }
260    /// Get an environment variable
261    fn var(&self, name: &str) -> Option<String> {
262        None
263    }
264    /// Get the size of the terminal
265    fn term_size(&self) -> Result<(usize, usize), String> {
266        Err("Getting the terminal size is not supported in this environment".into())
267    }
268    /// Exit the program with a status code
269    fn exit(&self, status: i32) -> Result<(), String> {
270        Err("Exiting is not supported in this environment".into())
271    }
272    /// Check if a file or directory exists
273    fn file_exists(&self, path: &str) -> bool {
274        false
275    }
276    /// List the contents of a directory
277    fn list_dir(&self, path: &str) -> Result<Vec<String>, String> {
278        Err("Listing directories is not supported in this environment".into())
279    }
280    /// Check if a path is a file
281    fn is_file(&self, path: &str) -> Result<bool, String> {
282        Err("Checking if a path is a file is not supported in this environment".into())
283    }
284    /// Delete a file or directory
285    fn delete(&self, path: &str) -> Result<(), String> {
286        Err("Deleting files is not supported in this environment".into())
287    }
288    /// Move a file or directory to the trash
289    fn trash(&self, path: &str) -> Result<(), String> {
290        Err("Trashing files is not supported in this environment".into())
291    }
292    /// Read at most `count` bytes from a stream
293    fn read(&self, handle: Handle, count: usize) -> Result<Vec<u8>, String> {
294        Err("Reading from streams is not supported in this environment".into())
295    }
296    /// Read from a stream until the end
297    fn read_all(&self, handle: Handle) -> Result<Vec<u8>, String> {
298        Err("Reading from streams is not supported in this environment".into())
299    }
300    /// Read from a stream until a delimiter is reached
301    fn read_until(&self, handle: Handle, delim: &[u8]) -> Result<Vec<u8>, String> {
302        let mut buffer = Vec::new();
303        loop {
304            let bytes = self.read(handle, 1)?;
305            if bytes.is_empty() {
306                break;
307            }
308            buffer.extend_from_slice(&bytes);
309            if buffer.ends_with(delim) {
310                break;
311            }
312        }
313        Ok(buffer)
314    }
315    /// Read lines from a stream
316    fn read_lines<'a>(&self, handle: Handle) -> Result<ReadLinesReturnFn<'a>, String> {
317        Err("Reading from streams is not supported in this environment".into())
318    }
319    /// Write bytes to a stream
320    fn write(&self, handle: Handle, contents: &[u8]) -> Result<(), String> {
321        Err("Writing to streams is not supported in this environment".into())
322    }
323    /// Go to an absolute file position
324    fn seek(&self, handle: Handle, offset: StreamSeek) -> Result<(), String> {
325        Err("Seeking streams is not supported in this environment".into())
326    }
327    /// Create a file
328    fn create_file(&self, path: &Path) -> Result<Handle, String> {
329        Err("Creating files is not supported in this environment".into())
330    }
331    /// Open a file
332    fn open_file(&self, path: &Path, write: bool) -> Result<Handle, String> {
333        Err("Opening files is not supported in this environment".into())
334    }
335    /// Create a directory
336    fn make_dir(&self, path: &Path) -> Result<(), String> {
337        Err("Creating directories is not supported in this environment".into())
338    }
339    /// Read all bytes from a file
340    fn file_read_all(&self, path: &Path) -> Result<Vec<u8>, String> {
341        let handle = self.open_file(path, false)?;
342        let bytes = self.read(handle, usize::MAX)?;
343        self.close(handle)?;
344        Ok(bytes)
345    }
346    /// Write all bytes to a file
347    fn file_write_all(&self, path: &Path, contents: &[u8]) -> Result<(), String> {
348        let handle = self.create_file(path)?;
349        self.write(handle, contents)?;
350        self.close(handle)?;
351        Ok(())
352    }
353    /// Get the clipboard contents
354    fn clipboard(&self) -> Result<String, String> {
355        Err("Getting the clipboard is not supported in this environment".into())
356    }
357    /// Set the clipboard contents
358    fn set_clipboard(&self, contents: &str) -> Result<(), String> {
359        Err("Setting the clipboard is not supported in this environment".into())
360    }
361    /// Sleep the current thread for `seconds` seconds
362    fn sleep(&self, seconds: f64) -> Result<(), String> {
363        Err("Sleeping is not supported in this environment".into())
364    }
365    /// Whether thread spawning is allowed
366    fn allow_thread_spawning(&self) -> bool {
367        false
368    }
369    /// Show an image
370    #[cfg(feature = "image")]
371    fn show_image(&self, image: DynamicImage, label: Option<&str>) -> Result<(), String> {
372        Err("Showing images is not supported in this environment".into())
373    }
374    /// Show a GIF
375    fn show_gif(&self, gif_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
376        Err("Showing gifs is not supported in this environment".into())
377    }
378    /// Show an APNG
379    fn show_apng(&self, apng_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
380        Err("Showing APNGs is not supported in this environment".into())
381    }
382    /// Play audio from WAV bytes
383    fn play_audio(&self, wave_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
384        Err("Playing audio is not supported in this environment".into())
385    }
386    /// Get the audio sample rate
387    fn audio_sample_rate(&self) -> u32 {
388        44100
389    }
390    /// Stream audio
391    fn stream_audio(&self, f: AudioStreamFn) -> Result<(), String> {
392        Err("Streaming audio is not supported in this environment".into())
393    }
394    /// The result of the `now` function
395    ///
396    /// Should be in seconds
397    fn now(&self) -> f64 {
398        now()
399    }
400    /// Create a TCP listener and bind it to an address
401    fn tcp_listen(&self, addr: &str) -> Result<Handle, String> {
402        Err("TCP listeners are not supported in this environment".into())
403    }
404    /// Create a TLS listener and bind it to an address
405    fn tls_listen(&self, addr: &str, cert: &[u8], key: &[u8]) -> Result<Handle, String> {
406        Err("TLS listeners are not supported in this environment".into())
407    }
408    /// Accept a connection with a TCP listener
409    fn tcp_accept(&self, handle: Handle) -> Result<Handle, String> {
410        Err("TCP listeners are not supported in this environment".into())
411    }
412    /// Create a TCP socket and connect it to an address
413    fn tcp_connect(&self, addr: &str) -> Result<Handle, String> {
414        Err("TCP sockets are not supported in this environment".into())
415    }
416    /// Create a TCP socket with TLS support and connect it to an address
417    fn tls_connect(&self, addr: &str) -> Result<Handle, String> {
418        Err("TLS sockets are not supported in this environment".into())
419    }
420    /// Get the connection address of a TCP socket or listener
421    fn tcp_addr(&self, handle: Handle) -> Result<SocketAddr, String> {
422        Err("TCP sockets are not supported in this environment".into())
423    }
424    /// Set a TCP socket to non-blocking mode
425    fn tcp_set_non_blocking(&self, handle: Handle, non_blocking: bool) -> Result<(), String> {
426        Err("TCP sockets are not supported in this environment".into())
427    }
428    /// Set the read timeout of a TCP socket
429    fn tcp_set_read_timeout(
430        &self,
431        handle: Handle,
432        timeout: Option<Duration>,
433    ) -> Result<(), String> {
434        Err("TCP sockets are not supported in this environment".into())
435    }
436    /// Set the write timeout of a TCP socket
437    fn tcp_set_write_timeout(
438        &self,
439        handle: Handle,
440        timeout: Option<Duration>,
441    ) -> Result<(), String> {
442        Err("TCP sockets are not supported in this environment".into())
443    }
444    /// Fetch a URL, respecting protocol and port (defaults to https and 443)
445    fn fetch(&self, url: &str) -> Result<Vec<u8>, String> {
446        let is_https = url.starts_with("https://");
447        let is_http = url.starts_with("http://");
448        let no_protocol = url
449            .trim_start_matches("https://")
450            .trim_start_matches("http://");
451        let (host, route) = match no_protocol.find('/') {
452            Some(i) => no_protocol.split_at(i),
453            None => (no_protocol, "/"),
454        };
455        let (host_only, port, use_tls) = match host.rsplit_once(':') {
456            Some((h, p)) if p.parse::<u16>().is_ok() => (h, p, is_https || !is_http),
457            _ => {
458                let tls = is_https || !is_http;
459                let port = if tls { "443" } else { "80" };
460                (host, port, tls)
461            }
462        };
463        let addr = format!("{host_only}:{port}");
464        let default_port = if use_tls { "443" } else { "80" };
465        let host_header = if port == default_port {
466            host_only.to_owned()
467        } else {
468            format!("{host_only}:{port}")
469        };
470        let req = format!(
471            "\
472            GET {route} HTTP/1.1\r\n\
473            Host: {host_header}\r\n\
474            Connection: close\r\n\
475            User-Agent: Uiua/{}\r\n\
476            \r\n",
477            crate::VERSION
478        );
479        let handle = if use_tls {
480            self.tls_connect(&addr)?
481        } else {
482            self.tcp_connect(&addr)?
483        };
484        self.write(handle, req.as_bytes())?;
485
486        let mut bytes = match self.read_all(handle) {
487            Ok(b) => b,
488            Err(e) => {
489                if e.contains("close_notify") || e.contains("UnexpectedEof") {
490                    return Err("Connection closed without close_notify".into());
491                } else {
492                    return Err(e);
493                }
494            }
495        };
496
497        let _ = self.close(handle);
498
499        if bytes.is_empty() {
500            return Err("Empty HTTP response".into());
501        }
502        let status_line = bytes.split(|&b| b == b'\n').next().unwrap();
503        let status = status_line
504            .split(|&b| b == b' ')
505            .nth(1)
506            .ok_or("Invalid HTTP response: missing status code")?;
507        let status_str = String::from_utf8_lossy(status);
508        if !status_str.starts_with('2') && !status_str.starts_with('3') {
509            return Err(format!("HTTP {status_str}"));
510        }
511        let body_start = bytes
512            .windows(4)
513            .position(|w| w == b"\r\n\r\n")
514            .map(|i| i + 4)
515            .or(bytes.windows(2).position(|w| w == b"\n\n").map(|i| i + 2))
516            .ok_or("Invalid HTTP response: missing header separator")?;
517        bytes.rotate_left(body_start);
518        bytes.truncate(bytes.len() - body_start);
519        Ok(bytes)
520    }
521    /// Create a UDP socket and bind it to an address
522    fn udp_bind(&self, addr: &str) -> Result<Handle, String> {
523        Err("UDP sockets are not supported in this environment".into())
524    }
525    /// Receive a single datagram on a UDP socket
526    fn udp_recv(&self, handle: Handle) -> Result<(Vec<u8>, SocketAddr), String> {
527        Err("UDP sockets are not supported in this environment".into())
528    }
529    /// Send a datagram to an address over a UDP socket
530    fn udp_send(&self, handle: Handle, packet: &[u8], addr: &str) -> Result<(), String> {
531        Err("UDP sockets are not supported in this environment".into())
532    }
533    /// Set the maximum message length for a UDP socket
534    fn udp_set_max_msg_length(&self, handle: Handle, max_len: usize) -> Result<(), String> {
535        Err("UDP sockets are not supported in this environment".into())
536    }
537    /// Close a stream
538    fn close(&self, handle: Handle) -> Result<(), String> {
539        Ok(())
540    }
541    /// Invoke a path with the system's default program
542    fn invoke(&self, path: &str) -> Result<(), String> {
543        Err("Invoking paths is not supported in this environment".into())
544    }
545    /// Run a command, inheriting standard IO
546    fn run_command_inherit(&self, command: &str, args: &[&str]) -> Result<i32, String> {
547        Err("Running inheritting commands is not supported in this environment".into())
548    }
549    /// Run a command, capturing standard IO
550    fn run_command_capture(
551        &self,
552        command: &str,
553        args: &[&str],
554    ) -> Result<(i32, String, String), String> {
555        Err("Running capturing commands is not supported in this environment".into())
556    }
557    /// Run a command and return an IO stream handle
558    fn run_command_stream(&self, command: &str, args: &[&str]) -> Result<[Handle; 3], String> {
559        Err("Running streamed commands is not supported in this environment".into())
560    }
561    /// Change the current directory
562    fn change_directory(&self, path: &str) -> Result<(), String> {
563        Err("Changing directories is not supported in this environment".into())
564    }
565    /// Get the current directory
566    fn get_current_directory(&self) -> Result<String, String> {
567        Err("Getting the current directory is not supported in this environment".into())
568    }
569    /// Capture an image from the webcam
570    fn webcam_capture(&self, index: usize) -> Result<WebcamImage, String> {
571        Err("Capturing from webcam is not supported in this environment".into())
572    }
573    /// List available webcams
574    fn webcam_list(&self) -> Result<Vec<String>, String> {
575        Err("Listing webcams is not supported in this environment".into())
576    }
577    /// Call a foreign function interface
578    fn ffi(
579        &self,
580        file: &str,
581        result_ty: FfiType,
582        name: &str,
583        arg_tys: &[FfiArg],
584        args: Vec<Value>,
585    ) -> Result<Value, String> {
586        Err("FFI is not supported in this environment".into())
587    }
588    /// Copy the data from a pointer into an array
589    fn mem_copy(&self, ptr: MetaPtr, len: usize) -> Result<Value, String> {
590        Err("Pointer copying is not supported in this environment".into())
591    }
592    /// Write data from an array into a pointer
593    fn mem_set(&self, ptr: MetaPtr, idx: usize, value: Value) -> Result<(), String> {
594        Err("Pointer writing is not supported in this environment".into())
595    }
596    /// Free a pointer
597    fn mem_free(&self, ptr: &MetaPtr) -> Result<(), String> {
598        Err("Pointer freeing is not supported in this environment".into())
599    }
600    /// Load a git repo as a module
601    ///
602    /// The returned path should be loadable via [`SysBackend::file_read_all`]
603    fn load_git_module(&self, url: &str, target: GitTarget) -> Result<PathBuf, String> {
604        Err("Loading git modules is not supported in this environment".into())
605    }
606    /// Get the local timezone offset in hours
607    fn timezone(&self) -> Result<f64, String> {
608        if cfg!(target_arch = "wasm32") {
609            return Err("Getting the timezone is not supported in this environment".into());
610        }
611        let offset = UtcOffset::current_local_offset().map_err(|e| e.to_string())?;
612        let (h, m, s) = offset.as_hms();
613        let mut o = h as f64;
614        o += m as f64 / 60.0;
615        o += s as f64 / 3600.0;
616        Ok(o)
617    }
618    /// Hit a breakpoint
619    ///
620    /// Returns whether to continue the program
621    fn breakpoint(&self, env: &Uiua) -> Result<bool, String> {
622        Err("Breakpoints are not supported in this environment".into())
623    }
624    /// Resolve a big constant
625    fn big_constant(&self, key: BigConstant) -> Result<Cow<'static, [u8]>, String> {
626        Err("Retrieval of big constants is not supported in this environment".into())
627    }
628}
629
630/// A target for a git repository
631#[derive(Debug, Clone, Default)]
632pub enum GitTarget {
633    /// The latest commit on the default branch
634    #[default]
635    Default,
636    /// The latest commit on a specific branch
637    Branch(String),
638    /// A specific commit
639    Commit(String),
640}
641
642impl fmt::Debug for dyn SysBackend {
643    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644        write!(f, "<sys backend>")
645    }
646}
647
648/// A safe backend with no IO other than captured stdout and stderr
649#[derive(Default)]
650pub struct SafeSys {
651    stdout: Arc<Mutex<Vec<u8>>>,
652    stderr: Arc<Mutex<Vec<u8>>>,
653    /// Whether to allow thread spawning
654    pub allow_thread_spawning: bool,
655}
656impl SysBackend for SafeSys {
657    fn any(&self) -> &dyn Any {
658        self
659    }
660    fn any_mut(&mut self) -> &mut dyn Any {
661        self
662    }
663    fn print_str_stdout(&self, s: &str) -> Result<(), String> {
664        self.stdout.lock().extend_from_slice(s.as_bytes());
665        Ok(())
666    }
667    fn print_str_stderr(&self, s: &str) -> Result<(), String> {
668        self.stderr.lock().extend_from_slice(s.as_bytes());
669        Ok(())
670    }
671    fn allow_thread_spawning(&self) -> bool {
672        self.allow_thread_spawning
673    }
674    #[cfg(feature = "native_sys")]
675    fn big_constant(&self, key: BigConstant) -> Result<Cow<'static, [u8]>, String> {
676        NativeSys.big_constant(key)
677    }
678}
679
680impl SafeSys {
681    /// Create a new safe system backend
682    pub fn new() -> Self {
683        Self::default()
684    }
685    /// Create a new safe system backend that allows thread spawning
686    pub fn with_thread_spawning() -> Self {
687        Self {
688            allow_thread_spawning: true,
689            ..Self::default()
690        }
691    }
692    /// Take the captured stdout
693    pub fn take_stdout(&self) -> Vec<u8> {
694        take(&mut *self.stdout.lock())
695    }
696    /// Take the captured stderr
697    pub fn take_stderr(&self) -> Vec<u8> {
698        take(&mut *self.stderr.lock())
699    }
700}
701
702/// Trait for converting to a system backend
703pub trait IntoSysBackend {
704    /// Convert to a reference counted system backend
705    fn into_sys_backend(self) -> Arc<dyn SysBackend>;
706}
707
708impl<T> IntoSysBackend for T
709where
710    T: SysBackend + Send + Sync + 'static,
711{
712    fn into_sys_backend(self) -> Arc<dyn SysBackend> {
713        Arc::new(self)
714    }
715}
716
717impl IntoSysBackend for Arc<dyn SysBackend> {
718    fn into_sys_backend(self) -> Arc<dyn SysBackend> {
719        self
720    }
721}
722
723pub(crate) fn run_sys_op(op: &SysOp, env: &mut Uiua) -> UiuaResult {
724    match op {
725        SysOp::Show => {
726            let val = env.pop(1)?;
727            env.rt.backend.show(val).map_err(|e| env.error(e))?;
728        }
729        SysOp::Prin => {
730            let s = env.pop(1)?.format();
731            (env.rt.backend)
732                .print_str_stdout(&s)
733                .map_err(|e| env.error(e))?;
734        }
735        SysOp::Print => {
736            let s = env.pop(1)?.format();
737            (env.rt.backend)
738                .print_str_stdout(&format!("{s}\n"))
739                .map_err(|e| env.error(e))?;
740        }
741        SysOp::PrinErr => {
742            let s = env.pop(1)?.format();
743            (env.rt.backend)
744                .print_str_stderr(&s)
745                .map_err(|e| env.error(e))?;
746        }
747        SysOp::PrintErr => {
748            let s = env.pop(1)?.format();
749            (env.rt.backend)
750                .print_str_stderr(&format!("{s}\n"))
751                .map_err(|e| env.error(e))?;
752        }
753        SysOp::ScanLine => {
754            let start = env.rt.backend.now();
755            let res = env.rt.backend.scan_line_stdin().map_err(|e| env.error(e));
756            env.rt.execution_start += env.rt.backend.now() - start;
757            if let Some(line) = res? {
758                env.push(line);
759            } else {
760                env.push(0u8);
761            }
762        }
763        SysOp::TermSize => {
764            let (width, height) = env.rt.backend.term_size().map_err(|e| env.error(e))?;
765            env.push(cowslice![height as f64, width as f64])
766        }
767        SysOp::Exit => {
768            let status = env.pop(1)?.as_int(env, "Status must be an integer")? as i32;
769            (env.rt.backend).exit(status).map_err(|e| env.error(e))?;
770        }
771        SysOp::RawMode => {
772            let raw_mode = env.pop(1)?.as_bool(env, "Raw mode must be a boolean")?;
773            (env.rt.backend)
774                .set_raw_mode(raw_mode)
775                .map_err(|e| env.error(e))?;
776        }
777        SysOp::EnvArgs => {
778            let mut args = Vec::new();
779            args.push(env.file_path().to_string_lossy().into_owned());
780            args.extend(env.args().to_owned());
781            env.push(Array::<Boxed>::from_iter(args));
782        }
783        SysOp::Var => {
784            let key = env
785                .pop(1)?
786                .as_string(env, "Augument to var must be a string")?;
787            let var = env
788                .rt
789                .backend
790                .var(&key)
791                .ok_or_else(|| env.error(format!("Environment variable `{key}` is not set")))?;
792            env.push(var);
793        }
794        SysOp::FOpen => {
795            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
796            let handle = (env.rt.backend)
797                .open_file(path.as_ref(), true)
798                .map_err(|e| env.error(e))?
799                .value(HandleKind::File(path.into()));
800            env.push(handle);
801        }
802        SysOp::FCreate => {
803            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
804            let handle: Value = (env.rt.backend)
805                .create_file(path.as_ref())
806                .map_err(|e| env.error(e))?
807                .value(HandleKind::File(path.into()));
808            env.push(handle);
809        }
810        SysOp::FMakeDir => {
811            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
812            (env.rt.backend)
813                .make_dir(path.as_ref())
814                .map_err(|e| env.error(e))?;
815        }
816        SysOp::FDelete => {
817            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
818            env.rt.backend.delete(&path).map_err(|e| env.error(e))?;
819        }
820        SysOp::FTrash => {
821            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
822            env.rt.backend.trash(&path).map_err(|e| env.error(e))?;
823        }
824        SysOp::ReadStr => {
825            let count = env
826                .pop(1)?
827                .as_nat_or_inf(env, "Count must be an integer or infinity")?;
828            if let Some(count) = count {
829                validate_size::<char>([count], env)?;
830            }
831            let handle = env.pop(2)?.as_handle(env, None)?;
832            let s = match handle {
833                Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
834                Handle::STDERR => return Err(env.error("Cannot read from stderr")),
835                Handle::STDIN => {
836                    let buf = env.rt.backend.scan_stdin(count).map_err(|e| env.error(e))?;
837                    match String::from_utf8(buf) {
838                        Ok(s) => s,
839                        Err(e) => {
840                            let valid_to = e.utf8_error().valid_up_to();
841                            let mut buf = e.into_bytes();
842                            let mut rest = buf.split_off(valid_to);
843                            for _ in 0..3 {
844                                rest.extend(
845                                    env.rt
846                                        .backend
847                                        .scan_stdin(Some(1))
848                                        .map_err(|e| env.error(e))?,
849                                );
850                                if let Ok(s) = std::str::from_utf8(&rest) {
851                                    buf.extend_from_slice(s.as_bytes());
852                                    break;
853                                }
854                            }
855                            String::from_utf8(buf).map_err(|e| env.error(e))?
856                        }
857                    }
858                }
859                _ => {
860                    if let Some(count) = count {
861                        let buf = env
862                            .rt
863                            .backend
864                            .read(handle, count)
865                            .map_err(|e| env.error(e))?;
866                        match String::from_utf8(buf) {
867                            Ok(s) => s,
868                            Err(e) => {
869                                let valid_to = e.utf8_error().valid_up_to();
870                                let mut buf = e.into_bytes();
871                                let mut rest = buf.split_off(valid_to);
872                                for _ in 0..3 {
873                                    rest.extend(
874                                        env.rt.backend.read(handle, 1).map_err(|e| env.error(e))?,
875                                    );
876                                    if let Ok(s) = std::str::from_utf8(&rest) {
877                                        buf.extend_from_slice(s.as_bytes());
878                                        break;
879                                    }
880                                }
881                                String::from_utf8(buf).map_err(|e| env.error(e))?
882                            }
883                        }
884                    } else {
885                        let bytes = env.rt.backend.read_all(handle).map_err(|e| env.error(e))?;
886                        String::from_utf8(bytes).map_err(|e| env.error(e))?
887                    }
888                }
889            };
890            env.push(s);
891        }
892        SysOp::ReadBytes => {
893            let count = env
894                .pop(1)?
895                .as_nat_or_inf(env, "Count must be an integer or infinity")?;
896            if let Some(count) = count {
897                validate_size::<u8>([count], env)?;
898            }
899            let handle = env.pop(2)?.as_handle(env, None)?;
900            let bytes = match handle {
901                Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
902                Handle::STDERR => return Err(env.error("Cannot read from stderr")),
903                Handle::STDIN => env.rt.backend.scan_stdin(count).map_err(|e| env.error(e))?,
904                _ => {
905                    if let Some(count) = count {
906                        env.rt
907                            .backend
908                            .read(handle, count)
909                            .map_err(|e| env.error(e))?
910                    } else {
911                        env.rt.backend.read_all(handle).map_err(|e| env.error(e))?
912                    }
913                }
914            };
915            env.push(Array::from(bytes.as_slice()));
916        }
917        SysOp::ReadUntil => {
918            let delim = env.pop(1)?;
919            let handle = env.pop(2)?.as_handle(env, None)?;
920            if delim.rank() > 1 {
921                return Err(env.error("Delimiter must be a rank 0 or 1 string or byte array"));
922            }
923            match handle {
924                Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
925                Handle::STDERR => return Err(env.error("Cannot read from stderr")),
926                Handle::STDIN => {
927                    let mut is_string = false;
928                    let delim_bytes: Vec<u8> = match delim {
929                        Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
930                        Value::Byte(arr) => arr.data.into(),
931                        Value::Char(arr) => {
932                            is_string = true;
933                            arr.data.iter().collect::<String>().into()
934                        }
935                        _ => return Err(env.error("Delimiter must be a string or byte array")),
936                    };
937                    let buffer = env
938                        .rt
939                        .backend
940                        .scan_until_stdin(&delim_bytes)
941                        .map_err(|e| env.error(e))?;
942                    if is_string {
943                        let s = String::from_utf8_lossy(&buffer).into_owned();
944                        env.push(s);
945                    } else {
946                        env.push(Array::from(buffer.as_slice()));
947                    }
948                }
949                _ => match delim {
950                    Value::Num(arr) => {
951                        let delim: Vec<u8> = arr.data.iter().map(|&x| x as u8).collect();
952                        let bytes = env
953                            .rt
954                            .backend
955                            .read_until(handle, &delim)
956                            .map_err(|e| env.error(e))?;
957                        env.push(Array::from(bytes.as_slice()));
958                    }
959                    Value::Byte(arr) => {
960                        let delim: Vec<u8> = arr.data.into();
961                        let bytes = env
962                            .rt
963                            .backend
964                            .read_until(handle, &delim)
965                            .map_err(|e| env.error(e))?;
966                        env.push(Array::from(bytes.as_slice()));
967                    }
968                    Value::Char(arr) => {
969                        let delim: Vec<u8> = arr.data.iter().collect::<String>().into();
970                        let bytes = env
971                            .rt
972                            .backend
973                            .read_until(handle, &delim)
974                            .map_err(|e| env.error(e))?;
975                        let s = String::from_utf8(bytes).map_err(|e| env.error(e))?;
976                        env.push(s);
977                    }
978                    _ => return Err(env.error("Delimiter must be a string or byte array")),
979                },
980            }
981        }
982        SysOp::Write => {
983            let data = env.pop(1)?;
984            let handle = env.pop(2)?.as_handle(env, None)?;
985            let bytes: Vec<u8> = match data {
986                Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
987                Value::Byte(arr) => arr.data.into(),
988                Value::Char(arr) => arr.data.iter().collect::<String>().into(),
989                val => return Err(env.error(format!("Cannot write {} array", val.type_name()))),
990            };
991            match handle {
992                Handle::STDOUT => (env.rt.backend)
993                    .print_str_stdout(&String::from_utf8_lossy(&bytes))
994                    .map_err(|e| env.error(e))?,
995                Handle::STDERR => (env.rt.backend)
996                    .print_str_stderr(&String::from_utf8_lossy(&bytes))
997                    .map_err(|e| env.error(e))?,
998                Handle::STDIN => return Err(env.error("Cannot write to stdin")),
999                _ => (env.rt.backend.write(handle, &bytes)).map_err(|e| env.error(e))?,
1000            }
1001        }
1002        SysOp::Seek => {
1003            let pos = env.pop(1)?.as_int_or_inf(env, None)?;
1004            let pos = match pos {
1005                Ok(pos @ ..0) => StreamSeek::End(pos.unsigned_abs()),
1006                Ok(pos @ 0..) => StreamSeek::Start(pos.unsigned_abs()),
1007                Err(false) => StreamSeek::End(0),
1008                Err(true) => StreamSeek::Start(0),
1009            };
1010            let handle = env.pop(2)?.as_handle(env, None)?;
1011            env.rt.backend.seek(handle, pos).map_err(|e| env.error(e))?;
1012        }
1013        SysOp::FReadAllStr => {
1014            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1015            let bytes = (env.rt.backend)
1016                .file_read_all(path.as_ref())
1017                .or_else(|e| match path.as_str() {
1018                    "example.ua" => Ok(EXAMPLE_UA.as_bytes().to_vec()),
1019                    "example.txt" => Ok(EXAMPLE_TXT.as_bytes().to_vec()),
1020                    _ => Err(e),
1021                })
1022                .map_err(|e| env.error(e))?;
1023            let s = String::from_utf8(bytes).map_err(|e| env.error(e))?;
1024            env.push(s);
1025        }
1026        SysOp::FReadAllBytes => {
1027            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1028            let bytes = (env.rt.backend)
1029                .file_read_all(path.as_ref())
1030                .or_else(|e| match path.as_str() {
1031                    "example.ua" => Ok(EXAMPLE_UA.as_bytes().to_vec()),
1032                    "example.txt" => Ok(EXAMPLE_TXT.as_bytes().to_vec()),
1033                    _ => Err(e),
1034                })
1035                .map_err(|e| env.error(e))?;
1036            env.push(Array::<u8>::from_iter(bytes));
1037        }
1038        SysOp::FWriteAll => {
1039            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1040            let data = env.pop(2)?;
1041            let bytes: Vec<u8> = match data {
1042                Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
1043                Value::Byte(arr) => arr.data.into(),
1044                Value::Char(arr) => arr.data.iter().collect::<String>().into(),
1045                val => {
1046                    return Err(
1047                        env.error(format!("Cannot write {} array to file", val.type_name()))
1048                    );
1049                }
1050            };
1051            (env.rt.backend)
1052                .file_write_all(path.as_ref(), &bytes)
1053                .or_else(|e| {
1054                    if path == "example.ua" {
1055                        let new_ex = String::from_utf8(bytes).map_err(|e| e.to_string())?;
1056                        example_ua(move |ex| *ex = new_ex);
1057                        Ok(())
1058                    } else {
1059                        Err(e)
1060                    }
1061                })
1062                .map_err(|e| env.error(e))?;
1063        }
1064        SysOp::FExists => {
1065            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1066            let exists = env.rt.backend.file_exists(&path);
1067            env.push(exists);
1068        }
1069        SysOp::FListDir => {
1070            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1071            let paths = env.rt.backend.list_dir(&path).map_err(|e| env.error(e))?;
1072            env.push(Array::<Boxed>::from_iter(paths));
1073        }
1074        SysOp::FIsFile => {
1075            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1076            let is_file = env.rt.backend.is_file(&path).map_err(|e| env.error(e))?;
1077            env.push(is_file);
1078        }
1079        SysOp::Invoke => {
1080            let path = env.pop(1)?.as_string(env, "Invoke path must be a string")?;
1081            env.rt.backend.invoke(&path).map_err(|e| env.error(e))?;
1082        }
1083        SysOp::ImShow => {
1084            #[cfg(feature = "image")]
1085            {
1086                let value = env.pop(1)?;
1087                let image = crate::media::value_to_image(&value).map_err(|e| env.error(e))?;
1088                (env.rt.backend)
1089                    .show_image(image, value.meta.label.as_deref())
1090                    .map_err(|e| env.error(e))?;
1091            }
1092            #[cfg(not(feature = "image"))]
1093            return Err(env.error("Image encoding is not supported in this environment"));
1094        }
1095        SysOp::GifShow => {
1096            #[cfg(feature = "gif")]
1097            {
1098                let delay = env.pop(1)?.as_num(env, "Framerate must be a number")?;
1099                let value = env.pop(2)?;
1100                let start = env.rt.backend.now();
1101                let bytes =
1102                    crate::media::value_to_gif_bytes(&value, delay).map_err(|e| env.error(e))?;
1103                env.rt.execution_start += env.rt.backend.now() - start;
1104                (env.rt.backend)
1105                    .show_gif(bytes, value.meta.label.as_deref())
1106                    .map_err(|e| env.error(e))?;
1107            }
1108            #[cfg(not(feature = "gif"))]
1109            return Err(env.error("GIF showing is not supported in this environment"));
1110        }
1111        SysOp::ApngShow => {
1112            #[cfg(feature = "apng")]
1113            {
1114                let delay = env.pop(1)?.as_num(env, "Framerate must be a number")?;
1115                let value = env.pop(2)?;
1116                let start = env.rt.backend.now();
1117                let bytes =
1118                    crate::media::value_to_apng_bytes(&value, delay).map_err(|e| env.error(e))?;
1119                env.rt.execution_start += env.rt.backend.now() - start;
1120                (env.rt.backend)
1121                    .show_apng(bytes.into_iter().collect(), value.meta.label.as_deref())
1122                    .map_err(|e| env.error(e))?;
1123            }
1124            #[cfg(not(feature = "apng"))]
1125            return Err(env.error("APNG showing is not supported in this environment"));
1126        }
1127        SysOp::AudioPlay => {
1128            #[cfg(feature = "audio_encode")]
1129            {
1130                let value = env.pop(1)?;
1131                let bytes =
1132                    crate::media::value_to_wav_bytes(&value, env.rt.backend.audio_sample_rate())
1133                        .map_err(|e| env.error(e))?;
1134                (env.rt.backend)
1135                    .play_audio(bytes, value.meta.label.as_deref())
1136                    .map_err(|e| env.error(e))?;
1137            }
1138            #[cfg(not(feature = "audio_encode"))]
1139            return Err(env.error("Audio encoding is not supported in this environment"));
1140        }
1141        SysOp::AudioSampleRate => {
1142            let sample_rate = env.rt.backend.audio_sample_rate();
1143            env.push(f64::from(sample_rate));
1144        }
1145        SysOp::Clip => {
1146            let contents = env.rt.backend.clipboard().map_err(|e| env.error(e))?;
1147            env.push(contents);
1148        }
1149        SysOp::Sleep => {
1150            let mut seconds = env.pop(1)?.as_num(env, "Sleep time must be a number")?;
1151            if seconds < 0.0 {
1152                return Err(env.error("Sleep time must be positive"));
1153            }
1154            if seconds.is_infinite() {
1155                return Err(env.error("Sleep time cannot be infinite"));
1156            }
1157            if let Some(limit) = env.rt.execution_limit {
1158                let elapsed = env.rt.backend.now() - env.rt.execution_start;
1159                let max = limit - elapsed;
1160                seconds = seconds.min(max);
1161            }
1162            env.rt.backend.sleep(seconds).map_err(|e| env.error(e))?;
1163        }
1164        SysOp::TcpListen => {
1165            let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
1166            let handle = (env.rt.backend)
1167                .tcp_listen(&addr)
1168                .map_err(|e| env.error(e))?;
1169            let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
1170            let handle = handle.value(HandleKind::TcpListener(sock_addr));
1171            env.push(handle);
1172        }
1173        SysOp::TlsListen => {
1174            let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
1175            let cert = env
1176                .pop(2)?
1177                .into_bytes(env, "Cert must be a byte or character array")?;
1178            let key = env
1179                .pop(3)?
1180                .into_bytes(env, "Key must be a byte or character array")?;
1181            let handle = (env.rt.backend)
1182                .tls_listen(&addr, &cert, &key)
1183                .map_err(|e| env.error(e))?;
1184            let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
1185            let handle = handle.value(HandleKind::TlsListener(sock_addr));
1186            env.push(handle);
1187        }
1188        SysOp::TcpAccept => {
1189            let handle = env.pop(1)?.as_handle(env, None)?;
1190            let handle = (env.rt.backend)
1191                .tcp_accept(handle)
1192                .map_err(|e| env.error(e))?;
1193            let addr = (env.rt.backend)
1194                .tcp_addr(handle)
1195                .map_err(|e| env.error(e))?;
1196            let handle = handle.value(HandleKind::TcpSocket(addr));
1197            env.push(handle);
1198        }
1199        SysOp::TcpConnect => {
1200            let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
1201            let handle = (env.rt.backend)
1202                .tcp_connect(&addr)
1203                .map_err(|e| env.error(e))?;
1204            let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
1205            let handle = handle.value(HandleKind::TcpSocket(sock_addr));
1206            env.push(handle);
1207        }
1208        SysOp::TlsConnect => {
1209            let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
1210            let handle = (env.rt.backend)
1211                .tls_connect(&addr)
1212                .map_err(|e| env.error(e))?;
1213            let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
1214            let handle = handle.value(HandleKind::TlsSocket(sock_addr));
1215            env.push(handle);
1216        }
1217        SysOp::TcpAddr => {
1218            let handle = env.pop(1)?.as_handle(env, None)?;
1219            let addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
1220            env.push(addr.to_string());
1221        }
1222        SysOp::TcpSetNonBlocking => {
1223            let handle = env.pop(1)?.as_handle(env, None)?;
1224            (env.rt.backend)
1225                .tcp_set_non_blocking(handle, true)
1226                .map_err(|e| env.error(e))?;
1227        }
1228        SysOp::TcpSetReadTimeout => {
1229            let timeout = env.pop(1)?.as_num(env, "Timeout must be a number")?.abs();
1230            let timeout = if timeout.is_infinite() {
1231                None
1232            } else {
1233                Some(Duration::from_secs_f64(timeout))
1234            };
1235            let handle = env.pop(2)?.as_handle(env, None)?;
1236            (env.rt.backend)
1237                .tcp_set_read_timeout(handle, timeout)
1238                .map_err(|e| env.error(e))?;
1239        }
1240        SysOp::TcpSetWriteTimeout => {
1241            let timeout = env.pop(1)?.as_num(env, "Timeout must be a number")?.abs();
1242            let timeout = if timeout.is_infinite() {
1243                None
1244            } else {
1245                Some(Duration::from_secs_f64(timeout))
1246            };
1247            let handle = env.pop(2)?.as_handle(env, None)?;
1248            (env.rt.backend)
1249                .tcp_set_write_timeout(handle, timeout)
1250                .map_err(|e| env.error(e))?;
1251        }
1252        SysOp::Fetch => {
1253            let url = env.pop(1)?.as_string(env, "Url must be a string")?;
1254            let bytes = env.rt.backend.fetch(&url).map_err(|e| env.error(e))?;
1255            env.push(bytes);
1256        }
1257        SysOp::UdpBind => {
1258            let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
1259            let handle = (env.rt.backend).udp_bind(&addr).map_err(|e| env.error(e))?;
1260            let handle = handle.value(HandleKind::UdpSocket(addr));
1261            env.push(handle)
1262        }
1263        SysOp::UdpReceive => {
1264            let handle = env.pop(1)?.as_handle(env, None)?;
1265            let (bytes, addr) = (env.rt.backend)
1266                .udp_recv(handle)
1267                .map_err(|e| env.error(e))?;
1268            env.push(addr.to_string());
1269            env.push(Array::from(bytes.as_slice()));
1270        }
1271        SysOp::UdpSend => {
1272            let bytes = env.pop(1)?;
1273            let bytes = bytes.as_bytes(env, "Datagram must be bytes")?;
1274            let addr = env.pop(2)?.as_string(env, "Address must be a string")?;
1275            let handle = env.pop(3)?.as_handle(env, None)?;
1276            (env.rt.backend)
1277                .udp_send(handle, &bytes, &addr)
1278                .map_err(|e| env.error(e))?;
1279        }
1280        SysOp::UdpSetMaxMsgLength => {
1281            let length = env
1282                .pop(1)?
1283                .as_nat(env, "Message length must be a natural number")?;
1284            let handle = env.pop(2)?.as_handle(env, None)?;
1285            (env.rt.backend)
1286                .udp_set_max_msg_length(handle, length)
1287                .map_err(|e| env.error(e))?;
1288        }
1289        SysOp::Close => {
1290            let handle = env.pop(1)?.as_handle(env, None)?;
1291            env.rt.backend.close(handle).map_err(|e| env.error(e))?;
1292        }
1293        SysOp::RunInherit => {
1294            let (command, args) = value_to_command(&env.pop(1)?, env)?;
1295            let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
1296            let code = (env.rt.backend)
1297                .run_command_inherit(&command, &args)
1298                .map_err(|e| env.error(e))?;
1299            env.push(code);
1300        }
1301        SysOp::RunCapture => {
1302            let (command, args) = value_to_command(&env.pop(1)?, env)?;
1303            let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
1304            let (code, stdout, stderr) = (env.rt.backend)
1305                .run_command_capture(&command, &args)
1306                .map_err(|e| env.error(e))?;
1307            env.push(stderr);
1308            env.push(stdout);
1309            env.push(code);
1310        }
1311        SysOp::RunStream => {
1312            let (command, args) = value_to_command(&env.pop(1)?, env)?;
1313            let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
1314            let handles = (env.rt.backend)
1315                .run_command_stream(&command, &args)
1316                .map_err(|e| env.error(e))?;
1317            for (handle, kind) in handles
1318                .into_iter()
1319                .zip([
1320                    HandleKind::ChildStdin,
1321                    HandleKind::ChildStdout,
1322                    HandleKind::ChildStderr,
1323                ])
1324                .rev()
1325            {
1326                env.push(handle.value(kind(command.clone())));
1327            }
1328        }
1329        SysOp::ChangeDirectory => {
1330            let path = env.pop(1)?.as_string(env, "Path must be a string")?;
1331            (env.rt.backend)
1332                .change_directory(&path)
1333                .map_err(|e| env.error(e))?;
1334        }
1335        SysOp::WebcamCapture => {
1336            let index = env.pop(1)?;
1337            let index = match index.as_nat(env, "Webcam selector must be an integer or string") {
1338                Ok(index) => index,
1339                Err(e) => {
1340                    let name = index.as_string(env, "").map_err(|_| e)?;
1341                    let names = env.rt.backend.webcam_list().map_err(|e| env.error(e))?;
1342                    (names.iter().position(|n| n == &name))
1343                        .ok_or_else(|| env.error(format!("Webcam {name:?} is not available")))?
1344                }
1345            };
1346            let _image = (env.rt.backend)
1347                .webcam_capture(index)
1348                .map_err(|e| env.error(e))?;
1349            #[cfg(feature = "image")]
1350            env.push(crate::media::rgb_image_to_array(_image));
1351            #[cfg(not(feature = "image"))]
1352            return Err(env.error("Webcam capture is not supported in this environment"));
1353        }
1354        SysOp::WebcamList => {
1355            let names = env.rt.backend.webcam_list().map_err(|e| env.error(e))?;
1356            let arr = Array::from_iter(names.into_iter().map(Into::into).map(Boxed));
1357            env.push(arr);
1358        }
1359        SysOp::Ffi => {
1360            let sig_def = env.pop(1)?;
1361            let sig_def = match sig_def {
1362                Value::Box(arr) => arr,
1363                val => {
1364                    return Err(env.error(format!(
1365                        "FFI signature must be a box array, but it is {}",
1366                        val.type_name_plural()
1367                    )));
1368                }
1369            };
1370            if sig_def.rank() != 1 {
1371                return Err(env.error(format!(
1372                    "FFI signature must be a rank 1 array, but it is rank {}",
1373                    sig_def.rank()
1374                )));
1375            }
1376            if sig_def.row_count() < 3 {
1377                return Err(env.error("FFI signature array must have at least two elements"));
1378            }
1379            let mut sig_frags = sig_def.data.into_iter().map(|b| b.0);
1380            let file_name =
1381                (sig_frags.next().unwrap()).as_string(env, "FFI file name must be a string")?;
1382            let result_ty = (sig_frags.next().unwrap())
1383                .as_string(env, "FFI result type must be a string")?
1384                .parse::<FfiType>()
1385                .map_err(|e| env.error(e))?;
1386            let name = (sig_frags.next().unwrap()).as_string(env, "FFI name must be a string")?;
1387            let arg_tys = sig_frags
1388                .map(|frag| {
1389                    frag.as_string(env, "FFI argument type must be a string")
1390                        .and_then(|ty| ty.parse::<FfiArg>().map_err(|e| env.error(e)))
1391                })
1392                .collect::<UiuaResult<Vec<_>>>()?;
1393            let args = env.pop(2)?;
1394            let args: Vec<Value> = args.into_rows().map(Value::unpacked).collect();
1395            let result = (env.rt.backend)
1396                .ffi(&file_name, result_ty, &name, &arg_tys, args)
1397                .map_err(|e| env.error(e))?;
1398            env.push(result);
1399        }
1400        SysOp::MemCopy => {
1401            let ptr = env
1402                .pop("pointer")?
1403                .meta
1404                .pointer
1405                .as_ref()
1406                .ok_or_else(|| env.error("Copied pointer must be a pointer value"))?
1407                .clone();
1408            let len = env
1409                .pop("length")?
1410                .as_nat(env, "Copied length must be a non-negative integer")?;
1411            let value = (env.rt.backend)
1412                .mem_copy(ptr, len)
1413                .map_err(|e| env.error(e))?;
1414            env.push(value);
1415        }
1416        SysOp::MemSet => {
1417            let ptr = env
1418                .pop("pointer")?
1419                .meta
1420                .pointer
1421                .as_ref()
1422                .ok_or_else(|| env.error("Target pointer must be a pointer value"))?
1423                .clone();
1424            let idx = env
1425                .pop("index")?
1426                .as_nat(env, "Target index must be a non-negative integer")?;
1427            let value = env.pop(3)?;
1428            (env.rt.backend)
1429                .mem_set(ptr, idx, value)
1430                .map_err(|e| env.error(e))?;
1431        }
1432        SysOp::MemFree => {
1433            let val = env.pop(1)?;
1434            let ptr = val
1435                .meta
1436                .pointer
1437                .as_ref()
1438                .ok_or_else(|| env.error("Freed pointer must be a pointer value"))?;
1439
1440            (env.rt.backend).mem_free(ptr).map_err(|e| env.error(e))?;
1441        }
1442        SysOp::Breakpoint => {
1443            if !env.rt.backend.breakpoint(env).map_err(|e| env.error(e))? {
1444                return Err(UiuaErrorKind::Interrupted.into());
1445            }
1446        }
1447        prim => {
1448            return Err(env.error(if prim.modifier_args().is_some() {
1449                format!(
1450                    "{} was not handled as a modifier. \
1451                        This is a bug in the interpreter",
1452                    Primitive::Sys(*prim)
1453                )
1454            } else {
1455                format!(
1456                    "{} was not handled as a function. \
1457                        This is a bug in the interpreter",
1458                    Primitive::Sys(*prim)
1459                )
1460            }));
1461        }
1462    }
1463    Ok(())
1464}
1465pub(crate) fn run_sys_op_mod(op: &SysOp, ops: Ops, env: &mut Uiua) -> UiuaResult {
1466    match op {
1467        SysOp::ReadLines => {
1468            let [f] = get_ops(ops, env)?;
1469            let handle = env.pop(1)?.as_handle(env, None)?;
1470            let mut read_lines = env
1471                .rt
1472                .backend
1473                .read_lines(handle)
1474                .map_err(|e| env.error(e))?;
1475            let sig = f.sig;
1476            if sig.args() == 0 {
1477                return env.exec(f);
1478            }
1479            let acc_count = sig.args().saturating_sub(1);
1480            let out_count = sig.outputs().saturating_sub(acc_count);
1481            let mut outputs = multi_output(out_count, Vec::new());
1482            env.without_fill(|env| {
1483                read_lines(
1484                    env,
1485                    Box::new(|s, env| {
1486                        let val = Value::from(s);
1487                        env.push(val);
1488                        env.exec(f.clone())?;
1489                        for i in 0..out_count {
1490                            outputs[i].push(env.pop("read lines output")?);
1491                        }
1492                        Ok(())
1493                    }),
1494                )
1495            })?;
1496            for rows in outputs.into_iter().rev() {
1497                let val = Value::from_row_values(rows, env)?;
1498                env.push(val);
1499            }
1500        }
1501        SysOp::AudioStream => {
1502            let [f] = get_ops(ops, env)?;
1503            let push_time = f.sig.args() > 0;
1504            if f.sig != (0, 1) && f.sig.args() != f.sig.outputs() {
1505                return Err(env.error(format!(
1506                    "&ast's function must have the same number \
1507                        of inputs and outputs, but its signature is {}",
1508                    f.sig
1509                )));
1510            }
1511            if f.sig == (0, 0) {
1512                return Ok(());
1513            }
1514            let mut stream_env = env.clone();
1515            let res = env.rt.backend.stream_audio(Box::new(move |time_array| {
1516                if push_time {
1517                    let time_array = Array::<f64>::from(time_array);
1518                    stream_env.push(time_array);
1519                }
1520                stream_env.exec(f.clone())?;
1521                let samples = &stream_env.pop(1)?;
1522                let samples = samples.as_num_array().ok_or_else(|| {
1523                    stream_env.error("Audio stream function must return a numeric array")
1524                })?;
1525                match &*samples.shape {
1526                    [_] => Ok(samples.data.iter().map(|&x| [x, x]).collect()),
1527                    &[n, 2] => {
1528                        let mut samps: Vec<[f64; 2]> = Vec::with_capacity(n);
1529                        for samp in samples.data.chunks_exact(2) {
1530                            samps.push([samp[0], samp[1]]);
1531                        }
1532                        Ok(samps)
1533                    }
1534                    &[2, n] => {
1535                        let mut samps: Vec<[f64; 2]> = Vec::with_capacity(n);
1536                        for i in 0..n {
1537                            samps.push([samples.data[i], samples.data[i + n]]);
1538                        }
1539                        Ok(samps)
1540                    }
1541                    _ => Err(stream_env.error(format!(
1542                        "Audio stream function must return either a \
1543                            rank 1 array or a rank 2 array with 2 rows, \
1544                            but its shape is {}",
1545                        samples.shape
1546                    ))),
1547                }
1548            }));
1549            res.map_err(|e| env.error(e))?;
1550        }
1551        prim => {
1552            return Err(env.error(if prim.modifier_args().is_some() {
1553                format!(
1554                    "{} was not handled as a modifier. \
1555                        This is a bug in the interpreter",
1556                    Primitive::Sys(*prim)
1557                )
1558            } else {
1559                format!(
1560                    "{} was handled as a modifier. \
1561                        This is a bug in the interpreter",
1562                    Primitive::Sys(*prim)
1563                )
1564            }));
1565        }
1566    }
1567    Ok(())
1568}
1569
1570fn value_to_command(value: &Value, env: &Uiua) -> UiuaResult<(String, Vec<String>)> {
1571    let mut strings = Vec::new();
1572    match value {
1573        Value::Char(arr) => match arr.rank() {
1574            0 | 1 => strings.push(arr.data.iter().collect::<String>()),
1575            2 => {
1576                for row in arr.rows() {
1577                    strings.push(row.data.iter().collect::<String>());
1578                }
1579            }
1580            n => {
1581                return Err(env.error(format!(
1582                    "Character array as command must be rank 0, 1, \
1583                    or 2, but its rank is {n}"
1584                )));
1585            }
1586        },
1587        Value::Box(arr) => match arr.rank() {
1588            0 | 1 => {
1589                for Boxed(val) in &arr.data {
1590                    match val {
1591                        Value::Char(arr) if arr.rank() <= 1 => {
1592                            strings.push(arr.data.iter().collect::<String>())
1593                        }
1594                        val => {
1595                            return Err(env.error(format!(
1596                                "Function array as command must be all boxed strings, \
1597                                but at least one is a {}",
1598                                val.type_name()
1599                            )));
1600                        }
1601                    }
1602                }
1603            }
1604            n => {
1605                return Err(env.error(format!(
1606                    "Function array as command must be rank 0 or 1, \
1607                    but its rank is {n}"
1608                )));
1609            }
1610        },
1611        val => {
1612            return Err(env.error(format!(
1613                "Command must be a string or box array, but it is {}",
1614                val.type_name_plural()
1615            )));
1616        }
1617    }
1618    if strings.is_empty() {
1619        return Err(env.error("Command array not be empty"));
1620    }
1621    let command = strings.remove(0);
1622    Ok((command, strings))
1623}
1624
1625/// Get the current time in seconds
1626///
1627/// This function works on both native and web targets.
1628pub fn now() -> f64 {
1629    #[cfg(not(target_arch = "wasm32"))]
1630    {
1631        std::time::SystemTime::now()
1632            .duration_since(std::time::SystemTime::UNIX_EPOCH)
1633            .expect("System clock was before 1970.")
1634            .as_secs_f64()
1635    }
1636    #[cfg(target_arch = "wasm32")]
1637    {
1638        #[cfg(not(feature = "web"))]
1639        {
1640            compile_error!("Web target requires the `web` feature")
1641        }
1642        #[cfg(feature = "web")]
1643        {
1644            use wasm_bindgen::{JsCast, prelude::*};
1645            js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("performance"))
1646                .expect("failed to get performance from global object")
1647                .unchecked_into::<web_sys::Performance>()
1648                .now()
1649                / 1000.0
1650        }
1651    }
1652}
1653
1654pub(crate) fn terminal_size() -> Option<(usize, usize)> {
1655    #[cfg(all(not(target_arch = "wasm32"), feature = "terminal_size"))]
1656    {
1657        terminal_size::terminal_size().map(|(w, h)| (w.0 as usize, h.0 as usize))
1658    }
1659
1660    #[cfg(not(all(not(target_arch = "wasm32"), feature = "terminal_size")))]
1661    None
1662}