以前に、std::iostream に代わる小規模なクラスの紹介をしました。
その中で、std::iostream に馴染めなくて、printf 形式が忘れられない人の為に、「boost::format.hpp」を紹介しました。
しかしながら、「boost::format.hpp」は、std::iostream に依存している為、そのままでは、結局リソースを大量に消費してしまい、小規模な組み込みマイコンでは使えません。
そこで、機能を絞った簡易的な format クラスに相当する物を実装してみましたので紹介します。
※機能が足りなければ、自分で拡張する事も出来ると思います。
※本家では、エラーの場合は、例外がスローされますが、それでは使いにくいと思い、エラー関数クラスでハンドリングするようにしています。
※「例外」をスローさせたい場合は、エラー関数から、例外を投げれば良いと思います。
※組み込みマイコン向けに、A/D 変換などの値(整数)を、10進表示する場合に小数点位置を指定して、それを簡単に表示できるようなフォーマットも用意しました。
このように使います。 int x = 1095; int y = 123; utils::format<output>("Pos: %d, %d\n") % x % y; Pos: 1095, 123 int adv = 257; utils::format<output>("A/D Ch0: %2.4:8y\n") % adv; /// 2.4 ---> 実数2桁、小数4桁。 /// :8 ---> 小数点以下8ビットとして扱う。 A/D Ch0: 1.0039 ここで「output」は、文字の出力クラスで、以下のような定義を行います。 struct output { void operator() (char ch) { serial_out_(ch); ///< ターミナルへ文字出力 } }; 「operator()」を定義する事で、以下のように関数オブジェクトとして使えます。 output o; o('a'); ///< 'a' を出力 o('\n'); ///< 改行を出力 ※「operator()」を「public」にする為、あえて、「struct」としています。
さて、実際の実装ですが、まず format の設計方針を決めます。
・名前空間を「utils」とします。
・float の表示は、基本的に行わない事とします。(今後コンパイルオプションで切り替える)
・整数計算のみを使い、巨大にならないよう配慮する。
・クラッシュは論外としても、きめ細かいエラーのハンドリングは省略する。(必要なら追加する事も可能)
・printf のフォーマットに近い仕様を網羅する。
・2進、8進、16進表示を行う。
・ゼロサプレスの制御
・有効表示数の制御
・オートフォーマットは未サポートとする。
format の中
・フォーマット文字列をスキャンして「%」以下の書式を読み取る。 void next_() { if(form_ == 0) { err_(error_case::NULL_PTR); return; } char ch; bool fm = false; bool point = false; bool ppos = false; uint8_t n = 0; while((ch = *form_++) != 0) { if(fm) { if(ch == '+') { sign_ = true; // 符号付きの場合 } else if(ch >= '0' && ch <= '9') { if(n == 0 && ch == '0') { zerosupp_ = true; // 最初の数字が「0」なら、0サプレスしない。 } else if(point || ppos) { if(point) { decimal_ *= 10; decimal_ += static_cast(ch - '0'); } else { ppos_ *= 10; ppos_ += static_cast (ch - '0'); } } else { real_ *= 10; real_ += static_cast (ch - '0'); } ++n; } else if(ch == '.') { ppos = false; point = true; } else if(ch == ':') { ppos = true; point = false; } else if(ch == 's') { mode_ = mode::STR; return; } else if(ch == 'c') { mode_ = mode::CHA; return; } else if(ch == 'b') { mode_ = mode::BINARY; return; #ifdef WITH_OCTAL_FORMAT } else if(ch == 'o') { mode_ = mode::OCTAL; return; #endif } else if(ch == 'd') { mode_ = mode::DECIMAL; return; } else if(ch == 'u') { mode_ = mode::U_DECIMAL; return; } else if(ch == 'x') { mode_ = mode::HEX; return; } else if(ch == 'X') { mode_ = mode::HEX_CAPS; return; } else if(ch == 'y') { mode_ = mode::FIXED_REAL; return; #if defined(WITH_FLOAT_FORMAT) | defined(WITH_DOUBLE_FORMAT) } else if(ch == 'f' || ch == 'F') { mode_ = mode::REAL; return; } else if(ch == 'e' || ch == 'E') { mode_ = mode::EXPONENT; return; } else if(ch == 'g' || ch == 'G') { mode_ = mode::REAL_AUTO; return; #endif } else if(ch == '%') { out_(ch); fm = false; } else { err_(error_case::UNKNOWN_TYPE); return; } } else if(ch == '%') { fm = true; // フォーマットの開始を検出! } else { out_(ch); // フォーマットに関係しない文字は、そのまま出力 } } }
・オペレーター「%」を定義する // この定義では、int 型の値が代入された場合の挙動を記述します。 // 事前に format 文字列の中をスキャン(next_() 関数)して、「%」を見つけ、それに続く「型」を「mode_」に格納しておきます。 format& operator % (int val) { if(mode_ == mode::BINARY) { out_bin_(val); } else if(mode_ == mode::OCTAL) { out_oct_(val); } else if(mode_ == mode::DECIMAL) { out_dec_(val); } else if(mode_ == mode::HEX) { out_hex_(static_cast(val), 'a'); } else if(mode_ == mode::HEX_CAPS) { out_hex_(static_cast (val), 'A'); } else if(mode_ == mode::FIXED_REAL) { if(decimal_ == 0) decimal_ = 3; out_fixed_point_(val, ppos_); } else { err_(error_case::DIFFERENT_TYPE); } reset_(); // 変数をリセット next_(); // 「%」のスキャンを再始動 return *this; }
※これらはソースの一部です。
「%」オペレーターでは、「int」型、「unsigned int」型、「const char*」型など、色々な型を定義してあり、コンパイラが適合する型を選択して呼び出してくれます。
組み込みマイコンでは、A/D 変換した値(大抵、電圧や電流値)を、小数点以下まで表示させたい場合があります、そこで、「y」フォーマットを用意しておきました、これは、整数値を固定小数として扱い、小数点以下も変換して表示します、小数点の位置は「:x」として自由に設定できます。(最大28ビット)
※浮動小数点が扱えない場合などに重宝します。
たとえば、12ビットのA/Dコンバーターで、基準電圧を2.5V(4096)の場合で、A/D入力に1/5の電圧が分圧される場合は、以下のようになります。
uint32_t adv = get_adc(); utils::format<output>("A/D Chanel: %2.3:13y\n") % (adv * 25); // 2.5 * 5 * 2 // 2.5 * 5 ---> 12.5 なので、さらに倍にして、小数点以下を12に1を加えて13ビットとする。
※8進数は、あまり使わないと思うので、コンパイルオプションとしました。(リソースの節約)
※逆に、2進数表示は大抵必要なので、「%b」フォーマットを追加してあります。
※浮動小数点は、実装中です、仕様が複雑なので、今後の対応、課題とします。
最終的なソースコードは、format.hpp ここにあります。
※いつもの github