# 機能設計書 38-CoffeeScript REPL

## 概要

本ドキュメントは、CoffeeScriptのREPL（Read-Eval-Print Loop）機能の設計を記述する。REPLは対話型のCoffeeScript実行環境を提供し、ユーザーがコードを入力し、即座に評価結果を確認できるインタラクティブなシェルである。

### 本機能の処理概要

**業務上の目的・背景**：開発者がCoffeeScriptコードを試験的に実行し、即座にフィードバックを得られる環境を提供する。Node.jsの標準REPLを拡張し、CoffeeScript固有の機能（構文、トップレベルawait等）をサポートする。プロトタイピング、デバッグ、学習において効率的なワークフローを実現する。

**機能の利用シーン**：
- CoffeeScriptの構文や機能を試験的に実行したい場合
- スクリプトを書く前にロジックを検証したい場合
- ライブラリやAPIの動作を対話的に確認したい場合
- 学習やドキュメント作成時にコード例を実行したい場合

**主要な処理内容**：
1. Node.js REPLモジュールをベースにCoffeeScript対応REPLを起動
2. カスタムeval関数でCoffeeScriptコードを評価
3. 入力をCoffeeScriptとしてトークン化・パース・コンパイル
4. 結果を`__`変数に代入して式として評価
5. async/await対応（トップレベルawait）
6. マルチラインモードのサポート
7. コマンド履歴の保存・読み込み
8. プレリュード（--require）の実行

**関連システム・外部連携**：Node.jsのreplモジュール、vmモジュールを使用。

**権限による制御**：特になし。

## 関連画面

本機能はCLI機能のため、画面との直接的な関連はない。ただし、関連ドキュメント画面がある。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | Annotated Source - REPL | 主機能 | REPLモジュールrepl.coffeeのソースコード解説表示 |
| 35 | Annotated Source - REPL (v2) | 主機能 | REPLモジュールrepl.coffeeのソースコード解説表示（v2） |

## 機能種別

対話型実行環境 / REPL

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| opts.prelude | String | No | REPL起動時に実行するコード（--require由来） | - |
| opts.transpile | Object | No | Babelトランスパイル設定 | - |
| opts.useGlobal | Boolean | No | グローバルコンテキストを使用するか | - |

### 入力データソース

- 標準入力（対話的なユーザー入力）
- opts経由のオプション

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | any | 評価結果（コンソールに出力） |
| __ | any | 最後に評価された値（グローバル変数） |

### 出力先

標準出力（対話的な結果表示）

## 処理フロー

### 処理シーケンス

```
1. start関数の呼び出し
   └─ オプションのマージ（replDefaultsと引数）
2. Node.jsバージョンチェック
   └─ 6未満の場合は警告して終了
3. CoffeeScript.register()
   └─ .coffeeファイルのrequireを有効化
4. process.argvの調整
   └─ ['coffee', ...]形式に変更
5. トランスパイル設定（オプション）
   └─ opts.transpileがある場合、Babelを設定
6. Node.js REPLの起動
   └─ nodeREPL.start(opts)
7. プレリュードの実行（オプション）
   └─ runInContext(opts.prelude, repl.context, 'prelude')
8. マルチラインハンドラの追加
   └─ addMultilineHandler(repl)
9. 履歴機能の追加（オプション）
   └─ addHistory(repl, historyFile, historyMaxInputSize)
```

### フローチャート

