318 lines
12 KiB
Rust
318 lines
12 KiB
Rust
|
|
use pyo3::prelude::*;
|
|||
|
|
use serde::{Deserialize, Serialize};
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|||
|
|
struct ParseResult {
|
|||
|
|
success: bool,
|
|||
|
|
dump: Option<String>,
|
|||
|
|
counts: ParseCounts,
|
|||
|
|
unsupported: Vec<String>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|||
|
|
struct ParseCounts {
|
|||
|
|
total_nodes: usize,
|
|||
|
|
functions: usize,
|
|||
|
|
classes: usize,
|
|||
|
|
supported: usize,
|
|||
|
|
unsupported: usize,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// FFI: プラグインABIバージョン
|
|||
|
|
#[no_mangle]
|
|||
|
|
pub extern "C" fn nyash_plugin_abi_version() -> u32 {
|
|||
|
|
1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// FFI: プラグイン初期化
|
|||
|
|
#[no_mangle]
|
|||
|
|
pub extern "C" fn nyash_plugin_init() -> i32 {
|
|||
|
|
// Python初期化は pyo3 の auto-initialize が処理
|
|||
|
|
0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// FFI: プラグインメソッド呼び出し(BID形式)
|
|||
|
|
#[no_mangle]
|
|||
|
|
pub extern "C" fn nyash_plugin_invoke(
|
|||
|
|
type_id: u32,
|
|||
|
|
method_id: u32,
|
|||
|
|
_instance_id: u32,
|
|||
|
|
args: *const u8,
|
|||
|
|
args_len: usize,
|
|||
|
|
result: *mut u8,
|
|||
|
|
result_len: *mut usize,
|
|||
|
|
) -> i32 {
|
|||
|
|
const TYPE_ID_PARSER: u32 = 60;
|
|||
|
|
const METHOD_BIRTH: u32 = 0;
|
|||
|
|
const METHOD_PARSE: u32 = 1;
|
|||
|
|
const METHOD_FINI: u32 = u32::MAX;
|
|||
|
|
|
|||
|
|
if type_id != TYPE_ID_PARSER {
|
|||
|
|
return -3; // NYB_E_INVALID_METHOD
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
match method_id {
|
|||
|
|
METHOD_BIRTH => {
|
|||
|
|
// インスタンスIDを返す
|
|||
|
|
unsafe {
|
|||
|
|
let instance_id = 1u32; // 簡易実装
|
|||
|
|
if *result_len < 4 {
|
|||
|
|
*result_len = 4;
|
|||
|
|
return -1; // NYB_E_SHORT_BUFFER
|
|||
|
|
}
|
|||
|
|
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 => {
|
|||
|
|
// 引数からコードを取得(TLV形式の文字列を期待)
|
|||
|
|
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 {
|
|||
|
|
// TLVデコード(簡易版)
|
|||
|
|
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)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// JSONにシリアライズ
|
|||
|
|
match serde_json::to_string(&parse_result) {
|
|||
|
|
Ok(json) => {
|
|||
|
|
unsafe {
|
|||
|
|
let bytes = json.as_bytes();
|
|||
|
|
let need = 4 + bytes.len();
|
|||
|
|
if *result_len < need {
|
|||
|
|
*result_len = need;
|
|||
|
|
return -1; // NYB_E_SHORT_BUFFER
|
|||
|
|
}
|
|||
|
|
let out = std::slice::from_raw_parts_mut(result, *result_len);
|
|||
|
|
// TLVエンコード(tag=6:string)
|
|||
|
|
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 // NYB_E_INVALID_METHOD
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// FFI: Pythonコードをパース
|
|||
|
|
#[no_mangle]
|
|||
|
|
pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
|
|||
|
|
let code = unsafe {
|
|||
|
|
if code.is_null() {
|
|||
|
|
return std::ptr::null_mut();
|
|||
|
|
}
|
|||
|
|
match std::ffi::CStr::from_ptr(code).to_str() {
|
|||
|
|
Ok(s) => s,
|
|||
|
|
Err(_) => return std::ptr::null_mut(),
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let result = Python::with_gil(|py| {
|
|||
|
|
parse_python_code(py, code)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
match serde_json::to_string(&result) {
|
|||
|
|
Ok(json) => {
|
|||
|
|
let c_str = std::ffi::CString::new(json).unwrap();
|
|||
|
|
c_str.into_raw()
|
|||
|
|
}
|
|||
|
|
Err(_) => std::ptr::null_mut(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// FFI: 文字列解放
|
|||
|
|
#[no_mangle]
|
|||
|
|
pub extern "C" fn nyash_python_free_string(ptr: *mut std::os::raw::c_char) {
|
|||
|
|
if !ptr.is_null() {
|
|||
|
|
unsafe {
|
|||
|
|
let _ = std::ffi::CString::from_raw(ptr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn parse_python_code(py: Python, code: &str) -> ParseResult {
|
|||
|
|
let mut result = ParseResult {
|
|||
|
|
success: false,
|
|||
|
|
dump: None,
|
|||
|
|
counts: ParseCounts {
|
|||
|
|
total_nodes: 0,
|
|||
|
|
functions: 0,
|
|||
|
|
classes: 0,
|
|||
|
|
supported: 0,
|
|||
|
|
unsupported: 0,
|
|||
|
|
},
|
|||
|
|
unsupported: Vec::new(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Pythonのastモジュールをインポート
|
|||
|
|
let ast_module = match py.import_bound("ast") {
|
|||
|
|
Ok(m) => m,
|
|||
|
|
Err(e) => {
|
|||
|
|
result.dump = Some(format!("Failed to import ast module: {}", e));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// コードをパース
|
|||
|
|
let tree = match ast_module.call_method1("parse", (code,)) {
|
|||
|
|
Ok(t) => t,
|
|||
|
|
Err(e) => {
|
|||
|
|
result.dump = Some(format!("Parse error: {}", e));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ASTをダンプ(文字列表現)
|
|||
|
|
if let Ok(dump_str) = ast_module.call_method1("dump", (&tree,)) {
|
|||
|
|
if let Ok(s) = dump_str.extract::<String>() {
|
|||
|
|
result.dump = Some(s);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ASTを解析してカウント
|
|||
|
|
analyze_ast(py, &tree, &mut result);
|
|||
|
|
|
|||
|
|
result.success = true;
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) {
|
|||
|
|
result.counts.total_nodes += 1;
|
|||
|
|
|
|||
|
|
// ノードタイプを取得 - __class__.__name__ を使用
|
|||
|
|
if let Ok(class_obj) = node.getattr("__class__") {
|
|||
|
|
if let Ok(name_obj) = class_obj.getattr("__name__") {
|
|||
|
|
if let Ok(type_name) = name_obj.extract::<String>() {
|
|||
|
|
match type_name.as_str() {
|
|||
|
|
"FunctionDef" => {
|
|||
|
|
result.counts.functions += 1;
|
|||
|
|
result.counts.supported += 1;
|
|||
|
|
}
|
|||
|
|
"AsyncFunctionDef" => {
|
|||
|
|
result.counts.functions += 1;
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
result.unsupported.push("async function".to_string());
|
|||
|
|
}
|
|||
|
|
"ClassDef" => {
|
|||
|
|
result.counts.classes += 1;
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
result.unsupported.push("class definition".to_string());
|
|||
|
|
}
|
|||
|
|
"For" | "While" | "If" => {
|
|||
|
|
result.counts.supported += 1;
|
|||
|
|
}
|
|||
|
|
"Yield" | "YieldFrom" => {
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
result.unsupported.push("generator".to_string());
|
|||
|
|
}
|
|||
|
|
_ => {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 子ノードを再帰的に解析
|
|||
|
|
// ast.walk() を使って全ノードを取得
|
|||
|
|
if let Ok(ast_module) = node.py().import_bound("ast") {
|
|||
|
|
if let Ok(walk_iter) = ast_module.call_method1("walk", (node,)) {
|
|||
|
|
if let Ok(nodes) = walk_iter.iter() {
|
|||
|
|
for child_result in nodes {
|
|||
|
|
if let Ok(child) = child_result {
|
|||
|
|
// 自分自身はスキップ(すでにカウント済み)
|
|||
|
|
if !child.is(node) {
|
|||
|
|
// 再帰的に解析(ただし walk は全ノードを返すので、
|
|||
|
|
// 実際には再帰なしでフラットに処理される)
|
|||
|
|
result.counts.total_nodes += 1;
|
|||
|
|
|
|||
|
|
if let Ok(class_obj) = child.getattr("__class__") {
|
|||
|
|
if let Ok(name_obj) = class_obj.getattr("__name__") {
|
|||
|
|
if let Ok(type_name) = name_obj.extract::<String>() {
|
|||
|
|
match type_name.as_str() {
|
|||
|
|
"FunctionDef" => {
|
|||
|
|
result.counts.functions += 1;
|
|||
|
|
result.counts.supported += 1;
|
|||
|
|
}
|
|||
|
|
"AsyncFunctionDef" => {
|
|||
|
|
result.counts.functions += 1;
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
if !result.unsupported.contains(&"async function".to_string()) {
|
|||
|
|
result.unsupported.push("async function".to_string());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
"ClassDef" => {
|
|||
|
|
result.counts.classes += 1;
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
if !result.unsupported.contains(&"class definition".to_string()) {
|
|||
|
|
result.unsupported.push("class definition".to_string());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
"For" | "While" | "If" => {
|
|||
|
|
result.counts.supported += 1;
|
|||
|
|
}
|
|||
|
|
"Yield" | "YieldFrom" => {
|
|||
|
|
result.counts.unsupported += 1;
|
|||
|
|
if !result.unsupported.contains(&"generator".to_string()) {
|
|||
|
|
result.unsupported.push("generator".to_string());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_ => {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_simple_parse() {
|
|||
|
|
pyo3::prepare_freethreaded_python();
|
|||
|
|
|
|||
|
|
Python::with_gil(|py| {
|
|||
|
|
let code = "def main():\n return 0";
|
|||
|
|
let result = parse_python_code(py, code);
|
|||
|
|
|
|||
|
|
assert!(result.success);
|
|||
|
|
assert_eq!(result.counts.functions, 1);
|
|||
|
|
assert_eq!(result.counts.supported, 1);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|