Phase 10.7/10.5c: include cycle detection (VM/Interpreter), minimal pyc IR→Nyash, String unification bridge (VM partial), add core plugins: RegexBox/EncodingBox/TOMLBox/PathBox + examples; wire nyash.toml; begin String interop for internal vs plugin boxes; update CURRENT_TASK.md
This commit is contained in:
@ -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 更新)
|
||||
|
||||
|
||||
105
apps/README.md
Normal file
105
apps/README.md
Normal file
@ -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」哲学に従い、プラグインシステムを活用することを推奨します。
|
||||
208
apps/ny-array-bench/main.nyash
Normal file
208
apps/ny-array-bench/main.nyash
Normal file
@ -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) // 仮の値
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
apps/ny-echo/main.nyash
Normal file
78
apps/ny-echo/main.nyash
Normal file
@ -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.")
|
||||
}
|
||||
}
|
||||
85
apps/ny-echo/test.sh
Normal file
85
apps/ny-echo/test.sh
Normal file
@ -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! ==="
|
||||
@ -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<ControlFlow, VMError> {
|
||||
// ... 値の設定 ...
|
||||
|
||||
// 常に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<ValueId, AllocInfo>,
|
||||
// escape状態
|
||||
escapes: HashSet<ValueId>,
|
||||
// 解析結果キャッシュ
|
||||
cache: HashMap<FunctionId, EscapeInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AllocInfo {
|
||||
location: BasicBlockId,
|
||||
kind: AllocKind,
|
||||
size: Option<usize>,
|
||||
}
|
||||
|
||||
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%改善
|
||||
- [ ] ドキュメント更新
|
||||
@ -0,0 +1,240 @@
|
||||
# Phase 11.5b: Atomic操作最適化によるsync処理高速化
|
||||
|
||||
## 🎯 目標
|
||||
Arc<Mutex>の重いロック操作を、可能な限り軽量なatomic操作に置き換えて性能を向上させる。
|
||||
|
||||
## 📊 現状の問題
|
||||
|
||||
### 現在の実装
|
||||
```rust
|
||||
// すべてのBox操作でMutexロック
|
||||
pub fn get_field(&self, name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
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<T: NyashBox> {
|
||||
// Read-optimized RwLock
|
||||
inner: Arc<RwLock<T>>,
|
||||
// Atomic cache for immutable data
|
||||
cached_string: AtomicPtr<String>,
|
||||
cached_int: AtomicI64,
|
||||
}
|
||||
|
||||
impl<T: NyashBox> AtomicBox<T> {
|
||||
/// Lock-free read for cached values
|
||||
pub fn read_cached(&self) -> Option<Box<dyn NyashBox>> {
|
||||
// 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<F, R>(&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統合
|
||||
- [ ] マルチスレッドベンチマーク
|
||||
- [ ] プラットフォーム別最適化
|
||||
@ -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<String>, body: Box<ASTNode> },
|
||||
|
||||
// 新規追加
|
||||
AsyncFunction {
|
||||
name: String,
|
||||
params: Vec<String>,
|
||||
body: Box<ASTNode>
|
||||
},
|
||||
AwaitExpression {
|
||||
expression: Box<ASTNode>
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: MIR Coroutine命令
|
||||
```rust
|
||||
// mir/instruction.rs
|
||||
pub enum MirInstruction {
|
||||
// 既存命令...
|
||||
|
||||
// Coroutine関連
|
||||
/// Coroutine生成
|
||||
CoroutineCreate {
|
||||
dst: ValueId,
|
||||
func: FunctionId,
|
||||
captures: Vec<ValueId>,
|
||||
},
|
||||
|
||||
/// Yield point(await)
|
||||
CoroutineYield {
|
||||
value: ValueId,
|
||||
resume_label: BasicBlockId,
|
||||
},
|
||||
|
||||
/// Coroutine再開
|
||||
CoroutineResume {
|
||||
dst: Option<ValueId>,
|
||||
coroutine: ValueId,
|
||||
value: Option<ValueId>,
|
||||
},
|
||||
|
||||
/// Promise結合
|
||||
PromiseAll {
|
||||
dst: ValueId,
|
||||
promises: Vec<ValueId>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: State Machine変換
|
||||
```rust
|
||||
// mir/coroutine_transform.rs
|
||||
pub struct CoroutineTransform {
|
||||
state_enum: HashMap<FunctionId, StateEnum>,
|
||||
}
|
||||
|
||||
/// 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<State>
|
||||
) -> 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<Value> {
|
||||
// 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<Mutex<PromiseState>>,
|
||||
base: BoxBase,
|
||||
}
|
||||
|
||||
enum PromiseState {
|
||||
Pending(Vec<Box<dyn FnOnce(Box<dyn NyashBox>)>>),
|
||||
Resolved(Box<dyn NyashBox>),
|
||||
Rejected(String),
|
||||
}
|
||||
|
||||
impl PromiseBox {
|
||||
pub fn all(promises: Vec<Box<PromiseBox>>) -> Box<PromiseBox> {
|
||||
// 全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並行処理ベンチマーク達成
|
||||
307
docs/development/roadmap/phases/phase-11.5/FIRST-FIVE-APPS.md
Normal file
307
docs/development/roadmap/phases/phase-11.5/FIRST-FIVE-APPS.md
Normal file
@ -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 <file.json>")
|
||||
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日で作った言語」の実力を世界に示せます!🎉
|
||||
@ -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<ValueId, AllocSite>,
|
||||
escapes: HashSet<ValueId>,
|
||||
}
|
||||
|
||||
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<ControlFlow, VMError> {
|
||||
// 既存のコード...
|
||||
|
||||
// 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<T> {
|
||||
inner: Arc<RwLock<T>>,
|
||||
cache: AtomicPtr<CachedValue>,
|
||||
}
|
||||
```
|
||||
|
||||
### 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<ASTNode, ParseError> {
|
||||
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は本当に世界クラスの言語になります!
|
||||
169
docs/development/roadmap/phases/phase-11.5/README.md
Normal file
169
docs/development/roadmap/phases/phase-11.5/README.md
Normal file
@ -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<Mutex>/Arc<RwLock>による完全スレッドセーフ設計
|
||||
- 全レイヤーでの一貫した同期化
|
||||
|
||||
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<Mutex>アクセスの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<Mutex>の重いロック
|
||||
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<ValueId, AllocSite>,
|
||||
escapes: HashSet<ValueId>,
|
||||
}
|
||||
|
||||
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日で作られたとは思えない**世界クラスの言語が完成します!
|
||||
162
docs/development/roadmap/phases/phase-11.5/WASM-ISSUES.md
Normal file
162
docs/development/roadmap/phases/phase-11.5/WASM-ISSUES.md
Normal file
@ -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<WasmModule, WasmError> {
|
||||
// 基本的な命令しか実装されていない
|
||||
// - 算術演算
|
||||
// - 制御フロー
|
||||
// - 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<Mutex>も含めて
|
||||
- **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の成熟を待つ
|
||||
|
||||
これが現実的な戦略です!
|
||||
@ -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)として処理され、完全な相互運用性が実現されています。
|
||||
@ -0,0 +1,115 @@
|
||||
# プラグインBoxは既に箱を引数に取れる!
|
||||
|
||||
## 🎯 重要な発見
|
||||
|
||||
**プラグインBoxは既にC ABIレベルで箱を引数に取ることができます!**
|
||||
|
||||
## 📊 実装の詳細
|
||||
|
||||
### 1. TLVプロトコルでのハンドルサポート
|
||||
|
||||
```rust
|
||||
// TLVタグ定義
|
||||
const TAG_HANDLE: u8 = 8; // プラグインハンドル用
|
||||
|
||||
// ハンドルエンコード関数
|
||||
pub fn plugin_handle(buf: &mut Vec<u8>, 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::<PluginBoxV2>() {
|
||||
// 箱引数はハンドルとしてエンコード
|
||||
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でも箱は箱を扱える!**
|
||||
9
examples/cycle_a.nyash
Normal file
9
examples/cycle_a.nyash
Normal file
@ -0,0 +1,9 @@
|
||||
// Cycle test A -> B -> A
|
||||
include "examples/cycle_b.nyash"
|
||||
|
||||
static box A {
|
||||
main() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
9
examples/cycle_b.nyash
Normal file
9
examples/cycle_b.nyash
Normal file
@ -0,0 +1,9 @@
|
||||
// Cycle test B -> A
|
||||
include "examples/cycle_a.nyash"
|
||||
|
||||
static box B {
|
||||
main() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
8
examples/encoding_min.nyash
Normal file
8
examples/encoding_min.nyash
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
8
examples/path_min.nyash
Normal file
8
examples/path_min.nyash
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
35
examples/plugin_box_as_arg_demo.nyash
Normal file
35
examples/plugin_box_as_arg_demo.nyash
Normal file
@ -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!")
|
||||
8
examples/regex_min.nyash
Normal file
8
examples/regex_min.nyash
Normal file
@ -0,0 +1,8 @@
|
||||
static box Main {
|
||||
main() {
|
||||
local r = new RegexBox()
|
||||
r.compile("a+")
|
||||
return r.isMatch("caaab")
|
||||
}
|
||||
}
|
||||
|
||||
8
examples/toml_min.nyash
Normal file
8
examples/toml_min.nyash
Normal file
@ -0,0 +1,8 @@
|
||||
static box Main {
|
||||
main() {
|
||||
local t = new TOMLBox()
|
||||
t.parse("[a]\nvalue=42\n")
|
||||
local j = t.toJson()
|
||||
return j
|
||||
}
|
||||
}
|
||||
70
nyash.toml
70
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 }
|
||||
|
||||
13
plugins/nyash-encoding-plugin/Cargo.toml
Normal file
13
plugins/nyash-encoding-plugin/Cargo.toml
Normal file
@ -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"
|
||||
|
||||
118
plugins/nyash-encoding-plugin/src/lib.rs
Normal file
118
plugins/nyash-encoding-plugin/src/lib.rs
Normal file
@ -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<Mutex<HashMap<u32, EncInstance>>> = 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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
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<String> {
|
||||
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<Vec<u8>> {
|
||||
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
|
||||
}
|
||||
12
plugins/nyash-path-plugin/Cargo.toml
Normal file
12
plugins/nyash-path-plugin/Cargo.toml
Normal file
@ -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"
|
||||
|
||||
114
plugins/nyash-path-plugin/src/lib.rs
Normal file
114
plugins/nyash-path-plugin/src/lib.rs
Normal file
@ -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<Mutex<HashMap<u32, PathInstance>>> = 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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
12
plugins/nyash-regex-plugin/Cargo.toml
Normal file
12
plugins/nyash-regex-plugin/Cargo.toml
Normal file
@ -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"
|
||||
|
||||
120
plugins/nyash-regex-plugin/src/lib.rs
Normal file
120
plugins/nyash-regex-plugin/src/lib.rs
Normal file
@ -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<Regex> }
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, RegexInstance>>> = 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<String> = if limit > 0 { re.splitn(&text, limit as usize).map(|s| s.to_string()).collect() } else { re.split(&text).map(|s| s.to_string()).collect() }; let out = parts.join("\n"); return write_tlv_string(&out, result, result_len); } else { return write_tlv_string(&text, result, result_len); } } else { return E_HANDLE; } } else { return E_PLUGIN; }
|
||||
}
|
||||
_ => E_METHOD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
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<i64> {
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
|
||||
13
plugins/nyash-toml-plugin/Cargo.toml
Normal file
13
plugins/nyash-toml-plugin/Cargo.toml
Normal file
@ -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"
|
||||
|
||||
127
plugins/nyash-toml-plugin/src/lib.rs
Normal file
127
plugins/nyash-toml-plugin/src/lib.rs
Normal file
@ -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<toml::Value> }
|
||||
|
||||
static INST: Lazy<Mutex<HashMap<u32, TomlInstance>>> = 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::<toml::Value>(&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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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::<crate::box_trait::StringBox>() {
|
||||
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<dyn crate::box_trait::NyashBox>))); }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(VMError::InvalidInstruction(format!("PluginInvoke requires PluginBox receiver; method={} got {:?}", method, recv)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> {
|
||||
if let Some(sb) = b.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
return Some(sb.value.clone());
|
||||
}
|
||||
if let Some(pb) = b.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
if pb.box_type == "StringBox" {
|
||||
let host = crate::runtime::get_global_plugin_host();
|
||||
let s_opt: Option<String> = {
|
||||
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::<crate::box_trait::StringBox>() {
|
||||
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::<crate::box_trait::IntegerBox>().map(|x| x.value)
|
||||
.or_else(|| li.as_any().downcast_ref::<crate::boxes::integer_box::IntegerBox>().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::<crate::box_trait::StringBox>() { Some(sb.value.clone()) } else {
|
||||
if let Some(pb) = ri.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
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::<crate::box_trait::StringBox>() { 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::<crate::box_trait::StringBox>() { Some(sb.value.clone()) } else {
|
||||
if let Some(pb) = li.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||
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::<crate::box_trait::StringBox>() { 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::<crate::box_trait::IntegerBox>().map(|x| x.value)
|
||||
|
||||
@ -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<String> = 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<String> = 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を初期化・取得して返す
|
||||
|
||||
@ -20,6 +20,9 @@ pub struct SharedState {
|
||||
|
||||
/// 読み込み済みファイル(重複防止)
|
||||
pub included_files: Arc<Mutex<HashSet<String>>>,
|
||||
|
||||
/// includeロード中スタック(循環検出用: A -> B -> A を検出)
|
||||
pub include_stack: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +96,11 @@ pub struct MirBuilder {
|
||||
pub(super) value_types: HashMap<ValueId, super::MirType>,
|
||||
/// Current static box name when lowering a static box body (e.g., "Main")
|
||||
current_static_box: Option<String>,
|
||||
|
||||
/// Include guards: currently loading file canonical paths
|
||||
include_loading: HashSet<String>,
|
||||
/// Include visited cache: canonical path -> box name
|
||||
include_box_map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
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![])
|
||||
},
|
||||
|
||||
@ -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<String>, visited: &mut HashSet<String>) {
|
||||
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<String> = Vec::new();
|
||||
let mut visited: HashSet<String> = HashSet::new();
|
||||
walk_with_state(ast, runtime, &mut stack, &mut visited);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user