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:
26
.github/workflows/plugin-v2-smoke.yml
vendored
Normal file
26
.github/workflows/plugin-v2-smoke.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: plugin-v2-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, "**" ]
|
||||
pull_request:
|
||||
branches: [ "**" ]
|
||||
|
||||
jobs:
|
||||
linux-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run plugin v2 smoke
|
||||
run: |
|
||||
bash tools/plugin_v2_smoke.sh
|
||||
|
||||
@ -155,3 +155,46 @@ Notes / Policies
|
||||
- No full tracing/moving GC yet; handles/Arc lifetimes govern object retention. Safepoint/barrier/roots are staging utilities.
|
||||
- GC mode UX: keep user‑facing modes minimal (rc+cycle, minorgen); advanced modes are opt‑in for language dev.
|
||||
- Legacy Interpreter/VM は段階的にアーカイブへ。日常の意味論確認は PyVM を基準として継続。
|
||||
|
||||
Plugin ABI v2 updates (2025‑09‑17)
|
||||
- v2 migration (TypeBox) 完了/進捗
|
||||
- 完了: FileBox / PathBox / RegexBox / MathBox / TimeBox
|
||||
- Net 完了: ClientBox / ResponseBox / RequestBox / ServerBox / SockServerBox / SockClientBox / SockConnBox
|
||||
- 既存: ConsoleBox / StringBox / IntegerBox / MapBox は v2 実装あり
|
||||
- ローダ診断強化
|
||||
- `NYASH_DEBUG_PLUGIN=1` で TypeBox 未検出/ABI不一致/invoke_id未定義の詳細をログ出力
|
||||
- Docs 追補
|
||||
- `docs/reference/plugin-abi/nyash_abi_v2.md` に命名規約・例(Regex/Net)を追記
|
||||
- `include/nyash_abi.h`(Cヘッダ)追加済み
|
||||
- 設定/スモーク/CI
|
||||
- `nyash.toml` に各 v2 Box を登録(type_id/method_id 定義)
|
||||
- スモーク: `tools/plugin_v2_smoke.sh`(Linux常時)。全 v2 プラグインのロード確認+簡易機能スモーク(`apps/tests/plugin_v2_functional.nyash`)
|
||||
- LLVM 共通化の足場
|
||||
- `tools/build_llvm.sh` に `NYASH_LLVM_COMPILER=crate|harness` を追加(`crate` は `ny-llvmc`。JSON は `NYASH_LLVM_MIR_JSON` 指定)
|
||||
- JSON スキーマ検証を可能なら実行(`tools/validate_mir_json.py`)
|
||||
|
||||
Plugin ABI v2 updates (2025‑09‑17 — Python family + Net smoke + JSON emit)
|
||||
- v2 migration(Python 系 完了)
|
||||
- `plugins/nyash-python-plugin`: `nyash_typebox_PyRuntimeBox` / `nyash_typebox_PyObjectBox` を追加(resolve/invoke_id 実装。既存 v1 は残存)
|
||||
- `plugins/nyash-python-parser-plugin`: `nyash_typebox_PythonParserBox` を追加(birth/parse/fini)
|
||||
- `plugins/nyash-python-compiler-plugin`: `nyash_typebox_PythonCompilerBox` を追加(birth/compile/fini)
|
||||
- `nyash.toml` に Python 系 3 ライブラリを登録(type_id: 40/41/60/61)
|
||||
- Net 往復スモーク(最小)
|
||||
- 追加: `apps/tests/net_roundtrip.nyash`(Server.start→Client.get→Server.accept/respond→Client.readBody)
|
||||
- 追加: `tools/plugin_v2_smoke.sh` に Net 機能スモークを条件付きで実行(CI常時ジョブに内包)
|
||||
- nyash → MIR JSON emit フラグ
|
||||
- CLI `--emit-mir-json <path>` を追加(`src/cli.rs`)。`runner.execute_mir_module` でファイル出力→即終了を実装。
|
||||
- これにより `ny-llvmc` へ JSON を直結しやすくなった(次の CI 経路で使用予定)
|
||||
|
||||
Plan after restart(次の計画)
|
||||
- Python 系プラグインの v2 化(parser/compiler/python-plugin)
|
||||
- Docs 追記(Net/Regex のメソッド表、型/戻りTLVの簡易表)
|
||||
- スモーク強化
|
||||
- Net: `ServerBox.start -> Client.get -> Request.respond -> Response.readBody` の往復最小ケースを追加
|
||||
- 主要 v2 Box の軽機能(String/Array/Map/Regex/Path/Math/Time)を 1 ジョブで走らせる
|
||||
- LLVM 共通化
|
||||
- `nyash` からの JSON emit コマンド/フラグ導入(`--emit-mir-json <path>` など)→ `ny-llvmc` 直結
|
||||
- CI に `ny-llvmc` 実 JSON 経路を追加(Linux 常時)
|
||||
- NyRT 整理(軽)
|
||||
- TLV/エラー定数を `include/nyash_abi.h` と整合させる(ヘッダ経由参照)
|
||||
- (必要時)`nyrt_last_error()` の追加検討
|
||||
|
||||
25
apps/tests/net_roundtrip.nyash
Normal file
25
apps/tests/net_roundtrip.nyash
Normal file
@ -0,0 +1,25 @@
|
||||
// Net round-trip smoke: ServerBox ↔ ClientBox via HTTP over localhost
|
||||
// Steps:
|
||||
// 1) start server on port 18081
|
||||
// 2) client GET http://127.0.0.1:18081/hello and get a ResponseBox handle
|
||||
// 3) server accept(), respond with status=200 and body "ok"
|
||||
// 4) client read status/body and print values
|
||||
|
||||
local srv = new ServerBox()
|
||||
srv.start(18081)
|
||||
|
||||
local cli = new ClientBox()
|
||||
local resp_c = cli.get("http://127.0.0.1:18081/hello")
|
||||
|
||||
local req = srv.accept()
|
||||
local resp_s = new ResponseBox()
|
||||
resp_s.setStatus(200)
|
||||
resp_s.setHeader("Content-Type", "text/plain")
|
||||
resp_s.write("ok")
|
||||
req.respond(resp_s)
|
||||
|
||||
print(resp_c.getStatus())
|
||||
print(resp_c.readBody())
|
||||
|
||||
// Clean up
|
||||
srv.stop()
|
||||
12
apps/tests/plugin_v2_functional.nyash
Normal file
12
apps/tests/plugin_v2_functional.nyash
Normal file
@ -0,0 +1,12 @@
|
||||
// Plugin v2 functional smoke (regex + response only)
|
||||
local r = new RegexBox()
|
||||
r.compile("h.llo")
|
||||
print(r.isMatch("hello"))
|
||||
print(r.isMatch("HALLO"))
|
||||
|
||||
local resp = new ResponseBox()
|
||||
resp.setStatus(201)
|
||||
resp.setHeader("X-Test", "ok")
|
||||
resp.write("body-123")
|
||||
print(resp.getStatus())
|
||||
print(resp.readBody())
|
||||
237
nyash.toml
237
nyash.toml
@ -10,3 +10,240 @@ selfhost.compiler.debug = "apps/selfhost-compiler/boxes/debug_box.nyash"
|
||||
selfhost.compiler.parser = "apps/selfhost-compiler/boxes/parser_box.nyash"
|
||||
selfhost.compiler.emitter = "apps/selfhost-compiler/boxes/emitter_box.nyash"
|
||||
|
||||
# v2 Plugin libraries (loader reads these for TypeBox ABI)
|
||||
[libraries]
|
||||
[libraries."libnyash_filebox_plugin.so"]
|
||||
boxes = ["FileBox"]
|
||||
path = "plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so"
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox]
|
||||
type_id = 6
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
open = { method_id = 1 }
|
||||
read = { method_id = 2 }
|
||||
write = { method_id = 3 }
|
||||
close = { method_id = 4 }
|
||||
exists = { method_id = 5 }
|
||||
copyFrom = { method_id = 7 }
|
||||
cloneSelf = { method_id = 8 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_path_plugin.so"]
|
||||
boxes = ["PathBox"]
|
||||
path = "plugins/nyash-path-plugin/target/release/libnyash_path_plugin.so"
|
||||
|
||||
[libraries."libnyash_path_plugin.so".PathBox]
|
||||
type_id = 55
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_path_plugin.so".PathBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
join = { method_id = 1 }
|
||||
dirname = { method_id = 2 }
|
||||
basename = { method_id = 3 }
|
||||
extname = { method_id = 4 }
|
||||
isAbs = { method_id = 5 }
|
||||
normalize = { method_id = 6 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_math_plugin.so"]
|
||||
boxes = ["MathBox", "TimeBox"]
|
||||
path = "plugins/nyash-math-plugin/target/release/libnyash_math_plugin.so"
|
||||
|
||||
[libraries."libnyash_math_plugin.so".MathBox]
|
||||
type_id = 50
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_math_plugin.so".MathBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
sqrt = { method_id = 1 }
|
||||
sin = { method_id = 2 }
|
||||
cos = { method_id = 3 }
|
||||
round = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_math_plugin.so".TimeBox]
|
||||
type_id = 51
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_math_plugin.so".TimeBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
now = { method_id = 1 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_regex_plugin.so"]
|
||||
boxes = ["RegexBox"]
|
||||
path = "plugins/nyash-regex-plugin/target/release/libnyash_regex_plugin.so"
|
||||
|
||||
[libraries."libnyash_regex_plugin.so".RegexBox]
|
||||
type_id = 52
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_regex_plugin.so".RegexBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
compile = { method_id = 1 }
|
||||
isMatch = { method_id = 2 }
|
||||
find = { method_id = 3 }
|
||||
replaceAll = { method_id = 4 }
|
||||
split = { method_id = 5 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so"]
|
||||
boxes = ["ClientBox", "ResponseBox", "RequestBox", "ServerBox", "SockServerBox", "SockClientBox", "SockConnBox"]
|
||||
path = "plugins/nyash-net-plugin/target/release/libnyash_net_plugin.so"
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ClientBox]
|
||||
type_id = 23
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ClientBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
get = { method_id = 1 }
|
||||
post = { method_id = 2 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ResponseBox]
|
||||
type_id = 22
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ResponseBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
setStatus = { method_id = 1 }
|
||||
setHeader = { method_id = 2 }
|
||||
write = { method_id = 3 }
|
||||
readBody = { method_id = 4 }
|
||||
getStatus = { method_id = 5 }
|
||||
getHeader = { method_id = 6 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".RequestBox]
|
||||
type_id = 21
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".RequestBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
path = { method_id = 1 }
|
||||
readBody = { method_id = 2 }
|
||||
respond = { method_id = 3 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ServerBox]
|
||||
type_id = 20
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".ServerBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
start = { method_id = 1 }
|
||||
stop = { method_id = 2 }
|
||||
accept = { method_id = 3 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockServerBox]
|
||||
type_id = 30
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockServerBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
start = { method_id = 1 }
|
||||
stop = { method_id = 2 }
|
||||
accept = { method_id = 3 }
|
||||
acceptTimeout = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockClientBox]
|
||||
type_id = 32
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockClientBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
connect = { method_id = 1 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockConnBox]
|
||||
type_id = 31
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_net_plugin.so".SockConnBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
send = { method_id = 1 }
|
||||
recv = { method_id = 2 }
|
||||
close = { method_id = 3 }
|
||||
recvTimeout = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
# Python (v2 TypeBox) plugins
|
||||
[libraries."libnyash_python_plugin.so"]
|
||||
boxes = ["PyRuntimeBox", "PyObjectBox"]
|
||||
path = "plugins/nyash-python-plugin/target/release/libnyash_python_plugin.so"
|
||||
|
||||
[libraries."libnyash_python_plugin.so".PyRuntimeBox]
|
||||
type_id = 40
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_python_plugin.so".PyRuntimeBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
eval = { method_id = 1 }
|
||||
import = { method_id = 2 }
|
||||
evalR = { method_id = 11 }
|
||||
importR = { method_id = 12 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_python_plugin.so".PyObjectBox]
|
||||
type_id = 41
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_python_plugin.so".PyObjectBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
getattr = { method_id = 1 }
|
||||
call = { method_id = 2 }
|
||||
str = { method_id = 3 }
|
||||
callKw = { method_id = 5 }
|
||||
getattrR = { method_id = 11 }
|
||||
callR = { method_id = 12 }
|
||||
callKwR = { method_id = 15 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_python_parser_plugin.so"]
|
||||
boxes = ["PythonParserBox"]
|
||||
path = "plugins/nyash-python-parser-plugin/target/release/libnyash_python_parser_plugin.so"
|
||||
|
||||
[libraries."libnyash_python_parser_plugin.so".PythonParserBox]
|
||||
type_id = 60
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_python_parser_plugin.so".PythonParserBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
parse = { method_id = 1 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
[libraries."libnyash_python_compiler_plugin.so"]
|
||||
boxes = ["PythonCompilerBox"]
|
||||
path = "plugins/nyash-python-compiler-plugin/target/release/libnyash_python_compiler_plugin.so"
|
||||
|
||||
[libraries."libnyash_python_compiler_plugin.so".PythonCompilerBox]
|
||||
type_id = 61
|
||||
abi_version = 1
|
||||
singleton = false
|
||||
|
||||
[libraries."libnyash_python_compiler_plugin.so".PythonCompilerBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
compile = { method_id = 1 }
|
||||
fini = { method_id = 4294967295 }
|
||||
|
||||
@ -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 don’t 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,
|
||||
};
|
||||
|
||||
@ -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>>>,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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.5b–d) 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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
10
src/cli.rs
10
src/cli.rs
@ -69,6 +69,8 @@ pub struct CliConfig {
|
||||
pub build_target: Option<String>,
|
||||
// Using (CLI)
|
||||
pub cli_usings: Vec<String>,
|
||||
// Emit MIR JSON to a file and exit (bridge mode)
|
||||
pub emit_mir_json: Option<String>,
|
||||
}
|
||||
|
||||
impl CliConfig {
|
||||
@ -131,6 +133,12 @@ impl CliConfig {
|
||||
.value_name("FILE")
|
||||
.help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("emit-mir-json")
|
||||
.long("emit-mir-json")
|
||||
.value_name("FILE")
|
||||
.help("Emit MIR JSON v0 to file (validation-friendly) and exit")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("stage3")
|
||||
.long("stage3")
|
||||
@ -482,6 +490,7 @@ impl CliConfig {
|
||||
.get_many::<String>("using")
|
||||
.map(|v| v.cloned().collect())
|
||||
.unwrap_or_else(|| Vec::new()),
|
||||
emit_mir_json: matches.get_one::<String>("emit-mir-json").cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -536,6 +545,7 @@ impl Default for CliConfig {
|
||||
build_profile: None,
|
||||
build_target: None,
|
||||
cli_usings: Vec::new(),
|
||||
emit_mir_json: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +171,16 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
|
||||
impl NyashRunner {
|
||||
pub(crate) fn execute_mir_module(&self, module: &crate::mir::MirModule) {
|
||||
// If CLI requested MIR JSON emit, write to file and exit immediately.
|
||||
if let Some(path) = self.config.emit_mir_json.as_ref() {
|
||||
let p = std::path::Path::new(path);
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, p) {
|
||||
eprintln!("❌ MIR JSON emit error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
println!("MIR JSON written: {}", p.display());
|
||||
std::process::exit(0);
|
||||
}
|
||||
use crate::backend::MirInterpreter;
|
||||
use crate::box_trait::{BoolBox, IntegerBox, StringBox};
|
||||
use crate::boxes::FloatBox;
|
||||
|
||||
@ -159,6 +159,16 @@ impl PluginLoaderV2 {
|
||||
let abi_ok = st.abi_tag == 0x5459_4258
|
||||
&& st.struct_size as usize >= std::mem::size_of::<NyashTypeBoxFfi>();
|
||||
if !abi_ok {
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: invalid TypeBox ABI for {}.{} (abi_tag=0x{:08x} size={} need>={})",
|
||||
lib_name,
|
||||
box_type,
|
||||
st.abi_tag,
|
||||
st.struct_size,
|
||||
std::mem::size_of::<NyashTypeBoxFfi>()
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Remember invoke_id in box_specs for (lib_name, box_type)
|
||||
@ -172,7 +182,19 @@ impl PluginLoaderV2 {
|
||||
invoke_id: None,
|
||||
});
|
||||
entry.invoke_id = Some(invoke_id);
|
||||
} else if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: TypeBox present but no invoke_id for {}.{} — plugin should export per-Box invoke",
|
||||
lib_name, box_type
|
||||
);
|
||||
}
|
||||
} else if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] NOTE: TypeBox symbol not found for {}.{} (symbol='{}'). Migrate plugin to Nyash ABI v2 to enable per-Box dispatch.",
|
||||
lib_name,
|
||||
box_type,
|
||||
sym_name.trim_end_matches('\0')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,8 +250,24 @@ impl PluginLoaderV2 {
|
||||
let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?;
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().ok()?;
|
||||
let spec = map.get(&key)?;
|
||||
spec.invoke_id
|
||||
let spec = map.get(&key);
|
||||
if let Some(s) = spec {
|
||||
if s.invoke_id.is_none() && dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
s.invoke_id
|
||||
} else {
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] INFO: no TypeBox spec loaded for {}.{} (type_id={}).",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata_for_type_id(&self, type_id: u32) -> Option<PluginBoxMetadata> {
|
||||
|
||||
@ -25,7 +25,6 @@ pub fn box_invoke_for_type_id(type_id: u32) -> Option<super::enabled::host_bridg
|
||||
}
|
||||
|
||||
/// Library-level shim to dispatch a v2 per-Box invoke function using type_id
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_invoke_v2_shim(
|
||||
type_id: u32,
|
||||
method_id: u32,
|
||||
|
||||
@ -59,14 +59,40 @@ stem=${stem%.nyash}
|
||||
OBJ="${NYASH_LLVM_OBJ_OUT:-$PWD/target/aot_objects/${stem}.o}"
|
||||
if [[ "${NYASH_LLVM_SKIP_EMIT:-0}" != "1" ]]; then
|
||||
rm -f "$OBJ"
|
||||
if [[ "${NYASH_LLVM_FEATURE:-llvm}" == "llvm-inkwell-legacy" ]]; then
|
||||
# Legacy path: do not use harness
|
||||
NYASH_LLVM_OBJ_OUT="$OBJ" LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \
|
||||
./target/release/nyash --backend llvm "$INPUT" >/dev/null || true
|
||||
else
|
||||
# Harness path
|
||||
NYASH_LLVM_OBJ_OUT="$OBJ" NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \
|
||||
./target/release/nyash --backend llvm "$INPUT" >/dev/null || true
|
||||
COMPILER_MODE=${NYASH_LLVM_COMPILER:-harness}
|
||||
case "$COMPILER_MODE" in
|
||||
crate)
|
||||
# Use crates/nyash-llvm-compiler (ny-llvmc): requires pre-generated MIR JSON path in NYASH_LLVM_MIR_JSON
|
||||
if [[ -z "${NYASH_LLVM_MIR_JSON:-}" ]]; then
|
||||
echo "[warn] NYASH_LLVM_COMPILER=crate but NYASH_LLVM_MIR_JSON is not set; falling back to harness" >&2
|
||||
COMPILER_MODE="harness"
|
||||
else
|
||||
echo " using ny-llvmc (crate) with JSON: $NYASH_LLVM_MIR_JSON" >&2
|
||||
cargo build --release -p nyash-llvm-compiler >/dev/null
|
||||
# Optional schema validation if tool is available
|
||||
if [[ -f tools/validate_mir_json.py ]]; then
|
||||
if ! python3 -m jsonschema --version >/dev/null 2>&1; then
|
||||
echo "[warn] jsonschema not available; skipping schema validation" >&2
|
||||
else
|
||||
echo " validating MIR JSON schema ..." >&2
|
||||
python3 tools/validate_mir_json.py "$NYASH_LLVM_MIR_JSON" || {
|
||||
echo "error: MIR JSON validation failed" >&2; exit 3; }
|
||||
fi
|
||||
fi
|
||||
./target/release/ny-llvmc --in "$NYASH_LLVM_MIR_JSON" --out "$OBJ"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
if [[ "$COMPILER_MODE" == "harness" ]]; then
|
||||
if [[ "${NYASH_LLVM_FEATURE:-llvm}" == "llvm-inkwell-legacy" ]]; then
|
||||
# Legacy path: do not use harness
|
||||
NYASH_LLVM_OBJ_OUT="$OBJ" LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \
|
||||
./target/release/nyash --backend llvm "$INPUT" >/dev/null || true
|
||||
else
|
||||
# Harness path (Python llvmlite)
|
||||
NYASH_LLVM_OBJ_OUT="$OBJ" NYASH_LLVM_USE_HARNESS=1 LLVM_SYS_181_PREFIX="${_LLVMPREFIX}" LLVM_SYS_180_PREFIX="${_LLVMPREFIX}" \
|
||||
./target/release/nyash --backend llvm "$INPUT" >/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ ! -f "$OBJ" ]]; then
|
||||
|
||||
114
tools/plugin_v2_smoke.sh
Normal file
114
tools/plugin_v2_smoke.sh
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." &>/dev/null && pwd)
|
||||
BIN="$ROOT_DIR/target/release/nyash"
|
||||
APP="$ROOT_DIR/test_filebox_simple.nyash"
|
||||
|
||||
echo "[plugin-v2-smoke] building nyash (release)…" >&2
|
||||
cargo build --release -j 2 >/dev/null
|
||||
|
||||
echo "[plugin-v2-smoke] building FileBox plugin (release)…" >&2
|
||||
pushd "$ROOT_DIR/plugins/nyash-filebox-plugin" >/dev/null
|
||||
cargo build --release -j 2 >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
echo "[plugin-v2-smoke] building PathBox plugin (release)…" >&2
|
||||
pushd "$ROOT_DIR/plugins/nyash-path-plugin" >/dev/null
|
||||
cargo build --release -j 2 >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
echo "[plugin-v2-smoke] building Math/Time plugin (release)…" >&2
|
||||
pushd "$ROOT_DIR/plugins/nyash-math-plugin" >/dev/null
|
||||
cargo build --release -j 2 >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
echo "[plugin-v2-smoke] building Regex plugin (release)…" >&2
|
||||
pushd "$ROOT_DIR/plugins/nyash-regex-plugin" >/dev/null
|
||||
cargo build --release -j 2 >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
echo "[plugin-v2-smoke] building Net plugin (release)…" >&2
|
||||
pushd "$ROOT_DIR/plugins/nyash-net-plugin" >/dev/null
|
||||
cargo build --release -j 2 >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
if [[ ! -x "$BIN" ]]; then
|
||||
echo "error: nyash binary not found at $BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export NYASH_CLI_VERBOSE=${NYASH_CLI_VERBOSE:-1}
|
||||
export NYASH_DEBUG_PLUGIN=${NYASH_DEBUG_PLUGIN:-1}
|
||||
export NYASH_VM_USE_PY=0
|
||||
unset NYASH_DISABLE_PLUGINS
|
||||
|
||||
echo "[plugin-v2-smoke] running: $BIN --backend vm $APP" >&2
|
||||
set +e
|
||||
timeout -s KILL 25s "$BIN" --backend vm "$APP" > /tmp/nyash-plugin-v2-smoke.out 2>&1
|
||||
code=$?
|
||||
set -e
|
||||
echo "--- plugin v2 smoke output ---"
|
||||
tail -n 80 /tmp/nyash-plugin-v2-smoke.out || true
|
||||
echo "-------------------------------"
|
||||
|
||||
if [[ $code -ne 0 ]]; then
|
||||
echo "plugin-v2-smoke: nyash exited with code $code" >&2
|
||||
exit $code
|
||||
fi
|
||||
|
||||
# Basic assertions: plugin loader ran and configured; program completed
|
||||
if ! grep -q "\[PluginLoaderV2\] load_plugin:" /tmp/nyash-plugin-v2-smoke.out; then
|
||||
echo "plugin-v2-smoke: plugin loader did not log load_plugin (NYASH_DEBUG_PLUGIN=1 required)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Confirm both FileBox and PathBox libraries were attempted
|
||||
grep -q "libnyash_filebox_plugin.so" /tmp/nyash-plugin-v2-smoke.out || {
|
||||
echo "plugin-v2-smoke: missing FileBox load log" >&2; exit 1; }
|
||||
grep -q "libnyash_path_plugin.so" /tmp/nyash-plugin-v2-smoke.out || {
|
||||
echo "plugin-v2-smoke: missing PathBox load log" >&2; exit 1; }
|
||||
grep -q "libnyash_math_plugin.so" /tmp/nyash-plugin-v2-smoke.out || {
|
||||
echo "plugin-v2-smoke: missing Math/Time load log" >&2; exit 1; }
|
||||
grep -q "libnyash_regex_plugin.so" /tmp/nyash-plugin-v2-smoke.out || {
|
||||
echo "plugin-v2-smoke: missing Regex load log" >&2; exit 1; }
|
||||
grep -q "libnyash_net_plugin.so" /tmp/nyash-plugin-v2-smoke.out || {
|
||||
echo "plugin-v2-smoke: missing Net load log" >&2; exit 1; }
|
||||
|
||||
echo "plugin-v2-smoke: OK" >&2
|
||||
|
||||
# Optional functional sample (regex + response only)
|
||||
FUNC_APP="$ROOT_DIR/apps/tests/plugin_v2_functional.nyash"
|
||||
if [[ -f "$FUNC_APP" ]]; then
|
||||
echo "[plugin-v2-smoke] functional: $BIN --backend vm $FUNC_APP" >&2
|
||||
set +e
|
||||
timeout -s KILL 25s "$BIN" --backend vm "$FUNC_APP" > /tmp/nyash-plugin-v2-func.out 2>&1
|
||||
code=$?
|
||||
set -e
|
||||
tail -n 50 /tmp/nyash-plugin-v2-func.out || true
|
||||
if [[ $code -eq 0 ]]; then
|
||||
# We don't assert printed values strictly (PyVM path may differ), only that it ran to completion.
|
||||
echo "plugin-v2-functional: OK" >&2
|
||||
else
|
||||
echo "plugin-v2-functional: nyash exited with code $code" >&2
|
||||
exit $code
|
||||
fi
|
||||
fi
|
||||
|
||||
# Optional functional sample (net round-trip)
|
||||
NET_APP="$ROOT_DIR/apps/tests/net_roundtrip.nyash"
|
||||
if [[ -f "$NET_APP" ]]; then
|
||||
echo "[plugin-v2-smoke] functional (net): $BIN --backend vm $NET_APP" >&2
|
||||
set +e
|
||||
timeout -s KILL 25s "$BIN" --backend vm "$NET_APP" > /tmp/nyash-plugin-v2-net.out 2>&1
|
||||
code=$?
|
||||
set -e
|
||||
tail -n 60 /tmp/nyash-plugin-v2-net.out || true
|
||||
if [[ $code -eq 0 ]]; then
|
||||
echo "plugin-v2-net-functional: OK" >&2
|
||||
else
|
||||
echo "plugin-v2-net-functional: nyash exited with code $code" >&2
|
||||
exit $code
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user