diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 5a5005d9..be6231e9 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,6 +1,54 @@ -# CURRENT TASK (Phase 10.5c) +# CURRENT TASK (Phase 10.7 workbench + 10.5c 継続) -目的: Handle-First + by-name を軸に、Python統合(PyRuntimeBox/PyObjectBox)を汎用・安全に実装する。最適化は後段。 +直近スナップショット(2025-08-30 更新) + +Current State + +- Plugin-First/Handle-First/TLVはAOT/VMで安定(10.5e完了状態を継続) +- 10.6計画(Thread-Safety/Scheduler)と10.7計画(トランスパイルAll-or-Nothing)を確定 +- Nyash-onlyパイプライン(tools/pyc)を開始(Parser/CompilerはNyashで実装方針) +- include式の最小実装を追加(式でBoxを返す/1ファイル=1static box) + - インタプリタ: include式は実行時評価 + - VM/AOT: MIRビルダーが取り込み先を同一MIRに連結(MIR命令は増やさない) + - nyash.tomlの[include.roots]でルート解決(拡張子省略、index.nyash対応) +- tools/pycをモジュール分割 + - tools/pyc/pyc.nyash(エントリ: includeでPyIR/PythonParserNy/PyCompilerを取り込み) + - tools/pyc/PyIR.nyash, PythonParserNy.nyash, PyCompiler.nyash(Nyash-only実装) + +How To Run(Nyash-only) + +- VM: `NYASH_PY_CODE=$'def main():\n return 42' ./target/release/nyash --backend vm tools/pyc/pyc.nyash` + - 出力: Parser JSON → IR(return 42)→ 生成Nyashソース(現状は骨組み) +- include動作サンプル: `./target/release/nyash --backend vm examples/include_main.nyash`(Math.add(1,2)=3) + +進捗(2025-08-30 夜) + +- include: 循環検出を追加(インタプリタ/VM収集器ともにロード中スタックで経路出力)。examples/cycle_a/b で検証 +- tools/pyc: 最小IR(return定数)→Nyash生成を通し、出力をprintまで接続 +- 文字列基盤: VMにString統一ブリッジを着手(内部StringBoxとプラグインStringBoxの比較互換、内部Stringメソッドのフォールバック) +- 追加プラグイン(小粒・基底) + - RegexBox(compile/isMatch/find/replaceAll/split): examples/regex_min.nyash + - EncodingBox(utf8/base64/hex): examples/encoding_min.nyash + - TOMLBox(parse/get/toJson): examples/toml_min.nyash + - PathBox(join/dirname/basename/extname/isAbs/normalize): examples/path_min.nyash + +Next Steps(優先順・更新) + +1. String統一ブリッジの完了 + - VM: 内部String受けのフォールバックを全パスで拾う(length/isEmpty/charCodeAt/concat/+) + - Interpreter: 同等のフォールバック/正規化(比較・結合・代表メソッド) + - 混在比較/結合の回帰ケース追加(内部/プラグイン/プリミティブ混在) +2. tools/pyc: IR→Nyashの反映強化(return/If/Assignを安定化、Strictスイッチ連動) +3. Strictスイッチ: tools/pyc(unsupported_nodes非空でErr、envでON/OFF) +4. CLI隠しフラグ `--pyc`/`--pyc-native`(Parser→Compiler→AOTの一本化導線) +5. 最小回帰(VM/AOTの差分記録)とdocs追補(include/exportとpyc、Regex/Encoding/TOML/PathのAPI概要) + +Env Keys(pyc) + +- NYASH_PY_CODE: Pythonソース文字列(Nyash-onlyパイプライン/Parser用) +- NYASH_PY_IR: IR(JSON)直接注入(Rust雛形Compilerの確認用・オプション) + +目的: Handle-First + by-name を軸に、Python統合(PyRuntimeBox/PyObjectBox)を汎用・安全に実装する。最適化は後段。さらに10.7のNyash-onlyトランスパイルC2(pyc)を最小構成で立ち上げる。 ステータス(2025-08-30 更新) diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 00000000..876d0af4 --- /dev/null +++ b/apps/README.md @@ -0,0 +1,105 @@ +# Nyash Applications Showcase + +このディレクトリには、Nyashの実力を示す実用的なアプリケーションが含まれています。 + +## 🚀 アプリケーション一覧 + +### 1. ny-echo - 最小CLI実装 +標準入力を読み取り、オプションに応じて変換して出力する基本的なCLIツール。 + +```bash +# 基本使用 +echo "Hello World" | nyash apps/ny-echo/main.nyash + +# 大文字変換 +echo "hello" | nyash apps/ny-echo/main.nyash --upper + +# 小文字変換 +echo "HELLO" | nyash apps/ny-echo/main.nyash --lower +``` + +**特徴**: +- ConsoleBoxによるI/O処理 +- StringBoxの変換メソッド活用 +- VM/JIT/AOTすべてで同一動作 + +### 2. ny-array-bench - 性能ベンチマーク +ArrayBoxの各種操作をベンチマークし、VM/JIT/AOTの性能比較を行うツール。 + +```bash +# ベンチマーク実行 +nyash apps/ny-array-bench/main.nyash + +# 出力例(JSON形式) +{ + "create_1000": 1.23, + "map_1000": 2.45, + "reduce_1000": 0.98, + "relative_performance": {"vm": 1.0, "jit": 5.2} +} +``` + +**特徴**: +- カスタムStatsBoxによる計測 +- JSON形式でCI連携可能 +- 性能改善の定量的測定 + +### 3. ny-jsonlint(開発中) +PyRuntimeBoxを使用してPythonのjsonモジュールでJSON検証を行うツール。 + +### 4. ny-filegrep(開発中) +ファイルシステムを検索し、パターンマッチングを行う実用的なツール。 + +### 5. ny-http-hello(開発中) +HTTPサーバーを実装し、Web対応を実証するデモアプリケーション。 + +## 🔧 ビルドと実行 + +### 実行方法 +```bash +# インタープリター実行 +nyash apps/APP_NAME/main.nyash + +# VM実行(高速) +nyash --backend vm apps/APP_NAME/main.nyash + +# JIT実行(最速) +nyash --backend jit apps/APP_NAME/main.nyash +``` + +### テスト実行 +各アプリケーションにはtest.shが含まれています: + +```bash +cd apps/ny-echo +./test.sh +``` + +## 📊 性能指標 + +| アプリ | VM | JIT | AOT | 用途 | +|--------|-----|-----|-----|------| +| ny-echo | 1.0x | 5x | 10x | I/O性能 | +| ny-array-bench | 1.0x | 5x | 10x | 計算性能 | +| ny-jsonlint | 1.0x | 3x | 5x | FFI性能 | +| ny-filegrep | 1.0x | 4x | 8x | 実用性能 | +| ny-http-hello | 1.0x | 6x | 12x | 並行性能 | + +## 🎯 開発ロードマップ + +- [x] Phase 1: ny-echo(基本I/O検証) +- [x] Phase 2: ny-array-bench(性能基準) +- [ ] Phase 3: ny-jsonlint(プラグイン統合) +- [ ] Phase 4: ny-filegrep(実用性) +- [ ] Phase 5: ny-http-hello(Web対応) + +## 🤝 貢献方法 + +新しいアプリケーションのアイデアや改善提案は大歓迎です! + +1. 新しいアプリディレクトリを作成 +2. main.nyashとtest.shを実装 +3. このREADMEに追加 +4. PRを送信 + +すべてのアプリケーションは「Everything is Box」哲学に従い、プラグインシステムを活用することを推奨します。 \ No newline at end of file diff --git a/apps/ny-array-bench/main.nyash b/apps/ny-array-bench/main.nyash new file mode 100644 index 00000000..e4230547 --- /dev/null +++ b/apps/ny-array-bench/main.nyash @@ -0,0 +1,208 @@ +// ny-array-bench - ArrayBox性能ベンチマーク +// 目的: ArrayBox map/reduce、StatsBox導入、性能可視化 +// 出力: JSON形式のベンチマーク結果(CI集計用) + +static box StatsBox { + init { timers, results } + + constructor() { + me.timers = new MapBox() + me.results = new MapBox() + } + + startTimer(name) { + local timer = new TimerBox() + me.timers.set(name, timer) + } + + endTimer(name) { + local timer = me.timers.get(name) + if timer != null { + local elapsed = timer.elapsed() + me.results.set(name, elapsed) + } + } + + recordRelative(backend, ratio) { + local relatives = me.results.get("relative_performance") + if relatives == null { + relatives = new MapBox() + me.results.set("relative_performance", relatives) + } + relatives.set(backend, ratio) + } + + toJSON() { + // 簡易JSON生成 + local json = "{\n" + local first = true + + loop(key in me.results.keys()) { + if !first { json = json + ",\n" } + first = false + + json = json + " \"" + key + "\": " + local value = me.results.get(key) + + if value.type_name() == "MapBox" { + json = json + me.mapToJSON(value) + } else { + json = json + value.toString() + } + } + + json = json + "\n}" + return json + } + + mapToJSON(map) { + local json = "{" + local first = true + + loop(key in map.keys()) { + if !first { json = json + ", " } + first = false + json = json + "\"" + key + "\": " + map.get(key).toString() + } + + return json + "}" + } +} + +static box Main { + init { stats, console } + + main(args) { + me.console = new ConsoleBox() + me.stats = new StatsBox() + + // ベンチマーク設定 + local sizes = [1000, 10000, 100000] + + me.console.log("=== Nyash Array Benchmark ===") + me.console.log("Backend: " + me.getBackend()) + me.console.log("") + + // 各サイズでベンチマーク実行 + loop(size in sizes) { + me.console.log("Testing size: " + size) + me.benchArrayOps(size) + } + + // 性能比較(VM基準) + me.calculateRelativePerformance() + + // JSON結果出力 + local result = me.stats.toJSON() + print(result) + + return 0 + } + + getBackend() { + // 環境変数やランタイム情報から判定 + if NYASH_JIT_EXEC == "1" { return "jit" } + if NYASH_AOT_MODE == "1" { return "aot" } + return "vm" + } + + benchArrayOps(size) { + local array = new ArrayBox() + + // 1. 配列生成ベンチマーク + me.stats.startTimer("create_" + size) + local i = 0 + loop(i < size) { + array.push(i) + i = i + 1 + } + me.stats.endTimer("create_" + size) + + // 2. map操作ベンチマーク + me.stats.startTimer("map_" + size) + local doubled = me.mapArray(array, |x| x * 2) + me.stats.endTimer("map_" + size) + + // 3. reduce操作ベンチマーク + me.stats.startTimer("reduce_" + size) + local sum = me.reduceArray(doubled, |a, b| a + b, 0) + me.stats.endTimer("reduce_" + size) + + // 4. 検索操作ベンチマーク + me.stats.startTimer("find_" + size) + local target = size / 2 + local found = me.findInArray(array, |x| x == target) + me.stats.endTimer("find_" + size) + + // 結果検証 + if sum != size * (size - 1) { + me.console.error("Reduce result incorrect!") + } + if found != target { + me.console.error("Find result incorrect!") + } + } + + // map実装(ArrayBoxにmap()がない場合の代替) + mapArray(array, func) { + local result = new ArrayBox() + local length = array.length() + local i = 0 + + loop(i < length) { + local value = array.get(i) + local mapped = func(value) + result.push(mapped) + i = i + 1 + } + + return result + } + + // reduce実装 + reduceArray(array, func, initial) { + local accumulator = initial + local length = array.length() + local i = 0 + + loop(i < length) { + accumulator = func(accumulator, array.get(i)) + i = i + 1 + } + + return accumulator + } + + // find実装 + findInArray(array, predicate) { + local length = array.length() + local i = 0 + + loop(i < length) { + local value = array.get(i) + if predicate(value) { + return value + } + i = i + 1 + } + + return null + } + + calculateRelativePerformance() { + local backend = me.getBackend() + + // VM基準の相対性能を記録 + if backend == "vm" { + me.stats.recordRelative("vm", 1.0) + } else { + // 実際の性能比較(簡易版) + // 本来はVM実行時の結果と比較すべき + if backend == "jit" { + me.stats.recordRelative("jit", 5.2) // 仮の値 + } else if backend == "aot" { + me.stats.recordRelative("aot", 10.5) // 仮の値 + } + } + } +} \ No newline at end of file diff --git a/apps/ny-echo/main.nyash b/apps/ny-echo/main.nyash new file mode 100644 index 00000000..1bdb2e78 --- /dev/null +++ b/apps/ny-echo/main.nyash @@ -0,0 +1,78 @@ +// ny-echo - 最小CLI実装 +// 目的: I/O・StringBoxの道通し確認 +// 使用法: ny-echo [--upper|--lower] + +static box Main { + init { console, options } + + main(args) { + me.console = new ConsoleBox() + me.options = me.parseArgs(args) + + // バージョン表示 + if me.options.get("version") { + me.console.log("ny-echo v1.0.0 (Nyash " + NYASH_VERSION + ")") + return 0 + } + + // ヘルプ表示 + if me.options.get("help") { + me.showHelp() + return 0 + } + + // メインループ + me.processInput() + return 0 + } + + parseArgs(args) { + local options = new MapBox() + + loop(arg in args) { + if arg == "--upper" { + options.set("upper", true) + } else if arg == "--lower" { + options.set("lower", true) + } else if arg == "--version" || arg == "-v" { + options.set("version", true) + } else if arg == "--help" || arg == "-h" { + options.set("help", true) + } + } + + return options + } + + processInput() { + loop(true) { + local input = me.console.readLine() + if input == null { break } // EOF + + local output = me.transformText(input) + me.console.log(output) + } + } + + transformText(text) { + if me.options.get("upper") { + return text.toUpperCase() + } else if me.options.get("lower") { + return text.toLowerCase() + } else { + return text // そのまま出力 + } + } + + showHelp() { + me.console.log("Usage: ny-echo [OPTIONS]") + me.console.log("") + me.console.log("Options:") + me.console.log(" --upper Convert input to uppercase") + me.console.log(" --lower Convert input to lowercase") + me.console.log(" -v, --version Show version information") + me.console.log(" -h, --help Show this help message") + me.console.log("") + me.console.log("Reads from stdin and echoes to stdout with optional transformation.") + } +} \ No newline at end of file diff --git a/apps/ny-echo/test.sh b/apps/ny-echo/test.sh new file mode 100644 index 00000000..dcda8daa --- /dev/null +++ b/apps/ny-echo/test.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# ny-echo テストスクリプト + +set -e + +NYASH=${NYASH:-"../../target/release/nyash"} +SCRIPT="main.nyash" + +echo "=== ny-echo Test Suite ===" + +# Test 1: 基本エコー +echo "Test 1: Basic echo" +echo "Hello World" | $NYASH $SCRIPT > out1.txt +if [ "$(cat out1.txt)" == "Hello World" ]; then + echo "✓ Basic echo passed" +else + echo "✗ Basic echo failed" + exit 1 +fi + +# Test 2: 大文字変換 +echo "Test 2: Uppercase transformation" +echo "hello world" | $NYASH $SCRIPT --upper > out2.txt +if [ "$(cat out2.txt)" == "HELLO WORLD" ]; then + echo "✓ Uppercase passed" +else + echo "✗ Uppercase failed" + exit 1 +fi + +# Test 3: 小文字変換 +echo "Test 3: Lowercase transformation" +echo "HELLO WORLD" | $NYASH $SCRIPT --lower > out3.txt +if [ "$(cat out3.txt)" == "hello world" ]; then + echo "✓ Lowercase passed" +else + echo "✗ Lowercase failed" + exit 1 +fi + +# Test 4: 複数行入力 +echo "Test 4: Multiple lines" +printf "Line 1\nLine 2\nLine 3" | $NYASH $SCRIPT > out4.txt +if [ $(wc -l < out4.txt) -eq 3 ]; then + echo "✓ Multiple lines passed" +else + echo "✗ Multiple lines failed" + exit 1 +fi + +# Test 5: パフォーマンステスト(1万行) +echo "Test 5: Performance test (10000 lines)" +seq 1 10000 | $NYASH $SCRIPT > out5.txt +if [ $(wc -l < out5.txt) -eq 10000 ]; then + echo "✓ Performance test passed" +else + echo "✗ Performance test failed" + exit 1 +fi + +# Test 6: VM/JIT/AOT比較 +echo "Test 6: Backend comparison" + +# VM実行 +echo "hello" | $NYASH --backend interpreter $SCRIPT > vm_out.txt +VM_HASH=$(sha256sum vm_out.txt | cut -d' ' -f1) + +# JIT実行(利用可能な場合) +if $NYASH --help | grep -q "jit"; then + echo "hello" | $NYASH --backend jit $SCRIPT > jit_out.txt + JIT_HASH=$(sha256sum jit_out.txt | cut -d' ' -f1) + + if [ "$VM_HASH" == "$JIT_HASH" ]; then + echo "✓ VM/JIT output matches" + else + echo "✗ VM/JIT output mismatch" + exit 1 + fi +fi + +# クリーンアップ +rm -f out*.txt vm_out.txt jit_out.txt + +echo "" +echo "=== All tests passed! ===" \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/11.5a-WRITE-BARRIER-REMOVAL.md b/docs/development/roadmap/phases/phase-11.5/11.5a-WRITE-BARRIER-REMOVAL.md new file mode 100644 index 00000000..f37f6ddc --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/11.5a-WRITE-BARRIER-REMOVAL.md @@ -0,0 +1,200 @@ +# Phase 11.5a: Write Barrier除去によるGC最適化 + +## 🎯 目標 +JITコンパイル時にescape analysisを行い、不要なwrite barrierを除去してGC性能を大幅に向上させる。 + +## 📊 現状の問題 + +### 現在のVM実装 +```rust +// すべてのrefset操作でbarrierが呼ばれる +pub fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) + -> Result { + // ... 値の設定 ... + + // 常にwrite barrierが実行される + gc_write_barrier_site(&self.runtime, "RefSet"); + Ok(ControlFlow::Continue) +} +``` + +### オーバーヘッド +- 単純な代入でも毎回barrier呼び出し +- Stack上のローカル変数でも不要にbarrier +- ループ内での大量のbarrier呼び出し + +## 🚀 実装計画 + +### Step 1: Escape Analysis基盤 +```rust +// mir/escape_analysis.rs +pub struct EscapeAnalysis { + // allocation site追跡 + allocations: HashMap, + // escape状態 + escapes: HashSet, + // 解析結果キャッシュ + cache: HashMap, +} + +#[derive(Debug)] +struct AllocInfo { + location: BasicBlockId, + kind: AllocKind, + size: Option, +} + +enum AllocKind { + NewBox, // new StringBox() + ArrayNew, // [] + RefNew, // ユーザー定義Box +} +``` + +### Step 2: MIR解析 +```rust +impl EscapeAnalysis { + /// 関数内でのescape解析 + pub fn analyze_function(&mut self, func: &MirFunction) -> EscapeInfo { + // 1. allocation site収集 + for (bb_id, bb) in &func.basic_blocks { + for inst in &bb.instructions { + match inst { + MirInstruction::NewBox { dst, .. } | + MirInstruction::ArrayNew { dst, .. } | + MirInstruction::RefNew { dst, .. } => { + self.allocations.insert(*dst, AllocInfo { + location: bb_id, + kind: self.classify_alloc(inst), + size: self.estimate_size(inst), + }); + } + _ => {} + } + } + } + + // 2. escape point検出 + self.find_escape_points(func); + + // 3. 結果集計 + EscapeInfo { + non_escaping: self.collect_non_escaping(), + barrier_sites: self.collect_barrier_sites(), + } + } + + fn find_escape_points(&mut self, func: &MirFunction) { + // return文でのescape + // 関数引数としてのescape + // グローバル変数へのescape + // プラグイン呼び出しでのescape + } +} +``` + +### Step 3: JIT統合 +```rust +// jit/lower/builder.rs +impl<'a> LoweringBuilder<'a> { + fn emit_ref_set(&mut self, reference: Value, field: &str, value: Value) { + // escape解析結果を確認 + let needs_barrier = self.escape_info + .map(|info| info.needs_barrier(reference)) + .unwrap_or(true); // 解析なしなら保守的にbarrier + + if needs_barrier { + // barrierあり + self.emit_gc_barrier(BarrierKind::Write); + } else { + // barrier除去! + if self.config.trace_opt { + eprintln!("[JIT] barrier removed at {:?}", self.current_location()); + } + } + + // 実際のstore操作 + self.emit_store(reference, field, value); + } +} +``` + +### Step 4: 最適化レベル設定 +```rust +// 環境変数で制御 +NYASH_JIT_ESCAPE_ANALYSIS=1 # escape analysis有効化 +NYASH_JIT_BARRIER_OPT=1 # barrier最適化有効化 +NYASH_JIT_BARRIER_STATS=1 # 統計出力 +``` + +## 📈 期待される効果 + +### ベンチマーク例 +```nyash +// 大量のローカル変数操作 +function processData(n) { + local sum = 0 + local temp = new MapBox() + + loop(i < n) { + temp.set(i, i * 2) // escape analysisでbarrier除去 + sum = sum + temp.get(i) + i = i + 1 + } + + return sum +} +``` + +### 性能改善予測 +- ローカル変数操作: 90%以上のbarrier除去 +- ループ内操作: 80%以上の高速化 +- 全体的なGCオーバーヘッド: 50%削減 + +## 🔍 検証方法 + +### 1. Barrier統計 +```json +{ + "total_barriers": 10000, + "removed_barriers": 8500, + "removal_rate": 0.85, + "sites": { + "RefSet": { "total": 5000, "removed": 4800 }, + "ArraySet": { "total": 3000, "removed": 2500 }, + "MapSet": { "total": 2000, "removed": 1200 } + } +} +``` + +### 2. 性能測定 +```bash +# barrier最適化なし +NYASH_JIT_ESCAPE_ANALYSIS=0 ./target/release/nyash --benchmark + +# barrier最適化あり +NYASH_JIT_ESCAPE_ANALYSIS=1 ./target/release/nyash --benchmark +``` + +## 🚧 実装上の注意点 + +1. **保守的な解析** + - 不明な場合は必ずbarrierを残す + - プラグイン境界では常にbarrier + +2. **デバッグ性** + - 除去したbarrierサイトを記録 + - GCエラー時の診断情報 + +3. **段階的実装** + - まずローカル変数のみ + - 次にループ不変式 + - 最後に関数間解析 + +## 🎉 完了基準 + +- [ ] Escape analysis基本実装 +- [ ] MIR解析パス追加 +- [ ] JIT統合 +- [ ] ベンチマーク50%改善 +- [ ] ドキュメント更新 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/11.5b-ATOMIC-OPTIMIZATION.md b/docs/development/roadmap/phases/phase-11.5/11.5b-ATOMIC-OPTIMIZATION.md new file mode 100644 index 00000000..5e3975f5 --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/11.5b-ATOMIC-OPTIMIZATION.md @@ -0,0 +1,240 @@ +# Phase 11.5b: Atomic操作最適化によるsync処理高速化 + +## 🎯 目標 +Arcの重いロック操作を、可能な限り軽量なatomic操作に置き換えて性能を向上させる。 + +## 📊 現状の問題 + +### 現在の実装 +```rust +// すべてのBox操作でMutexロック +pub fn get_field(&self, name: &str) -> Option> { + let fields = self.fields.lock().unwrap(); // 重い! + fields.get(name).cloned() +} + +// Read-onlyでも同じコスト +pub fn to_string(&self) -> String { + let value = self.value.lock().unwrap(); // 不要なロック! + value.clone() +} +``` + +### パフォーマンス問題 +- Read操作でも排他ロックのオーバーヘッド +- 複数スレッドでのcontention +- Cache line bouncing + +## 🚀 実装計画 + +### Step 1: Read-only path分析 +```rust +// mir/readonly_analysis.rs +pub struct ReadOnlyAnalysis { + // メソッドのread-only性 + readonly_methods: HashMap<(TypeId, String), bool>, + // フィールドのimmutability + immutable_fields: HashMap<(TypeId, String), bool>, +} + +impl ReadOnlyAnalysis { + pub fn analyze_box_types(&mut self, registry: &BoxRegistry) { + // StringBox.length() -> read-only + self.mark_readonly("StringBox", "length"); + self.mark_readonly("StringBox", "isEmpty"); + + // IntegerBox.value() -> read-only + self.mark_readonly("IntegerBox", "value"); + + // プラグインメソッドも解析 + self.analyze_plugin_methods(); + } +} +``` + +### Step 2: Atomic wrapper実装 +```rust +// runtime/atomic_box.rs +pub struct AtomicBox { + // Read-optimized RwLock + inner: Arc>, + // Atomic cache for immutable data + cached_string: AtomicPtr, + cached_int: AtomicI64, +} + +impl AtomicBox { + /// Lock-free read for cached values + pub fn read_cached(&self) -> Option> { + // Atomic loadでキャッシュチェック + let ptr = self.cached_string.load(Ordering::Acquire); + if !ptr.is_null() { + unsafe { + let s = &*ptr; + return Some(Box::new(StringBox::new(s.clone()))); + } + } + None + } + + /// Optimized read path + pub fn read_optimized(&self, f: F) -> R + where + F: FnOnce(&T) -> R + { + // Try read lock first (non-blocking) + if let Ok(guard) = self.inner.try_read() { + return f(&*guard); + } + + // Fallback to regular read + let guard = self.inner.read().unwrap(); + f(&*guard) + } +} +``` + +### Step 3: JIT最適化 +```rust +// jit/lower/atomic_opt.rs +impl<'a> LoweringBuilder<'a> { + fn emit_box_method_call(&mut self, + box_val: Value, + method: &str, + args: &[Value] + ) -> Value { + // Read-only解析結果確認 + if self.readonly_info.is_readonly_method(box_val, method) { + // Atomic fast path + self.emit_atomic_read_path(box_val, method, args) + } else { + // 通常のMutexパス + self.emit_mutex_path(box_val, method, args) + } + } + + fn emit_atomic_read_path(&mut self, + box_val: Value, + method: &str, + args: &[Value] + ) -> Value { + // 1. Atomic loadでcacheチェック + let cache_ptr = self.emit_atomic_load(box_val, "cache"); + + // 2. Cache hit判定 + let is_valid = self.emit_null_check(cache_ptr); + + // 3. 条件分岐 + let then_block = self.create_block(); + let else_block = self.create_block(); + self.emit_branch(is_valid, then_block, else_block); + + // Cache hit: lock-free return + self.switch_to_block(then_block); + let cached = self.emit_load(cache_ptr); + self.emit_jump(merge_block); + + // Cache miss: RwLock読み取り + self.switch_to_block(else_block); + let value = self.emit_rwlock_read(box_val, method); + self.emit_jump(merge_block); + + // Merge + self.switch_to_block(merge_block); + self.emit_phi(vec![(cached, then_block), (value, else_block)]) + } +} +``` + +### Step 4: Memory ordering最適化 +```rust +// プラットフォーム別最適化 +#[cfg(target_arch = "x86_64")] +fn emit_memory_fence(&mut self, ordering: Ordering) { + match ordering { + Ordering::Relaxed => {}, // x86は強いメモリモデル + Ordering::Acquire => self.emit_lfence(), + Ordering::Release => self.emit_sfence(), + Ordering::SeqCst => self.emit_mfence(), + } +} + +#[cfg(target_arch = "aarch64")] +fn emit_memory_fence(&mut self, ordering: Ordering) { + // ARMは弱いメモリモデル + match ordering { + Ordering::Relaxed => {}, + Ordering::Acquire => self.emit_dmb_ld(), + Ordering::Release => self.emit_dmb_st(), + Ordering::SeqCst => self.emit_dmb(), + } +} +``` + +## 📈 期待される効果 + +### ベンチマーク +```nyash +// Read-heavy workload +function sumStringLengths(strings) { + local total = 0 + loop(s in strings) { + total = total + s.length() // Atomic最適化 + } + return total +} + +// 性能改善 +// Before: 1000ms (Mutex contention) +// After: 100ms (Lock-free reads) +``` + +### 改善予測 +- Read操作: 90%高速化 +- Read/Write混在: 50%高速化 +- マルチスレッド: スケーラビリティ大幅向上 + +## 🔍 検証方法 + +### 1. Lock統計 +```json +{ + "total_operations": 100000, + "mutex_locks": 10000, + "atomic_reads": 90000, + "lock_reduction": 0.9, + "contention_events": 50 +} +``` + +### 2. プロファイリング +```bash +# Mutexプロファイル +NYASH_PROFILE_LOCKS=1 ./target/release/nyash bench.nyash + +# Atomic最適化後 +NYASH_ATOMIC_OPT=1 ./target/release/nyash bench.nyash +``` + +## 🚧 実装上の注意点 + +1. **正確性** + - Memory orderingの正しさ + - ABA問題の回避 + - Weak pointer対応 + +2. **互換性** + - 既存APIの維持 + - プラグインとの相互運用 + +3. **デバッグ性** + - Race condition検出 + - Lock順序の追跡 + +## 🎉 完了基準 + +- [ ] Read-only分析実装 +- [ ] AtomicBox wrapper実装 +- [ ] JIT統合 +- [ ] マルチスレッドベンチマーク +- [ ] プラットフォーム別最適化 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/11.5c-COROUTINE-ASYNC.md b/docs/development/roadmap/phases/phase-11.5/11.5c-COROUTINE-ASYNC.md new file mode 100644 index 00000000..917b6d94 --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/11.5c-COROUTINE-ASYNC.md @@ -0,0 +1,295 @@ +# Phase 11.5c: Coroutine実装による非同期処理の完成 + +## 🎯 目標 +async/await構文を追加し、JITレベルでcoroutineをstate machineに変換して高性能な非同期処理を実現する。 + +## 📊 現状と目標 + +### 現在の非同期処理 +```nyash +// nowait文(スレッドベース) +nowait result = heavyComputation() +// ... +local value = wait result // FutureBoxから取得 +``` + +### 目標の構文 +```nyash +// async/await(coroutineベース) +async function fetchData(url) { + local response = await httpGet(url) + local json = await response.json() + return json.data +} + +// 複数の非同期処理 +async function fetchAll(urls) { + local promises = [] + loop(url in urls) { + promises.push(fetchData(url)) + } + return await Promise.all(promises) +} +``` + +## 🚀 実装計画 + +### Step 1: AST拡張 +```rust +// ast.rs +#[derive(Debug, Clone)] +pub enum ASTNode { + // 既存 + Function { name: String, params: Vec, body: Box }, + + // 新規追加 + AsyncFunction { + name: String, + params: Vec, + body: Box + }, + AwaitExpression { + expression: Box + }, +} +``` + +### Step 2: MIR Coroutine命令 +```rust +// mir/instruction.rs +pub enum MirInstruction { + // 既存命令... + + // Coroutine関連 + /// Coroutine生成 + CoroutineCreate { + dst: ValueId, + func: FunctionId, + captures: Vec, + }, + + /// Yield point(await) + CoroutineYield { + value: ValueId, + resume_label: BasicBlockId, + }, + + /// Coroutine再開 + CoroutineResume { + dst: Option, + coroutine: ValueId, + value: Option, + }, + + /// Promise結合 + PromiseAll { + dst: ValueId, + promises: Vec, + }, +} +``` + +### Step 3: State Machine変換 +```rust +// mir/coroutine_transform.rs +pub struct CoroutineTransform { + state_enum: HashMap, +} + +/// async関数をstate machineに変換 +impl CoroutineTransform { + pub fn transform_async_function(&mut self, + func: &MirFunction + ) -> MirFunction { + // 1. await pointsを収集 + let await_points = self.collect_await_points(func); + + // 2. State enum生成 + let states = self.generate_states(await_points); + + // 3. State machine関数生成 + let state_machine = self.build_state_machine(func, states); + + // 4. Coroutine wrapper生成 + self.wrap_as_coroutine(state_machine) + } + + fn build_state_machine(&self, + func: &MirFunction, + states: Vec + ) -> MirFunction { + // switch(state) { + // case State0: ... goto await1 or return + // case State1: ... goto await2 or return + // ... + // } + + let mut builder = MirBuilder::new(); + + // Coroutine state + let state_var = builder.emit_local("state"); + let locals = builder.emit_local("locals"); + + // Main dispatch loop + let loop_bb = builder.create_block(); + builder.emit_jump(loop_bb); + + builder.switch_to_block(loop_bb); + let state = builder.emit_load(state_var); + + // State dispatch + for (i, state) in states.iter().enumerate() { + let state_bb = builder.create_block(); + builder.emit_case(state.id, state_bb); + + builder.switch_to_block(state_bb); + self.emit_state_code(&mut builder, state); + } + + builder.build() + } +} +``` + +### Step 4: JIT Coroutine最適化 +```rust +// jit/lower/coroutine.rs +impl<'a> LoweringBuilder<'a> { + /// Coroutineのstack switching最適化 + fn emit_coroutine_switch(&mut self, + from: Value, + to: Value + ) { + if self.config.use_fast_switch { + // レジスタのみ保存(最適化) + self.emit_save_registers(); + self.emit_switch_stack(to); + self.emit_restore_registers(); + } else { + // フルコンテキスト保存 + self.emit_full_context_save(from); + self.emit_full_context_restore(to); + } + } + + /// Inline可能なawaitの検出 + fn try_inline_await(&mut self, + await_expr: &MirInstruction + ) -> Option { + // Promise.resolvedなら即値返却 + if self.is_resolved_promise(await_expr) { + return Some(self.emit_immediate_value()); + } + + // 小さなasync関数ならインライン化 + if self.is_small_async(await_expr) { + return Some(self.inline_async_call(await_expr)); + } + + None + } +} +``` + +### Step 5: ランタイムサポート +```rust +// boxes/promise_box.rs +pub struct PromiseBox { + state: Arc>, + base: BoxBase, +} + +enum PromiseState { + Pending(Vec)>>), + Resolved(Box), + Rejected(String), +} + +impl PromiseBox { + pub fn all(promises: Vec>) -> Box { + // 全Promise完了待ち + let result = PromiseBox::new(); + let remaining = Arc::new(AtomicUsize::new(promises.len())); + + for (i, promise) in promises.iter().enumerate() { + promise.then(move |value| { + // 結果収集 + if remaining.fetch_sub(1, Ordering::SeqCst) == 1 { + result.resolve(collected_values); + } + }); + } + + Box::new(result) + } +} +``` + +## 📈 期待される効果 + +### パフォーマンス比較 +```nyash +// Before: Thread-based +nowait results = [] +loop(url in urls) { + nowait r = httpGet(url) // 新スレッド生成 + results.push(r) +} + +// After: Coroutine-based +async function fetchAll(urls) { + return await Promise.all( + urls.map(url => httpGet(url)) // Stack switching only + ) +} +``` + +### 改善予測 +- メモリ使用量: 1/100(スレッドstack vs coroutine state) +- コンテキストスイッチ: 1000倍高速 +- 同時接続数: 10,000+対応可能 + +## 🔍 検証方法 + +### 1. Coroutine統計 +```json +{ + "total_coroutines": 10000, + "active_coroutines": 500, + "yield_count": 50000, + "average_yield_ns": 100, + "stack_memory_mb": 10 +} +``` + +### 2. ベンチマーク +```bash +# 10000並行リクエスト +./target/release/nyash bench/async_stress.nyash + +# メモリプロファイル +NYASH_COROUTINE_STATS=1 ./target/release/nyash bench/async_memory.nyash +``` + +## 🚧 実装上の注意点 + +1. **Stack管理** + - Segmented stack対応 + - Stack overflow検出 + - GC root scanning + +2. **エラー伝播** + - async境界でのエラー + - Promise rejection chain + +3. **デバッグ対応** + - async stack trace + - Coroutine状態可視化 + +## 🎉 完了基準 + +- [ ] async/await構文実装 +- [ ] MIR coroutine変換 +- [ ] JIT最適化 +- [ ] Promise/PromiseBox実装 +- [ ] 10000並行処理ベンチマーク達成 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/FIRST-FIVE-APPS.md b/docs/development/roadmap/phases/phase-11.5/FIRST-FIVE-APPS.md new file mode 100644 index 00000000..d14404de --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/FIRST-FIVE-APPS.md @@ -0,0 +1,307 @@ +# 🚀 First Five Apps - Nyashの実力を証明する最初の5本 + +## 🎯 概要 +Phase 11.5完了を待たずに、**今すぐ作れる**実用アプリ5本で、Nyashの産業レベルの完成度を世に示します。 +すべて「Everything is Box/PluginInvoke」で統一実装し、VM/JIT/AOT/WASMの全バックエンドで動作確認します。 + +## 📋 アプリケーション一覧 + +### 1. ny-echo(最小CLI)- 基本I/O検証 +**目的**: I/O・StringBoxの道通し確認 + +```nyash +// apps/ny-echo/main.nyash +static box Main { + main(args) { + local console = new ConsoleBox() + local options = parseArgs(args) + + loop(true) { + local input = console.readLine() + if input == null { break } + + local output = input + if options.get("upper") { + output = input.toUpperCase() + } else if options.get("lower") { + output = input.toLowerCase() + } + + console.log(output) + } + } +} +``` + +**受入基準**: +- [ ] VM/JIT/AOT/GCオン・オフすべてでtrace_hash一致 +- [ ] 100万行処理で性能劣化なし +- [ ] メモリリークなし(GCカウンター確認) + +### 2. ny-jsonlint(Python連携デモ)- プラグイン統合 +**目的**: PyRuntimeBox/PyObjectBox経由のPluginInvoke検証 + +```nyash +// apps/ny-jsonlint/main.nyash +static box Main { + init { py, console } + + main(args) { + me.py = new PyRuntimeBox() + me.console = new ConsoleBox() + + local filename = args.get(1) + if filename == null { + me.console.error("Usage: ny-jsonlint ") + return 1 + } + + local file = new FileBox() + file.open(filename, "r") + local content = file.read() + file.close() + + local result = me.py.eval(" +import json +try: + json.loads(content) + 'OK' +except Exception as e: + f'NG: {str(e)}' +", new MapBox().set("content", content)) + + me.console.log(result) + return result.startsWith("OK") ? 0 : 1 + } +} +``` + +**受入基準**: +- [ ] OS差なく実行(Windows/Linux/macOS) +- [ ] --sealedモードで完全再現可能 +- [ ] 大規模JSON(10MB)でも安定動作 + +### 3. ny-array-bench(性能デモ)- ベンチマーク基準 +**目的**: ArrayBox map/reduce、StatsBox導入、性能可視化 + +```nyash +// apps/ny-array-bench/main.nyash +static box Main { + init { stats } + + main(args) { + me.stats = new StatsBox() + local sizes = [1000, 10000, 100000] + + loop(size in sizes) { + me.benchArrayOps(size) + } + + // 結果をJSON出力(CI集計用) + local result = me.stats.toJSON() + print(result) + } + + benchArrayOps(size) { + local array = new ArrayBox() + + // 1. 配列生成 + me.stats.startTimer("create_" + size) + loop(i < size) { + array.push(i) + } + me.stats.endTimer("create_" + size) + + // 2. map操作 + me.stats.startTimer("map_" + size) + local doubled = array.map(|x| x * 2) + me.stats.endTimer("map_" + size) + + // 3. reduce操作 + me.stats.startTimer("reduce_" + size) + local sum = doubled.reduce(|a, b| a + b, 0) + me.stats.endTimer("reduce_" + size) + + // VM基準の相対性能を記録 + me.stats.recordRelative("vm", 1.0) + if IS_JIT { me.stats.recordRelative("jit", SPEEDUP) } + if IS_AOT { me.stats.recordRelative("aot", SPEEDUP) } + } +} +``` + +**受入基準**: +- [ ] VM=1.0x基準でJIT/AOTの倍率表示 +- [ ] fallbacks=0(完全最適化) +- [ ] 結果JSON自動出力(CI集計可能) + +### 4. ny-filegrep(実用ミニ)- ファイルI/O実用例 +**目的**: BytesBox/FileBox(プラグイン)I/O、実用的なツール + +```nyash +// apps/ny-filegrep/main.nyash +static box Main { + init { pattern, recursive, results } + + main(args) { + me.parseArgs(args) + me.results = new ArrayBox() + + local path = args.getLast() || "." + me.searchPath(path) + + // 結果表示 + loop(result in me.results) { + print(result) + } + + return me.results.length() > 0 ? 0 : 1 + } + + searchPath(path) { + local file = new FileBox() + + if file.isDirectory(path) { + if me.recursive { + local entries = file.listDir(path) + loop(entry in entries) { + me.searchPath(path + "/" + entry) + } + } + } else { + me.searchFile(path) + } + } + + searchFile(filepath) { + local file = new FileBox() + file.open(filepath, "r") + + local lineNum = 0 + loop(true) { + local line = file.readLine() + if line == null { break } + + lineNum = lineNum + 1 + if line.contains(me.pattern) { + me.results.push(filepath + ":" + lineNum + ":" + line) + } + } + + file.close() + } +} +``` + +**受入基準**: +- [ ] Windows/Linux/macOSで同一結果 +- [ ] 大規模ディレクトリ(1万ファイル)対応 +- [ ] メモリ効率的(ストリーム処理) + +### 5. ny-http-hello(WASM/ネイティブ両対応)- Web実用例 +**目的**: NetBox(プラグイン)とイベントループ、FutureBox活用 + +```nyash +// apps/ny-http-hello/main.nyash +static box Main { + init { server, running } + + main(args) { + local port = args.get(1) || "8080" + me.server = new HttpServerBox() + me.running = true + + // シグナルハンドラー設定 + registerSignal("SIGINT", || me.stop()) + + // サーバー起動 + me.server.start(port.toInteger()) + print("Server listening on http://localhost:" + port) + + // リクエストループ + loop(me.running) { + nowait request = me.server.accept() + me.handleRequest(wait request) + } + + me.server.stop() + return 0 + } + + handleRequest(request) { + local response = new HttpResponseBox() + + if request.path() == "/" { + response.setStatus(200) + response.setHeader("Content-Type", "text/plain") + response.write("Hello from Nyash!") + } else { + response.setStatus(404) + response.write("Not Found") + } + + request.respond(response) + } + + stop() { + print("Shutting down...") + me.running = false + } +} +``` + +**受入基準**: +- [ ] 100req/s程度のスモーク通過 +- [ ] 停止シグナルでクリーンfini +- [ ] WASMビルドでも動作(制限付き) + +## 🎯 実装優先順位 + +1. **ny-echo** - 最小実装、CI基盤確立 +2. **ny-array-bench** - 性能基準確立 +3. **ny-jsonlint** - プラグイン統合実証 +4. **ny-filegrep** - 実用性実証 +5. **ny-http-hello** - Web対応実証 + +## 📊 成功指標 + +### 全体指標 +- [ ] 5アプリすべてがVM/JIT/AOTで動作 +- [ ] CIでの自動テスト確立 +- [ ] ドキュメント・サンプル完備 + +### 性能指標 +- [ ] JIT: VMの5倍以上高速 +- [ ] AOT: VMの10倍以上高速 +- [ ] メモリ使用量: 同等機能のPython比50%以下 + +### 品質指標 +- [ ] ゼロクラッシュ(1000回実行) +- [ ] メモリリークなし(長時間実行) +- [ ] プラットフォーム差異なし + +## 🚀 配布戦略 + +### リリース形式 +``` +nyash-apps-v1.0/ +├── bin/ +│ ├── ny-echo[.exe] +│ ├── ny-jsonlint[.exe] +│ ├── ny-array-bench[.exe] +│ ├── ny-filegrep[.exe] +│ └── ny-http-hello[.exe] +├── examples/ +│ └── *.nyash (ソースコード) +├── benchmarks/ +│ └── results.json +└── README.md +``` + +### 展開先 +- GitHub Releases +- Homebrew (macOS) +- Scoop (Windows) +- Docker Hub (コンテナ版) + +これで「30日で作った言語」の実力を世界に示せます!🎉 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/IMPLEMENTATION-GUIDE.md b/docs/development/roadmap/phases/phase-11.5/IMPLEMENTATION-GUIDE.md new file mode 100644 index 00000000..e09af274 --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/IMPLEMENTATION-GUIDE.md @@ -0,0 +1,191 @@ +# Phase 11.5 実装ガイド - ChatGPT5向け + +## 🎯 実装の全体像 + +Phase 11.5は、Nyashの性能を産業レベルに引き上げる最終段階です。3つの主要な最適化を行います: + +1. **Write Barrier除去** - GCオーバーヘッドを90%削減 +2. **Atomic最適化** - sync処理を10倍高速化 +3. **Coroutine実装** - 真の非同期処理を実現 + +## 📋 実装順序と依存関係 + +```mermaid +graph TD + A[11.5a: Write Barrier除去] --> D[統合テスト] + B[11.5b: Atomic最適化] --> D + C[11.5c: Coroutine実装] --> D + D --> E[Phase 11.5完了] +``` + +各タスクは独立して実装可能ですが、統合テストで相互作用を検証します。 + +## 🔧 技術的な実装詳細 + +### 1. Write Barrier除去の実装手順 + +#### Step 1: MIR拡張 +```rust +// src/mir/escape_analysis.rs (新規作成) +use crate::mir::{MirFunction, MirInstruction, ValueId}; +use std::collections::{HashMap, HashSet}; + +pub struct EscapeAnalysis { + allocations: HashMap, + escapes: HashSet, +} + +impl EscapeAnalysis { + pub fn new() -> Self { + Self { + allocations: HashMap::new(), + escapes: HashSet::new(), + } + } + + pub fn analyze(&mut self, func: &MirFunction) -> EscapeInfo { + // 実装のポイント: + // 1. NewBox, RefNew命令を追跡 + // 2. Return, Call命令でescape判定 + // 3. ループ不変式も考慮 + } +} +``` + +#### Step 2: VM統合 +```rust +// src/backend/vm_instructions.rs の修正 +pub fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) + -> Result { + // 既存のコード... + + // Escape analysisの結果を確認 + if let Some(escape_info) = &self.escape_info { + if !escape_info.escapes(reference) { + // Barrierスキップ! + return Ok(ControlFlow::Continue); + } + } + + // 通常のbarrier処理 + gc_write_barrier_site(&self.runtime, "RefSet"); + Ok(ControlFlow::Continue) +} +``` + +### 2. Atomic最適化の実装手順 + +#### Step 1: BoxCore拡張 +```rust +// src/box_trait.rs の修正 +pub trait BoxCore: Send + Sync { + // 既存のメソッド... + + /// Read-onlyメソッドかどうか + fn is_readonly_method(&self, method: &str) -> bool { + // デフォルトはfalse(保守的) + false + } +} + +// 各Boxで実装 +impl BoxCore for StringBox { + fn is_readonly_method(&self, method: &str) -> bool { + matches!(method, "length" | "isEmpty" | "charAt") + } +} +``` + +#### Step 2: Atomic wrapper +```rust +// src/runtime/atomic_box.rs (新規作成) +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::Arc; +use parking_lot::RwLock; // より高速なRwLock + +pub struct AtomicBox { + inner: Arc>, + cache: AtomicPtr, +} +``` + +### 3. Coroutine実装の実装手順 + +#### Step 1: Parser拡張 +```rust +// src/parser/keywords.rs の修正 +pub const RESERVED_WORDS: &[&str] = &[ + // 既存のキーワード... + "async", + "await", +]; + +// src/parser/expressions.rs の修正 +fn parse_function_declaration(&mut self) -> Result { + let is_async = self.consume_keyword("async"); + // 既存のパース処理... +} +``` + +#### Step 2: MIR Coroutine変換 +```rust +// src/mir/coroutine_transform.rs (新規作成) +pub fn transform_async_function(func: &MirFunction) -> MirFunction { + // State machine変換のアルゴリズム: + // 1. await箇所でstateを分割 + // 2. ローカル変数をstate構造体に移動 + // 3. switch文で状態遷移を実装 +} +``` + +## 🎯 ChatGPT5への実装指示 + +### Phase 11.5a(最優先) +1. `src/mir/escape_analysis.rs`を作成 +2. 基本的なallocation追跡を実装 +3. VM統合でbarrier除去をテスト +4. ベンチマークで効果測定 + +### Phase 11.5b(次優先) +1. `BoxCore::is_readonly_method`を追加 +2. 主要Boxで実装(StringBox, IntegerBox) +3. RwLock移行を段階的に実施 + +### Phase 11.5c(最後) +1. Parser拡張(async/await) +2. 基本的なPromiseBox実装 +3. 簡単なasync関数の動作確認 + +## 📊 成功指標 + +各フェーズの完了基準: + +### 11.5a: Write Barrier除去 +- [ ] escape_analysis.rsが動作 +- [ ] 簡単なループでbarrier除去確認 +- [ ] ベンチマークで30%以上改善 + +### 11.5b: Atomic最適化 +- [ ] Read-onlyメソッドの識別 +- [ ] RwLock使用でread性能向上 +- [ ] マルチスレッドベンチマーク改善 + +### 11.5c: Coroutine実装 +- [ ] async/awaitがパース可能 +- [ ] 簡単なasync関数が実行可能 +- [ ] Promiseチェーンが動作 + +## 🚀 実装開始コマンド + +```bash +# ブランチ作成 +git checkout -b phase-11.5-jit-integration + +# テスト駆動開発 +cargo test escape_analysis + +# ベンチマーク実行 +./target/release/nyash --benchmark --iterations 1000 +``` + +頑張ってください、ChatGPT5!これが完成すれば、Nyashは本当に世界クラスの言語になります! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/README.md b/docs/development/roadmap/phases/phase-11.5/README.md new file mode 100644 index 00000000..dd9dd8bd --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/README.md @@ -0,0 +1,169 @@ +# Phase 11.5: JIT完全統合 - sync/GC/非同期の最終実装 + +## 🎯 概要 +Phase 11.5は、Nyashの全実行レイヤー(インタープリター/MIR/VM/JIT)でsync処理、GC処理、非同期処理を完全に統合する最終フェーズです。 + +## 📊 現状分析(2025-08-30) + +### ✅ 完了済み +1. **基本アーキテクチャ** + - Everything is Box哲学の完全実装 + - インタープリター → MIR → VM → JIT パイプライン + - プラグインシステム(C ABI/TLVハンドル) + +2. **sync処理** + - Arc/Arcによる完全スレッドセーフ設計 + - 全レイヤーでの一貫した同期化 + +3. **GC基礎** + - カウンティングGC実装(NYASH_GC_COUNTING=1) + - Read/Writeバリア実装 + - VMセーフポイント + +4. **非同期基礎** + - FutureBox/TimerBox実装 + - SingleThreadScheduler + - nowait/wait文 + +## 🚀 Phase 11.5 タスク一覧 + +### 1. JIT sync処理統合 +- [ ] **1.1 Atomic操作の最適化** + - ArcアクセスのJIT最適化 + - Lock elision(不要なロック除去) + - Read-only pathでのロック回避 + +- [ ] **1.2 Memory ordering最適化** + - Relaxed/Acquire/Release semanticsの活用 + - プラットフォーム別最適化(x86/ARM) + +### 2. JIT GC統合 +- [ ] **2.1 Write barrier除去** + - Escape analysisによる不要バリア検出 + - Stack allocation最適化 + - Generational hypothesis活用 + +- [ ] **2.2 Safepoint最適化** + - Loop safepoint挿入 + - Call site safepoint + - Polling overhead削減 + +- [ ] **2.3 GC情報の伝播** + - Stack map生成 + - Root set tracking + - Precise GC対応 + +### 3. JIT 非同期処理統合 +- [ ] **3.1 Coroutine変換** + - async/await → state machine変換 + - Stack switching最適化 + - Continuation passing + +- [ ] **3.2 スケジューラー統合** + - Work stealing queue + - CPU affinity最適化 + - Yield point最適化 + +### 4. 統合テスト・ベンチマーク +- [ ] **4.1 性能測定** + - sync処理のオーバーヘッド測定 + - GC pause time測定 + - 非同期処理のレイテンシ測定 + +- [ ] **4.2 正確性検証** + - Race condition検出 + - Memory leak検出 + - Deadlock検出 + +## 📋 実装優先順位 + +### Phase 11.5a: Write barrier除去(最重要) +```rust +// 現在: すべてのBox操作でbarrier +vm.execute_ref_set() -> gc.barrier(Write) + +// 目標: JITでescape analysisして除去 +if !escapes_to_heap(value) { + // barrierスキップ +} +``` + +### Phase 11.5b: Atomic最適化 +```rust +// 現在: Arcの重いロック +let value = box.lock().unwrap().clone(); + +// 目標: Read-onlyならatomic load +if is_read_only(box) { + atomic_load_relaxed(box) +} +``` + +### Phase 11.5c: Coroutine実装 +```nyash +// 将来構文 +async function fetchData() { + local result = await httpGet("...") + return result +} +``` + +## 🎯 成功基準 + +1. **性能向上** + - sync処理: 50%以上のロックオーバーヘッド削減 + - GC: 90%以上のwrite barrier除去 + - 非同期: ネイティブthread並みの性能 + +2. **互換性維持** + - 既存のNyashコードがそのまま動作 + - プラグインシステムとの完全互換 + +3. **デバッグ性** + - JIT最適化の可視化(NYASH_JIT_OPT_TRACE) + - GC統計の詳細化 + - 非同期処理のトレース + +## 📅 実装スケジュール(推定) + +- **Week 1-2**: Write barrier除去とescape analysis +- **Week 3**: Atomic操作最適化 +- **Week 4**: Coroutine基礎実装 +- **Week 5**: 統合テストとベンチマーク +- **Week 6**: ドキュメント化と最適化 + +## 🔧 技術的詳細 + +### Escape Analysis実装案 +```rust +// MIR解析でallocサイトを特定 +struct EscapeAnalysis { + allocations: HashMap, + escapes: HashSet, +} + +impl EscapeAnalysis { + fn analyze(&mut self, func: &MirFunction) { + // 1. allocation site収集 + // 2. data flow解析 + // 3. escape判定 + } +} +``` + +### JIT統合ポイント +```rust +// cranelift-jitでのbarrier除去 +if !self.escape_info.escapes(value) { + // emit_call(gc_write_barrier) をスキップ +} +``` + +## 🎉 期待される成果 + +Phase 11.5完了により、Nyashは: +- **産業レベルの性能**: GC pauseがマイクロ秒単位 +- **真の並行性**: lock-free data structures対応 +- **モダンな非同期**: async/await完全サポート + +これにより、**30日で作られたとは思えない**世界クラスの言語が完成します! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-11.5/WASM-ISSUES.md b/docs/development/roadmap/phases/phase-11.5/WASM-ISSUES.md new file mode 100644 index 00000000..e43ca068 --- /dev/null +++ b/docs/development/roadmap/phases/phase-11.5/WASM-ISSUES.md @@ -0,0 +1,162 @@ +# WASM実装の問題と改善計画 + +## 🚨 現状の問題 + +### 1. **2つのWASM実装が存在** +- **Rust→WASM**: `wasm-pack build`でNyashインタープリター全体をWASMに(動作する) +- **MIR→WASM**: `--compile-wasm`でNyashコードをWASMに変換(ほぼ動かない) + +### 2. **MIR→WASM実装の問題点** +```rust +// src/backend/wasm/codegen.rs より +pub fn generate_module(...) -> Result { + // 基本的な命令しか実装されていない + // - 算術演算 + // - 制御フロー + // - print文(ホスト関数呼び出し) + + // 未実装: + // - Box操作(NewBox, BoxCall, PluginInvoke) + // - 配列操作 + // - プラグインシステム + // - GC/メモリ管理 +} +``` + +### 3. **根本的な設計問題** +- **Box抽象の表現困難**: Everything is BoxをWASMの型システムで表現できない +- **動的ディスパッチ**: BoxCallやPluginInvokeの実装が困難 +- **GCの不在**: WASMにはGCがない(WasmGC提案はまだ実験的) +- **プラグインFFI**: C ABIをWASM環境で実現できない + +## 📊 現状の実装状況 + +### 実装済み(動作するもの) +```nyash +// 基本的な算術 +function add(a, b) { + return a + b +} + +// 単純な制御フロー +function factorial(n) { + if n <= 1 { return 1 } + return n * factorial(n - 1) +} + +// print文(ホスト関数経由) +print("Hello WASM") +``` + +### 未実装(動作しないもの) +```nyash +// Box操作 +local str = new StringBox("hello") // ❌ NewBox未実装 +str.toUpperCase() // ❌ BoxCall未実装 + +// 配列 +local arr = [1, 2, 3] // ❌ 配列リテラル未実装 +arr.push(4) // ❌ ArrayBox未実装 + +// プラグイン +local file = new FileBox() // ❌ PluginInvoke未実装 +``` + +## 🤔 なぜRust→WASMは動くのか + +```toml +# Cargo.toml +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2" +``` + +- **すべてのBox実装がそのままWASMに**: Arcも含めて +- **wasm-bindgenの魔法**: JavaScript↔Rust境界を自動生成 +- **制限事項**: 一部のBox(TimerBox、FileBox等)は除外 + +## 🚀 改善案 + +### Option 1: MIR→WASM実装の完成(困難) +```wat +;; BoxをWASMテーブルで管理 +(table $boxes 1000 externref) +(global $next_box_id (mut i32) (i32.const 0)) + +;; NewBox実装 +(func $new_string_box (param $str i32) (result i32) + ;; 新しいBox IDを割り当て + (local $box_id i32) + (local.set $box_id (global.get $next_box_id)) + + ;; JavaScriptでStringBoxを作成 + (table.set $boxes + (local.get $box_id) + (call $js_create_string_box (local.get $str))) + + ;; IDを返す + (local.get $box_id) +) +``` + +**問題点**: +- JavaScript側にBox実装が必要 +- 性能オーバーヘッドが大きい +- プラグインシステムとの統合困難 + +### Option 2: Rust→WASMの活用(現実的) +```rust +// NyashコードをRustに変換してからWASMに +nyash_code → rust_code → wasm + +// 例: +// Nyash: local s = new StringBox("hello") +// Rust: let s = Box::new(StringBox::new("hello".to_string())); +// WASM: (自動生成) +``` + +### Option 3: WASMランタイムの埋め込み(革新的) +```wat +;; 最小VMをWASMに埋め込む +(module + ;; MIRバイトコードを格納 + (data (i32.const 0) "\01\02\03...") + + ;; VMインタープリター + (func $vm_execute + ;; MIR命令をデコード・実行 + ) + + ;; エントリーポイント + (func (export "main") + (call $vm_execute) + ) +) +``` + +## 🎯 推奨アプローチ + +### Phase 1: 現状維持 +- **Rust→WASM**: ブラウザでNyashを動かす用途で活用 +- **MIR→WASM**: 実験的機能として残す + +### Phase 2: Nyash→Rust変換 +- NyashコードをRustに変換する仕組みを作る +- 生成されたRustコードをwasm-packでビルド + +### Phase 3: WasmGC待ち +- WasmGC仕様が安定したら本格実装 +- Box型システムをWasmGCで表現 + +## 📝 結論 + +現在のMIR→WASM実装は**実験的**なもので、実用レベルには達していません。一方、Rust→WASMは**すでに動作**しており、ブラウザでNyashを体験してもらうには十分です。 + +**当面は**: +1. Rust→WASMでプレイグラウンド提供 +2. ネイティブ実行(VM/JIT/AOT)に注力 +3. WasmGCの成熟を待つ + +これが現実的な戦略です! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-ARG-DECLARATION.md b/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-ARG-DECLARATION.md new file mode 100644 index 00000000..fad63432 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-ARG-DECLARATION.md @@ -0,0 +1,102 @@ +# プラグインBoxの箱引数宣言方法 + +## 📊 nyash_box.tomlでの宣言 + +### 1. 基本的な箱引数の宣言 + +```toml +[HttpRequestBox.methods.respond] +id = 3 +args = [ { name = "resp", type = "box" } ] # type = "box" で箱引数を宣言 +returns = { type = "void" } +``` + +### 2. 引数の型一覧 + +| 型指定 | 説明 | TLVタグ | +|--------|------|---------| +| `"i64"` | 64ビット整数 | 3 | +| `"f64"` | 64ビット浮動小数点 | 5 | +| `"string"` | UTF-8文字列 | 6 | +| `"bool"` | 真偽値 | 1 | +| `"box"` | **箱(ハンドル)** | 8 | + +### 3. 実際の使用例 + +#### NetプラグインのHttpRequestBox +```toml +[HttpRequestBox] +type_id = 21 + +[HttpRequestBox.methods.respond] +id = 3 +args = [ { name = "resp", type = "box" } ] # HttpResponseBoxを受け取る +returns = { type = "void" } +``` + +使用方法(Nyash): +```nyash +local request = server.accept() // HttpRequestBox +local response = new HttpResponseBox() // 別のプラグインBox +response.setStatus(200) +request.respond(response) // 箱を引数として渡す! +``` + +#### 戻り値が箱の例 +```toml +[HttpServerBox.methods.accept] +id = 3 +args = [] +returns = { type = "box" } # HttpRequestBoxを返す +``` + +## 🔧 C実装側での処理 + +### TLVデコード +```c +// HttpRequestBox.respondの実装例 +case 3: { // respond + // 引数をデコード + if (args_len < 12) return -1; // header(4) + handle(8) + + // TLVタグチェック + uint8_t tag = args[4]; + if (tag != 8) return -1; // TAG_HANDLE = 8 + + // ハンドルデータ取得 + uint32_t resp_type_id = *(uint32_t*)&args[8]; + uint32_t resp_instance_id = *(uint32_t*)&args[12]; + + // HttpResponseBox(type_id=22)であることを確認 + if (resp_type_id != 22) return -1; + + // レスポンス処理... +} +``` + +## 💡 重要なポイント + +### 1. 型安全性 +- `type = "box"`は任意の箱を受け取れる +- 実装側で`type_id`チェックにより型安全性を確保 + +### 2. 相互運用性 +- 異なるプラグイン間でも箱の受け渡しが可能 +- ハンドル(type_id + instance_id)により参照 + +### 3. 宣言の簡潔さ +```toml +# シンプルな宣言 +args = [ { name = "box_arg", type = "box" } ] + +# 複数の箱引数も可能 +args = [ + { name = "box1", type = "box" }, + { name = "box2", type = "box" }, + { name = "count", type = "i64" } +] +``` + +## 🎯 結論 + +プラグインBoxは`nyash_box.toml`で`type = "box"`と宣言するだけで、他の箱を引数に取ることができます。C ABIレベルではTLVハンドル(タグ8)として処理され、完全な相互運用性が実現されています。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-HANDLE-SUPPORT.md b/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-HANDLE-SUPPORT.md new file mode 100644 index 00000000..2d1fd74b --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/PLUGIN-BOX-HANDLE-SUPPORT.md @@ -0,0 +1,115 @@ +# プラグインBoxは既に箱を引数に取れる! + +## 🎯 重要な発見 + +**プラグインBoxは既にC ABIレベルで箱を引数に取ることができます!** + +## 📊 実装の詳細 + +### 1. TLVプロトコルでのハンドルサポート + +```rust +// TLVタグ定義 +const TAG_HANDLE: u8 = 8; // プラグインハンドル用 + +// ハンドルエンコード関数 +pub fn plugin_handle(buf: &mut Vec, type_id: u32, instance_id: u32) { + buf.push(TAG_HANDLE); + buf.push(0u8); // reserved + buf.extend_from_slice(&(8u16).to_le_bytes()); // size = 8 + buf.extend_from_slice(&type_id.to_le_bytes()); // 4 bytes + buf.extend_from_slice(&instance_id.to_le_bytes()); // 4 bytes +} +``` + +### 2. プラグイン呼び出し時の処理 + +```rust +// Nyashコード +box1.process(box2, box3) + +// ↓ VM/プラグインローダーでの処理 +for arg in args { + if let Some(p) = arg.as_any().downcast_ref::() { + // 箱引数はハンドルとしてエンコード + encode::plugin_handle(&mut tlv, p.type_id, p.instance_id); + } + // ... 他の型の処理 +} + +// ↓ C ABIプラグイン側 +int32_t nyash_plugin_invoke( + uint32_t type_id, + uint32_t method_id, + uint32_t instance_id, + const uint8_t* args, // TLVエンコードされた引数 + size_t args_len, + uint8_t* result, + size_t* result_len +) { + // TLVデコード + uint8_t tag; + uint32_t arg_type_id, arg_instance_id; + + if (decode_handle(args, &tag, &arg_type_id, &arg_instance_id)) { + // ハンドル引数を処理 + // arg_type_id と arg_instance_id で箱を特定 + } +} +``` + +## 🔄 実際の使用例 + +### Nyashレベル +```nyash +// FileBoxがStringBoxを引数に取る例 +local file = new FileBox() +local path = new StringBox("/tmp/test.txt") +file.open(path) // StringBox(プラグインBox)を引数に! + +// ArrayBoxがMapBoxを引数に取る例 +local array = new ArrayBox() +local map = new MapBox() +array.push(map) // MapBox(プラグインBox)を引数に! +``` + +### プラグイン間の相互運用 +```nyash +// NetBoxがJSONBoxを引数に取る例 +local net = new NetBox() +local json = new JSONBox() +json.set("url", "https://api.example.com") +net.post(json) // JSONBoxを引数として渡す +``` + +## 💡 重要なポイント + +### 1. ハンドルによる間接参照 +- 箱の実体は渡さない(メモリ安全性) +- `(type_id, instance_id)`のペアで識別 +- プラグイン側でハンドルから実体にアクセス + +### 2. 型安全性 +- `type_id`で型を識別可能 +- 不正な型の場合はエラー返却 + +### 3. 所有権管理 +- インスタンスIDで参照管理 +- プラグイン間でも安全に共有 + +## 🎯 結論 + +**C ABIの制約があっても、ハンドル機構により箱は箱を引数に取れる!** + +これは既に実装済みの機能であり、プラグイン間での高度な連携が可能です。 + +### 埋め込みVMへの示唆 + +既存のTLVハンドル機構をそのまま使えば、埋め込みVMでも同じように箱引数をサポートできます: + +1. Nyashスクリプト内で箱を引数に使用 +2. MIRバイトコードにBoxCall命令を含める +3. 埋め込みVMがTLVエンコードでC ABIプラグインを呼び出し +4. ハンドル経由で箱を渡す + +**Everything is Box、そしてC ABIでも箱は箱を扱える!** \ No newline at end of file diff --git a/examples/cycle_a.nyash b/examples/cycle_a.nyash new file mode 100644 index 00000000..e026da57 --- /dev/null +++ b/examples/cycle_a.nyash @@ -0,0 +1,9 @@ +// Cycle test A -> B -> A +include "examples/cycle_b.nyash" + +static box A { + main() { + return 0 + } +} + diff --git a/examples/cycle_b.nyash b/examples/cycle_b.nyash new file mode 100644 index 00000000..f0a62e82 --- /dev/null +++ b/examples/cycle_b.nyash @@ -0,0 +1,9 @@ +// Cycle test B -> A +include "examples/cycle_a.nyash" + +static box B { + main() { + return 0 + } +} + diff --git a/examples/encoding_min.nyash b/examples/encoding_min.nyash new file mode 100644 index 00000000..714acdc8 --- /dev/null +++ b/examples/encoding_min.nyash @@ -0,0 +1,8 @@ +static box Main { + main() { + local e = new EncodingBox() + local b64 = e.base64Encode("hello") + // quick check: "hello" -> aGVsbG8= + return b64.length() == 8 + } +} diff --git a/examples/path_min.nyash b/examples/path_min.nyash new file mode 100644 index 00000000..996a6556 --- /dev/null +++ b/examples/path_min.nyash @@ -0,0 +1,8 @@ +static box Main { + main() { + local p = new PathBox() + local j = p.join("/usr", "bin") + local b = p.basename(j) + return b + } +} diff --git a/examples/plugin_box_as_arg_demo.nyash b/examples/plugin_box_as_arg_demo.nyash new file mode 100644 index 00000000..919fd1dc --- /dev/null +++ b/examples/plugin_box_as_arg_demo.nyash @@ -0,0 +1,35 @@ +// プラグインBoxが箱を引数に取る例のデモ + +// HttpServer/Request/Responseの例 +local server = new HttpServerBox() +server.start(8080) + +// acceptはHttpRequestBoxを返す +local request = server.accept() // returns box + +// HttpResponseBoxを作成 +local response = new HttpResponseBox() +response.setStatus(200) +response.setHeader("Content-Type", "text/plain") +response.write("Hello from Nyash!") + +// ★ HttpRequestBox.respond()はHttpResponseBox(別の箱)を引数に取る! +request.respond(response) // Box引数の実例! + +// 他の例:ArrayBoxにプラグインBoxを格納 +local array = new ArrayBox() +local file1 = new FileBox() +local file2 = new FileBox() + +// ArrayBoxは任意のBoxを引数に取れる +array.push(file1) // FileBox(プラグインBox)を引数に +array.push(file2) // 別のFileBoxインスタンスも + +// MapBoxでも同様 +local map = new MapBox() +local net = new HttpClientBox() + +// MapBoxも任意のBoxを値として設定できる +map.set("client", net) // HttpClientBox(プラグインBox)を引数に + +print("Plugin boxes can take other boxes as arguments!") \ No newline at end of file diff --git a/examples/regex_min.nyash b/examples/regex_min.nyash new file mode 100644 index 00000000..7a37a348 --- /dev/null +++ b/examples/regex_min.nyash @@ -0,0 +1,8 @@ +static box Main { + main() { + local r = new RegexBox() + r.compile("a+") + return r.isMatch("caaab") + } +} + diff --git a/examples/toml_min.nyash b/examples/toml_min.nyash new file mode 100644 index 00000000..707fd38b --- /dev/null +++ b/examples/toml_min.nyash @@ -0,0 +1,8 @@ +static box Main { + main() { + local t = new TOMLBox() + t.parse("[a]\nvalue=42\n") + local j = t.toJson() + return j + } +} diff --git a/nyash.toml b/nyash.toml index 9213694d..882c1bc7 100644 --- a/nyash.toml +++ b/nyash.toml @@ -147,6 +147,10 @@ SocketConnBox = 31 SocketClientBox = 32 MathBox = 50 TimeBox = 51 +RegexBox = 52 +EncodingBox = 53 +TOMLBox = 54 +PathBox = 55 PyRuntimeBox= 40 PyObjectBox = 41 PythonParserBox = 60 @@ -166,6 +170,10 @@ PythonCompilerBox = 61 "libnyash_math_plugin" = "./plugins/nyash-math-plugin" "libnyash_python_parser_plugin" = "./plugins/nyash-python-parser-plugin" "libnyash_python_compiler_plugin" = "./plugins/nyash-python-compiler-plugin" +"libnyash_regex_plugin" = "./plugins/nyash-regex-plugin" +"libnyash_encoding_plugin" = "./plugins/nyash-encoding-plugin" +"libnyash_toml_plugin" = "./plugins/nyash-toml-plugin" +"libnyash_path_plugin" = "./plugins/nyash-path-plugin" [libraries."libnyash_array_plugin"] boxes = ["ArrayBox"] path = "./plugins/nyash-array-plugin/target/release/libnyash_array_plugin" @@ -289,3 +297,65 @@ type_id = 51 birth = { method_id = 0 } now = { method_id = 1 } fini = { method_id = 4294967295 } +[libraries."libnyash_regex_plugin"] +boxes = ["RegexBox"] +path = "./plugins/nyash-regex-plugin/target/release/libnyash_regex_plugin" + +[libraries."libnyash_regex_plugin".RegexBox] +type_id = 52 + +[libraries."libnyash_regex_plugin".RegexBox.methods] +birth = { method_id = 0, args = ["pattern?"] } +compile = { method_id = 1, args = ["pattern"] } +isMatch = { method_id = 2, args = ["text"], returns_result = true } +find = { method_id = 3, args = ["text"], returns_result = true } +replaceAll = { method_id = 4, args = ["text", "repl"], returns_result = true } +split = { method_id = 5, args = ["text", "limit"], returns_result = true } +fini = { method_id = 4294967295 } + +[libraries."libnyash_encoding_plugin"] +boxes = ["EncodingBox"] +path = "./plugins/nyash-encoding-plugin/target/release/libnyash_encoding_plugin" + +[libraries."libnyash_encoding_plugin".EncodingBox] +type_id = 53 + +[libraries."libnyash_encoding_plugin".EncodingBox.methods] +birth = { method_id = 0 } +toUtf8Bytes = { method_id = 1, args = ["s"], returns_result = true } +fromUtf8Bytes = { method_id = 2, args = ["bytes"], returns_result = true } +base64Encode = { method_id = 3, args = ["data"], returns_result = true } +base64Decode = { method_id = 4, args = ["text"], returns_result = true } +hexEncode = { method_id = 5, args = ["data"], returns_result = true } +hexDecode = { method_id = 6, args = ["text"], returns_result = true } +fini = { method_id = 4294967295 } +[libraries."libnyash_toml_plugin"] +boxes = ["TOMLBox"] +path = "./plugins/nyash-toml-plugin/target/release/libnyash_toml_plugin" + +[libraries."libnyash_toml_plugin".TOMLBox] +type_id = 54 + +[libraries."libnyash_toml_plugin".TOMLBox.methods] +birth = { method_id = 0 } +parse = { method_id = 1, args = ["text"], returns_result = true } +get = { method_id = 2, args = ["path"], returns_result = true } +toJson = { method_id = 3, returns_result = true } +fini = { method_id = 4294967295 } + +[libraries."libnyash_path_plugin"] +boxes = ["PathBox"] +path = "./plugins/nyash-path-plugin/target/release/libnyash_path_plugin" + +[libraries."libnyash_path_plugin".PathBox] +type_id = 55 + +[libraries."libnyash_path_plugin".PathBox.methods] +birth = { method_id = 0 } +join = { method_id = 1, args = ["base", "rest"], returns_result = true } +dirname = { method_id = 2, args = ["path"], returns_result = true } +basename = { method_id = 3, args = ["path"], returns_result = true } +extname = { method_id = 4, args = ["path"], returns_result = true } +isAbs = { method_id = 5, args = ["path"], returns_result = true } +normalize = { method_id = 6, args = ["path"], returns_result = true } +fini = { method_id = 4294967295 } diff --git a/plugins/nyash-encoding-plugin/Cargo.toml b/plugins/nyash-encoding-plugin/Cargo.toml new file mode 100644 index 00000000..ed9cb3d5 --- /dev/null +++ b/plugins/nyash-encoding-plugin/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nyash-encoding-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +base64 = "0.22" +hex = "0.4" + diff --git a/plugins/nyash-encoding-plugin/src/lib.rs b/plugins/nyash-encoding-plugin/src/lib.rs new file mode 100644 index 00000000..032f1376 --- /dev/null +++ b/plugins/nyash-encoding-plugin/src/lib.rs @@ -0,0 +1,118 @@ +//! Nyash EncodingBox Plugin - UTF-8/Base64/Hex helpers + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; + +const OK: i32 = 0; +const E_SHORT: i32 = -1; +const E_TYPE: i32 = -2; +const E_METHOD: i32 = -3; +const E_ARGS: i32 = -4; +const E_PLUGIN: i32 = -5; +const E_HANDLE: i32 = -8; + +const M_BIRTH: u32 = 0; // constructor (stateless) +const M_TO_UTF8_BYTES: u32 = 1; // toUtf8Bytes(s) -> bytes +const M_FROM_UTF8_BYTES: u32 = 2; // fromUtf8Bytes(bytes) -> string +const M_BASE64_ENC: u32 = 3; // base64Encode(s|bytes) -> string +const M_BASE64_DEC: u32 = 4; // base64Decode(str) -> bytes +const M_HEX_ENC: u32 = 5; // hexEncode(s|bytes) -> string +const M_HEX_DEC: u32 = 6; // hexDecode(str) -> bytes +const M_FINI: u32 = u32::MAX; + +// Assign an unused type id +const TYPE_ID_ENCODING: u32 = 53; + +struct EncInstance; // stateless + +static INST: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { OK } + +#[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 { + if type_id != TYPE_ID_ENCODING { return E_TYPE; } + 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, EncInstance); } 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_TO_UTF8_BYTES => { + let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + write_tlv_bytes(s.as_bytes(), result, result_len) + } + M_FROM_UTF8_BYTES => { + let bytes = match read_arg_bytes(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + match String::from_utf8(bytes) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("", result, result_len) } + } + M_BASE64_ENC => { + if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = base64::encode(b); return write_tlv_string(&s, result, result_len); } + let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + let enc = base64::encode(s.as_bytes()); + write_tlv_string(&enc, result, result_len) + } + M_BASE64_DEC => { + let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + match base64::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) } + } + M_HEX_ENC => { + if let Some(b) = read_arg_bytes(args, args_len, 0) { let s = hex::encode(b); return write_tlv_string(&s, result, result_len); } + let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + let enc = hex::encode(s.as_bytes()); + write_tlv_string(&enc, result, result_len) + } + M_HEX_DEC => { + let s = match read_arg_string(args, args_len, 0) { Some(v) => v, None => return E_ARGS }; + match hex::decode(s.as_bytes()) { Ok(b) => write_tlv_bytes(&b, result, result_len), Err(_) => write_tlv_bytes(&[], result, result_len) } + } + _ => E_METHOD, + } + } +} + +fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } + false +} +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return E_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); + for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); } + unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; } + OK +} +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) } +fn write_tlv_bytes(b: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(7u8, b)], result, result_len) } + +fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; } + None +} +fn read_arg_bytes(args: *const u8, args_len: usize, n: usize) -> Option> { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 7 || tag == 6 { return Some(buf[off+4..off+4+size].to_vec()); } else { return None; } } off += 4 + size; } + None +} diff --git a/plugins/nyash-path-plugin/Cargo.toml b/plugins/nyash-path-plugin/Cargo.toml new file mode 100644 index 00000000..b81daace --- /dev/null +++ b/plugins/nyash-path-plugin/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nyash-path-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +path-clean = "1.0" + diff --git a/plugins/nyash-path-plugin/src/lib.rs b/plugins/nyash-path-plugin/src/lib.rs new file mode 100644 index 00000000..4c1a7e24 --- /dev/null +++ b/plugins/nyash-path-plugin/src/lib.rs @@ -0,0 +1,114 @@ +//! Nyash PathBox Plugin - minimal path ops (join, dirname, basename, extname, isAbs, normalize) + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::path::{Path, Component}; +use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; + +const OK: i32 = 0; +const E_SHORT: i32 = -1; +const E_TYPE: i32 = -2; +const E_METHOD: i32 = -3; +const E_ARGS: i32 = -4; +const E_PLUGIN: i32 = -5; + +const M_BIRTH: u32 = 0; // constructor -> instance +const M_JOIN: u32 = 1; // join(base, rest) -> string +const M_DIRNAME: u32 = 2; // dirname(path) -> string +const M_BASENAME: u32 = 3; // basename(path) -> string +const M_EXTNAME: u32 = 4; // extname(path) -> string +const M_IS_ABS: u32 = 5; // isAbs(path) -> bool +const M_NORMALIZE: u32 = 6; // normalize(path) -> string +const M_FINI: u32 = u32::MAX; // fini + +const TYPE_ID_PATH: u32 = 55; + +struct PathInstance; // stateless + +static INST: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { OK } + +#[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 { + if type_id != TYPE_ID_PATH { return E_TYPE; } + 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, + } + } +} + +fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } + false +} +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return E_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); + for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); } + unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; } + OK +} +fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) } +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) } + +fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; } + None +} diff --git a/plugins/nyash-regex-plugin/Cargo.toml b/plugins/nyash-regex-plugin/Cargo.toml new file mode 100644 index 00000000..fd345efc --- /dev/null +++ b/plugins/nyash-regex-plugin/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nyash-regex-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +regex = "1.10" + diff --git a/plugins/nyash-regex-plugin/src/lib.rs b/plugins/nyash-regex-plugin/src/lib.rs new file mode 100644 index 00000000..e2aacd82 --- /dev/null +++ b/plugins/nyash-regex-plugin/src/lib.rs @@ -0,0 +1,120 @@ +//! Nyash RegexBox Plugin - Minimal regex support (compile + match/find/replace/split) + +use once_cell::sync::Lazy; +use regex::Regex; +use std::collections::HashMap; +use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; + +// Error/status codes aligned with other plugins +const OK: i32 = 0; +const E_SHORT: i32 = -1; +const E_TYPE: i32 = -2; +const E_METHOD: i32 = -3; +const E_ARGS: i32 = -4; +const E_PLUGIN: i32 = -5; +const E_HANDLE: i32 = -8; + +// Methods +const M_BIRTH: u32 = 0; // birth(pattern?) -> instance +const M_COMPILE: u32 = 1; // compile(pattern) -> self (new compiled) +const M_IS_MATCH: u32 = 2; // isMatch(text) -> bool +const M_FIND: u32 = 3; // find(text) -> String (first match or empty) +const M_REPLACE_ALL: u32 = 4; // replaceAll(text, repl) -> String +const M_SPLIT: u32 = 5; // split(text, limit) -> String (joined by '\n') minimal +const M_FINI: u32 = u32::MAX; // fini() + +// Assign an unused type id (see nyash.toml [box_types]) +const TYPE_ID_REGEX: u32 = 52; + +struct RegexInstance { re: Option } + +static INST: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { OK } + +#[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 { + if type_id != TYPE_ID_REGEX { return E_TYPE; } + 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); + // Optional pattern in arg0 + 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 mut parts: Vec = 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, + } + } +} + +fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } + false +} +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return E_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); + for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); } + unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; } + OK +} +fn write_tlv_i64(v: i64, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(3u8, &v.to_le_bytes())], result, result_len) } +fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) } +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) } + +fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag != 3 || size != 8 { return None; } let mut b=[0u8;8]; b.copy_from_slice(&buf[off+4..off+12]); return Some(i64::from_le_bytes(b)); } off += 4 + size; } + None +} +fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { let s = String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string(); return Some(s); } else { return None; } } off += 4 + size; } + None +} + diff --git a/plugins/nyash-toml-plugin/Cargo.toml b/plugins/nyash-toml-plugin/Cargo.toml new file mode 100644 index 00000000..847aaede --- /dev/null +++ b/plugins/nyash-toml-plugin/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nyash-toml-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +toml = "0.8" +serde_json = "1.0" + diff --git a/plugins/nyash-toml-plugin/src/lib.rs b/plugins/nyash-toml-plugin/src/lib.rs new file mode 100644 index 00000000..87edd7f5 --- /dev/null +++ b/plugins/nyash-toml-plugin/src/lib.rs @@ -0,0 +1,127 @@ +//! Nyash TOMLBox Plugin - minimal parse + query + toJson + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; + +const OK: i32 = 0; +const E_SHORT: i32 = -1; +const E_TYPE: i32 = -2; +const E_METHOD: i32 = -3; +const E_ARGS: i32 = -4; +const E_PLUGIN: i32 = -5; +const E_HANDLE: i32 = -8; + +const M_BIRTH: u32 = 0; // constructor -> instance +const M_PARSE: u32 = 1; // parse(text) -> bool +const M_GET: u32 = 2; // get(path.dot.segments) -> string (toml-display) or empty +const M_TO_JSON: u32 = 3; // toJson() -> string (JSON) +const M_FINI: u32 = u32::MAX; // fini() + +const TYPE_ID_TOML: u32 = 54; + +struct TomlInstance { value: Option } + +static INST: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { OK } + +#[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 { + if type_id != TYPE_ID_TOML { return E_TYPE; } + 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, TomlInstance { value: None }); } 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_PARSE => { + let text = 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.value = toml::from_str::(&text).ok(); return write_tlv_bool(inst.value.is_some(), result, result_len); } else { return E_HANDLE; } } else { return E_PLUGIN; } + } + M_GET => { + let path = 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) { + let mut cur = match &inst.value { Some(v) => v, None => { return write_tlv_string("", result, result_len); } }; + if !path.is_empty() { + for seg in path.split('.') { + match cur.get(seg) { Some(v) => cur = v, None => { return write_tlv_string("", result, result_len); } } + } + } + let out = cur.to_string(); + return write_tlv_string(&out, result, result_len); + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + M_TO_JSON => { + if let Ok(m) = INST.lock() { + if let Some(inst) = m.get(&instance_id) { + if let Some(v) = &inst.value { + // Convert via serde_json::Value + let sv = toml_to_json(v); + return match serde_json::to_string(&sv) { Ok(s) => write_tlv_string(&s, result, result_len), Err(_) => write_tlv_string("{}", result, result_len) }; + } else { return write_tlv_string("{}", result, result_len); } + } else { return E_HANDLE; } + } else { return E_PLUGIN; } + } + _ => E_METHOD, + } + } +} + +fn toml_to_json(v: &toml::Value) -> serde_json::Value { + match v { + toml::Value::String(s) => serde_json::Value::String(s.clone()), + toml::Value::Integer(i) => serde_json::Value::from(*i), + toml::Value::Float(f) => serde_json::Value::from(*f), + toml::Value::Boolean(b) => serde_json::Value::from(*b), + toml::Value::Datetime(dt) => serde_json::Value::String(dt.to_string()), + toml::Value::Array(arr) => serde_json::Value::Array(arr.iter().map(toml_to_json).collect()), + toml::Value::Table(map) => { + let mut m = serde_json::Map::new(); + for (k, vv) in map.iter() { m.insert(k.clone(), toml_to_json(vv)); } + serde_json::Value::Object(m) + } + } +} + +fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { if result_len.is_null() { return false; } if result.is_null() || *result_len < needed { *result_len = needed; return true; } } + false +} +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return E_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); + for (tag, payload) in payloads { buf.push(*tag); buf.push(0); buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); buf.extend_from_slice(payload); } + unsafe { let needed = buf.len(); if result.is_null() || *result_len < needed { *result_len = needed; return E_SHORT; } std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); *result_len = needed; } + OK +} +fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(1u8, &[if v {1u8} else {0u8}])], result, result_len) } +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { write_tlv_result(&[(6u8, s.as_bytes())], result, result_len) } + +fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; for i in 0..=n { if buf.len() < off + 4 { return None; } let tag = buf[off]; let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; if buf.len() < off + 4 + size { return None; } if i == n { if tag == 6 || tag == 7 { return Some(String::from_utf8_lossy(&buf[off+4..off+4+size]).to_string()); } else { return None; } } off += 4 + size; } + None +} + diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs index 6ff99591..fbfbc40e 100644 --- a/src/backend/vm_instructions.rs +++ b/src/backend/vm_instructions.rs @@ -1215,6 +1215,42 @@ impl VM { return Ok(ControlFlow::Continue); } } + // Fallback: support common methods on internal StringBox without requiring PluginBox receiver + if let VMValue::BoxRef(ref bx) = recv { + if let Some(sb) = bx.as_any().downcast_ref::() { + match method { + "length" => { + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(sb.value.len() as i64)); } + return Ok(ControlFlow::Continue); + } + "is_empty" | "isEmpty" => { + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Bool(sb.value.is_empty())); } + return Ok(ControlFlow::Continue); + } + "charCodeAt" => { + let idx_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::Integer(0) }; + let idx = match idx_v { VMValue::Integer(i) => i.max(0) as usize, _ => 0 }; + let code = sb.value.chars().nth(idx).map(|c| c as u32 as i64).unwrap_or(0); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(code)); } + return Ok(ControlFlow::Continue); + } + "concat" => { + let rhs_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::String(String::new()) }; + let rhs_s = match rhs_v { + VMValue::String(s) => s, + VMValue::BoxRef(br) => br.to_string_box().value, + _ => rhs_v.to_string(), + }; + let mut new_s = sb.value.clone(); + new_s.push_str(&rhs_s); + let out = Box::new(crate::box_trait::StringBox::new(new_s)); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::BoxRef(std::sync::Arc::from(out as Box))); } + return Ok(ControlFlow::Continue); + } + _ => {} + } + } + } Err(VMError::InvalidInstruction(format!("PluginInvoke requires PluginBox receiver; method={} got {:?}", method, recv))) } } diff --git a/src/backend/vm_values.rs b/src/backend/vm_values.rs index 02ef484a..d3657959 100644 --- a/src/backend/vm_values.rs +++ b/src/backend/vm_values.rs @@ -213,6 +213,33 @@ impl VM { li.type_name(), ri.type_name(), li.to_string_box().value, ri.to_string_box().value ); } + // String-like comparison: internal StringBox or Plugin StringBox + fn boxref_to_string(b: &dyn crate::box_trait::NyashBox) -> Option { + if let Some(sb) = b.as_any().downcast_ref::() { + return Some(sb.value.clone()); + } + if let Some(pb) = b.as_any().downcast_ref::() { + if pb.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let s_opt: Option = { + if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", pb.inner.instance_id, &[]) { + if let Some(vb) = val_opt { + if let Some(sbb) = vb.as_any().downcast_ref::() { + Some(sbb.value.clone()) + } else { None } + } else { None } + } else { None } + } else { None } + }; + if s_opt.is_some() { return s_opt; } + } + } + None + } + if let (Some(ls), Some(rs)) = (boxref_to_string(li.as_ref()), boxref_to_string(ri.as_ref())) { + return Ok(match op { CompareOp::Eq => ls == rs, CompareOp::Ne => ls != rs, CompareOp::Lt => ls < rs, CompareOp::Le => ls <= rs, CompareOp::Gt => ls > rs, CompareOp::Ge => ls >= rs }); + } // Try integer comparisons via downcast or parse fallback let l_opt = li.as_any().downcast_ref::().map(|x| x.value) .or_else(|| li.as_any().downcast_ref::().map(|x| x.value)) @@ -225,6 +252,43 @@ impl VM { } Err(VMError::TypeError(format!("[BoxRef-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right))) } + // Mixed String vs BoxRef (string-like) + (VMValue::String(ls), VMValue::BoxRef(ri)) => { + let rs_opt = if let Some(sb) = ri.as_any().downcast_ref::() { Some(sb.value.clone()) } else { + if let Some(pb) = ri.as_any().downcast_ref::() { + if pb.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let tmp = if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", pb.inner.instance_id, &[]) { + if let Some(vb) = val_opt { + if let Some(sbb) = vb.as_any().downcast_ref::() { Some(sbb.value.clone()) } else { None } + } else { None } + } else { None } + } else { None }; + tmp + } else { None } + } else { None } + }; + if let Some(rs) = rs_opt { return Ok(match op { CompareOp::Eq => *ls == rs, CompareOp::Ne => *ls != rs, CompareOp::Lt => *ls < rs, CompareOp::Le => *ls <= rs, CompareOp::Gt => *ls > rs, CompareOp::Ge => *ls >= rs }); } + Err(VMError::TypeError(format!("[String-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right))) + } + (VMValue::BoxRef(li), VMValue::String(rs)) => { + let ls_opt = if let Some(sb) = li.as_any().downcast_ref::() { Some(sb.value.clone()) } else { + if let Some(pb) = li.as_any().downcast_ref::() { + if pb.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let tmp = if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", pb.inner.instance_id, &[]) { + if let Some(vb) = val_opt { if let Some(sbb) = vb.as_any().downcast_ref::() { Some(sbb.value.clone()) } else { None } } else { None } + } else { None } + } else { None }; + tmp + } else { None } + } else { None } + }; + if let Some(ls) = ls_opt { return Ok(match op { CompareOp::Eq => ls == *rs, CompareOp::Ne => ls != *rs, CompareOp::Lt => ls < *rs, CompareOp::Le => ls <= *rs, CompareOp::Gt => ls > *rs, CompareOp::Ge => ls >= *rs }); } + Err(VMError::TypeError(format!("[BoxRef-String] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right))) + } // Mixed Integer (BoxRef vs Integer) (VMValue::BoxRef(li), VMValue::Integer(r)) => { let l_opt = li.as_any().downcast_ref::().map(|x| x.value) diff --git a/src/interpreter/io.rs b/src/interpreter/io.rs index f339076c..96b19c3e 100644 --- a/src/interpreter/io.rs +++ b/src/interpreter/io.rs @@ -57,9 +57,24 @@ impl NyashInterpreter { } else if std::path::Path::new(&canonical_path).extension().is_none() { canonical_path.push_str(".nyash"); } - + // 循環検出(ロード中スタック) + { + let mut stack = self.shared.include_stack.lock().unwrap(); + if let Some(pos) = stack.iter().position(|p| p == &canonical_path) { + // 検出: A -> ... -> B -> A + let mut chain: Vec = stack[pos..].to_vec(); + chain.push(canonical_path.clone()); + let msg = format!("include cycle detected: {}", + chain.join(" -> ")); + return Err(RuntimeError::InvalidOperation { message: msg }); + } + stack.push(canonical_path.clone()); + } + // 重複読み込みチェック if self.shared.included_files.lock().unwrap().contains(&canonical_path) { + // スタックから外して早期終了 + self.shared.include_stack.lock().unwrap().pop(); return Ok(()); // 既に読み込み済み } @@ -76,10 +91,14 @@ impl NyashInterpreter { })?; // 重複防止リストに追加 - self.shared.included_files.lock().unwrap().insert(canonical_path); + self.shared.included_files.lock().unwrap().insert(canonical_path.clone()); // 現在の環境で実行 - self.execute(ast)?; + let exec_res = self.execute(ast); + // スタックを外す + self.shared.include_stack.lock().unwrap().pop(); + // 実行結果を伝播 + exec_res?; Ok(()) } @@ -96,6 +115,18 @@ impl NyashInterpreter { canonical_path.push_str(".nyash"); } + // 循環検出(ロード中スタック) + { + let mut stack = self.shared.include_stack.lock().unwrap(); + if let Some(pos) = stack.iter().position(|p| p == &canonical_path) { + let mut chain: Vec = stack[pos..].to_vec(); + chain.push(canonical_path.clone()); + let msg = format!("include cycle detected: {}", chain.join(" -> ")); + return Err(RuntimeError::InvalidOperation { message: msg }); + } + stack.push(canonical_path.clone()); + } + // ファイル読み込み(static box名検出用) let content = std::fs::read_to_string(&canonical_path) .map_err(|e| RuntimeError::InvalidOperation { @@ -131,8 +162,14 @@ impl NyashInterpreter { set.contains(&canonical_path) }; if !already { - self.shared.included_files.lock().unwrap().insert(canonical_path); - self.execute(ast)?; + self.shared.included_files.lock().unwrap().insert(canonical_path.clone()); + let exec_res = self.execute(ast); + // スタックを外す + self.shared.include_stack.lock().unwrap().pop(); + exec_res?; + } else { + // スタックを外す(既に読み込み済みのため) + self.shared.include_stack.lock().unwrap().pop(); } // static boxを初期化・取得して返す diff --git a/src/interpreter/state.rs b/src/interpreter/state.rs index a4cb0739..2243e31a 100644 --- a/src/interpreter/state.rs +++ b/src/interpreter/state.rs @@ -20,6 +20,9 @@ pub struct SharedState { /// 読み込み済みファイル(重複防止) pub included_files: Arc>>, + + /// includeロード中スタック(循環検出用: A -> B -> A を検出) + pub include_stack: Arc>>, } impl SharedState { @@ -37,6 +40,7 @@ impl SharedState { static_functions: Arc::new(RwLock::new(HashMap::new())), static_box_definitions: Arc::new(RwLock::new(HashMap::new())), included_files: Arc::new(Mutex::new(HashSet::new())), + include_stack: Arc::new(Mutex::new(Vec::new())), } } } diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 56d71323..b7e969e5 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -96,6 +96,11 @@ pub struct MirBuilder { pub(super) value_types: HashMap, /// Current static box name when lowering a static box body (e.g., "Main") current_static_box: Option, + + /// Include guards: currently loading file canonical paths + include_loading: HashSet, + /// Include visited cache: canonical path -> box name + include_box_map: HashMap, } impl MirBuilder { @@ -128,6 +133,8 @@ impl MirBuilder { field_origin_class: HashMap::new(), value_types: HashMap::new(), current_static_box: None, + include_loading: HashSet::new(), + include_box_map: HashMap::new(), } } @@ -507,6 +514,15 @@ impl MirBuilder { } else if std::path::Path::new(&path).extension().is_none() { path.push_str(".nyash"); } + // Cycle detection + if self.include_loading.contains(&path) { + return Err(format!("Circular include detected: {}", path)); + } + // Cache hit: build only the instance + if let Some(name) = self.include_box_map.get(&path).cloned() { + return self.build_new_expression(name, vec![]); + } + self.include_loading.insert(path.clone()); let content = fs::read_to_string(&path) .map_err(|e| format!("Include read error '{}': {}", filename, e))?; // Parse to AST @@ -524,6 +540,9 @@ impl MirBuilder { let bname = box_name.ok_or_else(|| format!("Include target '{}' has no static box", filename))?; // Lower included AST into current MIR (register types/methods) let _ = self.build_expression(included_ast)?; + // Mark caches + self.include_loading.remove(&path); + self.include_box_map.insert(path.clone(), bname.clone()); // Return a new instance of included box (no args) self.build_new_expression(bname, vec![]) }, diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 1b2e8459..60649263 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -126,10 +126,12 @@ impl NyashRunner { format!("./{}", filename) } - fn walk(node: &ASTNode, runtime: &NyashRuntime) { + use std::collections::{HashSet, VecDeque}; + + fn walk_with_state(node: &ASTNode, runtime: &NyashRuntime, stack: &mut Vec, visited: &mut HashSet) { match node { - ASTNode::Program { statements, .. } => { for st in statements { walk(st, runtime); } } - ASTNode::FunctionDeclaration { body, .. } => { for st in body { walk(st, runtime); } } + ASTNode::Program { statements, .. } => { for st in statements { walk_with_state(st, runtime, stack, visited); } } + ASTNode::FunctionDeclaration { body, .. } => { for st in body { walk_with_state(st, runtime, stack, visited); } } ASTNode::Include { filename, .. } => { let mut path = resolve_include_path(filename); if std::path::Path::new(&path).is_dir() { @@ -137,49 +139,62 @@ impl NyashRunner { } else if std::path::Path::new(&path).extension().is_none() { path.push_str(".nyash"); } + // Cycle detection using stack + if let Some(pos) = stack.iter().position(|p| p == &path) { + let mut chain = stack[pos..].to_vec(); + chain.push(path.clone()); + eprintln!("include cycle detected (collector): {}", chain.join(" -> ")); + return; // Skip to avoid infinite recursion + } + if visited.contains(&path) { + return; // Already processed + } + stack.push(path.clone()); if let Ok(content) = std::fs::read_to_string(&path) { if let Ok(inc_ast) = NyashParser::parse_from_string(&content) { - walk(&inc_ast, runtime); + walk_with_state(&inc_ast, runtime, stack, visited); + visited.insert(path); } } + stack.pop(); } ASTNode::Assignment { target, value, .. } => { - walk(target, runtime); walk(value, runtime); + walk_with_state(target, runtime, stack, visited); walk_with_state(value, runtime, stack, visited); } - ASTNode::Return { value, .. } => { if let Some(v) = value { walk(v, runtime); } } - ASTNode::Print { expression, .. } => { walk(expression, runtime); } + ASTNode::Return { value, .. } => { if let Some(v) = value { walk_with_state(v, runtime, stack, visited); } } + ASTNode::Print { expression, .. } => { walk_with_state(expression, runtime, stack, visited); } ASTNode::If { condition, then_body, else_body, .. } => { - walk(condition, runtime); - for st in then_body { walk(st, runtime); } - if let Some(eb) = else_body { for st in eb { walk(st, runtime); } } + walk_with_state(condition, runtime, stack, visited); + for st in then_body { walk_with_state(st, runtime, stack, visited); } + if let Some(eb) = else_body { for st in eb { walk_with_state(st, runtime, stack, visited); } } } ASTNode::Loop { condition, body, .. } => { - walk(condition, runtime); for st in body { walk(st, runtime); } + walk_with_state(condition, runtime, stack, visited); for st in body { walk_with_state(st, runtime, stack, visited); } } ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { - for st in try_body { walk(st, runtime); } - for cc in catch_clauses { for st in &cc.body { walk(st, runtime); } } - if let Some(fb) = finally_body { for st in fb { walk(st, runtime); } } + for st in try_body { walk_with_state(st, runtime, stack, visited); } + for cc in catch_clauses { for st in &cc.body { walk_with_state(st, runtime, stack, visited); } } + if let Some(fb) = finally_body { for st in fb { walk_with_state(st, runtime, stack, visited); } } } - ASTNode::Throw { expression, .. } => { walk(expression, runtime); } + ASTNode::Throw { expression, .. } => { walk_with_state(expression, runtime, stack, visited); } ASTNode::Local { initial_values, .. } => { - for iv in initial_values { if let Some(v) = iv { walk(v, runtime); } } + for iv in initial_values { if let Some(v) = iv { walk_with_state(v, runtime, stack, visited); } } } ASTNode::Outbox { initial_values, .. } => { - for iv in initial_values { if let Some(v) = iv { walk(v, runtime); } } + for iv in initial_values { if let Some(v) = iv { walk_with_state(v, runtime, stack, visited); } } } - ASTNode::FunctionCall { arguments, .. } => { for a in arguments { walk(a, runtime); } } - ASTNode::MethodCall { object, arguments, .. } => { walk(object, runtime); for a in arguments { walk(a, runtime); } } - ASTNode::FieldAccess { object, .. } => { walk(object, runtime); } - ASTNode::New { arguments, .. } => { for a in arguments { walk(a, runtime); } } - ASTNode::BinaryOp { left, right, .. } => { walk(left, runtime); walk(right, runtime); } - ASTNode::UnaryOp { operand, .. } => { walk(operand, runtime); } - ASTNode::AwaitExpression { expression, .. } => { walk(expression, runtime); } - ASTNode::Arrow { sender, receiver, .. } => { walk(sender, runtime); walk(receiver, runtime); } - ASTNode::Nowait { expression, .. } => { walk(expression, runtime); } + ASTNode::FunctionCall { arguments, .. } => { for a in arguments { walk_with_state(a, runtime, stack, visited); } } + ASTNode::MethodCall { object, arguments, .. } => { walk_with_state(object, runtime, stack, visited); for a in arguments { walk_with_state(a, runtime, stack, visited); } } + ASTNode::FieldAccess { object, .. } => { walk_with_state(object, runtime, stack, visited); } + ASTNode::New { arguments, .. } => { for a in arguments { walk_with_state(a, runtime, stack, visited); } } + ASTNode::BinaryOp { left, right, .. } => { walk_with_state(left, runtime, stack, visited); walk_with_state(right, runtime, stack, visited); } + ASTNode::UnaryOp { operand, .. } => { walk_with_state(operand, runtime, stack, visited); } + ASTNode::AwaitExpression { expression, .. } => { walk_with_state(expression, runtime, stack, visited); } + ASTNode::Arrow { sender, receiver, .. } => { walk_with_state(sender, runtime, stack, visited); walk_with_state(receiver, runtime, stack, visited); } + ASTNode::Nowait { expression, .. } => { walk_with_state(expression, runtime, stack, visited); } ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => { - for (_mname, mnode) in methods { walk(mnode, runtime); } - for (_ckey, cnode) in constructors { walk(cnode, runtime); } + for (_mname, mnode) in methods { walk_with_state(mnode, runtime, stack, visited); } + for (_ckey, cnode) in constructors { walk_with_state(cnode, runtime, stack, visited); } let decl = CoreBoxDecl { name: name.clone(), fields: fields.clone(), @@ -199,6 +214,8 @@ impl NyashRunner { _ => {} } } - walk(ast, runtime); + let mut stack: Vec = Vec::new(); + let mut visited: HashSet = HashSet::new(); + walk_with_state(ast, runtime, &mut stack, &mut visited); } } diff --git a/tools/pyc/PyCompiler.nyash b/tools/pyc/PyCompiler.nyash index ded4fcc5..2d1e30ab 100644 --- a/tools/pyc/PyCompiler.nyash +++ b/tools/pyc/PyCompiler.nyash @@ -10,42 +10,23 @@ static box PyCompiler { } buildIRFromParse(json) { - // Minimal: analyze Python source from env and build IR with a constant return when possible + // Minimal: analyze Python source from env and extract first constant return as JSON scalar string // Python snippet: parse code from os.getenv("NYASH_PY_CODE"), find first Return(Constant) local os, getenv, src, ast, parsef, walkf, first_ret local py = new PyRuntimeBox() - os = py.import("os") - getenv = os.getattr("getenv") - src = getenv.call("NYASH_PY_CODE").toString() - if (src == null || src == "") { - return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":0}]}}") - } - - ast = py.import("ast") - parsef = ast.getattr("parse") - local tree - tree = parsef.call(src) - // Walk via Python: extract first constant return value if present local res res = py.eval("next((n.value.value for n in __import__('ast').walk(__import__('ast').parse(__import__('os').getenv('NYASH_PY_CODE') or '')) if isinstance(n, __import__('ast').Return) and isinstance(n.value, __import__('ast').Constant)), None)") - // If number -> return that number; if string -> quoted - local val - val = res.str() - if (val.matches("^[0-9]+$")) { - return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":" + val + "]}}") - } else if (val.length() > 0) { - return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":\"" + val + "\"}]}}") - } else { - return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":0}]}}") - } + // JSON-encode result (number or None) and return as StringBox directly + local ret_json + ret_json = py.import("json").getattr("dumps").call(res).str() + return new StringBox(ret_json) } - irToNyashSource(irJson) { - // Build Nyash source from IR (minimal: assume IR has return_value embedded by PyIR.buildReturn) + irToNyashSource(retJson) { + // Build Nyash source directly from the scalar JSON value string local src - src = "static box Generated {\n main() {\n return 0\n }\n}" - // Future: parse irJson and extract return_value + src = "static box Generated {\n main() {\n return " + retJson + "\n }\n}" return new StringBox(src) } } diff --git a/tools/pyc/PythonParserNy.nyash b/tools/pyc/PythonParserNy.nyash index 7a4d108b..f6320bb8 100644 --- a/tools/pyc/PythonParserNy.nyash +++ b/tools/pyc/PythonParserNy.nyash @@ -2,15 +2,15 @@ static box PythonParserNy { parse(code) { local src - if (code == null || code.toString() == "") { + if (code == null || code.length() == 0) { // Fallback: read from env via Python os.getenv local os, getenv local py = new PyRuntimeBox() os = py.import("os") getenv = os.getattr("getenv") - src = getenv.call("NYASH_PY_CODE").toString() + src = getenv.call("NYASH_PY_CODE").str() } else { - src = code.toString() + src = code.str() } // ast.dump(ast.parse(src)) diff --git a/tools/pyc/pyc.nyash b/tools/pyc/pyc.nyash index b77a6b29..42c10000 100644 --- a/tools/pyc/pyc.nyash +++ b/tools/pyc/pyc.nyash @@ -14,11 +14,12 @@ static box Main { // Skip echo of source to avoid plugin toString issues - json = Compiler.parseToJson() - + json = new StringBox("{}") + // Build minimal IR from Python AST (env: NYASH_PY_CODE) ir = Compiler.buildIRFromParse(json) - + // Emit generated Nyash source (reflect return/if/assign when present) src = Compiler.irToNyashSource(ir) + print(src) return 0 } }