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:
@ -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` に包んで返す
|
||||
|
||||
47
docs/reference/plugin-system/returns-result.md
Normal file
47
docs/reference/plugin-system/returns-result.md
Normal 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)。
|
||||
- 段階導入により、既存コードを壊さずに移行できます。
|
||||
|
||||
80
docs/予定/when-pattern-matching.md
Normal file
80
docs/予定/when-pattern-matching.md
Normal 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 協調開発チーム
|
||||
14
nyash.toml
14
nyash.toml
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
"#;
|
||||
|
||||
|
||||
@ -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
|
||||
"#;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user