feat(plugins): migrate Python family to TypeBox v2; complete first‑party v2 set (Regex/Net/Path/Math/Time/File)\n\nfeat(smoke): add Net round‑trip sample and wire to plugin v2 smoke\n\nfeat(cli): add --emit-mir-json and runner emit+exit path\n\nchore(config): register Python v2 boxes in nyash.toml\n\nchore(loader): improve v2 diagnostics and ensure v2 shim dispatch\n\nchore(build): integrate ny-llvmc env gate in build_llvm.sh\n\nchore(tasks): update CURRENT_TASK with v2 Python/Net/emit flag

This commit is contained in:
Selfhosting Dev
2025-09-17 22:01:29 +09:00
parent fcf8042622
commit 8aa01668ff
19 changed files with 1782 additions and 19 deletions

View File

@ -3,6 +3,7 @@
//! Provides file I/O operations as a Nyash plugin
use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::c_char;
// std::ptr削除未使用
use std::io::{Read, Seek, SeekFrom, Write};
@ -633,3 +634,317 @@ pub extern "C" fn nyash_plugin_shutdown() {
// ============ Unified Plugin API ============
// Note: Metadata (Box types, methods) now comes from nyash.toml
// This plugin provides only the actual processing functions
// ===== TypeBox ABI v2 (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX' (0x54594258)
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const c_char, // C string, e.g., "FileBox\0"
pub resolve: Option<extern "C" fn(*const c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn filebox_resolve(name: *const c_char) -> u32 {
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
// lifecycle (optional to expose via resolve)
"birth" => METHOD_BIRTH,
"fini" => METHOD_FINI,
// methods
"open" => METHOD_OPEN,
"read" => METHOD_READ,
"write" => METHOD_WRITE,
"close" => METHOD_CLOSE,
"exists" => METHOD_EXISTS,
"copyFrom" => METHOD_COPY_FROM,
"cloneSelf" => METHOD_CLONE_SELF,
_ => 0,
}
}
extern "C" fn filebox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
// Support birth through per-Box invoke for v2 shim
METHOD_BIRTH => {
if result_len.is_null() {
return NYB_E_INVALID_ARGS;
}
if preflight(result, result_len, 4) {
return NYB_E_SHORT_BUFFER;
}
let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(
id,
FileBoxInstance { file: None, path: String::new(), buffer: None },
);
} else {
return NYB_E_PLUGIN_ERROR;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
NYB_SUCCESS
}
METHOD_FINI => {
if let Ok(mut map) = INSTANCES.lock() {
map.remove(&instance_id);
NYB_SUCCESS
} else {
NYB_E_PLUGIN_ERROR
}
}
METHOD_OPEN => {
// args: TLV { String path, String mode }
let slice = std::slice::from_raw_parts(args, args_len);
match tlv_parse_two_strings(slice) {
Ok((path, mode)) => {
if preflight(result, result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
match open_file(&mode, &path) {
Ok(file) => {
inst.file = Some(file);
return write_tlv_void(result, result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
METHOD_READ => {
let slice = std::slice::from_raw_parts(args, args_len);
if args_len > 0 {
match tlv_parse_string(slice) {
Ok(path) => match open_file("r", &path) {
Ok(mut file) => {
let mut buf = Vec::new();
if let Err(_) = file.read_to_end(&mut buf) {
return NYB_E_PLUGIN_ERROR;
}
let need = 8usize.saturating_add(buf.len());
if preflight(result, result_len, need) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bytes(&buf, result, result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
},
Err(_) => return NYB_E_INVALID_ARGS,
}
} else {
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
if let Some(file) = inst.file.as_mut() {
let _ = file.seek(SeekFrom::Start(0));
let mut buf = Vec::new();
match file.read_to_end(&mut buf) {
Ok(_) => {
let need = 8usize.saturating_add(buf.len());
if preflight(result, result_len, need) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bytes(&buf, result, result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
}
METHOD_WRITE => {
let slice = std::slice::from_raw_parts(args, args_len);
match tlv_parse_optional_string_and_bytes(slice) {
Ok((Some(path), data)) => {
if preflight(result, result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
match open_file("w", &path) {
Ok(mut file) => {
if let Err(_) = file.write_all(&data) {
return NYB_E_PLUGIN_ERROR;
}
if let Err(_) = file.flush() {
return NYB_E_PLUGIN_ERROR;
}
return write_tlv_i32(data.len() as i32, result, result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
}
Ok((None, data)) => {
if preflight(result, result_len, 12) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
if let Some(file) = inst.file.as_mut() {
match file.write(&data) {
Ok(n) => {
if let Err(_) = file.flush() {
return NYB_E_PLUGIN_ERROR;
}
inst.buffer = Some(data.clone());
return write_tlv_i32(n as i32, result, result_len);
}
Err(_) => return NYB_E_PLUGIN_ERROR,
}
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
METHOD_CLOSE => {
if preflight(result, result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
if let Some(inst) = map.get_mut(&instance_id) {
inst.file = None;
return write_tlv_void(result, result_len);
} else {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
METHOD_COPY_FROM => {
let slice = std::slice::from_raw_parts(args, args_len);
match tlv_parse_handle(slice) {
Ok((_type_id, other_id)) => {
if preflight(result, result_len, 8) {
return NYB_E_SHORT_BUFFER;
}
if let Ok(mut map) = INSTANCES.lock() {
// Extract data from src
let mut data: Vec<u8> = Vec::new();
if let Some(src) = map.get(&other_id) {
let mut read_ok = false;
if let Some(file) = src.file.as_ref() {
if let Ok(mut f) = file.try_clone() {
let _ = f.seek(SeekFrom::Start(0));
if f.read_to_end(&mut data).is_ok() {
read_ok = true;
}
}
}
if !read_ok {
if let Some(buf) = src.buffer.as_ref() {
data.extend_from_slice(buf);
read_ok = true;
}
}
if !read_ok {
return NYB_E_PLUGIN_ERROR;
}
} else {
return NYB_E_INVALID_HANDLE;
}
// Write into dst
if let Some(dst) = map.get_mut(&instance_id) {
if let Some(fdst) = dst.file.as_mut() {
let _ = fdst.seek(SeekFrom::Start(0));
if fdst.write_all(&data).is_err() {
return NYB_E_PLUGIN_ERROR;
}
let _ = fdst.set_len(data.len() as u64);
let _ = fdst.flush();
}
dst.buffer = Some(data);
return write_tlv_void(result, result_len);
} else {
return NYB_E_INVALID_HANDLE;
}
} else {
return NYB_E_PLUGIN_ERROR;
}
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
METHOD_CLONE_SELF => {
if preflight(result, result_len, 16) {
return NYB_E_SHORT_BUFFER;
}
let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut map) = INSTANCES.lock() {
map.insert(
new_id,
FileBoxInstance { file: None, path: String::new(), buffer: None },
);
}
// Return Handle TLV (type_id from config resolves host-side; we encode (6,new_id) here if needed)
let mut payload = [0u8; 8];
// We dont embed type_id here (host can ignore); keep zero for neutrality
payload[0..4].copy_from_slice(&0u32.to_le_bytes());
payload[4..8].copy_from_slice(&new_id.to_le_bytes());
return write_tlv_result(&[(8u8, &payload)], result, result_len);
}
METHOD_EXISTS => {
let slice = std::slice::from_raw_parts(args, args_len);
match tlv_parse_string(slice) {
Ok(path) => {
let exists = std::path::Path::new(&path).exists();
if preflight(result, result_len, 9) {
return NYB_E_SHORT_BUFFER;
}
return write_tlv_bool(exists, result, result_len);
}
Err(_) => NYB_E_INVALID_ARGS,
}
}
_ => NYB_E_INVALID_METHOD,
}
}
}
#[no_mangle]
pub static nyash_typebox_FileBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258, // 'TYBX'
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"FileBox\0".as_ptr() as *const c_char,
resolve: Some(filebox_resolve),
invoke_id: Some(filebox_invoke_id),
capabilities: 0,
};

View File

@ -118,6 +118,105 @@ pub extern "C" fn nyash_plugin_invoke(
}
}
// ===== TypeBox ABI v2 (resolve/invoke_id per box) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr;
extern "C" fn mathbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"sqrt" => M_SQRT,
"sin" => M_SIN,
"cos" => M_COS,
"round" => M_ROUND,
"birth" => M_BIRTH,
"fini" => M_FINI,
_ => 0,
}
}
extern "C" fn timebox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"now" => T_NOW,
"birth" => M_BIRTH,
"fini" => M_FINI,
_ => 0,
}
}
extern "C" fn mathbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_BIRTH => birth(TID_MATH, &MATH_INST, result, result_len),
M_FINI => fini(&MATH_INST, instance_id),
M_SQRT => sqrt_call(args, args_len, result, result_len),
M_SIN => trig_call(args, args_len, result, result_len, true),
M_COS => trig_call(args, args_len, result, result_len, false),
M_ROUND => round_call(args, args_len, result, result_len),
_ => E_METHOD,
}
}
}
extern "C" fn timebox_invoke_id(
instance_id: u32,
method_id: u32,
_args: *const u8,
_args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_BIRTH => birth(TID_TIME, &TIME_INST, result, result_len),
M_FINI => fini(&TIME_INST, instance_id),
T_NOW => now_call(result, result_len),
_ => E_METHOD,
}
}
}
#[no_mangle]
pub static nyash_typebox_MathBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"MathBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(mathbox_resolve),
invoke_id: Some(mathbox_invoke_id),
capabilities: 0,
};
#[no_mangle]
pub static nyash_typebox_TimeBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"TimeBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(timebox_resolve),
invoke_id: Some(timebox_invoke_id),
capabilities: 0,
};
unsafe fn birth<T>(
tid: u32,
map: &Lazy<Mutex<HashMap<u32, T>>>,

View File

@ -213,6 +213,260 @@ pub extern "C" fn nyash_plugin_invoke(
}
}
// ===== TypeBox ABI v2 (per-Box resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr;
extern "C" fn responsebox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"setStatus" => M_RESP_SET_STATUS,
"setHeader" => M_RESP_SET_HEADER,
"write" => M_RESP_WRITE,
"readBody" => M_RESP_READ_BODY,
"getStatus" => M_RESP_GET_STATUS,
"getHeader" => M_RESP_GET_HEADER,
"birth" => M_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn clientbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"get" => M_CLIENT_GET,
"post" => M_CLIENT_POST,
"birth" => M_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn responsebox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { response_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
extern "C" fn clientbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { client_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_ResponseBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"ResponseBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(responsebox_resolve),
invoke_id: Some(responsebox_invoke_id),
capabilities: 0,
};
#[no_mangle]
pub static nyash_typebox_ClientBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"ClientBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(clientbox_resolve),
invoke_id: Some(clientbox_invoke_id),
capabilities: 0,
};
// --- ServerBox ---
extern "C" fn serverbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"start" => M_SERVER_START,
"stop" => M_SERVER_STOP,
"accept" => M_SERVER_ACCEPT,
"birth" => M_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn serverbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { server_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_ServerBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"ServerBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(serverbox_resolve),
invoke_id: Some(serverbox_invoke_id),
capabilities: 0,
};
// --- SockServerBox ---
extern "C" fn sockserver_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"start" => M_SRV_START,
"stop" => M_SRV_STOP,
"accept" => M_SRV_ACCEPT,
"acceptTimeout" => M_SRV_ACCEPT_TIMEOUT,
"birth" => M_SRV_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn sockserver_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { sock_server_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_SockServerBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"SockServerBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(sockserver_resolve),
invoke_id: Some(sockserver_invoke_id),
capabilities: 0,
};
// --- SockClientBox ---
extern "C" fn sockclient_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"connect" => M_SC_CONNECT,
"birth" => M_SC_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn sockclient_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { sock_client_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_SockClientBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"SockClientBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(sockclient_resolve),
invoke_id: Some(sockclient_invoke_id),
capabilities: 0,
};
// --- SockConnBox ---
extern "C" fn sockconn_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"send" => M_CONN_SEND,
"recv" => M_CONN_RECV,
"close" => M_CONN_CLOSE,
"recvTimeout" => M_CONN_RECV_TIMEOUT,
"birth" => M_CONN_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn sockconn_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { sock_conn_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_SockConnBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"SockConnBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(sockconn_resolve),
invoke_id: Some(sockconn_invoke_id),
capabilities: 0,
};
extern "C" fn requestbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"path" => M_REQ_PATH,
"readBody" => M_REQ_READ_BODY,
"respond" => M_REQ_RESPOND,
"birth" => M_BIRTH,
"fini" => u32::MAX,
_ => 0,
}
}
extern "C" fn requestbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe { request_invoke(method_id, instance_id, args, args_len, result, result_len) }
}
#[no_mangle]
pub static nyash_typebox_RequestBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"RequestBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(requestbox_resolve),
invoke_id: Some(requestbox_invoke_id),
capabilities: 0,
};
unsafe fn server_invoke(
m: u32,
id: u32,

View File

@ -2,6 +2,7 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::ffi::CStr;
use std::path::{Component, Path};
use std::sync::{
atomic::{AtomicU32, Ordering},
@ -152,6 +153,154 @@ pub extern "C" fn nyash_plugin_invoke(
}
}
// ===== TypeBox ABI (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char, // C string
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn pathbox_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() {
return 0;
}
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"join" => M_JOIN,
"dirname" => M_DIRNAME,
"basename" => M_BASENAME,
"extname" => M_EXTNAME,
"isAbs" | "is_absolute" => M_IS_ABS,
"normalize" => M_NORMALIZE,
"birth" => M_BIRTH,
"fini" => M_FINI,
_ => 0,
}
}
extern "C" fn pathbox_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_BIRTH => {
if result_len.is_null() {
return E_ARGS;
}
if preflight(result, result_len, 4) {
return E_SHORT;
}
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = INST.lock() {
m.insert(id, PathInstance);
} else {
return E_PLUGIN;
}
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4;
OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() {
m.remove(&instance_id);
OK
} else {
E_PLUGIN
}
}
M_JOIN => {
let base = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let rest = match read_arg_string(args, args_len, 1) {
Some(s) => s,
None => return E_ARGS,
};
let joined = if base.ends_with('/') || base.ends_with('\\') {
format!("{}{}", base, rest)
} else {
format!("{}/{}", base, rest)
};
write_tlv_string(&joined, result, result_len)
}
M_DIRNAME => {
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let d = Path::new(&p)
.parent()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_else(|| "".to_string());
write_tlv_string(&d, result, result_len)
}
M_BASENAME => {
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let b = Path::new(&p)
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_else(|| "".to_string());
write_tlv_string(&b, result, result_len)
}
M_EXTNAME => {
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let ext = Path::new(&p)
.extension()
.map(|x| format!(".{}", x.to_string_lossy()))
.unwrap_or_else(|| "".to_string());
write_tlv_string(&ext, result, result_len)
}
M_IS_ABS => {
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let abs = Path::new(&p).is_absolute() || p.contains(":\\");
write_tlv_bool(abs, result, result_len)
}
M_NORMALIZE => {
let p = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let norm = path_clean::PathClean::clean(Path::new(&p));
write_tlv_string(norm.to_string_lossy().as_ref(), result, result_len)
}
_ => E_METHOD,
}
}
}
#[no_mangle]
pub static nyash_typebox_PathBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258, // 'TYBX'
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"PathBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(pathbox_resolve),
invoke_id: Some(pathbox_invoke_id),
capabilities: 0,
};
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() {

View File

@ -125,3 +125,104 @@ pub extern "C" fn nyash_plugin_invoke(
_ => NYB_E_INVALID_METHOD,
}
}
// ===== TypeBox ABI v2 (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr;
extern "C" fn pycompiler_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"birth" => METHOD_BIRTH,
"compile" => METHOD_COMPILE,
"fini" => METHOD_FINI,
_ => 0,
}
}
extern "C" fn pycompiler_invoke_id(
_instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
match method_id {
METHOD_BIRTH => unsafe {
let mut id_g = NEXT_ID.lock().unwrap();
let id = *id_g; *id_g += 1;
if result_len.is_null() { return NYB_E_SHORT_BUFFER; }
let need = 4usize;
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..4].copy_from_slice(&(id as u32).to_le_bytes());
*result_len = need;
NYB_SUCCESS
},
METHOD_COMPILE => unsafe {
let ir = if args.is_null() || args_len < 8 { None } else {
let buf = std::slice::from_raw_parts(args, args_len);
let tag = u16::from_le_bytes([buf[4], buf[5]]);
let len = u16::from_le_bytes([buf[6], buf[7]]) as usize;
if tag == 6 && 8 + len <= buf.len() {
std::str::from_utf8(&buf[8..8+len]).ok().map(|s| s.to_string())
} else { None }
};
let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) {
match serde_json::from_str::<Json>(&s).ok() {
Some(Json::Object(map)) => {
if let Some(Json::String(src)) = map.get("nyash_source") { src.clone() }
else if let Some(module) = map.get("module") {
let mut ret_expr = "0".to_string();
if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) {
if let Some(fun0) = funcs.get(0) {
if let Some(retv) = fun0.get("return_value") {
if retv.is_number() { ret_expr = retv.to_string(); }
else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); }
}
}
}
format!("static box Generated {\n main() {\n return {}\n }\n}}", ret_expr)
} else { "static box Generated { main() { return 0 } }".to_string() }
}
_ => "static box Generated { main() { return 0 } }".to_string(),
}
} else { "static box Generated { main() { return 0 } }".to_string() };
let bytes = nyash_source.as_bytes();
if result_len.is_null() { return NYB_E_SHORT_BUFFER; }
let need = 4 + bytes.len();
if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; }
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..2].copy_from_slice(&6u16.to_le_bytes());
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
out[4..4+bytes.len()].copy_from_slice(bytes);
*result_len = need;
NYB_SUCCESS
},
METHOD_FINI => NYB_SUCCESS,
_ => NYB_E_INVALID_METHOD,
}
}
#[no_mangle]
pub static nyash_typebox_PythonCompilerBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"PythonCompilerBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(pycompiler_resolve),
invoke_id: Some(pycompiler_invoke_id),
capabilities: 0,
};