```mermaid
flowchart TD
    A[coffee --interactive / coffee] --> B[start opts]
    B --> C{Node.js バージョン >= 6?}
    C -->|No| D[警告して終了]
    C -->|Yes| E[CoffeeScript.register]
    E --> F[process.argv調整]
    F --> G{opts.transpile?}
    G -->|Yes| H[Babel設定]
    G -->|No| I[replDefaultsとマージ]
    H --> I
    I --> J[nodeREPL.start opts]
    J --> K{opts.prelude?}
    K -->|Yes| L[runInContext prelude]
    K -->|No| M[addMultilineHandler]
    L --> M
    M --> N{opts.historyFile?}
    N -->|Yes| O[addHistory]
    N -->|No| P[REPL準備完了]
    O --> P
    P --> Q[ユーザー入力待ち]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-38-01 | Node.jsバージョン要件 | Node.js 6以上が必要 | 常時 |
| BR-38-02 | グローバルコンテキスト | CLIからの起動時はuseGlobal: yesでグローバルコンテキストを使用 | CLI起動時 |
| BR-38-03 | 結果変数 | 評価結果は`__`変数に代入される | 常時 |
| BR-38-04 | トップレベルawait | async関数外でもawaitが使用可能 | 常時 |
| BR-38-05 | 履歴ファイルパス | XDG_CACHE_HOMEまたはHOMEディレクトリの`.coffee_history` | 履歴有効時 |
| BR-38-06 | 履歴最大サイズ | デフォルト10240バイト | 履歴有効時 |

### 計算ロジック

**カスタムeval関数のロジック**:
```coffeescript
# 入力の正規化
input = input.replace /\uFF00/g, '\n'                    # マルチラインハック
input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'        # 括弧除去
input = input.replace /^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1'  # try/catch除去

# トークン化とAST生成
tokens = CoffeeScript.tokens input
ast = CoffeeScript.nodes(tokens).body

# 結果変数への代入を追加
ast = new Block [new Assign (new Value new Literal '__'), ast, '=']

# クロージャでラップ（トップレベルawait対応）
ast = new Code [], ast
isAsync = ast.isAsync
ast = new Root new Block [new Call ast]

# コンパイルと実行
js = ast.compile {bare: yes, locals: Object.keys(context), ...}
result = runInContext js, context, filename

# async結果の処理
if isAsync
  result.then (resolvedResult) -> cb null, resolvedResult
else
  cb null, result
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バージョンエラー | Node.js 6未満 | Node.jsをアップグレード |
| SYNTAX_ERROR | SyntaxError | CoffeeScript構文エラー | コードを修正 |
| - | 実行時エラー | JavaScript実行時エラー | コードを修正 |
| - | Babelエラー | Babelが見つからない（--transpile時） | @babel/coreをインストール |

### リトライ仕様

REPLはエラー後も継続して入力を受け付ける（対話型のため）。

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

該当なし。

## パフォーマンス要件

- 対話的なレスポンス（即座のフィードバック）
- 履歴読み込みは最大10KBに制限

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

- REPLは任意のコードを実行できるため、信頼できる環境でのみ使用
- グローバルコンテキスト使用時は、グローバル変数の汚染に注意

## 備考

**replDefaults設定**:
```coffeescript
replDefaults =
  prompt: 'coffee> '
  historyFile: path.join(historyPath, '.coffee_history')
  historyMaxInputSize: 10240
  eval: (input, context, filename, cb) -> ...
```

**プロンプト表示例**:
```
coffee> x = 1 + 2
3
coffee> console.log x
3
undefined
coffee> await Promise.resolve(42)
42
```

---

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

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

### 推奨読解順序

#### Step 1: replDefaultsを理解する

まず、REPLのデフォルト設定を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | repl.coffee | `src/repl.coffee` | replDefaults定義 |

**主要処理フロー**:
- **11-69行目**: replDefaults定義
  - **12行目**: `prompt: 'coffee> '` - プロンプト文字列
  - **13-15行目**: `historyFile` - 履歴ファイルパス
  - **16行目**: `historyMaxInputSize: 10240` - 履歴最大サイズ
  - **17-69行目**: `eval` - カスタム評価関数

#### Step 2: カスタムeval関数を理解する

REPLの核心であるeval関数を確認する。

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

**主要処理フロー**:
- **17-69行目**: eval関数
- **19-25行目**: 入力の正規化
  ```coffeescript
  input = input.replace /\uFF00/g, '\n'           # マルチラインハック
  input = input.replace /^\(([\s\S]*)\n\)$/m, '$1' # 括弧除去
  input = input.replace /^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1'  # try/catch除去
  ```
