ログイン
編集不可のページディスカッション情報添付ファイル
"clear/note/2011-02/21-config_loader"の差分

MMA
5と6のリビジョン間の差分
2011-02-23 22:20:15時点のリビジョン5
サイズ: 5687
編集者: clear
コメント:
2011-02-23 23:41:23時点のリビジョン6
サイズ: 5746
編集者: clear
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 1: 行 1:
## page was renamed from clear/note/2011-02/config_reader

Try & Errorの記録のような何か

動機

  • kagisys向けに設定ファイルを読み込む機構が欲しい
    • nest <-> rise間の通信に使うアドレスとかポートとか、ログファイル名とかを指定できるようにしたい

  • いつか実装する予定のウィンドウマネージャでも使いたい

設定ファイル仕様その1

  • <key>=<value>の形式

  • #で始まる行はコメント行

  • 空白のみの行は無視
  • サンプル
    # sample.conf
    
    hello=world
    pi=3.14

実装1

  • config_tableというクラスを定義

    • load(filename)で指定されたファイルから読み込み、メンバのmap<string, string>にプールする

    • get(key)でキーに対応する値(文字列)を取得

  • 実装の詳細はpimplイディオムを使って隠蔽した
    • 外部とはconst char *のやりとりのみ

実装1の問題点

  • 動くまでにパースエラーの嵐、特に空白の扱いに柔軟性がないにも程がある
    • そもそも設定ファイルの仕様が曖昧な上に、行き当たりばったりな実装をしたのが悪かった
  • 出来上がったものを良く見たら、先にだいたい出来ていたkagisysの鍵ファイル読み込み部分とほとんど変わらない
    • 空白処理の有無を除けば、だいたいmap<string, string>map<uint64_t, string>になっただけ

実装1変更案とその問題点

  • 鍵ファイルも設定ファイルも一つのクラスで読めるように変更
    • おまけとして、鍵ファイルにも空行を入れたりコメントを使うことができるようになる
  • map<string, string>を使う以上、鍵の比較はstringで行うことになり、これはスマートでない

    • 実装が隠蔽されているので、利用者はキーを丸ごと投げ込むことしか出来ない
    • 少なくとも、16バイトのIDm全体を用いて比較するのはあまりに芸が無い
  • デリミタが違う(' ' or '=')
    • ファイルの形式を統一することも考える

実装1変更案++とその問題点

  1. 読み込み時にフィルタ関数を与えてあれこれする
    • 文字列をトリムしたり
    • 結局文字列であることに代わりはない
  2. mapのイテレータを返すようにして、利用側でmap<string, string>からmap<int, string>などを作れるようにする

    • そもそも実装を隠蔽しているのでこんなことは出来ないし、mapが表に出てくるようではpimplを使う意味が無い

そうだ、namespaceで十分

  • そもそもクラスである必要があるのか
    • 利用者から見て、欲しいのは「書式に従ってファイルから読み出し、構造化する」機能に尽きる
      • 別に読んだものを抱き込んでブラックボックス化する必要はない
    • 単に「指定されたファイルから読み込んでstd::map<string, string>に放り込む関数」でいいじゃない

      • 後はトリムするなり、別のmapに変換するなり、煮るなり焼くなりご自由に

      • 面倒だけど、値の種類(文字列|数値、など)を判別してunionなり何なりを使う手もある

    • C++では、全ての関数がクラスに属していなければならないとかそんなことはない

設定ファイル仕様その2

  • BNFのような何かで書いてみた
    • <string>は長さ0以上の任意の一続きの(空白を含まない)文字列、ただし<eol>は含まない。EOFはファイル終端。一部、文字の表現にCのエスケープシーケンスを使用

    • 矛盾や不足がある可能性非常に大
      • <comment><string>であるのが不味い気がしてならない。<entry>が特に怪しい

    • デリミタは空白と'='の両方を受け付けるようにしてみたが、いずれかに限定した方が良さそう
    <line> ::= <ws-opt> <entry-opt> <comment-opt> <eol>
    <eol> ::= "\n" | EOF
    
    <ws-opt> ::= <ws> | ""
    <ws> ::= <ws-char> <ws> | <ws-char>
    <ws-char> ::= " " | "\t"
    
    <entry-opt> ::= <entry> | ""
    <entry> ::= <string> <ws-opt> <delimiter> <ws-opt> <value>
    <delimiter> ::= <ws> | "="
    <value> ::= <quoted-string> | <string>
    <quoted-string> ::= "\"" <string> "\"" | "\'" <string> "\'"
    
    <comment-opt> ::= <comment> | ""
    <comment> ::= "#" <string>

実装その2

  • できた
  • こんなのが読めるようになりました
    # sample.conf
    # #でコメント
    
    # <キー>=<値>。デリミタは指定可能
    key=value
    
    # =がない場合はスペース、タブ区切りと判断して扱う
    space separated
    # 貼り付けたらタブがタブじゃなくなったけどちゃんと読めます、一応
    tab     separated
    
    # 一続きでないと後ろは無視される。引用符が必要
    # 空白があっても、複数でもOK
    hello   =               world spam
    
            # ずれててもOK
            pi      =3.14159
    
    # 二重引用符
    config = "test.conf"
    
    # 一重引用符 & #を混ぜるテスト
    quote = 'quoted # string'
    
    # 引用符が異なれば一段のみ入れ子が可能
    with-quote='This is a "sample".'
    
    # 2段以上は残念なことになる
    zannnen = "abc'def"ghi"jkl'mno"
    
    # 空
    null = ""
    
    # 引用符の対応が取れていないとパースエラーにするようにした。以下は駄目な例
    # error = "error'
    
    # これはコメントにならない
    sharp# aaa
    
    # これはコメントになる
    sharp #aaa

clear/note/2011-02/21-config_loader (最終更新日時 2011-04-03 05:29:25 更新者 clear)