# 機能設計書: parsecfg - 設定ファイルパーサー

## 1. 機能概要

### 1.1 目的
parsecfgモジュールは、高性能な設定ファイルパーサーを提供する。WindowsのINI形式に似た構文をサポートしつつ、Nim言語の文字列リテラル（raw文字列、トリプルクォート）も使用可能である。

### 1.2 機能範囲
- 設定ファイルの解析（ストリーム/ファイル）
- セクションとキー・バリューペアの検出
- コマンドラインオプション形式（`--key=value`）の検出
- 設定ファイルの作成・変更・保存
- Config辞書型による高レベル操作

### 1.3 関連コンポーネント
- `streams` モジュール: 入力ストリーム
- `lexbase` モジュール: 基盤となるレクサー
- `tables` モジュール: OrderedTable使用

## 2. データ構造

### 2.1 型定義

```nim
type
  CfgEventKind* = enum  ## パース中に発生するイベント
    cfgEof,             ## ファイル終端に到達
    cfgSectionStart,    ## [section] が検出された
    cfgKeyValuePair,    ## key=value ペアが検出された
    cfgOption,          ## --key=value コマンドオプションが検出された
    cfgError            ## パースエラーが発生

  CfgEvent* = object of RootObj  ## パースイベントを表すオブジェクト
    case kind*: CfgEventKind
    of cfgEof: nil
    of cfgSectionStart:
      section*: string           ## セクション名
    of cfgKeyValuePair, cfgOption:
      key*, value*: string       ## キーと値のペア
    of cfgError:
      msg*: string               ## エラーメッセージ

  CfgParser* = object of BaseLexer  ## パーサーオブジェクト
    tok: Token
    filename: string

  Config* = OrderedTableRef[string, OrderedTableRef[string, string]]
    ## 設定辞書型（セクション→キー→値のマッピング）
```

### 2.2 特徴
- **イベント駆動型**: `next()`を呼び出すごとに次のイベントを返す
- **行ベースでない**: 複数行の文字列リテラルをサポート
- **Nim文字列構文**: 通常の文字列、raw文字列（r"..."）、トリプルクォート（"""..."""）を使用可能
- **コメント**: `#` または `;` で始まる行はコメント

## 3. 主要機能

### 3.1 パーサー操作

#### open
```nim
proc open*(c: var CfgParser, input: Stream, filename: string,
           lineOffset = 0) {.rtl, extern: "npc$1".}
```
- **目的**: 入力ストリームでパーサーを初期化
- **パラメータ**:
  - `input`: 入力ストリーム
  - `filename`: エラーメッセージ用のファイル名
  - `lineOffset`: 行番号オフセット（エラーメッセージ調整用）
- **ソース位置**: `lib/pure/parsecfg.nim:225-235`

#### close
```nim
proc close*(c: var CfgParser) {.rtl, extern: "npc$1".}
```
- **目的**: パーサーを閉じる
- **ソース位置**: `lib/pure/parsecfg.nim:237-239`

#### next
```nim
proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".}
```
- **目的**: 次のイベントを取得
- **ソース位置**: `lib/pure/parsecfg.nim:469-495`

### 3.2 位置情報

```nim
proc getColumn*(c: CfgParser): int {.rtl, extern: "npc$1".}  ## 列番号
proc getLine*(c: CfgParser): int {.rtl, extern: "npc$1".}    ## 行番号
proc getFilename*(c: CfgParser): string {.rtl, extern: "npc$1".}  ## ファイル名
```
- **ソース位置**: `lib/pure/parsecfg.nim:241-251`

### 3.3 エラーメッセージ

```nim
proc errorStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".}
proc warningStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".}
proc ignoreMsg*(c: CfgParser, e: CfgEvent): string {.rtl, extern: "npc$1".}
```
- **ソース位置**: `lib/pure/parsecfg.nim:428-447`

### 3.4 Config辞書操作