View File

@ -208,6 +208,113 @@ fn parse_python_code(py: Python, code: &str) -> ParseResult {
result
}
// ===== TypeBox ABI v2 (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
const TYPE_ID_PARSER: u32 = 60;
const METHOD_BIRTH: u32 = 0;
const METHOD_PARSE: u32 = 1;
const METHOD_FINI: u32 = u32::MAX;
use std::ffi::CStr;
extern "C" fn pyparser_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"birth" => METHOD_BIRTH,
"parse" => METHOD_PARSE,
"fini" => METHOD_FINI,
_ => 0,
}
}
extern "C" fn pyparser_invoke_id(
_instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
match method_id {
METHOD_BIRTH => unsafe {
let instance_id = 1u32; // simple singleton
if result_len.is_null() { return -1; }
if *result_len < 4 {
*result_len = 4;
return -1;
}
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..4].copy_from_slice(&instance_id.to_le_bytes());
*result_len = 4;
0
},
METHOD_PARSE => {
// Decode TLV string from args if present, else env fallback
let code = unsafe {
if args.is_null() || args_len < 4 {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
} else {
let buf = std::slice::from_raw_parts(args, args_len);
if args_len >= 8 {
let tag = u16::from_le_bytes([buf[0], buf[1]]);
let len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
if tag == 6 && 4 + len <= args_len {
match std::str::from_utf8(&buf[4..4 + len]) { Ok(s) => s.to_string(), Err(_) => std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) }
} else {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
}
} else {
std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string())
}
}
};
let parse_result = Python::with_gil(|py| parse_python_code(py, &code));
match serde_json::to_string(&parse_result) {
Ok(json) => unsafe {
if result_len.is_null() { return -1; }
let bytes = json.as_bytes();
let need = 4 + bytes.len();
if *result_len < need {
*result_len = need;
return -1;
}
let out = std::slice::from_raw_parts_mut(result, *result_len);
out[0..2].copy_from_slice(&6u16.to_le_bytes());
out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes());
out[4..4 + bytes.len()].copy_from_slice(bytes);
*result_len = need;
0
},
Err(_) => -4,
}
}
METHOD_FINI => 0,
_ => -3,
}
}
#[no_mangle]
pub static nyash_typebox_PythonParserBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"PythonParserBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(pyparser_resolve),
invoke_id: Some(pyparser_invoke_id),
capabilities: 0,
};
fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
result.counts.total_nodes += 1;

