# 機能設計書 41-async/await対応

## 概要

本ドキュメントは、CoffeeScript REPLにおけるトップレベルasync/await機能の設計を記載する。

### 本機能の処理概要

CoffeeScript REPLでトップレベルのawait式を使用可能にする機能である。通常のJavaScriptでは、await式はasync関数内でのみ使用可能であるが、本機能によりREPL環境においてトップレベルで直接await式を記述し、非同期処理の結果を待機できるようになる。

**業務上の目的・背景**：CoffeeScriptは対話型開発環境（REPL）を提供しており、開発者はコードの動作確認やプロトタイピングを迅速に行える。しかし、現代のJavaScript開発ではPromiseやasync/awaitによる非同期処理が一般的であり、REPLでこれらを直接評価できないと利便性が大幅に低下する。本機能により、API呼び出しやファイル読み込みなどの非同期処理をREPL上で直感的に実行でき、開発効率が向上する。

**機能の利用シーン**：
- 開発者がREPLで非同期API（fetch、fsのPromise版など）を試す場合
- Promiseチェーンの結果を確認したい場合
- 非同期関数の戻り値をインタラクティブに検証する場合

**主要な処理内容**：
1. REPL入力をトークン化し、AST（抽象構文木）を生成する
2. 生成されたASTをCode（関数定義）ノードでラップする
3. ラップしたCode内にawait演算子またはAwaitReturn、または`for await`が含まれているかを検出
4. async関数として検出された場合、生成されたJavaScriptを実行後、Promiseの解決を待機してから結果をコールバックで返す

**関連システム・外部連携**：
- Node.jsのREPLモジュール（`repl`）と連携
- VMモジュール（`vm`）によるコード実行
- Babelトランスパイラ（オプション）との連携

**権限による制御**：特になし。REPLを使用できるユーザーは全てこの機能を利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | コマンドラインREPL | 主画面 | coffeeコマンドで起動するREPL環境 |

## 機能種別

計算処理 / コード評価

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | string | Yes | ユーザーがREPLに入力したCoffeeScriptコード | CoffeeScript構文として有効であること |
| context | object | Yes | REPL実行コンテキスト（変数スコープ） | Node.js VMコンテキストオブジェクト |
| filename | string | No | ソースファイル名（デフォルト: 'repl'） | 文字列 |

### 入力データソース

REPLへの標準入力（stdin）からのユーザー入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | any | 評価結果の値。asyncの場合はPromise解決後の値 |
| error | Error | 評価中に発生したエラー |

### 出力先

REPLの標準出力（stdout）に評価結果を表示

## 処理フロー

### 処理シーケンス

```
1. REPL入力の前処理
   └─ マルチラインハック文字の置換、パーレン/try-catchのアンラップ

2. トークン化
   └─ CoffeeScript.tokens()でトークン列を生成

3. AST生成
   └─ CoffeeScript.nodes()で構文木を生成

4. 式のラップ
   └─ __変数への代入でブロックをラップ

5. Codeノードでのラップとasync検出
   └─ new Code [], ast でラップし、isAsyncプロパティを取得

6. コンパイル
   └─ ast.compile()でJavaScriptコードを生成

7. 実行
   └─ vm.runInContext()またはvm.runInThisContext()で実行

8. 結果処理
   └─ isAsyncの場合はPromise.then()で解決を待機、そうでなければ即座にコールバック
```

### フローチャート

```mermaid
flowchart TD
    A[REPL入力受信] --> B[入力の前処理]
    B --> C[トークン化]
    C --> D[AST生成]
    D --> E["Codeノードでラップ"]
    E --> F{isAsync?}
    F -->|Yes| G[async functionとしてコンパイル]
    F -->|No| H[通常の関数としてコンパイル]
    G --> I[JavaScriptを実行]
    H --> I
    I --> J{isAsync?}
    J -->|Yes| K[Promise解決を待機]
    J -->|No| L[結果をコールバック]
    K --> M{SIGINT?}
    M -->|No| L
    M -->|Yes| N[結果を破棄]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | async検出 | await演算子、AwaitReturn、またはfor await構文が含まれる場合、関数をasyncとしてマーク | コード内にこれらの構文が存在する場合 |
| BR-02 | SIGINT処理 | 非同期待機中にSIGINTを受信した場合、結果を破棄 | ユーザーがCtrl+Cを押下した場合 |
| BR-03 | 変数バインド | 評価結果は常に`__`変数にバインドされる | 全ての入力に適用 |

### 計算ロジック

async判定ロジック（nodes.coffee内Code.constructor）:
```coffeescript
@body.traverseChildren no, (node) =>
  if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
    @isAsync = yes
  if node instanceof For and node.isAwait()
    @isAsync = yes
