I'm trying to add proper binary I/O capabilities to the boa engine (GitHub - boa-dev/boa: Boa is an embeddable Javascript engine written in Rust. · GitHub).
This is what I have so far
use boa_engine::{Context, Source, JsResult, JsValue, JsObject, NativeFunction, js_string};
use boa_engine::object::builtins::{JsArrayBuffer, JsUint8Array};
use std::io::{self, Read, Write};
/// High-performance synchronous block parsing from WASI stdin (fd: 0)
fn read_exactly(bytes_to_read: usize) -> Option<Vec<u8>> {
let mut buffer = vec![0u8; bytes_to_read];
let mut total_read = 0;
let mut stdin = io::stdin().lock();
while total_read < bytes_to_read {
match stdin.read(&mut buffer[total_read..bytes_to_read]) {
Ok(0) => return None,
Ok(n) => total_read += n,
Err(_) => return None,
}
}
Some(buffer)
}
/// Dispatches binary blocks back across WASI stdout (fd: 1)
fn write_stdout(bytes: &[u8]) -> bool {
let mut stdout = io::stdout().lock();
if stdout.write_all(bytes).is_err() {
return false;
}
stdout.flush().is_ok()
}
/// Forwards system panic states safely across WASI stderr (fd: 2)
fn write_stderr(message: String) {
let mut stderr = io::stderr().lock();
let _ = stderr.write_all(message.as_bytes());
let _ = stderr.flush();
}
fn main() -> JsResult<()> {
// 1. Initialize the official execution context
let mut context = Context::default();
// 2. Build the top-level 'std' namespace object template
let std_obj = JsObject::with_object_proto(context.intrinsics());
// Inject std.read
let read_fn = NativeFunction::from_copy_closure(|_this, args, ctx| {
let len = args.get(0).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
if let Some(bytes) = read_exactly(len) {
let ab = JsArrayBuffer::from_byte_block(bytes, ctx)?;
let ua = JsUint8Array::from_array_buffer(ab, ctx)?;
return Ok(JsValue::from(ua));
}
Ok(JsValue::null())
});
// FIX: Replaced from_proto_and_data with direct function builder template allocation
let read_js_fn = context.intrinsics().templates().function().create(read_fn, vec![]);
std_obj.set(js_string!("read"), JsValue::from(read_js_fn), true, &mut context)?;
// Inject std.write
let write_fn = NativeFunction::from_copy_closure(|_this, args, ctx| {
let fd = args.get(0).and_then(|v| v.as_number()).unwrap_or(1.0) as i32;
let js_buffer = args.get(1).and_then(|v| v.as_object().cloned());
if let Some(obj) = js_buffer {
if let Ok(typed_array) = JsUint8Array::from_object(obj) {
// FIX: Changed .get_buffer(ctx) to .buffer(ctx)
let array_buffer = typed_array.buffer(ctx)?;
let bytes = array_buffer.take_buffer(ctx)?;
if fd == 1 {
return Ok(JsValue::from(write_stdout(&bytes)));
} else if fd == 2 {
write_stderr(String::from_utf8_lossy(&bytes).into_owned());
return Ok(JsValue::from(true));
}
}
}
Ok(JsValue::from(false))
});
// FIX: Replaced from_proto_and_data with direct function builder template allocation
let write_js_fn = context.intrinsics().templates().function().create(write_fn, vec![]);
std_obj.set(js_string!("write"), JsValue::from(write_js_fn), true, &mut context)?;
// Register 'std' globally
context.global_object().set(js_string!("std"), JsValue::from(std_obj), true, &mut context)?;
// 3. Native Messaging loop refactored for the clean 'std' object API
let js_code = r#"
const MAX_CHUNK_ELEMENTS = 209715;
async function readExactly(bytesToRead) {
const buffer = new Uint8Array(bytesToRead);
let totalRead = 0;
while (totalRead < bytesToRead) {
const chunkBytes = std.read(bytesToRead - totalRead);
if (!chunkBytes) return null;
buffer.set(chunkBytes, totalRead);
totalRead += chunkBytes.length;
}
return buffer;
}
async function sendNativeMessage(payloadData) {
if (Array.isArray(payloadData) && payloadData.length > MAX_CHUNK_ELEMENTS) {
for (let i = 0; i < payloadData.length; i += MAX_CHUNK_ELEMENTS) {
await transmitFrame(payloadData.slice(i, i + MAX_CHUNK_ELEMENTS));
}
} else {
await transmitFrame(payloadData);
}
}
async function transmitFrame(obj) {
try {
const encodedPayload = new TextEncoder().encode(JSON.stringify(obj));
const packet = new Uint8Array(4 + encodedPayload.length);
const view = new DataView(packet.buffer);
view.setUint32(0, encodedPayload.length, true);
packet.set(encodedPayload, 4);
std.write(1, packet);
} catch (_) {}
}
async function runHostLoop() {
while (true) {
const headerBytes = await readExactly(4);
if (!headerBytes) return;
const view = new DataView(headerBytes.buffer);
const messageLength = view.getUint32(0, true);
const bodyBytes = await readExactly(messageLength);
if (!bodyBytes) return;
try {
const parsedMessage = JSON.parse(new TextDecoder().decode(bodyBytes));
await sendNativeMessage(parsedMessage);
} catch (err) {
try {
std.write(2, new TextEncoder().encode(`Parse error: ${err.message}\n`));
} catch (_) {}
}
}
}
runHostLoop();
"#;
// 4. Parse source execution cleanly using native bytes slices
context.eval(Source::from_bytes(js_code.as_bytes()))?;
Ok(())
}
These are the errors I'm getting when compiling
cargo build --target wasm32-wasip1 --release
Compiling nm_wasi_host v0.1.0 (/media/user/z/boa)
error[E0624]: method `templates` is private
--> src/main.rs:56:43
|
56 | let read_js_fn = context.intrinsics().templates().function().create(...
| ^^^^^^^^^ private method
|
::: /media/user/x/rust/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/boa_engine-0.19.1/src/context/intrinsics.rs:68:5
|
68 | pub(crate) const fn templates(&self) -> &ObjectTemplates {
| -------------------------------------------------------- private method defined here
error[E0599]: no method named `take_buffer` found for enum `JsValue` in the current scope
--> src/main.rs:68:42
|
68 | let bytes = array_buffer.take_buffer(ctx)?;
| ^^^^^^^^^^^ method not found in `JsValue`
error[E0624]: method `templates` is private
--> src/main.rs:80:44
|
80 | let write_js_fn = context.intrinsics().templates().function().create...
| ^^^^^^^^^ private method
|
::: /media/user/x/rust/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/boa_engine-0.19.1/src/context/intrinsics.rs:68:5
|
68 | pub(crate) const fn templates(&self) -> &ObjectTemplates {
| -------------------------------------------------------- private method defined here
Some errors have detailed explanations: E0599, E0624.
For more information about an error, try `rustc --explain E0599`.
error: could not compile `nm_wasi_host` (bin "nm_wasi_host") due to 3 previous errors
How to fix?