#### loadConfig
```nim
proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config
proc loadConfig*(filename: string): Config
```
- **目的**: 設定ファイルをConfig辞書に読み込み
- **ソース位置**: `lib/pure/parsecfg.nim:506-551`

#### newConfig
```nim
proc newConfig*(): Config
```
- **目的**: 空のConfig辞書を作成
- **ソース位置**: `lib/pure/parsecfg.nim:501-504`

#### writeConfig
```nim
proc writeConfig*(dict: Config, stream: Stream)
proc writeConfig*(dict: Config, filename: string)
```
- **目的**: Config辞書をストリーム/ファイルに書き込み
- **ソース位置**: `lib/pure/parsecfg.nim:571-627`

#### getSectionValue
```nim
proc getSectionValue*(dict: Config, section, key: string, defaultVal = ""): string
```
- **目的**: セクション内のキーの値を取得
- **パラメータ**:
  - `section`: セクション名（""は共通セクション）
  - `key`: キー名
  - `defaultVal`: キーが存在しない場合のデフォルト値
- **ソース位置**: `lib/pure/parsecfg.nim:629-638`

#### setSectionKey
```nim
proc setSectionKey*(dict: var Config, section, key, value: string)
```
- **目的**: セクション内にキーと値を設定
- **ソース位置**: `lib/pure/parsecfg.nim:640-646`

#### delSection / delSectionKey
```nim
proc delSection*(dict: var Config, section: string)
proc delSectionKey*(dict: var Config, section, key: string)
```
- **目的**: セクションまたはキーを削除
- **ソース位置**: `lib/pure/parsecfg.nim:648-660`

## 4. 設定ファイル構文

### 4.1 基本構文

```ini
# コメント行
; これもコメント

charset = "utf-8"

[Package]
name = "hello"
version = "1.0.0"

[Author]
name = "nim-lang"
website = "nim-lang.org"

--threads:on   # コマンドラインオプション形式
```

### 4.2 文字列リテラル

```ini
# 通常の文字列（エスケープシーケンス有効）
path = "C:\\Users\\name"
message = "Line1\nLine2"

# raw文字列（エスケープなし）
regex = r"^\d+$"

# トリプルクォート（複数行）
description = """
  This is a
  multi-line string
"""
```

### 4.3 サポートされるエスケープシーケンス

| シーケンス | 意味 |
|-----------|------|
| `\n`, `\N` | 改行 |
| `\r`, `\R`, `\c`, `\C` | キャリッジリターン |
| `\l`, `\L` | ラインフィード |
| `\f`, `\F` | フォームフィード |
| `\t`, `\T` | タブ |
| `\a`, `\A` | ベル |
| `\b`, `\B` | バックスペース |
| `\v`, `\V` | 垂直タブ |
| `\e`, `\E` | エスケープ |
| `\\` | バックスラッシュ |
| `\'`, `\"` | 引用符 |
| `\xHH` | 16進数 |
| `\0-9` | 10進数 |

## 5. 使用例

### 5.1 低レベルAPI
```nim
import std/[parsecfg, streams]

var f = newFileStream("config.ini", fmRead)
var p: CfgParser
open(p, f, "config.ini")
while true:
  var e = next(p)
  case e.kind
  of cfgEof: break
  of cfgSectionStart:
    echo "Section: ", e.section
  of cfgKeyValuePair:
    echo "Key: ", e.key, " = ", e.value
  of cfgOption:
    echo "Option: ", e.key, " = ", e.value
  of cfgError:
    echo "Error: ", e.msg
close(p)
```

### 5.2 高レベルAPI
```nim
import std/parsecfg

# 読み込み
let dict = loadConfig("config.ini")
let charset = dict.getSectionValue("", "charset")
let name = dict.getSectionValue("Package", "name")

# 作成・変更
var newDict = newConfig()
newDict.setSectionKey("", "charset", "utf-8")
newDict.setSectionKey("Package", "name", "hello")
newDict.writeConfig("new_config.ini")

# 削除
var dict2 = loadConfig("config.ini")
dict2.delSectionKey("Author", "website")
dict2.writeConfig("config.ini")
```

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