View File

@ -1,11 +1,6 @@
//! Nyash Python Plugin (Phase 10.5a scaffold)
//! - ABI v1 compatible entry points
//! - Defines two Box types: PyRuntimeBox (TYPE_ID=40) and PyObjectBox (TYPE_ID=41)
//! - Currently stubs; returns NYB_E_INVALID_METHOD for unimplemented routes
//!
//! This crate intentionally does not link to CPython yet. 10.5a focuses on
//! ABI alignment and loader wiring. Future subphases (10.5bd) will implement
//! actual CPython embedding and conversion.
//! Nyash Python Plugin (Phase 15):
//! - ABI v1 compatible entry points + ABI v2 TypeBox exports
//! - Two Box types: PyRuntimeBox (TYPE_ID=40) and PyObjectBox (TYPE_ID=41)
use libloading::Library;
use once_cell::sync::Lazy;
@ -806,6 +801,92 @@ fn handle_py_object(
}
// ===== Minimal TLV helpers (copy from other plugins for consistency) =====
// ===== TypeBox ABI v2 (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
extern "C" fn pyruntime_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"birth" => PY_METHOD_BIRTH,
"eval" | "evalR" => { if s.as_ref() == "evalR" { PY_METHOD_EVAL_R } else { PY_METHOD_EVAL } }
"import" | "importR" => { if s.as_ref() == "importR" { PY_METHOD_IMPORT_R } else { PY_METHOD_IMPORT } }
"fini" => PY_METHOD_FINI,
_ => 0,
}
}
extern "C" fn pyruntime_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
handle_py_runtime(method_id, instance_id, args, args_len, result, result_len)
}
extern "C" fn pyobject_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"getattr" | "getAttr" | "getattrR" | "getAttrR" => {
if s.ends_with('R') { PYO_METHOD_GETATTR_R } else { PYO_METHOD_GETATTR }
}
"call" | "callR" => { if s.ends_with('R') { PYO_METHOD_CALL_R } else { PYO_METHOD_CALL } }
"callKw" | "callKW" | "call_kw" | "callKwR" | "callKWR" => {
if s.to_lowercase().ends_with('r') { PYO_METHOD_CALL_KW_R } else { PYO_METHOD_CALL_KW }
}
"str" | "toString" => PYO_METHOD_STR,
"birth" => PYO_METHOD_BIRTH,
"fini" => PYO_METHOD_FINI,
_ => 0,
}
}
extern "C" fn pyobject_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
handle_py_object(method_id, instance_id, args, args_len, result, result_len)
}
#[no_mangle]
pub static nyash_typebox_PyRuntimeBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"PyRuntimeBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(pyruntime_resolve),
invoke_id: Some(pyruntime_invoke_id),
capabilities: 0,
};
#[no_mangle]
pub static nyash_typebox_PyObjectBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"PyObjectBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(pyobject_resolve),
invoke_id: Some(pyobject_invoke_id),
capabilities: 0,
};
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() {

View File

@ -211,6 +211,123 @@ pub extern "C" fn nyash_plugin_invoke(
}
}
// ===== TypeBox ABI v2 (resolve/invoke_id) =====
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const std::os::raw::c_char,
pub resolve: Option<extern "C" fn(*const std::os::raw::c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
unsafe impl Sync for NyashTypeBoxFfi {}
use std::ffi::CStr;
extern "C" fn regex_resolve(name: *const std::os::raw::c_char) -> u32 {
if name.is_null() { return 0; }
let s = unsafe { CStr::from_ptr(name) }.to_string_lossy();
match s.as_ref() {
"compile" => M_COMPILE,
"isMatch" | "is_match" => M_IS_MATCH,
"find" => M_FIND,
"replaceAll" | "replace_all" => M_REPLACE_ALL,
"split" => M_SPLIT,
"birth" => M_BIRTH,
"fini" => M_FINI,
_ => 0,
}
}
extern "C" fn regex_invoke_id(
instance_id: u32,
method_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32 {
unsafe {
match method_id {
M_BIRTH => {
// mirror v1: birth may take optional pattern
if result_len.is_null() { return E_ARGS; }
if preflight(result, result_len, 4) { return E_SHORT; }
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let inst = if let Some(pat) = read_arg_string(args, args_len, 0) {
match Regex::new(&pat) { Ok(re) => RegexInstance { re: Some(re) }, Err(_) => RegexInstance { re: None } }
} else { RegexInstance { re: None } };
if let Ok(mut m) = INST.lock() { m.insert(id, inst); } else { return E_PLUGIN; }
let b = id.to_le_bytes();
std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4);
*result_len = 4; OK
}
M_FINI => {
if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_PLUGIN }
}
M_COMPILE => {
let pat = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(mut m) = INST.lock() {
if let Some(inst) = m.get_mut(&instance_id) { inst.re = Regex::new(&pat).ok(); OK } else { E_HANDLE }
} else { E_PLUGIN }
}
M_IS_MATCH => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re { return write_tlv_bool(re.is_match(&text), result, result_len); } else { return write_tlv_bool(false, result, result_len); }
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
}
M_FIND => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
let s = re.find(&text).map(|m| m.as_str().to_string()).unwrap_or_else(|| "".to_string());
return write_tlv_string(&s, result, result_len);
} else { return write_tlv_string("", result, result_len); }
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
}
M_REPLACE_ALL => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let repl = match read_arg_string(args, args_len, 1) { Some(s) => s, None => return E_ARGS };
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re { let out = re.replace_all(&text, repl.as_str()).to_string(); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); }
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
}
M_SPLIT => {
let text = match read_arg_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS };
let limit = read_arg_i64(args, args_len, 1).unwrap_or(0);
if let Ok(m) = INST.lock() {
if let Some(inst) = m.get(&instance_id) {
if let Some(re) = &inst.re {
let parts: Vec<String> = if limit > 0 { re.splitn(&text, limit as usize).map(|s| s.to_string()).collect() } else { re.split(&text).map(|s| s.to_string()).collect() };
let out = parts.join("\n");
return write_tlv_string(&out, result, result_len);
} else { return write_tlv_string(&text, result, result_len); }
} else { return E_HANDLE; }
} else { return E_PLUGIN; }
}
_ => E_METHOD,
}
}
}
#[no_mangle]
pub static nyash_typebox_RegexBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
abi_tag: 0x54594258,
version: 1,
struct_size: std::mem::size_of::<NyashTypeBoxFfi>() as u16,
name: b"RegexBox\0".as_ptr() as *const std::os::raw::c_char,
resolve: Some(regex_resolve),
invoke_id: Some(regex_invoke_id),
capabilities: 0,
};
fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool {
unsafe {
if result_len.is_null() {