use anyhow::{Error, Result, anyhow, bail};
use std::io::{Read, Stdin, Write};
use crate::{
Args, hold, hold_and_release,
quickjs::{Ctx, Function, Object, Value, qjs::JS_GetArrayBuffer},
to_js_error,
};
pub(crate) fn register(this: Ctx<'_>) -> Result<()> {
let globals = this.globals();
if globals.get::<_, Object>("Javy").is_err() {
globals.set("Javy", Object::new(this.clone())?)?
}
globals.set(
"__javy_io_writeSync",
Function::new(this.clone(), |cx, args| {
let (cx, args) = hold_and_release!(cx, args);
write(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
globals.set(
"__javy_io_readSync",
Function::new(this.clone(), |cx, args| {
let (cx, args) = hold_and_release!(cx, args);
read(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
this.eval::<(), _>(include_str!("io.js"))?;
Ok::<_, Error>(())
}
fn extract_args<'a, 'js: 'a>(
args: &'a [Value<'js>],
for_func: &str,
) -> Result<(
&'a Value<'js>,
&'a Value<'js>,
&'a Value<'js>,
&'a Value<'js>,
)> {
let [fd, data, offset, length, ..] = args else {
bail!(
r#"
{} expects 4 parameters: the file descriptor, the
TypedArray buffer, the TypedArray byteOffset and the TypedArray
byteLength.
Got: {} parameters.
"#,
for_func,
args.len()
);
};
Ok((fd, data, offset, length))
}
fn write(args: Args<'_>) -> Result<Value<'_>> {
enum Fd {
Stdout,
Stderr,
}
let (cx, args) = args.release();
let (fd, data, offset, length) = extract_args(&args, "Javy.IO.writeSync")?;
let fd = match fd
.as_int()
.ok_or_else(|| anyhow!("File descriptor must be a number"))?
{
1 => Fd::Stdout,
2 => Fd::Stderr,
x => anyhow::bail!(
"Unsupported file descriptor: {x}. Only stdout(1) and stderr(2) are supported"
),
};
let data = data
.as_object()
.ok_or_else(|| anyhow!("Data must be an Object"))?
.as_array_buffer()
.ok_or_else(|| anyhow!("Data must be an ArrayBuffer"))?
.as_bytes()
.ok_or_else(|| anyhow!("Could not represent data as &[u8]"))?;
let offset = offset
.as_number()
.ok_or_else(|| anyhow!("offset must be a number"))? as usize;
let length = length
.as_number()
.ok_or_else(|| anyhow!("offset must be a number"))? as usize;
let data = &data[offset..(offset + length)];
let n = match fd {
Fd::Stdout => {
let mut fd = std::io::stdout();
let n = fd.write(data)?;
fd.flush()?;
n
}
Fd::Stderr => {
let mut fd = std::io::stderr();
let n = fd.write(data)?;
fd.flush()?;
n
}
};
Ok(Value::new_number(cx, n as f64))
}
fn read(args: Args<'_>) -> Result<Value<'_>> {
let (cx, args) = args.release();
let (fd, data, offset, length) = extract_args(&args, "Javy.IO.readSync")?;
let mut fd: Stdin = match fd
.as_int()
.ok_or_else(|| anyhow!("File descriptor must be a number"))?
{
0 => std::io::stdin(),
x => anyhow::bail!("Unsupported file descriptor: {x}. Only stdin(0) is supported"),
};
let offset = offset
.as_number()
.ok_or_else(|| anyhow!("offset must be a number"))? as usize;
let length = length
.as_number()
.ok_or_else(|| anyhow!("length must be a number"))? as usize;
let data = unsafe {
let mut len = 0;
let ptr = JS_GetArrayBuffer(cx.as_raw().as_ptr(), &mut len, data.as_raw());
if ptr.is_null() {
bail!("Data must be an ArrayBuffer");
}
Ok::<_, Error>(std::slice::from_raw_parts_mut(ptr, len as _))
}?;
let data = &mut data[offset..(offset + length)];
let n = fd.read(data)?;
Ok(Value::new_number(cx, n as f64))
}