feat: Add returns_result support and when pattern-matching design

- Add returns_result.md documenting Result正規化 (returns_result = true)
- Add when-pattern-matching.md with future pattern matching syntax design
- Update E2E tests to use get_value() for HTTP responses
- Update plugin system README with B案 (Result-based) support
- Remove singleton from HttpServerBox and SocketServerBox for stability

This prepares for gradual migration to Result-based error handling,
starting with Net plugin methods, as agreed with ChatGPT5's design.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-22 06:05:58 +09:00
parent f2761004d3
commit ce9e60972a
6 changed files with 168 additions and 18 deletions

View File

@ -28,6 +28,9 @@
- **[net-plugin.md](./net-plugin.md)** - NetプラグインHTTP/TCP PoC
- GET/POST、ヘッダ、Content-Length、環境変数によるログ
- **[returns-result.md](./returns-result.md)** - 可選のResultBox正規化
- `returns_result = true` で成功/失敗を `Ok/Err` に統一(段階導入推奨)
### ⚙️ 戻り値のResult化B案サポート
- `nyash.toml` のメソッド定義に `returns_result = true` を付けると、
- 成功: `Ok(value)``ResultBox` に包んで返す

View File

@ -0,0 +1,47 @@
# 可選: ResultBox 正規化returns_result
最終更新: 2025-08-22
## 概要
`nyash.toml` のメソッド定義に `returns_result = true` を付けると、そのメソッドの戻りが `ResultBox` で正規化されます。
- 成功: `Ok(value)`voidは `Ok(void)`
- 失敗: `Err(ErrorBox("... (code: N)"))`BID負エラーコードをErr化
これは「おすすめルール」で、強制ではありません。段階的に、必要なメソッドから選んで導入できます。
## 使い方
`nyash.toml` を編集し、対象メソッドに `returns_result = true` を付けます。
```toml
[libraries."libnyash_net_plugin.so".HttpClientBox.methods]
# birth = { method_id = 0 }
# デフォルト(従来通り)
get = { method_id = 1 }
post = { method_id = 2 }
# 推奨: Result正規化有効化する場合
# get = { method_id = 1, returns_result = true }
# post = { method_id = 2, returns_result = true }
```
## 呼び出し側パターン
```nyash
res = http.get(url)
if res.is_ok() {
resp = res.get_value()
print(resp.readBody())
} else {
err = res.get_error()
print("HTTP error: " + err.toString())
}
```
## 推奨の導入順序
- Stage 1: `HttpClientBox.get/post`, `SocketClientBox.connect`
- Stage 2: `HttpServerBox.start/stop/accept`(起動・待受系)
- Stage 3: 失敗が起きうる便宜メソッド(必要なところだけ)
## 注意
- `returns_result` を付けたメソッドのみ Result化されます。未指定メソッドは従来動作生値/void/例外)。
- タイムアウトをErrにしたい場合は、プラグイン側がBID負エラーを返すよう拡張してください現状は空bytes/void
- 段階導入により、既存コードを壊さずに移行できます。

View File

@ -0,0 +1,80 @@
# when構文 - パターンマッチング(将来実装予定)
## 概要
Nyashに「when構文」を導入し、より直感的で安全なエラー処理とパターンマッチングを実現する。
## 背景
- ChatGPT5提案の`returns_result = true`による段階的Result正規化
- 現在の`if res.is_ok()`パターンは冗長
- Nyashの「Everything is Box」哲学に合致した統一的な構文が必要
## 提案構文
### 基本形 - ResultBoxのパターンマッチング
```nyash
when res {
ok(resp) -> {
body = resp.readBody()
print(body)
}
error(err) -> {
print("Error: " + err.message())
}
}
```
### 汎用形 - あらゆるBoxでのパターンマッチング
```nyash
when value {
StringBox(s) -> print("文字列: " + s)
IntegerBox(n) -> print("数値: " + n.toString())
ArrayBox(arr) -> print("配列の長さ: " + arr.length())
NullBox -> print("nullです")
_ -> print("その他のBox") // デフォルトケース
}
```
### ネスト可能
```nyash
when httpResult {
ok(response) -> {
when response.getStatus() {
200 -> print("成功!")
404 -> print("見つからない")
_ -> print("その他のステータス")
}
}
error(e) -> print("エラー: " + e)
}
```
## 実装前提条件
1. **MIRダイエット完了**現在33個→目標20-26個
- 新しいパターンマッチング命令の追加余地が必要
2. **VM最適化完了**Phase 8.6
- 効率的なジャンプテーブル実装が必要
3. **`returns_result = true`の段階導入**
- Net系Boxから開始
## 実装計画
1. Phase 9後半: MIR命令追加Match, MatchBranch等
2. Phase 10: VM/JITでの最適化実装
3. Phase 11: 言語仕様への正式組み込み
## 利点
- **直感的**: switch-case的な馴染みやすい構文
- **型安全**: 各パターンで正しい型のメソッドが呼べる
- **網羅的**: すべてのケースをカバー可能
- **拡張性**: 将来の新Box型にも対応可能
- **Nyash哲学**: Everything is Boxに完全に合致
## 他の検討案
1. **chain構文**: `res.onSuccess({}).onError({})`
2. **try-else構文**: `try resp = res {} else err {}`
3. **?演算子**: `resp = res.unwrap?()`
これらも将来的に検討可能だが、when構文が最もNyashらしい。
---
提案日: 2025-08-20
提案者: Claude & ChatGPT5 協調開発チーム

