# 機能設計書 48-eval関数

## 概要

本ドキュメントは、CoffeeScriptコードを評価して結果を返すeval関数の設計を記載する。

### 本機能の処理概要

eval関数は、CoffeeScriptのソースコード文字列をコンパイルし、Node.jsのVMモジュールを使用してサンドボックス環境またはグローバル環境で評価・実行し、その結果を返す機能である。CoffeeScript REPLの入力評価や、動的コード実行のユースケースで使用される。

**業務上の目的・背景**：対話型開発環境（REPL）やコードの動的実行において、CoffeeScriptコードを評価してその結果を取得したいケースがある。eval関数は、サンドボックス化された安全な実行環境を提供しつつ、評価結果を返すことで、REPLでのインタラクティブな開発体験やスクリプトの動的評価を可能にする。

**機能の利用シーン**：
- CoffeeScript REPLでユーザー入力を評価するとき
- 動的に生成されたCoffeeScriptコードを実行するとき
- テスト環境でコードスニペットを評価するとき
- 設定ファイルをCoffeeScriptで記述して評価するとき

**主要な処理内容**：
1. 入力コードの前後空白をトリム
2. サンドボックスコンテキストの作成（オプション）
3. `__filename`、`__dirname`、`module`、`require`の設定
4. CoffeeScriptをbareモードでコンパイル
5. vm.runInContext または vm.runInThisContext で実行
6. 評価結果を返す

**関連システム・外部連携**：
- Node.jsのVMモジュール（vm.runInContext、vm.runInThisContext）
- CoffeeScriptコンパイラ
- Node.jsのモジュールシステム（Moduleクラス）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CoffeeScript REPL | 主画面 | ユーザー入力の評価に使用 |

## 機能種別

コード評価 / 式の実行

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | string | Yes | 評価するCoffeeScriptソースコード | 文字列であること |
| options.sandbox | object | No | 実行コンテキスト（グローバル変数）。指定しない場合はglobalを使用 | オブジェクト |
| options.filename | string | No | ソースファイル名（__filenameの値）。デフォルト: 'eval' | 文字列 |
| options.modulename | string | No | モジュール名。デフォルト: 'eval' | 文字列 |

### 入力データソース

- REPLからのユーザー入力
- プログラムから渡されるコード文字列

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | any | 評価結果。最後の式の値 |

### 出力先

関数の戻り値として呼び出し元に返却

## 処理フロー

### 処理シーケンス

```
1. コードのトリム
   └─ code.trim()で前後の空白を除去

2. 空コードチェック
   └─ 空の場合はundefinedを返す

3. サンドボックスの判定
   └─ options.sandboxがvm.isContextの場合はそのまま使用、
      それ以外の場合はcreateContextで新規作成

4. サンドボックスへの変数設定
   └─ __filename、__dirname、module、requireを設定

5. bareモードでコンパイル
   └─ o.bare = on でコンパイル

6. 実行
   └─ sandbox === global なら runInThisContext、
      それ以外なら runInContext

7. 結果を返す
```

### フローチャート

```mermaid
flowchart TD
    A["CoffeeScript.eval(code, options)"] --> B["code = code.trim()"]
    B --> C{code空?}
    C -->|Yes| D[return undefined]
    C -->|No| E{sandbox指定?}
    E -->|Yes| F{isContext?}
    E -->|No| G["sandbox = global"]
    F -->|Yes| H["sandbox = options.sandbox"]
    F -->|No| I["sandbox = createContext()"]
    I --> J["sandbox[k] = v コピー"]
    H --> K["sandbox設定"]
    J --> K
    G --> K
    K --> L["__filename, __dirname設定"]
    L --> M{sandbox != global and !module/require?}
    M -->|Yes| N["module, require設定"]
    M -->|No| O["o.bare = on"]
    N --> O
    O --> P["CoffeeScript.compile(code, o)"]
    P --> Q{sandbox === global?}
    Q -->|Yes| R["vm.runInThisContext(js)"]
    Q -->|No| S["vm.runInContext(js, sandbox)"]
    R --> T[return 結果]
    S --> T
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | bareモードコンパイル | 評価結果を返すためにbareモード（関数ラッパーなし）でコンパイル | 常時 |
| BR-02 | 空コード処理 | トリム後に空のコードはundefinedを返す | code.trim()が空の場合 |
| BR-03 | サンドボックスのコンテキスト判定 | options.sandboxがVMコンテキストかどうかを判定 | sandbox指定時 |
| BR-04 | グローバル参照設定 | sandbox.global、sandbox.root、sandbox.GLOBALにsandbox自身を設定 | sandbox使用時 |
| BR-05 | module/require自動設定 | sandboxにmodule/requireがない場合は自動で設定 | sandbox使用かつmodule未定義時 |

### 計算ロジック

`__dirname`の計算（index.coffee 84行目）:
```coffeescript
sandbox.__dirname = path.dirname sandbox.__filename
```

### サンドボックスコンテキスト判定

```coffeescript
isContext = vm.isContext ? (ctx) ->
  options.sandbox instanceof createContext().constructor
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SyntaxError | 構文エラー | CoffeeScriptコードに構文エラーがある | エラー箇所を修正 |
| ReferenceError | 参照エラー | 未定義の変数を参照 | 変数を定義するかsandboxに追加 |
| Error | 実行時エラー | コード実行中にエラー発生 | エラーメッセージを確認 |

