talc/talc-std/src/file.rs

693 lines
21 KiB
Rust

use std::{cell::RefCell, collections::HashMap, fs::{File, OpenOptions}, io::{BufRead, BufReader, BufWriter, ErrorKind, IntoInnerError, Read, Write}, net::{TcpListener, TcpStream, ToSocketAddrs}, os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, process::{Child, Command, Stdio}, time::Duration};
use talc_lang::{exception::Result, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, Vm};
use talc_macros::native_func;
use lazy_static::lazy_static;
use crate::{unpack_args, SYM_IO_ERROR};
type OpenOptFn = for<'a> fn(&'a mut OpenOptions, bool) -> &'a mut OpenOptions;
lazy_static! {
static ref SYM_STD_FILE: Symbol = Symbol::get("std.file");
static ref SYM_STD_PROCESS: Symbol = Symbol::get("std.process");
static ref SYM_R: Symbol = Symbol::get("r");
static ref SYM_W: Symbol = Symbol::get("w");
static ref SYM_A: Symbol = Symbol::get("a");
static ref SYM_T: Symbol = Symbol::get("t");
static ref SYM_C: Symbol = Symbol::get("c");
static ref SYM_N: Symbol = Symbol::get("n");
static ref SYM_U: Symbol = Symbol::get("u");
static ref OPEN_OPT_MAP: HashMap<Symbol, OpenOptFn> = {
let mut map = HashMap::new();
map.insert(*SYM_R, OpenOptions::read as OpenOptFn);
map.insert(*SYM_W, OpenOptions::write);
map.insert(*SYM_A, OpenOptions::append);
map.insert(*SYM_T, OpenOptions::truncate);
map.insert(*SYM_C, OpenOptions::create);
map.insert(*SYM_N, OpenOptions::create_new);
map
};
static ref SYM_STDIN: Symbol = Symbol::get("stdin");
static ref SYM_STDOUT: Symbol = Symbol::get("stdout");
static ref SYM_STDERR: Symbol = Symbol::get("stderr");
static ref SYM_CD: Symbol = Symbol::get("cd");
static ref SYM_DELENV: Symbol = Symbol::get("delenv");
static ref SYM_ENV: Symbol = Symbol::get("env");
static ref SYM_PROCESS: Symbol = Symbol::get("process");
static ref SYM_INHERIT: Symbol = Symbol::get("inherit");
static ref SYM_PIPED: Symbol = Symbol::get("piped");
static ref SYM_NULL: Symbol = Symbol::get("null");
static ref SYM_ALL: Symbol = Symbol::get("all");
}
thread_local! {
static F_STDIN: Value = {
let fd = std::io::stdin().lock().as_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
let bf = BufFile::new(file, true).expect("failed to create stdin buffered file");
ValueFile::from(bf).into()
};
static F_STDOUT: Value = {
let fd = std::io::stdout().lock().as_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
let bf = BufFile::new(file, true).expect("failed to create stdout buffered file");
ValueFile::from(bf).into()
};
static F_STDERR: Value = {
let fd = std::io::stderr().lock().as_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
let bf = BufFile::new(file, true).expect("failed to create stderr buffered file");
ValueFile::from(bf).into()
};
}
#[derive(Debug)]
enum BufFile {
Buffered { r: BufReader<File>, w: BufWriter<File> },
Unbuffered { f: File },
}
impl BufFile {
fn new(file: File, buffered: bool) -> std::io::Result<Self> {
if buffered {
let file2 = file.try_clone()?;
Ok(Self::Buffered { r: BufReader::new(file), w: BufWriter::new(file2) })
} else {
Ok(Self::Unbuffered { f: file })
}
}
fn from_raw_fd(fd: RawFd, buffered: bool) -> std::io::Result<Self> {
Self::new(unsafe { File::from_raw_fd(fd) }, buffered)
}
fn try_clone(&self) -> std::io::Result<BufFile> {
match self {
Self::Buffered { r, .. } => {
let file = r.get_ref().try_clone()?;
Self::new(file, true)
},
Self::Unbuffered { f } => Ok(Self::Unbuffered { f: f.try_clone()? })
}
}
fn try_into_raw_fd(self) -> std::result::Result<RawFd, IntoInnerError<BufWriter<File>>> {
match self {
BufFile::Buffered { r, w } => {
w.into_inner()?.into_raw_fd();
Ok(r.into_inner().into_raw_fd())
}
BufFile::Unbuffered { f } => Ok(f.into_raw_fd()),
}
}
}
impl AsRawFd for BufFile {
fn as_raw_fd(&self) -> RawFd {
match self {
BufFile::Buffered { r, .. } => r.get_ref().as_raw_fd(),
BufFile::Unbuffered { f } => f.as_raw_fd(),
}
}
}
impl Read for BufFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
BufFile::Buffered { r, .. } => r.read(buf),
BufFile::Unbuffered { f } => f.read(buf),
}
}
}
impl Write for BufFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
BufFile::Buffered { w, .. } => w.write(buf),
BufFile::Unbuffered { f } => f.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
BufFile::Buffered { w, .. } => w.flush(),
BufFile::Unbuffered { f } => f.flush(),
}
}
}
#[derive(Debug)]
pub struct ValueFile(RefCell<BufFile>);
impl From<BufFile> for ValueFile {
fn from(value: BufFile) -> Self {
Self(RefCell::new(value))
}
}
impl NativeValue for ValueFile {
fn get_type(&self) -> Symbol { *SYM_STD_FILE }
fn as_any(&self) -> &dyn std::any::Any { self }
fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> {
w.push_str("<file>");
Ok(())
}
}
#[derive(Debug)]
pub struct ValueProcess(RefCell<Child>);
impl From<Child> for ValueProcess {
fn from(value: Child) -> Self {
Self(RefCell::new(value))
}
}
impl NativeValue for ValueProcess {
fn get_type(&self) -> Symbol { *SYM_STD_PROCESS }
fn as_any(&self) -> &dyn std::any::Any { self }
fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> {
let id = self.0.borrow().id();
write!(w, "<process {id}>")
}
}
pub fn load(vm: &mut Vm) {
vm.set_global_name("open", open().into());
vm.set_global_name("read", read().into());
vm.set_global_name("read_all", read_all().into());
vm.set_global_name("read_until", read_until().into());
vm.set_global_name("read_line", read_line().into());
vm.set_global_name("write", write().into());
vm.set_global_name("flush", flush().into());
vm.set_global_name("stdin", stdin().into());
vm.set_global_name("stdout", stdout().into());
vm.set_global_name("stderr", stderr().into());
vm.set_global_name("tcp_connect", tcp_connect().into());
vm.set_global_name("tcp_connect_timeout", tcp_connect_timeout().into());
vm.set_global_name("tcp_listen", tcp_listen().into());
vm.set_global_name("spawn", spawn().into());
vm.set_global_name("system", system().into());
vm.set_global_name("exit_code", exit_code().into());
vm.set_global_name("process_id", process_id().into());
}
#[native_func(2)]
pub fn open(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, path, opts] = unpack_args!(args);
let Value::String(path) = path else {
throw!(*SYM_TYPE_ERROR, "path to open must be a string")
};
let mut oo = std::fs::OpenOptions::new();
let mut buffered = true;
match opts {
Value::Symbol(s) => {
if s == *SYM_U {
oo.read(true).write(true);
buffered = false;
} else {
match OPEN_OPT_MAP.get(&s) {
Some(f) => f(&mut oo, true),
None => throw!(*SYM_TYPE_ERROR, "invalid option for open")
};
}
},
Value::List(l) => for s in l.borrow().iter() {
let Value::Symbol(s) = s else {
throw!(*SYM_TYPE_ERROR, "invalid option for open")
};
if *s == *SYM_U {
buffered = false;
} else {
match OPEN_OPT_MAP.get(s) {
Some(f) => f(&mut oo, true),
None => throw!(*SYM_TYPE_ERROR, "invalid option for open")
};
}
},
Value::Nil => {
oo.read(true).write(true);
},
_ => throw!(*SYM_TYPE_ERROR, "invalid option for open")
}
match oo.open(path.to_os_str()) {
Ok(f) => match BufFile::new(f, buffered) {
Ok(bf) => Ok(ValueFile::from(bf).into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
}
#[native_func(2)]
pub fn read(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file, nbytes] = unpack_args!(args);
let Value::Int(nbytes) = nbytes else {
throw!(*SYM_TYPE_ERROR, "read expected integer, got {nbytes:#}")
};
let Ok(nbytes) = usize::try_from(nbytes) else {
throw!(*SYM_TYPE_ERROR, "number of bytes to read must be nonnegative")
};
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "read expected file, got {file:#}")
};
let mut file = file.0.borrow_mut();
let mut buf = vec![0; nbytes];
if let Err(e) = file.read_exact(&mut buf) {
if e.kind() == ErrorKind::UnexpectedEof {
Ok(Value::Nil)
} else {
throw!(*SYM_IO_ERROR, "{e}")
}
} else {
Ok(LString::from(buf).into())
}
}
#[native_func(1)]
pub fn read_all(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file] = unpack_args!(args);
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "read_all expected file, got {file:#}")
};
let mut file = file.0.borrow_mut();
let mut buf = Vec::new();
if let Err(e) = file.read_to_end(&mut buf) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(LString::from(buf).into())
}
fn read_until_impl(r: &mut impl BufRead, end: &[u8]) -> std::io::Result<LString> {
let last = *end.last().unwrap();
let mut buf = Vec::new();
loop {
let n = r.read_until(last, &mut buf)?;
if n == 0 || buf.ends_with(end) {
break
}
}
Ok(buf.into())
}
#[native_func(2)]
pub fn read_until(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file, end] = unpack_args!(args);
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "read_until expected file, got {file:#}")
};
let Value::String(end) = end else {
throw!(*SYM_TYPE_ERROR, "read_until expected string, got {end:#}")
};
if end.is_empty() {
throw!(*SYM_TYPE_ERROR, "read_until: end string must not be empty")
}
let mut file = file.0.borrow_mut();
if let BufFile::Buffered { r, .. } = &mut *file {
match read_until_impl(r, end.as_bytes()) {
Ok(s) if s.is_empty() => Ok(Value::Nil),
Ok(s) => Ok(s.into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
}
} else {
throw!(*SYM_TYPE_ERROR, "read_until: file must be buffered")
}
}
#[native_func(1)]
pub fn read_line(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file] = unpack_args!(args);
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "read_line expected file, got {file:#}")
};
let mut file = file.0.borrow_mut();
if let BufFile::Buffered { r, .. } = &mut *file {
let mut buf = Vec::new();
match r.read_until(b'\n', &mut buf) {
Ok(0) => Ok(Value::Nil),
Ok(_) => Ok(LString::from(buf).into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
}
} else {
throw!(*SYM_TYPE_ERROR, "read_line: file must be buffered")
}
}
#[native_func(2)]
pub fn write(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file, data] = unpack_args!(args);
let Value::String(data) = data else {
throw!(*SYM_TYPE_ERROR, "write expected string, got {data:#}")
};
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "write expected file, got {file:#}")
};
let mut file = file.0.borrow_mut();
if let Err(e) = file.write_all(data.as_bytes()) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
}
#[native_func(1)]
pub fn flush(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, file] = unpack_args!(args);
let Some(file): Option<&ValueFile> = file.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "flush expected file, got {file:#}")
};
let mut file = file.0.borrow_mut();
if let Err(e) = file.flush() {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
}
#[native_func(0)]
pub fn stdin(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(F_STDIN.with(Clone::clone))
}
#[native_func(0)]
pub fn stdout(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(F_STDOUT.with(Clone::clone))
}
#[native_func(0)]
pub fn stderr(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(F_STDERR.with(Clone::clone))
}
//
// network
//
#[native_func(1)]
pub fn tcp_connect(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, addr] = unpack_args!(args);
let Value::String(addr) = addr else {
throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}")
};
let Ok(addr) = addr.to_str() else {
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
};
match TcpStream::connect(addr) {
Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
Ok(bf) => Ok(ValueFile::from(bf).into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
}
#[native_func(1)]
pub fn tcp_connect_timeout(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, addr, timeout] = unpack_args!(args);
let Value::String(addr) = addr else {
throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected string, got {addr:#}")
};
let Ok(addr) = addr.to_str() else {
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
};
let timeout = match timeout {
Value::Int(n) if n >= 0 => Duration::from_secs(n as u64),
Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => Duration::from_secs_f64(n),
Value::Int(_) | Value::Float(_) => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout: invalid timeout"),
_ => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected int or float, got {timeout:#}")
};
let mut addrs = match addr.to_socket_addrs() {
Ok(addrs) => addrs,
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
};
let Some(addr) = addrs.next() else {
throw!(*SYM_IO_ERROR, "invalid address");
};
match TcpStream::connect_timeout(&addr, timeout) {
Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
Ok(bf) => Ok(ValueFile::from(bf).into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
}
fn tcp_listen_inner(listener: TcpListener) -> Value {
let listener = RefCell::new(listener);
let func = move |_: &mut Vm, _: Vec<Value>| {
match listener.borrow_mut().accept() {
Ok((stream, addr)) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
Ok(bf) => Ok(vec![
ValueFile::from(bf).into(),
LString::from(addr.to_string()).into()
].into()),
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
};
NativeFunc::new(Box::new(func), 0).into()
}
#[native_func(1)]
pub fn tcp_listen(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, addr] = unpack_args!(args);
let Value::String(addr) = addr else {
throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}")
};
let Ok(addr) = addr.to_str() else {
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
};
match TcpListener::bind(addr) {
Ok(listener) => {
let addr = listener.local_addr()
.map(|a| LString::from(a.to_string()).into())
.unwrap_or(Value::Nil);
Ok(vec![
tcp_listen_inner(listener),
addr,
].into())
},
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
}
//
// processes
//
fn spawn_opt_stdio(
fname: &str,
proc: &mut Command,
func: fn(&mut Command, Stdio) -> &mut Command,
opt: &Value,
) -> Result<()> {
match opt {
Value::Nil => func(proc, Stdio::inherit()),
Value::Symbol(s) if *s == *SYM_INHERIT => func(proc, Stdio::inherit()),
Value::Symbol(s) if *s == *SYM_PIPED => func(proc, Stdio::piped()),
Value::Symbol(s) if *s == *SYM_NULL => func(proc, Stdio::null()),
Value::Native(n) if n.get_type() == *SYM_STD_FILE => {
let f: &ValueFile = opt.downcast_native().unwrap();
let bf = match f.0.borrow().try_clone() {
Ok(bf) => bf,
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
};
let fd = match bf.try_into_raw_fd() {
Ok(fd) => fd,
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
};
let stdio = unsafe { Stdio::from_raw_fd(fd) };
func(proc, stdio)
},
_ => throw!(*SYM_TYPE_ERROR, "{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}")
};
Ok(())
}
fn spawn_opt_cd(fname: &str, proc: &mut Command, cd: &Value) -> Result<()> {
if let Value::Nil = cd { return Ok(()) }
let Value::String(cd) = cd else {
throw!(*SYM_TYPE_ERROR, "{fname} cd option expected string, got {cd:#}")
};
proc.current_dir(cd.to_os_str());
Ok(())
}
fn spawn_opt_delenv(fname: &str, proc: &mut Command, delenv: &Value) -> Result<()> {
match delenv {
Value::Nil => (),
Value::Symbol(s) if *s == *SYM_ALL => {
proc.env_clear();
},
Value::List(l) => for e in l.borrow().iter() {
let Value::String(e) = e else {
throw!(*SYM_TYPE_ERROR,
"{fname} delenv option expected :all or list of strings, got {delenv:#}")
};
proc.env_remove(e.to_os_str());
},
_ => throw!(*SYM_TYPE_ERROR,
"{fname} delenv option expected :all or list of strings, got {delenv:#}")
}
Ok(())
}
fn spawn_opt_env(fname: &str, proc: &mut Command, env: &Value) -> Result<()> {
match env {
Value::Nil => (),
Value::Table(t) => for (k, v) in t.borrow().iter() {
let Value::String(k) = k.inner() else {
throw!(*SYM_TYPE_ERROR,
"{fname} efromnv option expected table with string keys, got {env:#}")
};
proc.env(k.to_os_str(), v.str().to_os_str());
},
_ => throw!(*SYM_TYPE_ERROR,
"{fname} env option expected table with string keys, got {env:#}")
}
Ok(())
}
fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result<Value> {
let (i, o, e) = match opts {
Value::Native(ref n) if n.get_type() == *SYM_STD_FILE => {
throw!(*SYM_TYPE_ERROR, "{fname} options expected :inherit, :piped, :null, or table, got {opts:#}")
}
Value::Table(t) => {
let t = t.borrow();
if let Some(cd) = t.get(&HashValue::from(*SYM_CD)) {
spawn_opt_cd(fname, proc, cd)?;
}
if let Some(delenv) = t.get(&HashValue::from(*SYM_DELENV)) {
spawn_opt_delenv(fname, proc, delenv)?;
}
if let Some(env) = t.get(&HashValue::from(*SYM_ENV)) {
spawn_opt_env(fname, proc, env)?;
}
let i = t.get(&HashValue::from(*SYM_STDIN))
.cloned()
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
let o = t.get(&HashValue::from(*SYM_STDOUT))
.cloned()
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
let e = t.get(&HashValue::from(*SYM_STDERR))
.cloned()
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
(i, o, e)
},
v => (v.clone(), v.clone(), v),
};
spawn_opt_stdio(fname, proc, Command::stdin, &i)?;
spawn_opt_stdio(fname, proc, Command::stdout, &o)?;
spawn_opt_stdio(fname, proc, Command::stderr, &e)?;
let mut child = match proc.spawn() {
Ok(c) => c,
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
};
let mut table = HashMap::new();
if let Some(stdin) = child.stdin.take() {
let bf = match BufFile::from_raw_fd(stdin.into_raw_fd(), true) {
Ok(bf) => bf,
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
};
table.insert(HashValue::from(*SYM_STDIN), ValueFile::from(bf).into());
}
if let Some(stdout) = child.stdout.take() {
let bf = match BufFile::from_raw_fd(stdout.into_raw_fd(), true) {
Ok(bf) => bf,
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
};
table.insert(HashValue::from(*SYM_STDOUT), ValueFile::from(bf).into());
}
if let Some(stderr) = child.stderr.take() {
let bf = match BufFile::from_raw_fd(stderr.into_raw_fd(), true) {
Ok(bf) => bf,
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
};
table.insert(HashValue::from(*SYM_STDERR), ValueFile::from(bf).into());
}
table.insert(HashValue::from(*SYM_PROCESS), ValueProcess::from(child).into());
Ok(table.into())
}
#[native_func(3)]
pub fn spawn(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, cmd, args, opts] = unpack_args!(args);
let Value::String(cmd) = cmd else {
throw!(*SYM_TYPE_ERROR, "spawn expected string, got {cmd:#}")
};
let Value::List(args) = args else {
throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got {args:#}")
};
let mut proc = Command::new(cmd.to_os_str());
for arg in args.borrow().iter() {
let Value::String(arg) = arg else {
throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got list containing {arg:#}")
};
proc.arg(arg.to_os_str());
}
spawn_inner("spawn", &mut proc, opts)
}
#[native_func(2)]
pub fn system(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, cmd, opts] = unpack_args!(args);
let Value::String(cmd) = cmd else {
throw!(*SYM_TYPE_ERROR, "spawn expected string, got {cmd:#}")
};
let mut proc;
if cfg!(target_os = "windows") {
proc = Command::new("cmd");
proc.arg("/C")
.arg(cmd.to_os_str())
} else {
proc = Command::new("sh");
proc.arg("-c")
.arg(cmd.to_os_str())
};
spawn_inner("system", &mut proc, opts)
}
#[native_func(1)]
pub fn exit_code(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, proc] = unpack_args!(args);
let Some(proc): Option<&ValueProcess> = proc.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "exit_code expected process, got {proc:#}")
};
let mut proc = proc.0.borrow_mut();
match proc.wait() {
Ok(code) => {
Ok(code.code()
.map(|c| Value::Int(c as i64))
.unwrap_or_default())
},
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
}
}
#[native_func(1)]
pub fn process_id(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, proc] = unpack_args!(args);
let Some(proc): Option<&ValueProcess> = proc.downcast_native() else {
throw!(*SYM_TYPE_ERROR, "exit_code expected process, got {proc:#}")
};
let proc = proc.0.borrow();
Ok((proc.id() as i64).into())
}