### 6.1 推奨読解順序

1. **型定義** (183-216行目)
   - `CfgEventKind`, `CfgEvent`, `CfgParser`, `Config`

2. **パーサー初期化** (225-251行目)
   - `open`, `close`, 位置情報取得

3. **トークナイザー** (253-426行目)
   - `getEscapedChar`, `getString`, `getSymbol`, `rawGetTok`

4. **イベント生成** (449-495行目)
   - `getKeyValPair`, `next`

5. **Config辞書操作** (497-660行目)
   - `loadConfig`, `writeConfig`, `getSectionValue`等

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

```
open(c, input, filename) [225行目]
├── lexbase.open()
├── c.filename = filename
└── rawGetTok() [最初のトークン取得]

next(c) [469行目]
├── tkEof → cfgEof
├── tkDashDash → cfgOption
│   └── getKeyValPair()
├── tkSymbol → cfgKeyValuePair
│   └── getKeyValPair()
├── tkBracketLe → cfgSectionStart
│   ├── rawGetTok() [セクション名取得]
│   └── rawGetTok() [']' 確認]
└── その他 → cfgError

rawGetTok(c, tok) [385行目]
├── skip() [空白・コメントスキップ]
├── '=' → tkEquals
├── '-' '-' → tkDashDash
├── ':' → tkColon
├── '[' → tkBracketLe
├── ']' → tkBracketRi
├── '"' → getString()
└── その他 → getSymbol()
```

### 6.3 データフロー図

```
[設定ファイル]
       │
       ▼
┌─────────────────────────────────────┐
│         CfgParser                    │
│  ┌─────────────────────────────┐    │
│  │     BaseLexer (バッファ)     │    │
│  └───────────────┬─────────────┘    │
│                  │                   │
│  ┌───────────────▼─────────────┐    │
│  │    rawGetTok (トークン化)    │    │
│  └───────────────┬─────────────┘    │
│                  │                   │
│  ┌───────────────▼─────────────┐    │
│  │    next() (イベント生成)     │    │
│  └───────────────┬─────────────┘    │
└──────────────────┼───────────────────┘
                   │
                   ▼
             CfgEvent
                   │
     ┌─────────────┼─────────────┐
     │             │             │
     ▼             ▼             ▼
cfgSection    cfgKeyValue    cfgOption
     │             │             │
     └─────────────┼─────────────┘
                   │
                   ▼
        loadConfig() → Config辞書
```

### 6.4 読解のコツ

- **SymChars**: シンボルに使用可能な文字セット（221行目）
- **skip()**: 空白とコメントをスキップ（371-383行目）
- **区切り文字**: `=` と `:` の両方がキー・値の区切りに使用可能
- **共通セクション**: セクション名 `""` は`[section]`前のキー・値を格納

## 7. 関連ファイル一覧

| ファイルパス | 種別 | 役割 |
|-------------|------|------|
| `lib/pure/parsecfg.nim` | ソース | メイン実装 |
| `lib/pure/lexbase.nim` | ソース | 基盤レクサー |
| `lib/pure/streams.nim` | ソース | ストリーム入力 |
| `lib/pure/tables.nim` | ソース | OrderedTable |

## 8. 設計上の考慮事項

### 8.1 INI形式との違い
- 行ベースでない（複数行文字列対応）
- Nim文字列構文（raw文字列、トリプルクォート）をサポート
- コマンドラインオプション形式（`--key=value`）をサポート
- キーと値の間に `=` または `:` を使用可能

### 8.2 Config型の特徴
- `OrderedTableRef`を使用してセクションとキーの順序を保持
- 共通セクション（セクション名 ""）をサポート
- コマンドオプション（`--key`）は `"--" & key` として格納

### 8.3 書き込み時の注意
- コメントは保持されない（読み込み時に無視）
- 特殊文字を含む値は自動的に引用符で囲まれる
- 引用符を含む値はトリプルクォートで囲まれる