### リトライ仕様

該当なし。エラー発生時は例外がスローされる。

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

該当なし

## パフォーマンス要件

- コンパイルと評価はeval呼び出し時に実行
- サンドボックスコンテキストの作成にはオーバーヘッドがある
- 評価時間はコードの複雑さに依存

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

- サンドボックスを使用しても、Node.jsのVMモジュールは完全な隔離を提供しない
- 信頼できないコードの評価には適さない
- サンドボックスにモジュールやrequireを設定するため、ファイルシステムへのアクセスが可能

## 備考

- eval関数はsrc/index.coffeeに定義されている（coffeescript.coffeeにはスタブのみ）
- REPLでは別途replDefaults.evalが使用されるが、内部ロジックは類似
- サンドボックスを使用しない場合（globalコンテキスト）は変数の永続化が可能

---

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

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

### 推奨読解順序

#### Step 1: eval関数の全体構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.coffee | `src/index.coffee` | eval関数の定義（66-103行目） |

**主要処理フロー**:
- **67行目**: `return unless code = code.trim()` で空コードチェック
- **68行目**: `createContext = vm.Script.createContext ? vm.createContext` でcreateContext取得
- **70-71行目**: isContextヘルパー定義
- **73-82行目**: サンドボックス判定とコンテキスト作成
- **83-84行目**: `__filename`, `__dirname`設定
- **86-95行目**: module, require設定（sandboxがglobalでない場合）
- **96-99行目**: オプションコピーと`o.bare = on`設定
- **99行目**: CoffeeScript.compile呼び出し
- **100-103行目**: vm.runInThisContext / vm.runInContext実行

**読解のコツ**:
- `?` はexistence operatorで、`a ? b` は `a` が存在すれば `a`、なければ `b`
- `for own k, v of obj` はオブジェクト自身のプロパティのみを走査
- `on` は `true` のエイリアス

#### Step 2: サンドボックス設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.coffee | `src/index.coffee` | サンドボックス判定と設定（73-82行目） |

**主要処理フロー**:
- **73行目**: `if createContext` でVMモジュールが利用可能か確認
- **74-75行目**: `isContext options.sandbox` でVMコンテキストかどうか判定
- **77-79行目**: コンテキストでない場合は新規作成してプロパティをコピー
- **80行目**: `sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox` でグローバル参照設定

#### Step 3: module/require設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.coffee | `src/index.coffee` | module/require自動設定（86-95行目） |

**主要処理フロー**:
- **86行目**: `unless sandbox isnt global or sandbox.module or sandbox.require` で設定条件判定
- **87行目**: `Module = require 'module'` でModuleクラス取得
- **88行目**: `new Module(options.modulename || 'eval')` でモジュール作成
- **89行目**: `_require = (path) -> Module._load path, _module, true` でrequire関数定義
- **91-92行目**: require関数のプロパティをコピー
- **94行目**: `Module._nodeModulePaths process.cwd()` でパス設定
- **95行目**: `_require.resolve` 定義

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

```
CoffeeScript.eval(code, options) (index.coffee:66)
    │
    ├─ code.trim() で空チェック
    │
    ├─ createContext判定
    │       │
    │       ├─ options.sandbox指定あり
    │       │       │
    │       │       ├─ isContext(sandbox) → そのまま使用
    │       │       │
    │       │       └─ !isContext → createContext() → プロパティコピー
    │       │
    │       └─ options.sandbox指定なし → global使用
    │
    ├─ __filename, __dirname設定
    │
    ├─ module, require設定（sandbox使用時）
    │       │
    │       ├─ new Module('eval')
    │       │
    │       └─ _require関数定義
    │
    ├─ CoffeeScript.compile(code, {bare: on, ...})
    │
    └─ vm.runInThisContext / vm.runInContext
           │
           └─ 評価結果を返す
```

### データフロー図

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

CoffeeScriptコード ─▶ eval(code, options)
                         │
options.sandbox ─────────┤
                         │
                         ├─▶ code.trim()
                         │
                         ├─▶ サンドボックス判定/作成
                         │       │
                         │       ▼
                         │   sandbox (global or VMコンテキスト)
                         │
                         ├─▶ __filename, __dirname設定
                         │
                         ├─▶ module, require設定
                         │
                         ├─▶ CoffeeScript.compile({bare: on})
                         │       │
                         │       ▼
                         │   JavaScript
                         │
                         └─▶ vm.runIn*Context
                                 │
                                 ▼
                           評価結果 ──▶ return
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.coffee | `src/index.coffee` | ソース | eval関数のメイン実装 |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | compile関数の実装 |
| repl.coffee | `src/repl.coffee` | ソース | REPL用のeval実装（replDefaults.eval） |