- **28行目**: `{Block, Assign, Value, Literal, Call, Code, Root} = require './nodes'` - ASTノードのインポート
- **32-40行目**: トークン化とコメントトークン除去
- **42行目**: `referencedVars` - 参照変数の収集
- **44行目**: `ast = CoffeeScript.nodes(tokens).body` - AST生成
- **46行目**: `ast = new Block [new Assign (new Value new Literal '__'), ast, '=']` - 結果変数代入
- **48-51行目**: クロージャラップとasync判定
- **52行目**: `js = ast.compile {...}` - JavaScript生成
- **53-57行目**: トランスパイル処理（オプション）
- **58行目**: `result = runInContext js, context, filename` - 実行
- **60-65行目**: async結果の処理

**読解のコツ**: eval関数はNode.jsのREPLが送信する様々なラッパー（括弧、try/catch）を除去してから処理している。`\uFF00`はマルチラインモードで改行を一時的に置換するためのハック。

#### Step 3: start関数を理解する

REPLの起動処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | repl.coffee | `src/repl.coffee` | start関数 |

**主要処理フロー**:
- **174-220行目**: start関数
- **176行目**: `process.versions.node.split('.')` - バージョン取得
- **178-180行目**: バージョンチェック
- **182行目**: `CoffeeScript.register()` - requireフック登録
- **183行目**: `process.argv = ['coffee'].concat process.argv[2..]` - argv調整
- **184-211行目**: トランスパイル設定
- **212行目**: `opts = merge replDefaults, opts` - オプションマージ
- **213行目**: `repl = nodeREPL.start opts` - REPL起動
- **214行目**: `runInContext opts.prelude, ...` - プレリュード実行
- **216行目**: `addMultilineHandler repl` - マルチラインハンドラ
- **217行目**: `addHistory repl, ...` - 履歴機能

#### Step 4: runInContext関数を理解する

コード実行の仕組みを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | repl.coffee | `src/repl.coffee` | runInContext関数 |

**主要処理フロー**:
- **71-75行目**: runInContext関数
  ```coffeescript
  runInContext = (js, context, filename) ->
    if context is global
      vm.runInThisContext js, filename
    else
      vm.runInContext js, context, filename
  ```

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

```
command.coffee:run
    │
    └─ require('./repl').start(replCliOpts)
           │
           ├─ バージョンチェック
           │
           ├─ CoffeeScript.register()
           │
           ├─ process.argv調整
           │
           ├─ トランスパイル設定（オプション）
           │      └─ require('@babel/core')
           │
           ├─ merge(replDefaults, opts)
           │
           ├─ nodeREPL.start(opts)
           │      │
           │      └─ opts.eval (カスタム評価関数)
           │             │
           │             ├─ 入力正規化
           │             ├─ CoffeeScript.tokens(input)
           │             ├─ CoffeeScript.nodes(tokens)
           │             ├─ AST操作（__ 代入、クロージャラップ）
           │             ├─ ast.compile()
           │             ├─ runInContext(js, context, filename)
           │             │      └─ vm.runInThisContext / vm.runInContext
           │             └─ cb(null, result)
           │
           ├─ runInContext(opts.prelude, repl.context, 'prelude')
           │
           ├─ addMultilineHandler(repl)
           │
           └─ addHistory(repl, historyFile, historyMaxInputSize)
```

### データフロー図

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

ユーザー入力              eval関数                        結果表示
"x = 1 + 2"                  │                               │
     │                       │                               │
     ▼                       ▼                               ▼
nodeREPL が              正規化・トークン化              コンソール
入力を受け取り ───▶      CoffeeScript.tokens    ───▶    "3"
                              │
                              ▼
                         AST生成
                         CoffeeScript.nodes
                              │
                              ▼
                         AST操作
                         __ = (評価式)
                              │
                              ▼
                         コンパイル
                         ast.compile
                              │
                              ▼
                         実行
                         runInContext
                              │
                              ▼
                         cb(null, result)
                              │
                              ▼
                         nodeREPL が
                         結果を表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| repl.coffee | `src/repl.coffee` | ソース | REPL本体、start関数、eval関数 |
| command.coffee | `src/command.coffee` | ソース | REPLの呼び出し元 |
| nodes.coffee | `src/nodes.coffee` | ソース | AST操作用ノードクラス |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | tokens、nodes関数 |
| vm | Node.js組み込み | モジュール | runInContext、runInThisContext |
| repl | Node.js組み込み | モジュール | nodeREPL.start |
