JIT cond path: keep compare result as b1; br_if accepts b1 or i64 (!=0); add branch_return to benchmark suite.
This commit is contained in:
@ -0,0 +1,553 @@
|
||||
# PythonParserBox ビルトインBox実装フロー(エキスパート統合版)
|
||||
~CPythonパーサー統合とPhase 1実装の具体的な流れ~
|
||||
更新日: 2025-08-27
|
||||
|
||||
## 🎯 全体の実装フロー
|
||||
|
||||
### Step 0: Python 3.11固定(エキスパート推奨)
|
||||
```
|
||||
- Python 3.11.9を使用(AST安定性確保)
|
||||
- pyenvまたはpython3.11コマンドで固定
|
||||
- py_versionとast_formatをJSON IRに必ず含める
|
||||
```
|
||||
|
||||
### Step 1: ビルトインBoxとしての基盤作成
|
||||
```
|
||||
1. src/boxes/python_parser_box/mod.rs を作成
|
||||
2. BoxBase + BoxCore統一アーキテクチャに準拠
|
||||
3. PythonParserBoxの基本構造を定義
|
||||
4. src/boxes/mod.rs に登録
|
||||
5. テレメトリー基盤を初期から組み込む
|
||||
```
|
||||
|
||||
### Step 2: pyo3統合とCPythonパーサー接続
|
||||
```
|
||||
1. Cargo.tomlに pyo3依存関係追加
|
||||
2. pyo3::prepare_freethreaded_python()で一度だけ初期化
|
||||
3. ast.parse()へのFFIブリッジ実装
|
||||
4. JSON中間表現への変換(Python側でJSON生成)
|
||||
5. GILは最小限に、py.allow_threads()でRust処理
|
||||
```
|
||||
|
||||
### Step 3: Phase 1機能の実装(必須意味論要素)
|
||||
```
|
||||
必須要素(Codex先生強調):
|
||||
- LEGBスコーピング + locals/freevars
|
||||
- デフォルト引数の定義時評価
|
||||
- イテレータプロトコル(for文)
|
||||
- for/else + while/else
|
||||
- Python真偽値判定
|
||||
- 短絡評価(and/or)
|
||||
|
||||
実装手順:
|
||||
1. 関数単位フォールバック戦略の実装
|
||||
2. 基本的なAST変換(def, if, for, while, return)
|
||||
3. 式の変換(算術/比較/論理演算子、関数呼び出し)
|
||||
4. Nyash ASTへの意味論を保ったマッピング
|
||||
5. Differential Testingフレームワーク
|
||||
```
|
||||
|
||||
## 📝 具体的な実装コード
|
||||
|
||||
### 1. ビルトインBox定義(src/boxes/python_parser_box/mod.rs)
|
||||
```rust
|
||||
use crate::core::{BoxBase, BoxCore, NyashBox};
|
||||
use crate::ast;
|
||||
use pyo3::prelude::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct PythonParserBox {
|
||||
base: BoxBase,
|
||||
py_helper: Arc<Mutex<PyHelper>>, // Python実行環境
|
||||
}
|
||||
|
||||
impl BoxCore for PythonParserBox {
|
||||
fn box_id(&self) -> u64 {
|
||||
self.base.box_id()
|
||||
}
|
||||
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> {
|
||||
self.base.parent_type_id()
|
||||
}
|
||||
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "PythonParserBox#{}", self.box_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashBox for PythonParserBox {
|
||||
fn type_name(&self) -> &'static str {
|
||||
"PythonParserBox"
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||
Box::new(PythonParserBox {
|
||||
base: BoxBase::new(),
|
||||
py_helper: self.py_helper.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. メソッド実装(Phase 1対応)
|
||||
```rust
|
||||
// テレメトリー用構造体
|
||||
#[derive(Default)]
|
||||
pub struct CompilationTelemetry {
|
||||
compiled_functions: Vec<String>,
|
||||
fallback_functions: Vec<(String, String, usize)>, // (name, reason, lineno)
|
||||
unsupported_nodes: HashMap<String, usize>, // node_type -> count
|
||||
}
|
||||
|
||||
impl PythonParserBox {
|
||||
// コンストラクタ
|
||||
pub fn new() -> Result<Self, String> {
|
||||
// 一度だけ初期化
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
INIT.call_once(|| {
|
||||
pyo3::prepare_freethreaded_python();
|
||||
});
|
||||
|
||||
// Python環境の初期化
|
||||
Python::with_gil(|py| {
|
||||
// Python 3.11確認
|
||||
let version = py.version_info();
|
||||
if version.major != 3 || version.minor != 11 {
|
||||
return Err(format!("Python 3.11 required, got {}.{}",
|
||||
version.major, version.minor));
|
||||
}
|
||||
|
||||
let helper = PyHelper::new(py)?;
|
||||
Ok(PythonParserBox {
|
||||
base: BoxBase::new(),
|
||||
py_helper: Arc::new(Mutex::new(helper)),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Python code → JSON AST
|
||||
pub fn parse_to_json(&self, code: &str) -> Result<String, String> {
|
||||
let helper = self.py_helper.lock().unwrap();
|
||||
Python::with_gil(|py| {
|
||||
helper.parse_to_json(py, code)
|
||||
})
|
||||
}
|
||||
|
||||
// JSON AST → Nyash AST(Phase 1機能のみ)
|
||||
pub fn json_to_nyash_ast(&self, json: &str) -> Result<ast::Program, String> {
|
||||
let py_ast: Phase1PythonAst = serde_json::from_str(json)
|
||||
.map_err(|e| format!("JSON parse error: {}", e))?;
|
||||
|
||||
let converter = Phase1Converter::new();
|
||||
converter.convert(py_ast)
|
||||
}
|
||||
|
||||
// 直接実行(関数単位フォールバック)
|
||||
pub fn run(&self, code: &str) -> Result<Box<dyn NyashBox>, String> {
|
||||
// まずJSON ASTを取得
|
||||
let json_ast = self.parse_to_json(code)?;
|
||||
let py_ast: serde_json::Value = serde_json::from_str(&json_ast)?;
|
||||
|
||||
// モジュール内の各関数をチェック
|
||||
let compiler = FunctionCompiler::new();
|
||||
let module_result = compiler.compile_module(&py_ast)?;
|
||||
|
||||
// テレメトリー出力(環境変数で制御)
|
||||
if std::env::var("NYASH_PYTHONPARSER_TELEMETRY").is_ok() {
|
||||
compiler.print_telemetry();
|
||||
}
|
||||
|
||||
// 実行(コンパイル済み関数はMIR、他はCPython)
|
||||
module_result.execute()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Python側ヘルパー実装
|
||||
```rust
|
||||
// Pythonコードを文字列として埋め込み
|
||||
const PYTHON_HELPER_CODE: &str = r#"
|
||||
import ast
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Python 3.11固定チェック
|
||||
assert sys.version_info[:2] == (3, 11), f"Python 3.11 required, got {sys.version}"
|
||||
|
||||
def ast_to_dict(node):
|
||||
"""Phase 1: 基本的なAST要素のみ変換(エキスパート推奨JSON IR)"""
|
||||
|
||||
result = {
|
||||
"node_type": node.__class__.__name__,
|
||||
"py_version": "3.11",
|
||||
"ast_format": "v1"
|
||||
}
|
||||
|
||||
# 位置情報(エラー診断用)
|
||||
if hasattr(node, 'lineno'):
|
||||
result['lineno'] = node.lineno
|
||||
result['col_offset'] = node.col_offset
|
||||
if hasattr(node, 'end_lineno'):
|
||||
result['end_lineno'] = node.end_lineno
|
||||
result['end_col_offset'] = node.end_col_offset
|
||||
if isinstance(node, ast.Module):
|
||||
return {
|
||||
"type": "Module",
|
||||
"body": [ast_to_dict(stmt) for stmt in node.body]
|
||||
}
|
||||
elif isinstance(node, ast.FunctionDef):
|
||||
# 意味論上重要:デフォルト引数情報を保存
|
||||
args_info = {
|
||||
"args": [arg.arg for arg in node.args.args],
|
||||
"defaults": [ast_to_dict(default) for default in node.args.defaults],
|
||||
"kwonlyargs": [arg.arg for arg in node.args.kwonlyargs],
|
||||
"kw_defaults": [ast_to_dict(d) if d else None for d in node.args.kw_defaults]
|
||||
}
|
||||
result.update({
|
||||
"name": node.name,
|
||||
"args": args_info,
|
||||
"body": [ast_to_dict(stmt) for stmt in node.body],
|
||||
"decorator_list": [], # Phase 1では未対応
|
||||
"support_level": "full" # コンパイル可能
|
||||
})
|
||||
return result
|
||||
elif isinstance(node, ast.Return):
|
||||
return {
|
||||
"type": "Return",
|
||||
"value": ast_to_dict(node.value) if node.value else None
|
||||
}
|
||||
elif isinstance(node, ast.BinOp):
|
||||
return {
|
||||
"type": "BinOp",
|
||||
"op": node.op.__class__.__name__,
|
||||
"left": ast_to_dict(node.left),
|
||||
"right": ast_to_dict(node.right)
|
||||
}
|
||||
elif isinstance(node, ast.Call):
|
||||
return {
|
||||
"type": "Call",
|
||||
"func": ast_to_dict(node.func),
|
||||
"args": [ast_to_dict(arg) for arg in node.args]
|
||||
}
|
||||
elif isinstance(node, ast.Name):
|
||||
return {
|
||||
"type": "Name",
|
||||
"id": node.id
|
||||
}
|
||||
elif isinstance(node, ast.Constant):
|
||||
return {
|
||||
"type": "Constant",
|
||||
"value": node.value
|
||||
}
|
||||
elif isinstance(node, ast.If):
|
||||
return {
|
||||
"type": "If",
|
||||
"test": ast_to_dict(node.test),
|
||||
"body": [ast_to_dict(stmt) for stmt in node.body],
|
||||
"orelse": [ast_to_dict(stmt) for stmt in node.orelse]
|
||||
}
|
||||
elif isinstance(node, ast.For):
|
||||
# 意味論上重要:for/else構文
|
||||
result.update({
|
||||
"target": ast_to_dict(node.target),
|
||||
"iter": ast_to_dict(node.iter),
|
||||
"body": [ast_to_dict(stmt) for stmt in node.body],
|
||||
"orelse": [ast_to_dict(stmt) for stmt in node.orelse], # else節
|
||||
"support_level": "full"
|
||||
})
|
||||
return result
|
||||
|
||||
elif isinstance(node, ast.While):
|
||||
# 意味論上重要:while/else構文
|
||||
result.update({
|
||||
"test": ast_to_dict(node.test),
|
||||
"body": [ast_to_dict(stmt) for stmt in node.body],
|
||||
"orelse": [ast_to_dict(stmt) for stmt in node.orelse], # else節
|
||||
"support_level": "full"
|
||||
})
|
||||
return result
|
||||
|
||||
elif isinstance(node, ast.BoolOp):
|
||||
# 意味論上重要:短絡評価
|
||||
result.update({
|
||||
"op": node.op.__class__.__name__, # And, Or
|
||||
"values": [ast_to_dict(v) for v in node.values],
|
||||
"support_level": "full"
|
||||
})
|
||||
return result
|
||||
else:
|
||||
# Phase 1では未対応(テレメトリー用)
|
||||
return {
|
||||
"node_type": "Unsupported",
|
||||
"original_type": type(node).__name__,
|
||||
"support_level": "fallback",
|
||||
"lineno": getattr(node, 'lineno', -1)
|
||||
}
|
||||
|
||||
def parse_to_json(code):
|
||||
try:
|
||||
tree = ast.parse(code)
|
||||
return json.dumps(ast_to_dict(tree))
|
||||
except Exception as e:
|
||||
return json.dumps({"type": "Error", "message": str(e)})
|
||||
"#;
|
||||
|
||||
struct PyHelper {
|
||||
// Python側のヘルパー関数への参照を保持
|
||||
parse_func: PyObject,
|
||||
}
|
||||
|
||||
impl PyHelper {
|
||||
fn new(py: Python) -> PyResult<Self> {
|
||||
// ヘルパーコードをPythonで実行
|
||||
let helpers = PyModule::from_code(py, PYTHON_HELPER_CODE, "helper.py", "helper")?;
|
||||
let parse_func = helpers.getattr("parse_to_json")?.to_object(py);
|
||||
|
||||
Ok(PyHelper { parse_func })
|
||||
}
|
||||
|
||||
fn parse_to_json(&self, py: Python, code: &str) -> Result<String, String> {
|
||||
match self.parse_func.call1(py, (code,)) {
|
||||
Ok(result) => result.extract::<String>(py)
|
||||
.map_err(|e| format!("Extract error: {}", e)),
|
||||
Err(e) => Err(format!("Parse error: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Phase 1 AST変換器
|
||||
```rust
|
||||
struct Phase1Converter;
|
||||
|
||||
impl Phase1Converter {
|
||||
fn new() -> Self {
|
||||
Phase1Converter
|
||||
}
|
||||
|
||||
fn convert(&self, py_ast: Phase1PythonAst) -> Result<ast::Program, String> {
|
||||
match py_ast {
|
||||
Phase1PythonAst::Module { body } => {
|
||||
let mut items = vec![];
|
||||
for stmt in body {
|
||||
match self.convert_statement(stmt)? {
|
||||
Some(item) => items.push(item),
|
||||
None => {} // 未対応要素はスキップ
|
||||
}
|
||||
}
|
||||
Ok(ast::Program { items })
|
||||
}
|
||||
_ => Err("Expected Module at top level".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_statement(&self, stmt: Phase1PythonAst) -> Result<Option<ast::ProgramItem>, String> {
|
||||
match stmt {
|
||||
Phase1PythonAst::FunctionDef { name, args, body } => {
|
||||
// Python def → Nyash function
|
||||
let params = args.into_iter()
|
||||
.map(|arg| ast::Parameter { name: arg, ty: None })
|
||||
.collect();
|
||||
|
||||
let nyash_body = self.convert_body(body)?;
|
||||
|
||||
Ok(Some(ast::ProgramItem::Function(ast::FunctionDef {
|
||||
name,
|
||||
params,
|
||||
body: nyash_body,
|
||||
return_type: None,
|
||||
})))
|
||||
}
|
||||
Phase1PythonAst::Return { value } => {
|
||||
let expr = value
|
||||
.map(|v| self.convert_expression(v))
|
||||
.transpose()?;
|
||||
|
||||
Ok(Some(ast::ProgramItem::Statement(ast::Statement::Return(expr))))
|
||||
}
|
||||
// 他の文も同様に変換
|
||||
_ => Ok(None) // Phase 1では未対応
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_expression(&self, expr: Phase1PythonAst) -> Result<ast::Expression, String> {
|
||||
match expr {
|
||||
Phase1PythonAst::BinOp { op, left, right } => {
|
||||
let left = Box::new(self.convert_expression(*left)?);
|
||||
let right = Box::new(self.convert_expression(*right)?);
|
||||
|
||||
let op = match op.as_str() {
|
||||
"Add" => ast::BinaryOp::Add,
|
||||
"Sub" => ast::BinaryOp::Sub,
|
||||
"Mul" => ast::BinaryOp::Mul,
|
||||
"Div" => ast::BinaryOp::Div,
|
||||
_ => return Err(format!("Unsupported operator: {}", op))
|
||||
};
|
||||
|
||||
Ok(ast::Expression::BinaryOp { op, left, right })
|
||||
}
|
||||
Phase1PythonAst::Constant { value } => {
|
||||
// Python定数 → Nyashリテラル
|
||||
match value {
|
||||
serde_json::Value::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
Ok(ast::Expression::Integer(i))
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Ok(ast::Expression::Float(f))
|
||||
} else {
|
||||
Err("Unsupported number type".into())
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(s) => {
|
||||
Ok(ast::Expression::String(s))
|
||||
}
|
||||
serde_json::Value::Bool(b) => {
|
||||
Ok(ast::Expression::Bool(b))
|
||||
}
|
||||
_ => Err("Unsupported constant type".into())
|
||||
}
|
||||
}
|
||||
// 他の式も同様
|
||||
_ => Err("Unsupported expression in Phase 1".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. インタープリター統合(src/interpreter/builtins.rs)
|
||||
```rust
|
||||
// ビルトインBox登録に追加
|
||||
pub fn register_builtin_boxes(env: &mut Environment) {
|
||||
// 既存のBox登録...
|
||||
|
||||
// PythonParserBox追加
|
||||
env.register_builtin_box("PythonParserBox", || {
|
||||
match PythonParserBox::new() {
|
||||
Ok(parser) => Arc::new(parser) as Arc<dyn NyashBox>,
|
||||
Err(e) => panic!("Failed to initialize PythonParserBox: {}", e)
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 使用例とテストケース
|
||||
```nyash
|
||||
// test_python_parser_phase1.nyash
|
||||
local py = new PythonParserBox()
|
||||
|
||||
// Phase 1: 基本的な関数定義と演算
|
||||
local code = """
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
def multiply(x, y):
|
||||
return x * y
|
||||
|
||||
def calculate(a, b):
|
||||
sum_val = add(a, b)
|
||||
prod_val = multiply(a, b)
|
||||
return sum_val + prod_val
|
||||
"""
|
||||
|
||||
// パースしてJSON ASTを確認
|
||||
local json_ast = py.parse_to_json(code)
|
||||
print("JSON AST: " + json_ast)
|
||||
|
||||
// Nyash ASTに変換
|
||||
local nyash_ast = py.json_to_nyash_ast(json_ast)
|
||||
print("Conversion successful!")
|
||||
|
||||
// 実行(最初はCPython経由)
|
||||
local result = py.run(code + "\nprint(calculate(10, 5))")
|
||||
```
|
||||
|
||||
## 📊 段階的な実装計画
|
||||
|
||||
### Week 1: 基盤構築
|
||||
- [ ] PythonParserBoxの基本構造
|
||||
- [ ] pyo3統合とPython環境初期化
|
||||
- [ ] parse_to_json基本実装
|
||||
- [ ] エラーハンドリング
|
||||
|
||||
### Week 2: Phase 1変換器
|
||||
- [ ] Phase1PythonAstの定義
|
||||
- [ ] Phase1Converterの実装
|
||||
- [ ] 基本的な文と式の変換
|
||||
- [ ] テストケース作成
|
||||
|
||||
### Week 3: 統合とテスト
|
||||
- [ ] インタープリター統合
|
||||
- [ ] CPython exec経由の実行
|
||||
- [ ] ベンチマーク準備
|
||||
- [ ] ドキュメント整備
|
||||
|
||||
## 🚀 期待される成果
|
||||
|
||||
### Phase 1完了時点で実現できること:
|
||||
1. **基本的なPythonコードの実行**
|
||||
- 関数定義、算術演算、条件分岐、ループ
|
||||
|
||||
2. **Nyash ASTへの変換**
|
||||
- 将来のMIR/JIT最適化への道筋
|
||||
|
||||
3. **統合開発環境**
|
||||
- PythonコードとNyashコードの混在実行
|
||||
|
||||
4. **性能測定基盤**
|
||||
- CPython実行 vs Nyash実行の比較
|
||||
|
||||
## 📡 テレメトリー出力例
|
||||
|
||||
```bash
|
||||
# 環境変数で制御
|
||||
export NYASH_PYTHONPARSER_TELEMETRY=1 # 基本統計
|
||||
export NYASH_PYTHONPARSER_TELEMETRY=2 # 詳細ログ
|
||||
export NYASH_PYTHONPARSER_STRICT=1 # フォールバック時にパニック
|
||||
|
||||
# 実行例
|
||||
./target/release/nyash test_python_parser.nyash
|
||||
|
||||
# 出力
|
||||
[PythonParser] Module: test.py (Python 3.11)
|
||||
Functions: 10 total
|
||||
Compiled: 7 (70%) → Nyash MIR/JIT
|
||||
Fallback: 3 (30%) → CPython exec
|
||||
- async_function: unsupported node 'AsyncFunctionDef' at line 23
|
||||
- generator_func: unsupported node 'Yield' at line 45
|
||||
- decorator_func: unsupported node 'decorator_list' at line 67
|
||||
|
||||
Unsupported Nodes Summary:
|
||||
AsyncFunctionDef: 1
|
||||
Yield: 2
|
||||
ClassDef: 1
|
||||
```
|
||||
|
||||
## 📊 Differential Testingフレームワーク
|
||||
|
||||
```rust
|
||||
// CPythonとNyashの出力比較
|
||||
pub fn differential_test(code: &str) -> TestResult {
|
||||
// CPythonで実行(オラクル)
|
||||
let python_result = Python::with_gil(|py| {
|
||||
capture_python_execution(py, code)
|
||||
})?;
|
||||
|
||||
// Nyashで実行
|
||||
let nyash_result = execute_with_pythonparser(code)?;
|
||||
|
||||
// 結果比較
|
||||
compare_results(python_result, nyash_result)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
作成日: 2025-08-27
|
||||
Phase 1実装の具体的な手順とエキスパートフィードバック統合
|
||||
@ -0,0 +1,361 @@
|
||||
# PythonParserBox実装計画(統合版)
|
||||
更新日: 2025-08-27
|
||||
|
||||
## 🎯 エキスパートからの統合フィードバック
|
||||
|
||||
### 最重要ポイント(両エキスパートが一致)
|
||||
1. **関数単位のフォールバック戦略**
|
||||
- ファイル全体でなく関数レベルでコンパイル/フォールバックを切り替え
|
||||
- 未対応機能を含む関数はCPython exec、対応済み関数はNyash MIR/JIT
|
||||
|
||||
2. **Python 3.11固定**
|
||||
- AST安定性の確保(3.8 Constant統一、3.10 match/case、3.12位置情報)
|
||||
- `py_version`と`ast_format`をJSON IRに埋め込む
|
||||
|
||||
3. **意味論の正確な実装が最優先**
|
||||
- 最適化より先にPython互換性を確保
|
||||
- 特に: イテレータプロトコル、真偽値判定、スコーピング規則(LEGB)
|
||||
|
||||
4. **GIL管理の最小化**
|
||||
- Python側でJSON生成(`ast.NodeVisitor` + `json.dumps`)
|
||||
- Rust側で解析(GIL外で実行)
|
||||
- `py.allow_threads(|| { ... })`で重い処理をGIL外実行
|
||||
|
||||
5. **テレメトリー重視**
|
||||
- 未対応ノードの記録(`support_level`フィールド)
|
||||
- フォールバック率の計測
|
||||
- ソース位置情報の保持(`lineno/col_offset/end_*`)
|
||||
|
||||
### Differential Testing戦略
|
||||
- **世界中のPythonコードがNyashのテストケース**
|
||||
- CPythonを「オラクル」として使用
|
||||
- 出力、戻り値、例外を比較
|
||||
- Grammar-based fuzzing(Hypothesis活用)
|
||||
|
||||
## 技術的実装方針
|
||||
|
||||
### 1. CPythonパーサー統合(pyo3使用)
|
||||
```rust
|
||||
// Cargo.toml
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.22", features = ["auto-initialize"] }
|
||||
pyo3-numpy = "0.22" // NumPy連携用
|
||||
|
||||
// 初期化(一度だけ)
|
||||
pyo3::prepare_freethreaded_python();
|
||||
|
||||
// Python側ヘルパー(embedded)
|
||||
const PYTHON_HELPER: &str = r#"
|
||||
import ast
|
||||
import json
|
||||
import sys
|
||||
|
||||
def parse_to_json(code, filename="<string>", mode="exec"):
|
||||
tree = ast.parse(code, filename, mode)
|
||||
return json.dumps(ast_to_dict(tree))
|
||||
|
||||
def ast_to_dict(node):
|
||||
result = {}
|
||||
|
||||
# 必須フィールド
|
||||
result['node_type'] = node.__class__.__name__
|
||||
result['py_version'] = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
# 位置情報(エラー診断用)
|
||||
if hasattr(node, 'lineno'):
|
||||
result['lineno'] = node.lineno
|
||||
result['col_offset'] = node.col_offset
|
||||
if hasattr(node, 'end_lineno'): # Python 3.8+
|
||||
result['end_lineno'] = node.end_lineno
|
||||
result['end_col_offset'] = node.end_col_offset
|
||||
|
||||
# サポートレベル(Nyash側で設定)
|
||||
result['support_level'] = 'unknown'
|
||||
|
||||
# ASTフィールド
|
||||
if isinstance(node, ast.AST):
|
||||
for field in node._fields:
|
||||
value = getattr(node, field)
|
||||
result[field] = ast_to_dict(value)
|
||||
elif isinstance(node, list):
|
||||
return [ast_to_dict(x) for x in node]
|
||||
else:
|
||||
return node
|
||||
|
||||
return result
|
||||
"#;
|
||||
```
|
||||
|
||||
### 2. 最小実装セット(Phase 1: Must-Have)
|
||||
```
|
||||
Phase 1 意味論の必須要素(Codex先生強調):
|
||||
- LEGB + locals/freevars(スコーピング)
|
||||
- デフォルト引数の評価タイミング(定義時)
|
||||
- イテレータベースのfor文
|
||||
- for/else + while/else(Python独特)
|
||||
- Python真偽値判定(__bool__ → __len__)
|
||||
- 短絡評価(and/or)
|
||||
|
||||
Phase 1 AST構造:
|
||||
├─ Module (py_version, ast_format)
|
||||
├─ FunctionDef (name, args, body, decorator_list=[])
|
||||
│ └─ arguments (args, defaults, kwonlyargs=[], kw_defaults=[])
|
||||
├─ Return (value)
|
||||
├─ Assign (targets, value)
|
||||
├─ AugAssign (target, op, value) # +=, -=等
|
||||
└─ Expr (value)
|
||||
|
||||
Phase 1 式:
|
||||
├─ BinOp (left, op, right)
|
||||
│ └─ ops: Add, Sub, Mult, Div, FloorDiv, Mod, Pow
|
||||
├─ Compare (left, ops, comparators)
|
||||
│ └─ ops: Eq, NotEq, Lt, LtE, Gt, GtE, Is, IsNot
|
||||
├─ BoolOp (op, values) # and/or
|
||||
├─ UnaryOp (op, operand) # not, -, +
|
||||
├─ Call (func, args, keywords=[])
|
||||
├─ Name (id, ctx=Load/Store/Del)
|
||||
├─ Constant (value) # Python 3.8+統一
|
||||
└─ IfExp (test, body, orelse) # 三項演算子
|
||||
|
||||
Phase 1 制御フロー:
|
||||
├─ If (test, body, orelse)
|
||||
├─ While (test, body, orelse) # else節対応必須
|
||||
├─ For (target, iter, body, orelse) # else節対応必須
|
||||
├─ Break
|
||||
└─ Continue
|
||||
```
|
||||
|
||||
### 3. 関数単位フォールバック戦略
|
||||
```rust
|
||||
// 関数単位のコンパイル判定
|
||||
pub struct FunctionCompiler {
|
||||
supported_nodes: HashSet<&'static str>,
|
||||
telemetry: CompilationTelemetry,
|
||||
}
|
||||
|
||||
impl FunctionCompiler {
|
||||
pub fn can_compile(&self, func_def: &PythonAst) -> CompileResult {
|
||||
let mut visitor = SupportChecker::new(&self.supported_nodes);
|
||||
visitor.visit(func_def);
|
||||
|
||||
if visitor.has_unsupported() {
|
||||
// CPython execへフォールバック
|
||||
CompileResult::Fallback {
|
||||
reason: visitor.unsupported_nodes(),
|
||||
location: func_def.location(),
|
||||
}
|
||||
} else {
|
||||
// Nyash MIR/JITへコンパイル
|
||||
CompileResult::Compile
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_module(&mut self, module: &PythonAst) -> ModuleUnit {
|
||||
let mut units = vec![];
|
||||
|
||||
// モジュールトップレベルはPythonで実行(globals設定)
|
||||
units.push(ExecutionUnit::PythonExec(module.top_level));
|
||||
|
||||
// 各関数を判定
|
||||
for func in module.functions() {
|
||||
match self.can_compile(func) {
|
||||
CompileResult::Compile => {
|
||||
let mir = self.compile_to_mir(func);
|
||||
units.push(ExecutionUnit::NyashFunction(mir));
|
||||
self.telemetry.record_compiled(func.name);
|
||||
}
|
||||
CompileResult::Fallback { reason, location } => {
|
||||
units.push(ExecutionUnit::PythonThunk(func));
|
||||
self.telemetry.record_fallback(func.name, reason, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModuleUnit { units }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. データ共有戦略
|
||||
```rust
|
||||
// NdArrayBox定義
|
||||
pub struct NdArrayBox {
|
||||
base: BoxBase,
|
||||
py_array: Py<PyArray<f64, Dim<[usize; 2]>>>, // Python側の参照保持
|
||||
// 操作時のみGIL取得してArrayViewを取る
|
||||
}
|
||||
|
||||
impl NdArrayBox {
|
||||
pub fn to_view(&self) -> PyResult<ArrayView2<f64>> {
|
||||
Python::with_gil(|py| {
|
||||
let array = self.py_array.as_ref(py);
|
||||
Ok(array.readonly())
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 実装ロードマップ
|
||||
|
||||
#### Phase 1: パーサー統合(1-2週間)
|
||||
- [ ] pyo3セットアップとPythonParserBox骨格
|
||||
- [ ] Python側parse_to_jsonヘルパー実装
|
||||
- [ ] JSON→Nyash AST最小変換
|
||||
- [ ] run()メソッド(CPython exec委譲)
|
||||
- [ ] 例外変換(PyErr → NyashError)
|
||||
|
||||
#### Phase 2: MIR変換(2-4週間)
|
||||
- [ ] AST→MIR変換器(最小セット)
|
||||
- [ ] 数値演算プリミティブ実装
|
||||
- [ ] スコープ解決(関数ローカル/グローバル)
|
||||
- [ ] 基本的な制御フロー(If/While)
|
||||
|
||||
#### Phase 3: NumPy統合(並行可能)
|
||||
- [ ] pyo3-numpy統合
|
||||
- [ ] NdArrayBox実装
|
||||
- [ ] ゼロコピーベンチマーク
|
||||
- [ ] バッファプロトコル対応
|
||||
|
||||
#### Phase 4: 最適化と拡張
|
||||
- [ ] 型特化とガード最適化
|
||||
- [ ] 例外処理(try/except)
|
||||
- [ ] クラス/メソッド対応
|
||||
- [ ] import統合
|
||||
|
||||
## 性能目標(現実的な見積もり)
|
||||
|
||||
| コードタイプ | 期待される高速化 | 備考 |
|
||||
|------------|----------------|------|
|
||||
| 純Pythonループ | 2-10倍 | 型安定なホットパス |
|
||||
| 関数呼び出し多 | 1.5-3倍 | インライン化効果 |
|
||||
| NumPy処理中心 | 1.0-1.2倍 | 既に最適化済み |
|
||||
| 動的特性多用 | 1.2-3倍 | ガード頻発で限定的 |
|
||||
|
||||
## 実装上の注意点(エキスパート推奨)
|
||||
|
||||
### 意味論の重要な違い(Phase 1で対応必須)
|
||||
|
||||
1. **制御フロー**
|
||||
- `for`文: イテレータプロトコル必須(`__iter__`/`__next__`)
|
||||
- `for/else`, `while/else`: breakしなかった場合のelse実行
|
||||
- 短絡評価: `and`は左がFalseなら右を評価しない
|
||||
|
||||
2. **スコープ規則(LEGB)**
|
||||
```python
|
||||
# Local → Enclosing → Global → Builtins
|
||||
global_var = 1
|
||||
|
||||
def outer():
|
||||
enclosing_var = 2
|
||||
|
||||
def inner():
|
||||
local_var = 3
|
||||
nonlocal enclosing_var # 明示的な宣言
|
||||
global global_var # 明示的な宣言
|
||||
```
|
||||
|
||||
3. **数値演算の違い**
|
||||
- `/`: Python 3では常にfloat(true division)
|
||||
- `//`: floor division(整数除算)
|
||||
- 大整数: デフォルトで無限精度
|
||||
- `is` vs `==`: オブジェクト同一性 vs 値の等価性
|
||||
|
||||
4. **関数定義の罠**
|
||||
```python
|
||||
def f(x, y=[]): # デフォルト引数は定義時に1度だけ評価!
|
||||
y.append(x) # 全呼び出しで同じリストを共有
|
||||
return y
|
||||
```
|
||||
|
||||
### GIL管理のベストプラクティス
|
||||
|
||||
```rust
|
||||
// ❌ 悪い例: GILを長時間保持
|
||||
let result = Python::with_gil(|py| {
|
||||
let ast = parse_python(py, code)?;
|
||||
let json = convert_to_json(py, ast)?; // ここまでGIL必要
|
||||
let nyash_ast = parse_json(&json)?; // GIL不要なのに保持
|
||||
compile_to_mir(nyash_ast)? // GIL不要なのに保持
|
||||
});
|
||||
|
||||
// ✅ 良い例: GILを最小限に
|
||||
let json = Python::with_gil(|py| {
|
||||
let ast = parse_python(py, code)?;
|
||||
convert_to_json(py, ast) // JSON生成まで
|
||||
})?;
|
||||
|
||||
// GIL外で重い処理
|
||||
let nyash_ast = parse_json(&json)?;
|
||||
let mir = compile_to_mir(nyash_ast)?;
|
||||
|
||||
// 必要時のみ再取得
|
||||
Python::with_gil(|py| {
|
||||
py.allow_threads(|| {
|
||||
// 時間のかかるRust処理
|
||||
optimize_mir(mir)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### テレメトリーとデバッグ
|
||||
|
||||
```rust
|
||||
// 環境変数で制御
|
||||
NYASH_PYTHONPARSER_TELEMETRY=1 # 基本統計
|
||||
NYASH_PYTHONPARSER_TELEMETRY=2 # 詳細ログ
|
||||
NYASH_PYTHONPARSER_STRICT=1 # フォールバック時にパニック(CI用)
|
||||
|
||||
// 出力例
|
||||
[PythonParser] Module: example.py
|
||||
Functions: 10 total
|
||||
Compiled: 7 (70%)
|
||||
Fallback: 3 (30%)
|
||||
- async_function: unsupported node 'AsyncFunctionDef' at line 23
|
||||
- generator_func: unsupported node 'Yield' at line 45
|
||||
- class_method: unsupported node 'ClassDef' at line 67
|
||||
```
|
||||
|
||||
## 次のステップ
|
||||
|
||||
### 即座に開始すべきこと
|
||||
|
||||
1. **Python 3.11環境固定**
|
||||
```bash
|
||||
pyenv install 3.11.9
|
||||
pyenv local 3.11.9
|
||||
```
|
||||
|
||||
2. **最小動作確認**
|
||||
```python
|
||||
# test_minimal.py
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
result = add(10, 5)
|
||||
print(f"Result: {result}") # → Nyashで15が出力されれば成功!
|
||||
```
|
||||
|
||||
3. **テレメトリー基盤構築**
|
||||
- 未対応ノードの記録システム
|
||||
- フォールバック率の可視化
|
||||
- ソース位置情報の保持
|
||||
|
||||
4. **Differential Testingの準備**
|
||||
- CPythonとの出力比較フレームワーク
|
||||
- 標準出力、戻り値、例外のキャプチャ
|
||||
- テストコーパスの選定
|
||||
|
||||
### 成功の測定基準
|
||||
|
||||
| フェーズ | 目標 | 測定指標 |
|
||||
|---------|------|----------|
|
||||
| Phase 1 | 基本動作 | 簡単な数値計算の70%がコンパイル可能 |
|
||||
| Phase 2 | 実用性 | scikit-learnの基本アルゴリズムが動作 |
|
||||
| Phase 3 | 性能 | 純Pythonループで5倍以上の高速化 |
|
||||
| Phase 4 | 成熟度 | PyPIトップ100の30%が基本動作 |
|
||||
|
||||
## まとめ
|
||||
|
||||
このPythonParserBox実装は、単なる機能追加ではなく、Nyash言語の成熟度を飛躍的に高める戦略的プロジェクト。
|
||||
エキスパートの指摘を踏まえ、関数単位のフォールバック、Python 3.11固定、意味論の正確な実装、
|
||||
GIL最小化、テレメトリー重視で着実に実装を進める。
|
||||
Reference in New Issue
Block a user