View File

@ -98,10 +98,12 @@ type_id = 23
[libraries."libnyash_net_plugin.so".HttpClientBox.methods]
birth = { method_id = 0 }
get = { method_id = 1 }
post = { method_id = 2 }
get = { method_id = 1, returns_result = true }
post = { method_id = 2, returns_result = true }
fini = { method_id = 4294967295 }
## ResultBox normalization enabled above for get/post
[libraries."libnyash_net_plugin.so".SocketServerBox]
type_id = 30
@ -113,6 +115,11 @@ accept = { method_id = 3 }
acceptTimeout = { method_id = 4, args = ["timeout_ms"] }
fini = { method_id = 4294967295 }
# Optional: ResultBox normalization (recommendation)
# start = { method_id = 1, args = ["port"], returns_result = true }
# stop = { method_id = 2, returns_result = true }
# accept = { method_id = 3, returns_result = true }
[libraries."libnyash_net_plugin.so".SocketClientBox]
type_id = 32
@ -121,6 +128,9 @@ birth = { method_id = 0 }
connect = { method_id = 1, args = ["host", "port"] }
fini = { method_id = 4294967295 }
# Optional: ResultBox normalization (recommendation)
# connect = { method_id = 1, args = ["host", "port"], returns_result = true }
[libraries."libnyash_net_plugin.so".SocketConnBox]
type_id = 31

View File

@ -25,7 +25,7 @@ fn e2e_http_stub_end_to_end() {
if !try_init_plugins() { return; }
let code = r#"
local srv, cli, r, req, resp, body
local srv, cli, r, resp, req, body
srv = new HttpServerBox()
srv.start(8080)
@ -38,7 +38,8 @@ resp.setStatus(200)
resp.write("OK")
req.respond(resp)
body = r.readBody()
resp = r.get_value()
body = resp.readBody()
body
"#;
@ -55,7 +56,7 @@ fn e2e_http_server_restart() {
if !try_init_plugins() { return; }
let code = r#"
local srv, cli, r, req, resp, body
local srv, cli, r, resp, req, body
srv = new HttpServerBox()
srv.start(8081)
@ -69,13 +70,17 @@ req.respond(resp)
srv.stop()
srv.start(8081)
resp = r.get_value()
_ = resp.readBody() # consume first response (optional)
r = cli.get("http://localhost:8081/test2")
req = srv.accept()
resp = new HttpResponseBox()
resp.write("B")
req.respond(resp)
body = r.readBody()
resp = r.get_value()
body = resp.readBody()
body
"#;
@ -93,7 +98,7 @@ fn e2e_http_server_shutdown_and_restart() {
// First run: start and respond
let code1 = r#"
local srv, cli, r, req, resp
local srv, cli, r, resp, req
srv = new HttpServerBox()
srv.start(8082)
cli = new HttpClientBox()
@ -122,7 +127,8 @@ req = srv.accept()
resp = new HttpResponseBox()
resp.write("Y")
req.respond(resp)
body = r.readBody()
resp = r.get_value()
body = resp.readBody()
body
"#;
let ast2 = NyashParser::parse_from_string(code2).expect("parse2");
@ -138,7 +144,7 @@ fn e2e_http_post_and_headers() {
if !try_init_plugins() { return; }
let code = r#"
local srv, cli, r, req, resp, body, st, hv
local srv, cli, r, resp, req, body, st, hv
srv = new HttpServerBox()
srv.start(8090)
@ -156,9 +162,10 @@ resp.write("R")
req.respond(resp)
// client reads status, header, body
st = r.getStatus()
hv = r.getHeader("X-Test")
body = r.readBody()
resp = r.get_value()
st = resp.getStatus()
hv = resp.getHeader("X-Test")
body = resp.readBody()
st.toString() + ":" + hv + ":" + body
"#;

View File

@ -25,7 +25,7 @@ fn e2e_http_two_servers_parallel() {
if !try_init_plugins() { return; }
let code = r#"
local s1, s2, c, r1, r2, req1, req2, p1, p2, x, y
local s1, s2, c, r1, r2, resp1, resp2, req1, req2, p1, p2, x, y
s1 = new HttpServerBox()
s2 = new HttpServerBox()
s1.start(8101)
@ -51,8 +51,10 @@ req1.respond(x)
req2.respond(y)
// read results
x = r1.readBody()
y = r2.readBody()
resp1 = r1.get_value()
resp2 = r2.get_value()
x = resp1.readBody()
y = resp2.readBody()
x + ":" + y
"#;
@ -69,7 +71,7 @@ fn e2e_http_long_body_and_headers() {
if !try_init_plugins() { return; }
let code = r#"
local s, c, r, q, resp, body, hv
local s, c, r, resp, q, body, hv
s = new HttpServerBox()
s.start(8103)
@ -85,8 +87,9 @@ resp.setHeader("X-Beta", "B")
resp.write("OK-LONG")
q.respond(resp)
body = r.readBody()
hv = r.getHeader("X-Alpha")
resp = r.get_value()
body = resp.readBody()
hv = resp.getHeader("X-Alpha")
hv + ":" + body
"#;