#![allow(unsafe_code)]
#![allow(unreachable_pub)]
use core::ffi::{c_char, c_int};
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KtStatus {
Ok = 0,
NullPointer = -1,
BufferTooSmall = -2,
InvalidInput = -3,
Internal = -100,
}
#[unsafe(no_mangle)]
pub extern "C" fn kt_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_version_copy(buf: *mut c_char, len: *mut usize) -> c_int {
let status = (|| {
if len.is_null() {
return KtStatus::NullPointer;
}
let version = env!("CARGO_PKG_VERSION").as_bytes();
let cap = unsafe { *len };
unsafe { *len = version.len() };
if cap < version.len() {
return KtStatus::BufferTooSmall;
}
if buf.is_null() {
return KtStatus::NullPointer;
}
unsafe {
core::ptr::copy_nonoverlapping(version.as_ptr(), buf as *mut u8, version.len());
}
KtStatus::Ok
})();
status as c_int
}
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_eval(
source: *const c_char,
source_len: usize,
out: *mut c_char,
out_len: *mut usize,
) -> c_int {
use std::panic::{AssertUnwindSafe, catch_unwind};
let outcome = catch_unwind(AssertUnwindSafe(|| {
if out_len.is_null() || (source.is_null() && source_len != 0) {
return KtStatus::NullPointer;
}
let bytes: &[u8] = if source.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(source as *const u8, source_len) }
};
let Ok(src) = core::str::from_utf8(bytes) else {
return KtStatus::InvalidInput;
};
let (text, ok) = match eval_to_string(src) {
Ok(value) => (value, true),
Err(message) => (message, false),
};
match unsafe { copy_out(text.as_bytes(), out, out_len) } {
KtStatus::Ok if !ok => KtStatus::InvalidInput,
other => other,
}
}));
match outcome {
Ok(status) => status as c_int,
Err(_) => KtStatus::Internal as c_int,
}
}
#[cfg(feature = "std")]
fn eval_to_string(src: &str) -> Result<alloc::string::String, alloc::string::String> {
crate::nbvm::execute(src).map(|(_output, completion)| completion)
}
#[cfg(feature = "std")]
fn compile_to_bytes(src: &str) -> Result<alloc::vec::Vec<u8>, alloc::string::String> {
use alloc::string::ToString;
let program = crate::parser::Parser::parse_program(src).map_err(|e| e.to_string())?;
let protos = crate::nbvm::compile_program(&program).map_err(|e| alloc::format!("{e:?}"))?;
Ok(crate::bytecode::serialize(&protos))
}
#[cfg(feature = "std")]
fn run_bytecode_to_string(bytes: &[u8]) -> Result<alloc::string::String, alloc::string::String> {
let protos =
crate::bytecode::deserialize_verified(bytes).map_err(|e| alloc::format!("{e:?}"))?;
let mut realm = crate::realm::Realm::new();
match crate::nbvm::run_program_capturing(&mut realm, &protos, 0, &[]) {
Ok((value, _output)) => Ok(realm.to_display_string(value)),
Err(e) => Err(alloc::format!("{e:?}")),
}
}
#[cfg(feature = "std")]
fn snapshot_source(src: &str) -> Result<alloc::vec::Vec<u8>, alloc::string::String> {
use alloc::string::ToString;
let program = crate::parser::Parser::parse_program(src).map_err(|e| e.to_string())?;
let mut interp = crate::nbexec::Interp::new();
let value = interp.run(&program).map_err(|e| alloc::format!("{e:?}"))?;
if value.as_handle().is_none() {
return Err("completion value is not a heap object to snapshot".to_string());
}
Ok(interp.snapshot(&[value]))
}
#[cfg(feature = "std")]
fn restore_to_string(bytes: &[u8]) -> Result<alloc::string::String, alloc::string::String> {
let mut interp = crate::nbexec::Interp::new();
let roots = interp
.restore_snapshot(bytes)
.map_err(|e| alloc::format!("{e:?}"))?;
let root = roots
.first()
.copied()
.unwrap_or(crate::nanbox::NanBox::undefined());
Ok(interp.realm().to_display_string(root))
}
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_compile(
source: *const c_char,
source_len: usize,
out: *mut c_char,
out_len: *mut usize,
) -> c_int {
use std::panic::{AssertUnwindSafe, catch_unwind};
let outcome = catch_unwind(AssertUnwindSafe(|| {
if out_len.is_null() || (source.is_null() && source_len != 0) {
return KtStatus::NullPointer;
}
let bytes: &[u8] = if source.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(source as *const u8, source_len) }
};
let Ok(src) = core::str::from_utf8(bytes) else {
return KtStatus::InvalidInput;
};
let (data, ok) = match compile_to_bytes(src) {
Ok(artifact) => (artifact, true),
Err(message) => (message.into_bytes(), false),
};
match unsafe { copy_out(&data, out, out_len) } {
KtStatus::Ok if !ok => KtStatus::InvalidInput,
other => other,
}
}));
match outcome {
Ok(status) => status as c_int,
Err(_) => KtStatus::Internal as c_int,
}
}
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_load_bytecode(
bytecode: *const c_char,
bytecode_len: usize,
out: *mut c_char,
out_len: *mut usize,
) -> c_int {
use std::panic::{AssertUnwindSafe, catch_unwind};
let outcome = catch_unwind(AssertUnwindSafe(|| {
if out_len.is_null() || (bytecode.is_null() && bytecode_len != 0) {
return KtStatus::NullPointer;
}
let bytes: &[u8] = if bytecode.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(bytecode as *const u8, bytecode_len) }
};
let (text, ok) = match run_bytecode_to_string(bytes) {
Ok(value) => (value, true),
Err(message) => (message, false),
};
match unsafe { copy_out(text.as_bytes(), out, out_len) } {
KtStatus::Ok if !ok => KtStatus::InvalidInput,
other => other,
}
}));
match outcome {
Ok(status) => status as c_int,
Err(_) => KtStatus::Internal as c_int,
}
}
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_snapshot(
source: *const c_char,
source_len: usize,
out: *mut c_char,
out_len: *mut usize,
) -> c_int {
use std::panic::{AssertUnwindSafe, catch_unwind};
let outcome = catch_unwind(AssertUnwindSafe(|| {
if out_len.is_null() || (source.is_null() && source_len != 0) {
return KtStatus::NullPointer;
}
let bytes: &[u8] = if source.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(source as *const u8, source_len) }
};
let Ok(src) = core::str::from_utf8(bytes) else {
return KtStatus::InvalidInput;
};
let (data, ok) = match snapshot_source(src) {
Ok(artifact) => (artifact, true),
Err(message) => (message.into_bytes(), false),
};
match unsafe { copy_out(&data, out, out_len) } {
KtStatus::Ok if !ok => KtStatus::InvalidInput,
other => other,
}
}));
match outcome {
Ok(status) => status as c_int,
Err(_) => KtStatus::Internal as c_int,
}
}
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn kt_restore(
snapshot: *const c_char,
snapshot_len: usize,
out: *mut c_char,
out_len: *mut usize,
) -> c_int {
use std::panic::{AssertUnwindSafe, catch_unwind};
let outcome = catch_unwind(AssertUnwindSafe(|| {
if out_len.is_null() || (snapshot.is_null() && snapshot_len != 0) {
return KtStatus::NullPointer;
}
let bytes: &[u8] = if snapshot.is_null() {
&[]
} else {
unsafe { core::slice::from_raw_parts(snapshot as *const u8, snapshot_len) }
};
let (text, ok) = match restore_to_string(bytes) {
Ok(value) => (value, true),
Err(message) => (message, false),
};
match unsafe { copy_out(text.as_bytes(), out, out_len) } {
KtStatus::Ok if !ok => KtStatus::InvalidInput,
other => other,
}
}));
match outcome {
Ok(status) => status as c_int,
Err(_) => KtStatus::Internal as c_int,
}
}
#[cfg(feature = "std")]
unsafe fn copy_out(data: &[u8], out: *mut c_char, out_len: *mut usize) -> KtStatus {
let cap = unsafe { *out_len };
unsafe { *out_len = data.len() };
if cap < data.len() {
return KtStatus::BufferTooSmall;
}
if out.is_null() {
return KtStatus::NullPointer;
}
unsafe {
core::ptr::copy_nonoverlapping(data.as_ptr(), out as *mut u8, data.len());
}
KtStatus::Ok
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_string_is_nul_terminated() {
let ptr = kt_version();
assert!(!ptr.is_null());
let s = unsafe { core::ffi::CStr::from_ptr(ptr) };
assert_eq!(s.to_str().unwrap(), crate::VERSION);
}
#[test]
fn version_copy_length_query_and_copy() {
let mut len: usize = 0;
let rc = unsafe { kt_version_copy(core::ptr::null_mut(), &mut len) };
assert_eq!(rc, KtStatus::BufferTooSmall as i32);
assert_eq!(len, crate::VERSION.len());
let mut buf = alloc::vec![0i8; len];
let rc = unsafe { kt_version_copy(buf.as_mut_ptr(), &mut len) };
assert_eq!(rc, KtStatus::Ok as i32);
let bytes: alloc::vec::Vec<u8> = buf.iter().map(|&b| b as u8).collect();
assert_eq!(core::str::from_utf8(&bytes).unwrap(), crate::VERSION);
let rc = unsafe { kt_version_copy(core::ptr::null_mut(), core::ptr::null_mut()) };
assert_eq!(rc, KtStatus::NullPointer as i32);
}
#[cfg(feature = "std")]
fn eval_str(src: &str) -> (KtStatus, alloc::string::String) {
let mut len: usize = 0;
let rc = unsafe {
kt_eval(
src.as_ptr() as *const c_char,
src.len(),
core::ptr::null_mut(),
&mut len,
)
};
let mut buf = alloc::vec![0i8; len];
let rc2 = unsafe {
kt_eval(
src.as_ptr() as *const c_char,
src.len(),
buf.as_mut_ptr(),
&mut len,
)
};
let _ = rc;
let bytes: alloc::vec::Vec<u8> = buf.iter().map(|&b| b as u8).collect();
let text = alloc::string::String::from_utf8(bytes).unwrap();
(
if rc2 == KtStatus::Ok as i32 {
KtStatus::Ok
} else {
KtStatus::InvalidInput
},
text,
)
}
#[cfg(feature = "std")]
#[test]
fn eval_runs_javascript() {
let (status, out) = eval_str("const f = (a, b) => a * b; f(6, 7)");
assert_eq!(status, KtStatus::Ok);
assert_eq!(out, "42");
let (status, out) = eval_str("[1, 2, 3].map(x => x * x).join(',')");
assert_eq!(status, KtStatus::Ok);
assert_eq!(out, "1,4,9");
}
#[cfg(feature = "std")]
fn snapshot_bytes(src: &str) -> (KtStatus, alloc::vec::Vec<u8>) {
let mut len: usize = 0;
unsafe {
kt_snapshot(
src.as_ptr() as *const c_char,
src.len(),
core::ptr::null_mut(),
&mut len,
)
};
let mut buf = alloc::vec![0i8; len];
let rc = unsafe {
kt_snapshot(
src.as_ptr() as *const c_char,
src.len(),
buf.as_mut_ptr(),
&mut len,
)
};
let bytes: alloc::vec::Vec<u8> = buf.iter().map(|&b| b as u8).collect();
let status = if rc == KtStatus::Ok as i32 {
KtStatus::Ok
} else {
KtStatus::InvalidInput
};
(status, bytes)
}
#[cfg(feature = "std")]
fn restore_str(snapshot: &[u8]) -> (KtStatus, alloc::string::String) {
let mut len: usize = 0;
unsafe {
kt_restore(
snapshot.as_ptr() as *const c_char,
snapshot.len(),
core::ptr::null_mut(),
&mut len,
)
};
let mut buf = alloc::vec![0i8; len];
let rc = unsafe {
kt_restore(
snapshot.as_ptr() as *const c_char,
snapshot.len(),
buf.as_mut_ptr(),
&mut len,
)
};
let bytes: alloc::vec::Vec<u8> = buf.iter().map(|&b| b as u8).collect();
let status = if rc == KtStatus::Ok as i32 {
KtStatus::Ok
} else {
KtStatus::InvalidInput
};
(status, alloc::string::String::from_utf8(bytes).unwrap())
}
#[cfg(feature = "std")]
#[test]
fn snapshot_and_restore_round_trip() {
let (s1, bytes) = snapshot_bytes("[1, 2, 3]");
assert_eq!(s1, KtStatus::Ok);
assert!(!bytes.is_empty());
let (s2, text) = restore_str(&bytes);
assert_eq!(s2, KtStatus::Ok);
assert_eq!(text, "1,2,3");
let (s3, _) = snapshot_bytes("42");
assert_eq!(s3, KtStatus::InvalidInput);
let (s4, _) = restore_str(b"not a snapshot");
assert_eq!(s4, KtStatus::InvalidInput);
}
#[cfg(feature = "std")]
#[test]
fn eval_reports_throws_and_parse_errors() {
let (status, out) = eval_str("throw new TypeError('boom')");
assert_eq!(status, KtStatus::InvalidInput);
assert_eq!(out, "TypeError: boom");
let (status, _) = eval_str("const = =");
assert_eq!(status, KtStatus::InvalidInput);
}
}