以前に、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