Files
hakorune/plugins/nyash-python-parser-plugin/src/lib.rs

336 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});
}
}