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
30pub 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
43pub const EXAMPLE_TXT: &str = "\
45This is a simple text file for
46use in example Uiua code ✨";
47
48pub 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
54pub 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#[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 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 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#[cfg(not(target_arch = "wasm32"))]
117pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + Send + 'a>;
118#[cfg(target_arch = "wasm32")]
120pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + 'a>;
121
122#[cfg(not(target_arch = "wasm32"))]
124pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + Send + 'a>;
125#[cfg(target_arch = "wasm32")]
127pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + 'a>;
128
129#[cfg(not(target_arch = "wasm32"))]
131pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>> + Send>;
132#[cfg(target_arch = "wasm32")]
134pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>>>;
135
136#[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
167pub enum StreamSeek {
170 Start(usize),
172 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#[allow(unused_variables)]
192pub trait SysBackend: Any + Send + Sync + 'static {
193 fn any(&self) -> &dyn Any;
195 fn any_mut(&mut self) -> &mut dyn Any;
197 fn save_error_color(&self, message: String, colored: String) {}
199 fn output_enabled(&self) -> bool {
201 true
202 }
203 fn set_output_enabled(&self, enabled: bool) -> bool {
209 true
210 }
211 fn print_str_stdout(&self, s: &str) -> Result<(), String> {
213 Err("Printing to stdout is not supported in this environment".into())
214 }
215 fn print_str_stderr(&self, s: &str) -> Result<(), String> {
217 Err("Printing to stderr is not supported in this environment".into())
218 }
219 fn print_str_trace(&self, s: &str) {}
221 fn show(&self, value: Value) -> Result<(), String> {
223 self.print_str_stdout(&format!("{}\n", value.show()))
224 }
225 fn scan_line_stdin(&self) -> Result<Option<String>, String> {
229 Err("Reading from stdin is not supported in this environment".into())
230 }
231 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 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 fn set_raw_mode(&self, raw_mode: bool) -> Result<(), String> {
254 Err("Setting raw mode is not supported in this environment".into())
255 }
256 fn get_raw_mode(&self) -> Result<bool, String> {
258 Err("Getting raw mode is not supported in this environment".into())
259 }
260 fn var(&self, name: &str) -> Option<String> {
262 None
263 }
264 fn term_size(&self) -> Result<(usize, usize), String> {
266 Err("Getting the terminal size is not supported in this environment".into())
267 }
268 fn exit(&self, status: i32) -> Result<(), String> {
270 Err("Exiting is not supported in this environment".into())
271 }
272 fn file_exists(&self, path: &str) -> bool {
274 false
275 }
276 fn list_dir(&self, path: &str) -> Result<Vec<String>, String> {
278 Err("Listing directories is not supported in this environment".into())
279 }
280 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 fn delete(&self, path: &str) -> Result<(), String> {
286 Err("Deleting files is not supported in this environment".into())
287 }
288 fn trash(&self, path: &str) -> Result<(), String> {
290 Err("Trashing files is not supported in this environment".into())
291 }
292 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 fn read_all(&self, handle: Handle) -> Result<Vec<u8>, String> {
298 Err("Reading from streams is not supported in this environment".into())
299 }
300 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 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 fn write(&self, handle: Handle, contents: &[u8]) -> Result<(), String> {
321 Err("Writing to streams is not supported in this environment".into())
322 }
323 fn seek(&self, handle: Handle, offset: StreamSeek) -> Result<(), String> {
325 Err("Seeking streams is not supported in this environment".into())
326 }
327 fn create_file(&self, path: &Path) -> Result<Handle, String> {
329 Err("Creating files is not supported in this environment".into())
330 }
331 fn open_file(&self, path: &Path, write: bool) -> Result<Handle, String> {
333 Err("Opening files is not supported in this environment".into())
334 }
335 fn make_dir(&self, path: &Path) -> Result<(), String> {
337 Err("Creating directories is not supported in this environment".into())
338 }
339 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 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 fn clipboard(&self) -> Result<String, String> {
355 Err("Getting the clipboard is not supported in this environment".into())
356 }
357 fn set_clipboard(&self, contents: &str) -> Result<(), String> {
359 Err("Setting the clipboard is not supported in this environment".into())
360 }
361 fn sleep(&self, seconds: f64) -> Result<(), String> {
363 Err("Sleeping is not supported in this environment".into())
364 }
365 fn allow_thread_spawning(&self) -> bool {
367 false
368 }
369 #[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 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 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 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 fn audio_sample_rate(&self) -> u32 {
388 44100
389 }
390 fn stream_audio(&self, f: AudioStreamFn) -> Result<(), String> {
392 Err("Streaming audio is not supported in this environment".into())
393 }
394 fn now(&self) -> f64 {
398 now()
399 }
400 fn tcp_listen(&self, addr: &str) -> Result<Handle, String> {
402 Err("TCP listeners are not supported in this environment".into())
403 }
404 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 fn tcp_accept(&self, handle: Handle) -> Result<Handle, String> {
410 Err("TCP listeners are not supported in this environment".into())
411 }
412 fn tcp_connect(&self, addr: &str) -> Result<Handle, String> {
414 Err("TCP sockets are not supported in this environment".into())
415 }
416 fn tls_connect(&self, addr: &str) -> Result<Handle, String> {
418 Err("TLS sockets are not supported in this environment".into())
419 }
420 fn tcp_addr(&self, handle: Handle) -> Result<SocketAddr, String> {
422 Err("TCP sockets are not supported in this environment".into())
423 }
424 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 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 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 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 fn udp_bind(&self, addr: &str) -> Result<Handle, String> {
523 Err("UDP sockets are not supported in this environment".into())
524 }
525 fn udp_recv(&self, handle: Handle) -> Result<(Vec<u8>, SocketAddr), String> {
527 Err("UDP sockets are not supported in this environment".into())
528 }
529 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 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 fn close(&self, handle: Handle) -> Result<(), String> {
539 Ok(())
540 }
541 fn invoke(&self, path: &str) -> Result<(), String> {
543 Err("Invoking paths is not supported in this environment".into())
544 }
545 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 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 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 fn change_directory(&self, path: &str) -> Result<(), String> {
563 Err("Changing directories is not supported in this environment".into())
564 }
565 fn get_current_directory(&self) -> Result<String, String> {
567 Err("Getting the current directory is not supported in this environment".into())
568 }
569 fn webcam_capture(&self, index: usize) -> Result<WebcamImage, String> {
571 Err("Capturing from webcam is not supported in this environment".into())
572 }
573 fn webcam_list(&self) -> Result<Vec<String>, String> {
575 Err("Listing webcams is not supported in this environment".into())
576 }
577 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 fn mem_copy(&self, ptr: MetaPtr, len: usize) -> Result<Value, String> {
590 Err("Pointer copying is not supported in this environment".into())
591 }
592 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 fn mem_free(&self, ptr: &MetaPtr) -> Result<(), String> {
598 Err("Pointer freeing is not supported in this environment".into())
599 }
600 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 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 fn breakpoint(&self, env: &Uiua) -> Result<bool, String> {
622 Err("Breakpoints are not supported in this environment".into())
623 }
624 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#[derive(Debug, Clone, Default)]
632pub enum GitTarget {
633 #[default]
635 Default,
636 Branch(String),
638 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#[derive(Default)]
650pub struct SafeSys {
651 stdout: Arc<Mutex<Vec<u8>>>,
652 stderr: Arc<Mutex<Vec<u8>>>,
653 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 pub fn new() -> Self {
683 Self::default()
684 }
685 pub fn with_thread_spawning() -> Self {
687 Self {
688 allow_thread_spawning: true,
689 ..Self::default()
690 }
691 }
692 pub fn take_stdout(&self) -> Vec<u8> {
694 take(&mut *self.stdout.lock())
695 }
696 pub fn take_stderr(&self) -> Vec<u8> {
698 take(&mut *self.stderr.lock())
699 }
700}
701
702pub trait IntoSysBackend {
704 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
1625pub 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}