```

## データベース操作仕様

本機能ではデータベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SyntaxError | 構文エラー | 入力がCoffeeScript構文として無効 | エラーメッセージを表示し、次の入力を待つ |
| ReferenceError | 参照エラー | 未定義の変数を参照 | エラーメッセージを表示 |
| RejectedPromise | Promise拒否 | awaitしたPromiseが拒否された | エラーをコールバックで返す |

### リトライ仕様

REPLは対話型であるため、エラー後は次の入力を受け付けてリトライ可能。自動リトライは行わない。

## トランザクション仕様

該当なし（データベーストランザクションは使用しない）

## パフォーマンス要件

- 通常の（非async）入力は即座に結果を返す
- async入力はPromise解決時間に依存する
- REPL起動時のNode.jsバージョンチェックにより、Node.js 6未満では警告を表示して終了

## セキュリティ考慮事項

- REPL入力は信頼されたユーザーからの入力として扱う
- VMコンテキストはREPLセッション内で共有される
- `use strict`モードは、transpileオプション使用時に削除される（`__`変数への代入が可能であるため）

## 備考

- マルチラインモード（Ctrl+V）との互換性あり
- トランスパイルオプション使用時は、Babelによる変換後にasync/awaitが処理される

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

isAsyncフラグがどのように判定・伝播されるかを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | nodes.coffee | `src/nodes.coffee` | Code クラスの isAsync プロパティの定義と判定ロジック |
| 1-2 | nodes.coffee | `src/nodes.coffee` | Root クラスの isAsync プロパティの初期化 |

**読解のコツ**: CoffeeScriptでは `@property` は `this.property` のシンタックスシュガー。`traverseChildren` はASTを再帰的に走査するメソッド。

#### Step 2: エントリーポイントを理解する

REPLのeval関数が処理の起点。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | repl.coffee | `src/repl.coffee` | replDefaults.eval関数の実装 |

**主要処理フロー**:
1. **17-69行目**: eval関数全体の定義
2. **28行目**: ASTノードクラスのrequire
3. **32行目**: トークン化処理
4. **44行目**: AST生成
5. **46行目**: `__`変数への代入でラップ
6. **48行目**: Code ノードでラップ
7. **49行目**: `isAsync = ast.isAsync` でasync判定を取得
8. **60-63行目**: isAsyncの場合のPromise.then処理

#### Step 3: async判定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | nodes.coffee | `src/nodes.coffee` | Code クラスのコンストラクタ（3897-3916行目付近） |

**主要処理フロー**:
- **3905行目**: `@isAsync = no` で初期化
- **3908-3914行目**: `traverseChildren` でノードを走査し、await関連ノードを検出
- **3911-3912行目**: `node.isAwait()` または `AwaitReturn` の場合に `@isAsync = yes`
- **3913-3914行目**: `For` ノードの `isAwait()` の場合も `@isAsync = yes`

#### Step 4: コンパイル時のasync処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | nodes.coffee | `src/nodes.coffee` | Root クラスの compileNode（524-532行目付近） |
| 4-2 | nodes.coffee | `src/nodes.coffee` | Code クラスの compileNode（4086-4091行目付近） |

**主要処理フロー**:
- **531行目**: `functionKeyword = "#{if @isAsync then 'async ' else ''}function"` でasync関数キーワードを生成
- **4089行目**: `modifiers.push 'async' if @isAsync` で関数修飾子にasyncを追加

### プログラム呼び出し階層図

```
nodeREPL.start (Node.js REPL)
    │
    └─ replDefaults.eval (repl.coffee:17)
           │
           ├─ CoffeeScript.tokens (coffeescript.coffee)
           │
           ├─ CoffeeScript.nodes (coffeescript.coffee)
           │
           ├─ new Code [], ast (nodes.coffee:3897)
           │      └─ traverseChildren -> isAsync判定
           │
           ├─ new Root new Block [new Call ast] (nodes.coffee:513)
           │      └─ @isAsync = (new Code [], @body).isAsync
           │
           ├─ ast.compile (nodes.coffee)
           │
           └─ runInContext (repl.coffee:71)
                  └─ vm.runInContext / vm.runInThisContext
```

### データフロー図

```
[入力]               [処理]                    [出力]

REPL入力 ───▶ トークン化 ───▶ AST生成 ───▶ Codeラップ ───▶ isAsync判定
    │
    │         コンパイル ◀─────────────────────────────────┘
    │             │
    │             ▼
    │         JavaScript
    │             │
    │             ▼
    │         vm.runIn*Context
    │             │
    │             ▼
    │         実行結果(Promise?)
    │             │
    │             ▼
    │         isAsync? ─────┬── Yes ── Promise.then ── 結果
    │                       └── No ─────────────────── 結果
    │
    └──────────────────────────────────────────────── 表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| repl.coffee | `src/repl.coffee` | ソース | REPLのメイン実装。eval関数でasync/await処理を実装 |
| nodes.coffee | `src/nodes.coffee` | ソース | ASTノード定義。Code, Root クラスでisAsync判定 |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | tokens, nodes関数の実装 |
| helpers.coffee | `src/helpers.coffee` | ソース | updateSyntaxErrorなどのユーティリティ |
| index.coffee | `src/index.coffee` | ソース | CoffeeScriptモジュールのエントリーポイント |
