#[cfg(feature = "native_sys")]
pub(crate) mod native;
use std::{
any::Any,
borrow::Cow,
fmt,
io::stdin,
mem::take,
net::SocketAddr,
path::{Path, PathBuf},
sync::{Arc, LazyLock},
time::Duration,
};
#[cfg(feature = "image")]
use image::DynamicImage;
use parking_lot::Mutex;
use time::UtcOffset;
#[cfg(feature = "native_sys")]
pub use self::native::*;
use crate::{
Array, BigConstant, Boxed, FfiArg, FfiType, MetaPtr, Ops, Primitive, SysOp, Uiua,
UiuaErrorKind, UiuaResult, Value,
algorithm::{multi_output, validate_size},
cowslice::cowslice,
get_ops,
};
pub const EXAMPLE_UA: &str = "\
# Uiua's example module
Square ← ˙×
Double ← ˙+
Increment ← +1
RangeDiff ↚ ⇡-
Span ← +⟜RangeDiff
Mac! ← /^0 [1 2 3 4 5]
Foo ← 5
Bar ← \"bar\"";
pub const EXAMPLE_TXT: &str = "\
This is a simple text file for
use in example Uiua code ✨";
pub fn example_ua<T>(f: impl FnOnce(&mut String) -> T) -> T {
static S: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(EXAMPLE_UA.to_string()));
f(&mut S.lock())
}
pub fn example_txt<T>(f: impl FnOnce(&mut String) -> T) -> T {
static S: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(EXAMPLE_TXT.to_string()));
f(&mut S.lock())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Handle(pub u64);
impl Handle {
const STDIN: Self = Self(0);
const STDOUT: Self = Self(1);
const STDERR: Self = Self(2);
pub const FIRST_UNRESERVED: Self = Self(3);
}
impl From<usize> for Handle {
fn from(n: usize) -> Self {
Self(n as u64)
}
}
impl Handle {
pub(crate) fn value(self, kind: HandleKind) -> Value {
let mut arr = Array::from(self.0 as f64);
arr.meta.handle_kind = Some(kind);
Boxed(arr.into()).into()
}
}
impl Value {
pub fn as_handle(
&self,
env: &Uiua,
requirement: impl Into<Option<&'static str>>,
) -> UiuaResult<Handle> {
let requirement = requirement
.into()
.unwrap_or("Expected value to be a stream handle");
match self {
Value::Box(b) => {
if let Some(b) = b.as_scalar() {
b.0.as_nat(env, requirement).map(|h| Handle(h as u64))
} else {
Err(env.error(format!("{requirement}, but it is rank {}", b.rank())))
}
}
value => value.as_nat(env, requirement).map(|h| Handle(h as u64)),
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + Send + 'a>;
#[cfg(target_arch = "wasm32")]
pub type ReadLinesFn<'a> = Box<dyn FnMut(String, &mut Uiua) -> UiuaResult + 'a>;
#[cfg(not(target_arch = "wasm32"))]
pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + Send + 'a>;
#[cfg(target_arch = "wasm32")]
pub type ReadLinesReturnFn<'a> = Box<dyn FnMut(&mut Uiua, ReadLinesFn) -> UiuaResult + 'a>;
#[cfg(not(target_arch = "wasm32"))]
pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>> + Send>;
#[cfg(target_arch = "wasm32")]
pub type AudioStreamFn = Box<dyn FnMut(&[f64]) -> UiuaResult<Vec<[f64; 2]>>>;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(missing_docs)]
pub enum HandleKind {
File(PathBuf),
TcpListener(SocketAddr),
TlsListener(SocketAddr),
TcpSocket(SocketAddr),
TlsSocket(SocketAddr),
UdpSocket(String),
ChildStdin(String),
ChildStdout(String),
ChildStderr(String),
}
impl fmt::Display for HandleKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(path) => write!(f, "file {}", path.display()),
Self::TcpListener(addr) => write!(f, "tcp listener {addr}"),
Self::TlsListener(addr) => write!(f, "tls listener {addr}"),
Self::TcpSocket(addr) => write!(f, "tcp socket {addr}"),
Self::TlsSocket(addr) => write!(f, "tls socket {addr}"),
Self::UdpSocket(addr) => write!(f, "udp socket {addr}"),
Self::ChildStdin(com) => write!(f, "stdin {com}"),
Self::ChildStdout(com) => write!(f, "stdout {com}"),
Self::ChildStderr(com) => write!(f, "stderr {com}"),
}
}
}
pub enum StreamSeek {
Start(usize),
End(usize),
}
impl From<StreamSeek> for std::io::SeekFrom {
fn from(value: StreamSeek) -> Self {
match value {
StreamSeek::Start(off) => std::io::SeekFrom::Start(off as u64),
StreamSeek::End(off) => std::io::SeekFrom::End(-(off as i64)),
}
}
}
#[cfg(feature = "image")]
pub(crate) type WebcamImage = image::RgbImage;
#[cfg(not(feature = "image"))]
pub(crate) type WebcamImage = ();
#[allow(unused_variables)]
pub trait SysBackend: Any + Send + Sync + 'static {
fn any(&self) -> &dyn Any;
fn any_mut(&mut self) -> &mut dyn Any;
fn save_error_color(&self, message: String, colored: String) {}
fn output_enabled(&self) -> bool {
true
}
fn set_output_enabled(&self, enabled: bool) -> bool {
true
}
fn print_str_stdout(&self, s: &str) -> Result<(), String> {
Err("Printing to stdout is not supported in this environment".into())
}
fn print_str_stderr(&self, s: &str) -> Result<(), String> {
Err("Printing to stderr is not supported in this environment".into())
}
fn print_str_trace(&self, s: &str) {}
fn show(&self, value: Value) -> Result<(), String> {
self.print_str_stdout(&format!("{}\n", value.show()))
}
fn scan_line_stdin(&self) -> Result<Option<String>, String> {
Err("Reading from stdin is not supported in this environment".into())
}
fn scan_stdin(&self, count: Option<usize>) -> Result<Vec<u8>, String> {
Err("Reading from stdin is not supported in this environment".into())
}
fn scan_until_stdin(&self, delim: &[u8]) -> Result<Vec<u8>, String> {
let mut buffer = Vec::new();
loop {
let bytes = self.scan_stdin(Some(1))?;
if bytes.is_empty() {
break;
}
buffer.extend_from_slice(&bytes);
if buffer.ends_with(delim) {
break;
}
}
Ok(buffer)
}
fn set_raw_mode(&self, raw_mode: bool) -> Result<(), String> {
Err("Setting raw mode is not supported in this environment".into())
}
fn get_raw_mode(&self) -> Result<bool, String> {
Err("Getting raw mode is not supported in this environment".into())
}
fn var(&self, name: &str) -> Option<String> {
None
}
fn term_size(&self) -> Result<(usize, usize), String> {
Err("Getting the terminal size is not supported in this environment".into())
}
fn exit(&self, status: i32) -> Result<(), String> {
Err("Exiting is not supported in this environment".into())
}
fn file_exists(&self, path: &str) -> bool {
false
}
fn list_dir(&self, path: &str) -> Result<Vec<String>, String> {
Err("Listing directories is not supported in this environment".into())
}
fn is_file(&self, path: &str) -> Result<bool, String> {
Err("Checking if a path is a file is not supported in this environment".into())
}
fn delete(&self, path: &str) -> Result<(), String> {
Err("Deleting files is not supported in this environment".into())
}
fn trash(&self, path: &str) -> Result<(), String> {
Err("Trashing files is not supported in this environment".into())
}
fn read(&self, handle: Handle, count: usize) -> Result<Vec<u8>, String> {
Err("Reading from streams is not supported in this environment".into())
}
fn read_all(&self, handle: Handle) -> Result<Vec<u8>, String> {
Err("Reading from streams is not supported in this environment".into())
}
fn read_until(&self, handle: Handle, delim: &[u8]) -> Result<Vec<u8>, String> {
let mut buffer = Vec::new();
loop {
let bytes = self.read(handle, 1)?;
if bytes.is_empty() {
break;
}
buffer.extend_from_slice(&bytes);
if buffer.ends_with(delim) {
break;
}
}
Ok(buffer)
}
fn read_lines<'a>(&self, handle: Handle) -> Result<ReadLinesReturnFn<'a>, String> {
Err("Reading from streams is not supported in this environment".into())
}
fn write(&self, handle: Handle, contents: &[u8]) -> Result<(), String> {
Err("Writing to streams is not supported in this environment".into())
}
fn seek(&self, handle: Handle, offset: StreamSeek) -> Result<(), String> {
Err("Seeking streams is not supported in this environment".into())
}
fn create_file(&self, path: &Path) -> Result<Handle, String> {
Err("Creating files is not supported in this environment".into())
}
fn open_file(&self, path: &Path, write: bool) -> Result<Handle, String> {
Err("Opening files is not supported in this environment".into())
}
fn make_dir(&self, path: &Path) -> Result<(), String> {
Err("Creating directories is not supported in this environment".into())
}
fn file_read_all(&self, path: &Path) -> Result<Vec<u8>, String> {
let handle = self.open_file(path, false)?;
let bytes = self.read(handle, usize::MAX)?;
self.close(handle)?;
Ok(bytes)
}
fn file_write_all(&self, path: &Path, contents: &[u8]) -> Result<(), String> {
let handle = self.create_file(path)?;
self.write(handle, contents)?;
self.close(handle)?;
Ok(())
}
fn clipboard(&self) -> Result<String, String> {
Err("Getting the clipboard is not supported in this environment".into())
}
fn set_clipboard(&self, contents: &str) -> Result<(), String> {
Err("Setting the clipboard is not supported in this environment".into())
}
fn sleep(&self, seconds: f64) -> Result<(), String> {
Err("Sleeping is not supported in this environment".into())
}
fn allow_thread_spawning(&self) -> bool {
false
}
#[cfg(feature = "image")]
fn show_image(&self, image: DynamicImage, label: Option<&str>) -> Result<(), String> {
Err("Showing images is not supported in this environment".into())
}
fn show_gif(&self, gif_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
Err("Showing gifs is not supported in this environment".into())
}
fn show_apng(&self, apng_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
Err("Showing APNGs is not supported in this environment".into())
}
fn play_audio(&self, wave_bytes: Vec<u8>, label: Option<&str>) -> Result<(), String> {
Err("Playing audio is not supported in this environment".into())
}
fn audio_sample_rate(&self) -> u32 {
44100
}
fn stream_audio(&self, f: AudioStreamFn) -> Result<(), String> {
Err("Streaming audio is not supported in this environment".into())
}
fn now(&self) -> f64 {
now()
}
fn tcp_listen(&self, addr: &str) -> Result<Handle, String> {
Err("TCP listeners are not supported in this environment".into())
}
fn tls_listen(&self, addr: &str, cert: &[u8], key: &[u8]) -> Result<Handle, String> {
Err("TLS listeners are not supported in this environment".into())
}
fn tcp_accept(&self, handle: Handle) -> Result<Handle, String> {
Err("TCP listeners are not supported in this environment".into())
}
fn tcp_connect(&self, addr: &str) -> Result<Handle, String> {
Err("TCP sockets are not supported in this environment".into())
}
fn tls_connect(&self, addr: &str) -> Result<Handle, String> {
Err("TLS sockets are not supported in this environment".into())
}
fn tcp_addr(&self, handle: Handle) -> Result<SocketAddr, String> {
Err("TCP sockets are not supported in this environment".into())
}
fn tcp_set_non_blocking(&self, handle: Handle, non_blocking: bool) -> Result<(), String> {
Err("TCP sockets are not supported in this environment".into())
}
fn tcp_set_read_timeout(
&self,
handle: Handle,
timeout: Option<Duration>,
) -> Result<(), String> {
Err("TCP sockets are not supported in this environment".into())
}
fn tcp_set_write_timeout(
&self,
handle: Handle,
timeout: Option<Duration>,
) -> Result<(), String> {
Err("TCP sockets are not supported in this environment".into())
}
fn fetch(&self, url: &str) -> Result<Vec<u8>, String> {
let is_https = url.starts_with("https://");
let is_http = url.starts_with("http://");
let no_protocol = url
.trim_start_matches("https://")
.trim_start_matches("http://");
let (host, route) = match no_protocol.find('/') {
Some(i) => no_protocol.split_at(i),
None => (no_protocol, "/"),
};
let (host_only, port, use_tls) = match host.rsplit_once(':') {
Some((h, p)) if p.parse::<u16>().is_ok() => (h, p, is_https || !is_http),
_ => {
let tls = is_https || !is_http;
let port = if tls { "443" } else { "80" };
(host, port, tls)
}
};
let addr = format!("{host_only}:{port}");
let default_port = if use_tls { "443" } else { "80" };
let host_header = if port == default_port {
host_only.to_owned()
} else {
format!("{host_only}:{port}")
};
let req = format!(
"\
GET {route} HTTP/1.1\r\n\
Host: {host_header}\r\n\
Connection: close\r\n\
User-Agent: Uiua/{}\r\n\
\r\n",
crate::VERSION
);
let handle = if use_tls {
self.tls_connect(&addr)?
} else {
self.tcp_connect(&addr)?
};
self.write(handle, req.as_bytes())?;
let mut bytes = match self.read_all(handle) {
Ok(b) => b,
Err(e) => {
if e.contains("close_notify") || e.contains("UnexpectedEof") {
return Err("Connection closed without close_notify".into());
} else {
return Err(e);
}
}
};
let _ = self.close(handle);
if bytes.is_empty() {
return Err("Empty HTTP response".into());
}
let status_line = bytes.split(|&b| b == b'\n').next().unwrap();
let status = status_line
.split(|&b| b == b' ')
.nth(1)
.ok_or("Invalid HTTP response: missing status code")?;
let status_str = String::from_utf8_lossy(status);
if !status_str.starts_with('2') && !status_str.starts_with('3') {
return Err(format!("HTTP {status_str}"));
}
let body_start = bytes
.windows(4)
.position(|w| w == b"\r\n\r\n")
.map(|i| i + 4)
.or(bytes.windows(2).position(|w| w == b"\n\n").map(|i| i + 2))
.ok_or("Invalid HTTP response: missing header separator")?;
bytes.rotate_left(body_start);
bytes.truncate(bytes.len() - body_start);
Ok(bytes)
}
fn udp_bind(&self, addr: &str) -> Result<Handle, String> {
Err("UDP sockets are not supported in this environment".into())
}
fn udp_recv(&self, handle: Handle) -> Result<(Vec<u8>, SocketAddr), String> {
Err("UDP sockets are not supported in this environment".into())
}
fn udp_send(&self, handle: Handle, packet: &[u8], addr: &str) -> Result<(), String> {
Err("UDP sockets are not supported in this environment".into())
}
fn udp_set_max_msg_length(&self, handle: Handle, max_len: usize) -> Result<(), String> {
Err("UDP sockets are not supported in this environment".into())
}
fn close(&self, handle: Handle) -> Result<(), String> {
Ok(())
}
fn invoke(&self, path: &str) -> Result<(), String> {
Err("Invoking paths is not supported in this environment".into())
}
fn run_command_inherit(&self, command: &str, args: &[&str]) -> Result<i32, String> {
Err("Running inheritting commands is not supported in this environment".into())
}
fn run_command_capture(
&self,
command: &str,
args: &[&str],
) -> Result<(i32, String, String), String> {
Err("Running capturing commands is not supported in this environment".into())
}
fn run_command_stream(&self, command: &str, args: &[&str]) -> Result<[Handle; 3], String> {
Err("Running streamed commands is not supported in this environment".into())
}
fn change_directory(&self, path: &str) -> Result<(), String> {
Err("Changing directories is not supported in this environment".into())
}
fn get_current_directory(&self) -> Result<String, String> {
Err("Getting the current directory is not supported in this environment".into())
}
fn webcam_capture(&self, index: usize) -> Result<WebcamImage, String> {
Err("Capturing from webcam is not supported in this environment".into())
}
fn webcam_list(&self) -> Result<Vec<String>, String> {
Err("Listing webcams is not supported in this environment".into())
}
fn ffi(
&self,
file: &str,
result_ty: FfiType,
name: &str,
arg_tys: &[FfiArg],
args: Vec<Value>,
) -> Result<Value, String> {
Err("FFI is not supported in this environment".into())
}
fn mem_copy(&self, ptr: MetaPtr, len: usize) -> Result<Value, String> {
Err("Pointer copying is not supported in this environment".into())
}
fn mem_set(&self, ptr: MetaPtr, idx: usize, value: Value) -> Result<(), String> {
Err("Pointer writing is not supported in this environment".into())
}
fn mem_free(&self, ptr: &MetaPtr) -> Result<(), String> {
Err("Pointer freeing is not supported in this environment".into())
}
fn mem_allocate(&self, size: usize) -> Result<Value, String> {
Err("Memory allocation is not supported in this environment".into())
}
fn load_git_module(&self, url: &str, target: GitTarget) -> Result<PathBuf, String> {
Err("Loading git modules is not supported in this environment".into())
}
fn timezone(&self) -> Result<f64, String> {
if cfg!(target_arch = "wasm32") {
return Err("Getting the timezone is not supported in this environment".into());
}
let offset = UtcOffset::current_local_offset().map_err(|e| e.to_string())?;
let (h, m, s) = offset.as_hms();
let mut o = h as f64;
o += m as f64 / 60.0;
o += s as f64 / 3600.0;
Ok(o)
}
fn breakpoint(&self, env: &Uiua) -> Result<bool, String> {
Err("Breakpoints are not supported in this environment".into())
}
fn big_constant(&self, key: BigConstant) -> Result<Cow<'static, [u8]>, String> {
Err("Retrieval of big constants is not supported in this environment".into())
}
}
#[derive(Debug, Clone, Default)]
pub enum GitTarget {
#[default]
Default,
Branch(String),
Commit(String),
}
impl fmt::Debug for dyn SysBackend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<sys backend>")
}
}
#[derive(Default)]
pub struct SafeSys {
stdout: Arc<Mutex<Vec<u8>>>,
stderr: Arc<Mutex<Vec<u8>>>,
pub allow_thread_spawning: bool,
}
impl SysBackend for SafeSys {
fn any(&self) -> &dyn Any {
self
}
fn any_mut(&mut self) -> &mut dyn Any {
self
}
fn print_str_stdout(&self, s: &str) -> Result<(), String> {
self.stdout.lock().extend_from_slice(s.as_bytes());
Ok(())
}
fn print_str_stderr(&self, s: &str) -> Result<(), String> {
self.stderr.lock().extend_from_slice(s.as_bytes());
Ok(())
}
fn allow_thread_spawning(&self) -> bool {
self.allow_thread_spawning
}
#[cfg(feature = "native_sys")]
fn big_constant(&self, key: BigConstant) -> Result<Cow<'static, [u8]>, String> {
NativeSys.big_constant(key)
}
}
impl SafeSys {
pub fn new() -> Self {
Self::default()
}
pub fn with_thread_spawning() -> Self {
Self {
allow_thread_spawning: true,
..Self::default()
}
}
pub fn take_stdout(&self) -> Vec<u8> {
take(&mut *self.stdout.lock())
}
pub fn take_stderr(&self) -> Vec<u8> {
take(&mut *self.stderr.lock())
}
}
pub trait IntoSysBackend {
fn into_sys_backend(self) -> Arc<dyn SysBackend>;
}
impl<T> IntoSysBackend for T
where
T: SysBackend + Send + Sync + 'static,
{
fn into_sys_backend(self) -> Arc<dyn SysBackend> {
Arc::new(self)
}
}
impl IntoSysBackend for Arc<dyn SysBackend> {
fn into_sys_backend(self) -> Arc<dyn SysBackend> {
self
}
}
pub(crate) fn run_sys_op(op: &SysOp, env: &mut Uiua) -> UiuaResult {
match op {
SysOp::Show => {
let val = env.pop(1)?;
env.rt.backend.show(val).map_err(|e| env.error(e))?;
}
SysOp::Prin => {
let s = env.pop(1)?.format();
(env.rt.backend)
.print_str_stdout(&s)
.map_err(|e| env.error(e))?;
}
SysOp::Print => {
let s = env.pop(1)?.format();
(env.rt.backend)
.print_str_stdout(&format!("{s}\n"))
.map_err(|e| env.error(e))?;
}
SysOp::PrinErr => {
let s = env.pop(1)?.format();
(env.rt.backend)
.print_str_stderr(&s)
.map_err(|e| env.error(e))?;
}
SysOp::PrintErr => {
let s = env.pop(1)?.format();
(env.rt.backend)
.print_str_stderr(&format!("{s}\n"))
.map_err(|e| env.error(e))?;
}
SysOp::ScanLine => {
let start = env.rt.backend.now();
let res = env.rt.backend.scan_line_stdin().map_err(|e| env.error(e));
env.rt.execution_start += env.rt.backend.now() - start;
if let Some(line) = res? {
env.push(line);
} else {
env.push(0u8);
}
}
SysOp::TermSize => {
let (width, height) = env.rt.backend.term_size().map_err(|e| env.error(e))?;
env.push(cowslice![height as f64, width as f64])
}
SysOp::Exit => {
let status = env.pop(1)?.as_int(env, "Status must be an integer")? as i32;
(env.rt.backend).exit(status).map_err(|e| env.error(e))?;
}
SysOp::RawMode => {
let raw_mode = env.pop(1)?.as_bool(env, "Raw mode must be a boolean")?;
(env.rt.backend)
.set_raw_mode(raw_mode)
.map_err(|e| env.error(e))?;
}
SysOp::EnvArgs => {
let mut args = Vec::new();
args.push(env.file_path().to_string_lossy().into_owned());
args.extend(env.args().to_owned());
env.push(Array::<Boxed>::from_iter(args));
}
SysOp::Var => {
let key = env
.pop(1)?
.as_string(env, "Augument to var must be a string")?;
let var = env
.rt
.backend
.var(&key)
.ok_or_else(|| env.error(format!("Environment variable `{key}` is not set")))?;
env.push(var);
}
SysOp::FOpen => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let handle = (env.rt.backend)
.open_file(path.as_ref(), true)
.map_err(|e| env.error(e))?
.value(HandleKind::File(path.into()));
env.push(handle);
}
SysOp::FCreate => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let handle: Value = (env.rt.backend)
.create_file(path.as_ref())
.map_err(|e| env.error(e))?
.value(HandleKind::File(path.into()));
env.push(handle);
}
SysOp::FMakeDir => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
(env.rt.backend)
.make_dir(path.as_ref())
.map_err(|e| env.error(e))?;
}
SysOp::FDelete => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
env.rt.backend.delete(&path).map_err(|e| env.error(e))?;
}
SysOp::FTrash => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
env.rt.backend.trash(&path).map_err(|e| env.error(e))?;
}
SysOp::ReadStr => {
let count = env
.pop(1)?
.as_nat_or_inf(env, "Count must be an integer or infinity")?;
if let Some(count) = count {
validate_size::<char>([count], env)?;
}
let handle = env.pop(2)?.as_handle(env, None)?;
let s = match handle {
Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
Handle::STDERR => return Err(env.error("Cannot read from stderr")),
Handle::STDIN => {
let buf = env.rt.backend.scan_stdin(count).map_err(|e| env.error(e))?;
match String::from_utf8(buf) {
Ok(s) => s,
Err(e) => {
let valid_to = e.utf8_error().valid_up_to();
let mut buf = e.into_bytes();
let mut rest = buf.split_off(valid_to);
for _ in 0..3 {
rest.extend(
env.rt
.backend
.scan_stdin(Some(1))
.map_err(|e| env.error(e))?,
);
if let Ok(s) = std::str::from_utf8(&rest) {
buf.extend_from_slice(s.as_bytes());
break;
}
}
String::from_utf8(buf).map_err(|e| env.error(e))?
}
}
}
_ => {
if let Some(count) = count {
let buf = env
.rt
.backend
.read(handle, count)
.map_err(|e| env.error(e))?;
match String::from_utf8(buf) {
Ok(s) => s,
Err(e) => {
let valid_to = e.utf8_error().valid_up_to();
let mut buf = e.into_bytes();
let mut rest = buf.split_off(valid_to);
for _ in 0..3 {
rest.extend(
env.rt.backend.read(handle, 1).map_err(|e| env.error(e))?,
);
if let Ok(s) = std::str::from_utf8(&rest) {
buf.extend_from_slice(s.as_bytes());
break;
}
}
String::from_utf8(buf).map_err(|e| env.error(e))?
}
}
} else {
let bytes = env.rt.backend.read_all(handle).map_err(|e| env.error(e))?;
String::from_utf8(bytes).map_err(|e| env.error(e))?
}
}
};
env.push(s);
}
SysOp::ReadBytes => {
let count = env
.pop(1)?
.as_nat_or_inf(env, "Count must be an integer or infinity")?;
if let Some(count) = count {
validate_size::<u8>([count], env)?;
}
let handle = env.pop(2)?.as_handle(env, None)?;
let bytes = match handle {
Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
Handle::STDERR => return Err(env.error("Cannot read from stderr")),
Handle::STDIN => env.rt.backend.scan_stdin(count).map_err(|e| env.error(e))?,
_ => {
if let Some(count) = count {
env.rt
.backend
.read(handle, count)
.map_err(|e| env.error(e))?
} else {
env.rt.backend.read_all(handle).map_err(|e| env.error(e))?
}
}
};
env.push(Array::from(bytes.as_slice()));
}
SysOp::ReadUntil => {
let delim = env.pop(1)?;
let handle = env.pop(2)?.as_handle(env, None)?;
if delim.rank() > 1 {
return Err(env.error("Delimiter must be a rank 0 or 1 string or byte array"));
}
match handle {
Handle::STDOUT => return Err(env.error("Cannot read from stdout")),
Handle::STDERR => return Err(env.error("Cannot read from stderr")),
Handle::STDIN => {
let mut is_string = false;
let delim_bytes: Vec<u8> = match delim {
Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
Value::Byte(arr) => arr.data.into(),
Value::Char(arr) => {
is_string = true;
arr.data.iter().collect::<String>().into()
}
_ => return Err(env.error("Delimiter must be a string or byte array")),
};
let buffer = env
.rt
.backend
.scan_until_stdin(&delim_bytes)
.map_err(|e| env.error(e))?;
if is_string {
let s = String::from_utf8_lossy(&buffer).into_owned();
env.push(s);
} else {
env.push(Array::from(buffer.as_slice()));
}
}
_ => match delim {
Value::Num(arr) => {
let delim: Vec<u8> = arr.data.iter().map(|&x| x as u8).collect();
let bytes = env
.rt
.backend
.read_until(handle, &delim)
.map_err(|e| env.error(e))?;
env.push(Array::from(bytes.as_slice()));
}
Value::Byte(arr) => {
let delim: Vec<u8> = arr.data.into();
let bytes = env
.rt
.backend
.read_until(handle, &delim)
.map_err(|e| env.error(e))?;
env.push(Array::from(bytes.as_slice()));
}
Value::Char(arr) => {
let delim: Vec<u8> = arr.data.iter().collect::<String>().into();
let bytes = env
.rt
.backend
.read_until(handle, &delim)
.map_err(|e| env.error(e))?;
let s = String::from_utf8(bytes).map_err(|e| env.error(e))?;
env.push(s);
}
_ => return Err(env.error("Delimiter must be a string or byte array")),
},
}
}
SysOp::Write => {
let data = env.pop(1)?;
let handle = env.pop(2)?.as_handle(env, None)?;
let bytes: Vec<u8> = match data {
Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
Value::Byte(arr) => arr.data.into(),
Value::Char(arr) => arr.data.iter().collect::<String>().into(),
val => return Err(env.error(format!("Cannot write {} array", val.type_name()))),
};
match handle {
Handle::STDOUT => (env.rt.backend)
.print_str_stdout(&String::from_utf8_lossy(&bytes))
.map_err(|e| env.error(e))?,
Handle::STDERR => (env.rt.backend)
.print_str_stderr(&String::from_utf8_lossy(&bytes))
.map_err(|e| env.error(e))?,
Handle::STDIN => return Err(env.error("Cannot write to stdin")),
_ => (env.rt.backend.write(handle, &bytes)).map_err(|e| env.error(e))?,
}
}
SysOp::Seek => {
let pos = env.pop(1)?.as_int_or_inf(env, None)?;
let pos = match pos {
Ok(pos @ ..0) => StreamSeek::End(pos.unsigned_abs()),
Ok(pos @ 0..) => StreamSeek::Start(pos.unsigned_abs()),
Err(false) => StreamSeek::End(0),
Err(true) => StreamSeek::Start(0),
};
let handle = env.pop(2)?.as_handle(env, None)?;
env.rt.backend.seek(handle, pos).map_err(|e| env.error(e))?;
}
SysOp::FReadAllStr => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let bytes = (env.rt.backend)
.file_read_all(path.as_ref())
.or_else(|e| match path.as_str() {
"example.ua" => Ok(EXAMPLE_UA.as_bytes().to_vec()),
"example.txt" => Ok(EXAMPLE_TXT.as_bytes().to_vec()),
_ => Err(e),
})
.map_err(|e| env.error(e))?;
let s = String::from_utf8(bytes).map_err(|e| env.error(e))?;
env.push(s);
}
SysOp::FReadAllBytes => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let bytes = (env.rt.backend)
.file_read_all(path.as_ref())
.or_else(|e| match path.as_str() {
"example.ua" => Ok(EXAMPLE_UA.as_bytes().to_vec()),
"example.txt" => Ok(EXAMPLE_TXT.as_bytes().to_vec()),
_ => Err(e),
})
.map_err(|e| env.error(e))?;
env.push(Array::<u8>::from_iter(bytes));
}
SysOp::FWriteAll => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let data = env.pop(2)?;
let bytes: Vec<u8> = match data {
Value::Num(arr) => arr.data.iter().map(|&x| x as u8).collect(),
Value::Byte(arr) => arr.data.into(),
Value::Char(arr) => arr.data.iter().collect::<String>().into(),
val => {
return Err(
env.error(format!("Cannot write {} array to file", val.type_name()))
);
}
};
(env.rt.backend)
.file_write_all(path.as_ref(), &bytes)
.or_else(|e| {
if path == "example.ua" {
let new_ex = String::from_utf8(bytes).map_err(|e| e.to_string())?;
example_ua(move |ex| *ex = new_ex);
Ok(())
} else {
Err(e)
}
})
.map_err(|e| env.error(e))?;
}
SysOp::FExists => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let exists = env.rt.backend.file_exists(&path);
env.push(exists);
}
SysOp::FListDir => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let paths = env.rt.backend.list_dir(&path).map_err(|e| env.error(e))?;
env.push(Array::<Boxed>::from_iter(paths));
}
SysOp::FIsFile => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
let is_file = env.rt.backend.is_file(&path).map_err(|e| env.error(e))?;
env.push(is_file);
}
SysOp::Invoke => {
let path = env.pop(1)?.as_string(env, "Invoke path must be a string")?;
env.rt.backend.invoke(&path).map_err(|e| env.error(e))?;
}
SysOp::ImShow => {
#[cfg(feature = "image")]
{
let value = env.pop(1)?;
let image = crate::media::value_to_image(&value).map_err(|e| env.error(e))?;
(env.rt.backend)
.show_image(image, value.meta.label.as_deref())
.map_err(|e| env.error(e))?;
}
#[cfg(not(feature = "image"))]
return Err(env.error("Image encoding is not supported in this environment"));
}
SysOp::GifShow => {
#[cfg(feature = "gif")]
{
let delay = env.pop(1)?.as_num(env, "Framerate must be a number")?;
let value = env.pop(2)?;
let start = env.rt.backend.now();
let bytes =
crate::media::value_to_gif_bytes(&value, delay).map_err(|e| env.error(e))?;
env.rt.execution_start += env.rt.backend.now() - start;
(env.rt.backend)
.show_gif(bytes, value.meta.label.as_deref())
.map_err(|e| env.error(e))?;
}
#[cfg(not(feature = "gif"))]
return Err(env.error("GIF showing is not supported in this environment"));
}
SysOp::ApngShow => {
#[cfg(feature = "apng")]
{
let delay = env.pop(1)?.as_num(env, "Framerate must be a number")?;
let value = env.pop(2)?;
let start = env.rt.backend.now();
let bytes =
crate::media::value_to_apng_bytes(&value, delay).map_err(|e| env.error(e))?;
env.rt.execution_start += env.rt.backend.now() - start;
(env.rt.backend)
.show_apng(bytes.into_iter().collect(), value.meta.label.as_deref())
.map_err(|e| env.error(e))?;
}
#[cfg(not(feature = "apng"))]
return Err(env.error("APNG showing is not supported in this environment"));
}
SysOp::AudioPlay => {
#[cfg(feature = "audio_encode")]
{
let value = env.pop(1)?;
let bytes =
crate::media::value_to_wav_bytes(&value, env.rt.backend.audio_sample_rate())
.map_err(|e| env.error(e))?;
(env.rt.backend)
.play_audio(bytes, value.meta.label.as_deref())
.map_err(|e| env.error(e))?;
}
#[cfg(not(feature = "audio_encode"))]
return Err(env.error("Audio encoding is not supported in this environment"));
}
SysOp::AudioSampleRate => {
let sample_rate = env.rt.backend.audio_sample_rate();
env.push(f64::from(sample_rate));
}
SysOp::Clip => {
let contents = env.rt.backend.clipboard().map_err(|e| env.error(e))?;
env.push(contents);
}
SysOp::Sleep => {
let mut seconds = env.pop(1)?.as_num(env, "Sleep time must be a number")?;
if seconds < 0.0 {
return Err(env.error("Sleep time must be positive"));
}
if seconds.is_infinite() {
return Err(env.error("Sleep time cannot be infinite"));
}
if let Some(limit) = env.rt.execution_limit {
let elapsed = env.rt.backend.now() - env.rt.execution_start;
let max = limit - elapsed;
seconds = seconds.min(max);
}
env.rt.backend.sleep(seconds).map_err(|e| env.error(e))?;
}
SysOp::TcpListen => {
let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
let handle = (env.rt.backend)
.tcp_listen(&addr)
.map_err(|e| env.error(e))?;
let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::TcpListener(sock_addr));
env.push(handle);
}
SysOp::TlsListen => {
let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
let cert = env
.pop(2)?
.into_bytes(env, "Cert must be a byte or character array")?;
let key = env
.pop(3)?
.into_bytes(env, "Key must be a byte or character array")?;
let handle = (env.rt.backend)
.tls_listen(&addr, &cert, &key)
.map_err(|e| env.error(e))?;
let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::TlsListener(sock_addr));
env.push(handle);
}
SysOp::TcpAccept => {
let handle = env.pop(1)?.as_handle(env, None)?;
let handle = (env.rt.backend)
.tcp_accept(handle)
.map_err(|e| env.error(e))?;
let addr = (env.rt.backend)
.tcp_addr(handle)
.map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::TcpSocket(addr));
env.push(handle);
}
SysOp::TcpConnect => {
let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
let handle = (env.rt.backend)
.tcp_connect(&addr)
.map_err(|e| env.error(e))?;
let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::TcpSocket(sock_addr));
env.push(handle);
}
SysOp::TlsConnect => {
let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
let handle = (env.rt.backend)
.tls_connect(&addr)
.map_err(|e| env.error(e))?;
let sock_addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::TlsSocket(sock_addr));
env.push(handle);
}
SysOp::TcpAddr => {
let handle = env.pop(1)?.as_handle(env, None)?;
let addr = env.rt.backend.tcp_addr(handle).map_err(|e| env.error(e))?;
env.push(addr.to_string());
}
SysOp::TcpSetNonBlocking => {
let handle = env.pop(1)?.as_handle(env, None)?;
(env.rt.backend)
.tcp_set_non_blocking(handle, true)
.map_err(|e| env.error(e))?;
}
SysOp::TcpSetReadTimeout => {
let timeout = env.pop(1)?.as_num(env, "Timeout must be a number")?.abs();
let timeout = if timeout.is_infinite() {
None
} else {
Some(Duration::from_secs_f64(timeout))
};
let handle = env.pop(2)?.as_handle(env, None)?;
(env.rt.backend)
.tcp_set_read_timeout(handle, timeout)
.map_err(|e| env.error(e))?;
}
SysOp::TcpSetWriteTimeout => {
let timeout = env.pop(1)?.as_num(env, "Timeout must be a number")?.abs();
let timeout = if timeout.is_infinite() {
None
} else {
Some(Duration::from_secs_f64(timeout))
};
let handle = env.pop(2)?.as_handle(env, None)?;
(env.rt.backend)
.tcp_set_write_timeout(handle, timeout)
.map_err(|e| env.error(e))?;
}
SysOp::Fetch => {
let url = env.pop(1)?.as_string(env, "Url must be a string")?;
let bytes = env.rt.backend.fetch(&url).map_err(|e| env.error(e))?;
env.push(bytes);
}
SysOp::UdpBind => {
let addr = env.pop(1)?.as_string(env, "Address must be a string")?;
let handle = (env.rt.backend).udp_bind(&addr).map_err(|e| env.error(e))?;
let handle = handle.value(HandleKind::UdpSocket(addr));
env.push(handle)
}
SysOp::UdpReceive => {
let handle = env.pop(1)?.as_handle(env, None)?;
let (bytes, addr) = (env.rt.backend)
.udp_recv(handle)
.map_err(|e| env.error(e))?;
env.push(addr.to_string());
env.push(Array::from(bytes.as_slice()));
}
SysOp::UdpSend => {
let bytes = env.pop(1)?;
let bytes = bytes.as_bytes(env, "Datagram must be bytes")?;
let addr = env.pop(2)?.as_string(env, "Address must be a string")?;
let handle = env.pop(3)?.as_handle(env, None)?;
(env.rt.backend)
.udp_send(handle, &bytes, &addr)
.map_err(|e| env.error(e))?;
}
SysOp::UdpSetMaxMsgLength => {
let length = env
.pop(1)?
.as_nat(env, "Message length must be a natural number")?;
let handle = env.pop(2)?.as_handle(env, None)?;
(env.rt.backend)
.udp_set_max_msg_length(handle, length)
.map_err(|e| env.error(e))?;
}
SysOp::Close => {
let handle = env.pop(1)?.as_handle(env, None)?;
env.rt.backend.close(handle).map_err(|e| env.error(e))?;
}
SysOp::RunInherit => {
let (command, args) = value_to_command(&env.pop(1)?, env)?;
let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
let code = (env.rt.backend)
.run_command_inherit(&command, &args)
.map_err(|e| env.error(e))?;
env.push(code);
}
SysOp::RunCapture => {
let (command, args) = value_to_command(&env.pop(1)?, env)?;
let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
let (code, stdout, stderr) = (env.rt.backend)
.run_command_capture(&command, &args)
.map_err(|e| env.error(e))?;
env.push(stderr);
env.push(stdout);
env.push(code);
}
SysOp::RunStream => {
let (command, args) = value_to_command(&env.pop(1)?, env)?;
let args: Vec<_> = args.iter().map(|s| s.as_str()).collect();
let handles = (env.rt.backend)
.run_command_stream(&command, &args)
.map_err(|e| env.error(e))?;
for (handle, kind) in handles
.into_iter()
.zip([
HandleKind::ChildStdin,
HandleKind::ChildStdout,
HandleKind::ChildStderr,
])
.rev()
{
env.push(handle.value(kind(command.clone())));
}
}
SysOp::ChangeDirectory => {
let path = env.pop(1)?.as_string(env, "Path must be a string")?;
(env.rt.backend)
.change_directory(&path)
.map_err(|e| env.error(e))?;
}
SysOp::WebcamCapture => {
let index = env.pop(1)?;
let index = match index.as_nat(env, "Webcam selector must be an integer or string") {
Ok(index) => index,
Err(e) => {
let name = index.as_string(env, "").map_err(|_| e)?;
let names = env.rt.backend.webcam_list().map_err(|e| env.error(e))?;
(names.iter().position(|n| n == &name))
.ok_or_else(|| env.error(format!("Webcam {name:?} is not available")))?
}
};
let _image = (env.rt.backend)
.webcam_capture(index)
.map_err(|e| env.error(e))?;
#[cfg(feature = "image")]
env.push(crate::media::rgb_image_to_array(_image));
#[cfg(not(feature = "image"))]
return Err(env.error("Webcam capture is not supported in this environment"));
}
SysOp::WebcamList => {
let names = env.rt.backend.webcam_list().map_err(|e| env.error(e))?;
let arr = Array::from_iter(names.into_iter().map(Into::into).map(Boxed));
env.push(arr);
}
SysOp::Ffi => {
let sig_def = env.pop(1)?;
let sig_def = match sig_def {
Value::Box(arr) => arr,
val => {
return Err(env.error(format!(
"FFI signature must be a box array, but it is {}",
val.type_name_plural()
)));
}
};
if sig_def.rank() != 1 {
return Err(env.error(format!(
"FFI signature must be a rank 1 array, but it is rank {}",
sig_def.rank()
)));
}
if sig_def.row_count() < 3 {
return Err(env.error("FFI signature array must have at least two elements"));
}
let mut sig_frags = sig_def.data.into_iter().map(|b| b.0);
let file_name =
(sig_frags.next().unwrap()).as_string(env, "FFI file name must be a string")?;
let result_ty = (sig_frags.next().unwrap())
.as_string(env, "FFI result type must be a string")?
.parse::<FfiType>()
.map_err(|e| env.error(e))?;
let name = (sig_frags.next().unwrap()).as_string(env, "FFI name must be a string")?;
let arg_tys = sig_frags
.map(|frag| {
frag.as_string(env, "FFI argument type must be a string")
.and_then(|ty| ty.parse::<FfiArg>().map_err(|e| env.error(e)))
})
.collect::<UiuaResult<Vec<_>>>()?;
let args = env.pop(2)?;
let args: Vec<Value> = args.into_rows().map(Value::unpacked).collect();
let result = (env.rt.backend)
.ffi(&file_name, result_ty, &name, &arg_tys, args)
.map_err(|e| env.error(e))?;
env.push(result);
}
SysOp::MemCopy => {
let ptr = env
.pop("pointer")?
.meta
.pointer
.as_ref()
.ok_or_else(|| env.error("Copied pointer must be a pointer value"))?
.clone();
let len = env
.pop("length")?
.as_nat(env, "Copied length must be a non-negative integer")?;
let value = (env.rt.backend)
.mem_copy(ptr, len)
.map_err(|e| env.error(e))?;
env.push(value);
}
SysOp::MemSet => {
let ptr = env
.pop("pointer")?
.meta
.pointer
.as_ref()
.ok_or_else(|| env.error("Target pointer must be a pointer value"))?
.clone();
let idx = env
.pop("index")?
.as_nat(env, "Target index must be a non-negative integer")?;
let value = env.pop(3)?;
(env.rt.backend)
.mem_set(ptr, idx, value)
.map_err(|e| env.error(e))?;
}
SysOp::MemFree => {
let val = env.pop(1)?;
let ptr = val
.meta
.pointer
.as_ref()
.ok_or_else(|| env.error("Freed pointer must be a pointer value"))?;
(env.rt.backend).mem_free(ptr).map_err(|e| env.error(e))?;
}
SysOp::Malloc => {
let size = env
.pop("size")?
.as_nat(env, "Allocated size must be a non-negative integer")?;
let val = (env.rt.backend)
.mem_allocate(size)
.map_err(|e| env.error(e))?;
env.push(val);
}
SysOp::Breakpoint => {
if !env.rt.backend.breakpoint(env).map_err(|e| env.error(e))? {
return Err(UiuaErrorKind::Interrupted.into());
}
}
prim => {
return Err(env.error(if prim.modifier_args().is_some() {
format!(
"{} was not handled as a modifier. \
This is a bug in the interpreter",
Primitive::Sys(*prim)
)
} else {
format!(
"{} was not handled as a function. \
This is a bug in the interpreter",
Primitive::Sys(*prim)
)
}));
}
}
Ok(())
}
pub(crate) fn run_sys_op_mod(op: &SysOp, ops: Ops, env: &mut Uiua) -> UiuaResult {
match op {
SysOp::ReadLines => {
let [f] = get_ops(ops, env)?;
let handle = env.pop(1)?.as_handle(env, None)?;
let mut read_lines: ReadLinesReturnFn = match handle {
Handle::STDIN => Box::new(|env, mut f| {
for line in stdin().lines() {
let line = line.map_err(|e| env.error(e))?;
f(line, env)?;
}
Ok(())
}),
_ => (env.rt.backend)
.read_lines(handle)
.map_err(|e| env.error(e))?,
};
let sig = f.sig;
if sig.args() == 0 {
return env.exec(f);
}
let acc_count = sig.args().saturating_sub(1);
let out_count = sig.outputs().saturating_sub(acc_count);
let mut outputs = multi_output(out_count, Vec::new());
env.without_fill(|env| {
read_lines(
env,
Box::new(|s, env| {
let val = Value::from(s);
env.push(val);
env.exec(f.clone())?;
for i in 0..out_count {
outputs[i].push(env.pop("read lines output")?);
}
Ok(())
}),
)
})?;
for rows in outputs.into_iter().rev() {
let val = Value::from_row_values(rows, env)?;
env.push(val);
}
}
SysOp::AudioStream => {
let [f] = get_ops(ops, env)?;
let push_time = f.sig.args() > 0;
if f.sig != (0, 1) && f.sig.args() != f.sig.outputs() {
return Err(env.error(format!(
"&ast's function must have the same number \
of inputs and outputs, but its signature is {}",
f.sig
)));
}
if f.sig == (0, 0) {
return Ok(());
}
let mut stream_env = env.clone();
let res = env.rt.backend.stream_audio(Box::new(move |time_array| {
if push_time {
let time_array = Array::<f64>::from(time_array);
stream_env.push(time_array);
}
stream_env.exec(f.clone())?;
let samples = &stream_env.pop(1)?;
let samples = samples.as_num_array().ok_or_else(|| {
stream_env.error("Audio stream function must return a numeric array")
})?;
match &*samples.shape {
[_] => Ok(samples.data.iter().map(|&x| [x, x]).collect()),
&[n, 2] => {
let mut samps: Vec<[f64; 2]> = Vec::with_capacity(n);
for samp in samples.data.chunks_exact(2) {
samps.push([samp[0], samp[1]]);
}
Ok(samps)
}
&[2, n] => {
let mut samps: Vec<[f64; 2]> = Vec::with_capacity(n);
for i in 0..n {
samps.push([samples.data[i], samples.data[i + n]]);
}
Ok(samps)
}
_ => Err(stream_env.error(format!(
"Audio stream function must return either a \
rank 1 array or a rank 2 array with 2 rows, \
but its shape is {}",
samples.shape
))),
}
}));
res.map_err(|e| env.error(e))?;
}
prim => {
return Err(env.error(if prim.modifier_args().is_some() {
format!(
"{} was not handled as a modifier. \
This is a bug in the interpreter",
Primitive::Sys(*prim)
)
} else {
format!(
"{} was handled as a modifier. \
This is a bug in the interpreter",
Primitive::Sys(*prim)
)
}));
}
}
Ok(())
}
fn value_to_command(value: &Value, env: &Uiua) -> UiuaResult<(String, Vec<String>)> {
let mut strings = Vec::new();
match value {
Value::Char(arr) => match arr.rank() {
0 | 1 => strings.push(arr.data.iter().collect::<String>()),
2 => {
for row in arr.rows() {
strings.push(row.data.iter().collect::<String>());
}
}
n => {
return Err(env.error(format!(
"Character array as command must be rank 0, 1, \
or 2, but its rank is {n}"
)));
}
},
Value::Box(arr) => match arr.rank() {
0 | 1 => {
for Boxed(val) in &arr.data {
match val {
Value::Char(arr) if arr.rank() <= 1 => {
strings.push(arr.data.iter().collect::<String>())
}
val => {
return Err(env.error(format!(
"Function array as command must be all boxed strings, \
but at least one is a {}",
val.type_name()
)));
}
}
}
}
n => {
return Err(env.error(format!(
"Function array as command must be rank 0 or 1, \
but its rank is {n}"
)));
}
},
val => {
return Err(env.error(format!(
"Command must be a string or box array, but it is {}",
val.type_name_plural()
)));
}
}
if strings.is_empty() {
return Err(env.error("Command array not be empty"));
}
let command = strings.remove(0);
Ok((command, strings))
}
pub fn now() -> f64 {
#[cfg(not(target_arch = "wasm32"))]
{
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("System clock was before 1970.")
.as_secs_f64()
}
#[cfg(target_arch = "wasm32")]
{
#[cfg(not(feature = "web"))]
{
compile_error!("Web target requires the `web` feature")
}
#[cfg(feature = "web")]
{
use wasm_bindgen::{JsCast, prelude::*};
js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("performance"))
.expect("failed to get performance from global object")
.unchecked_into::<web_sys::Performance>()
.now()
/ 1000.0
}
}
}
pub(crate) fn terminal_size() -> Option<(usize, usize)> {
#[cfg(all(not(target_arch = "wasm32"), feature = "terminal_size"))]
{
terminal_size::terminal_size().map(|(w, h)| (w.0 as usize, h.0 as usize))
}
#[cfg(not(all(not(target_arch = "wasm32"), feature = "terminal_size")))]
None
}