printf書式変換
Posted feedbacks - Nested
Flatten Hidden長々となってしまったので、まずはprintf書式→C++ストリームのコードへ変換するプログラムだけ投稿します。できれば、フラグ・精度・フィールド幅・変換修飾子なしといったサブセットだと楽なんですが。書式の解析は以前自分が書いたプログラムを基にしていますが、正規表現でやった方がスマートにできるんでしょうかね。
一番下のmain関数を見れば分かるように、PrintfFormatToStreamCodeには%dのような書式を1つ引数に与えます。すると、次のように、C++ストリームを使った等価なコードとテスト用のprintfを並べた文字列を返します:
cout << value << endl;
printf("%d\n", value);
フラグ指定のうち、空白文字("% d": "-5", " 5"のように正数のとき符号文字の場所に空白を置く)は対応するものが分からなかったので無視しています。
あと、%ldや%Lgなどのlong型やlong double型などの変換修飾子もC++ストリームでは多重定義で解決されるので出力には反映されていません(つまり、このプログラムでは、%ldも%dと同じ出力になります)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | #include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
enum FormatFlags
{
None = 0x0, //指定無し
Sign = 0x1, // +
Blank = 0x2, // 空白
Zero = 0x4, // 0
LeftSide = 0x8, // 左揃え
Alt = 0x10, // # 代替表記
Cap = 0x20, // 大文字化 [aefgx]→[AEFGX]にする
};
inline FormatFlags operator |(FormatFlags lhs, FormatFlags rhs)
{
return static_cast<FormatFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
}
inline FormatFlags& operator |=(FormatFlags& lhs, FormatFlags rhs)
{
lhs = lhs | rhs;
return lhs;
}
// フィールド幅、精度用の数値読取
// fmt[0]が*のときにはparamsから1つ読み取る。
// そうでなく、fmtに数字(先頭に-符号があっても可)が並んでいれば、それを読み取る。
bool ReadInt(const char*& fmt, int& ret)
{
char* p;
ret = static_cast<int>(std::strtol(fmt, &p, 10));
if (fmt != p)
{
fmt = p;
return true;
}
else
{
return false;
}
}
// フラグ指定の読み込み
// 読み込みが完了したらtrue、
// 読み取り中に文字列が終了した(ヌル文字が現れた)らfalseを返す。
bool ReadFlags(const char*& fmt, FormatFlags& flags)
{
for (;;)
{
switch (fmt[0])
{
case '#':
flags |= Alt;
break;
case '0':
flags |= Zero;
break;
case ' ': // 空白
flags |= Blank;
break;
case '+':
flags |= Sign;
break;
case '-':
flags |= LeftSide;
break;
case '\0':
return false;
default:
goto exit_for;
}
++fmt;
}
exit_for:
return true;
}
// フィールド幅指定の読み込み
void ReadFieldWidth(const char*& fmt, unsigned& fieldWidth, FormatFlags& flags)
{
int t;
if (ReadInt(fmt, t))
{
fieldWidth = (unsigned)t;
}
else
{
fieldWidth = 0;
}
}
// 精度の読み込み
// 読み取った精度の値を返す。。指定がなかったときには、DWORD_MAX。
unsigned ReadPrecision(const char*& fmt)
{
unsigned precision;
if (fmt[0] == '.')
{
++fmt;
int t;
if (ReadInt(fmt, t))
{
if (t > 0)
{
precision = (unsigned)t;
}
}
}
else
{
precision = UINT_MAX;
}
return precision;
}
#ifdef _WIN64
const int PtrLength = 1; // l相当
#else
const int PtrLength = 0; // 無相当
#endif
// 長さ指定の読み込み
void ReadLength(const char*& fmt, int& lengthSpec)
{
for (;;)
{
switch (fmt[0])
{
case 'l':
lengthSpec++;
break;
case 'h':
lengthSpec--;
break;
case 'j': // (u)intmax_t = QWord, Int64
lengthSpec = 1;
break;
case 't': // ptrdiff_t
lengthSpec = PtrLength;
break;
case 'z': // (s)size_t
lengthSpec = PtrLength;
break;
case 'p': // 本来は変換指定子だが、ここで先読み
lengthSpec = PtrLength;
return; //fmtを進められると困るので、ここで脱出
default:
return;
}
fmt++;
}
}
std::string PrintfFormatToStreamCode(const char* format)
{
const char* fmt = format;
std::ostringstream s;
s << "cout";
if (fmt[0] != '%')
{
throw std::exception("_");
}
++fmt;
// フラグの読取
FormatFlags flags = None;
if (ReadFlags(fmt, flags) == false)
{
return "";
}
// フィールド幅
unsigned fieldWidth;
ReadFieldWidth(fmt, fieldWidth, flags);
// 精度
auto precision = ReadPrecision(fmt);
// 幅指定の読取
int typeWidth;
ReadLength(fmt, typeWidth);
bool isFloat = false;
switch (fmt[0])
{
case 'd': // dとiは同じ
case 'i':
case 'u':
s << " << dec";
break;
case 'O':
flags |= Cap;
case 'o':
s << " << oct";
break;
case 'X':
flags |= Cap;
case 'x':
s << " << hex";
break;
case 'p':
break;
case 'E':
flags |= Cap;
case 'e':
s << " << scientific";
isFloat = true;
break;
case 'F':
flags |= Cap;
case 'f':
s << " << fixed";
isFloat = true;
break;
case 'G':
flags |= Cap;
case 'g':
isFloat = true;
break;
case 'A':
flags |= Cap;
case 'a':
s << " << hexfloat";
isFloat = true;
break;
case 's':
break;
case 'c':
break;
// case 'n':
case '%':
return "s << '%'";
default:
throw std::invalid_argument("");
}
if (flags & Sign)
{
s << " << showpos";
}
if (flags & Blank)
{
// 対応無しだと思う
}
if (flags & Zero)
{
// -フラグ付きのとき、整数型で精度指定のあるとき、0フラグは無視される。
// 無視条件に当てはまらないときだけ0フラグ相当の指定を出力。
if ((flags & LeftSide) == 0 && (isFloat != false || precision == UINT_MAX))
{
s << " << setfill('0')";
}
}
if (flags & LeftSide)
{
s << " << left";
}
if (flags & Alt)
{
s << " << showbase << showpoint";
}
if (flags & Cap)
{
s << " << uppercase";
}
if (fieldWidth > 0)
{
s << " << setw(" << fieldWidth << ')';
}
if (precision != UINT_MAX)
{
if (isFloat)
{
s << " << setprecision(" << precision << ')';
}
else
{
s << " << setfill('0') << setw(" << precision << ')';
}
}
s << " << value << endl;\n";
exit_for:
s << "printf(\"" << format << "\\n\", value);";
return s.str();
}
int main()
{
std::cout << PrintfFormatToStreamCode("%s") << std::endl;
std::cout << PrintfFormatToStreamCode("%+d") << std::endl;
std::cout << PrintfFormatToStreamCode("%10p") << std::endl;
std::cout << PrintfFormatToStreamCode("%.4x") << std::endl;
std::cout << PrintfFormatToStreamCode("%-10.5g") << std::endl;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | #include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <cctype>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
using namespace std;
template<typename T>
void numericCommonFormat(ostream& s, ostream& os)
{
ios_base::fmtflags f = os.flags();
if (f & ios_base::showpos)
{
s << '+';
}
if (f & ios_base::showbase)
{
s << '#';
}
if (f & ios_base::left)
{
s << '-';
}
else
{
if (os.fill() == '0')
{
s << '0';
}
}
streamsize w = os.width();
if (w != 0)
{
s << w;
}
if (boost::is_floating_point<T>::value)
{
streamsize p = os.precision();
if (p >= 0)
{
s << '.' << p;
}
}
}
// 整数型用
template<typename T>
std::string StreamToPrintfFormat(std::ostream& os, T value,
typename boost::enable_if<boost::is_integral<T>>::type* = 0)
{
ostringstream s;
s << '%';
numericCommonFormat<T>(s, os);
ios_base::fmtflags f = os.flags();
if (boost::is_same<T, long>::value || boost::is_same<T, unsigned long>::value)
{
s << 'l';
}
else if (boost::is_same<T, long long>::value
|| boost::is_same<T, unsigned long long>::value)
{
s << "ll";
}
if (boost::is_same<T, char>::value)
{
s << 'c';
}
else if (f & ios_base::hex)
{
if (f & ios_base::uppercase)
{
s << 'X';
}
else
{
s << 'x';
}
}
else if (f & ios_base::oct)
{
s << 'o';
}
else
{
if (boost::is_unsigned<T>::value)
{
s << 'u';
}
else
{
s << 'd';
}
}
return s.str();
}
// 浮動小数点型用
template<typename T>
std::string StreamToPrintfFormat(std::ostream& os, T value,
typename boost::enable_if<boost::is_floating_point<T>>::type* = 0)
{
using namespace std;
ostringstream s;
s << '%';
numericCommonFormat<T>(s, os);
if (boost::is_same<T, long double>::value)
{
s << 'L';
}
ios_base::fmtflags f = os.flags();
char type;
if (f & ios_base::fixed)
{
type = 'f';
}
else if (f & ios_base::hexfloat)
{
type = 'a';
}
else if (f & ios_base::scientific)
{
type = 'e';
}
else
{
type = 'g';
}
if (f & ios_base::uppercase)
{
type = std::isupper(type);
}
s << type;
return s.str();
}
template<typename T>
std::string StreamToPrintfFormat(std::ostream& os, T value, ...)
{
using namespace std;
ostringstream s;
s << '%';
ios_base::fmtflags f = os.flags();
if (f & ios_base::left)
{
s << '-';
}
streamsize w = os.width();
if (w != 0)
{
s << w;
}
if (boost::is_same<T, void*>::value)
{
s << 'p';
}
else if (boost::is_same<T, const char*>::value)
{
s << 's';
}
return s.str();
}
int main()
{
std::ostringstream dummy;
std::cout << StreamToPrintfFormat(dummy, 1) << std::endl;
std::cout << StreamToPrintfFormat(dummy, ' ') << std::endl;
std::cout << StreamToPrintfFormat(
dummy << std::hex, static_cast<long long>(7)) << std::endl;
dummy.flags(0);
std::cout << StreamToPrintfFormat(dummy << std::left << setw(6), 42u) << std::endl;
dummy.flags(0);;
std::cout << StreamToPrintfFormat(
dummy << std::left << setw(6) << setprecision(3), 123.456e78L) << std::endl;
dummy.flags(0);
std::cout << StreamToPrintfFormat(dummy << std::setw(10), "abc") << std::endl;
}
|
http://ja.doukaku.org/91/ と似ていますね。
Rにはprintfそのものはありませんが、sprintf()やformatC()と言ったC言語の書式フォーマット系関数のwrapperが存在します。
1 2 3 4 5 6 | > pi
[1] 3.141593
> sprintf("%.2f", pi)
[1] "3.14"
> formatC(pi, format="f", digits=2)
[1] "3.14"
|
COBOL --> Java移植案件とか聞いたことがあるが、そういう場合はどう作業しているのだろう? PIC文<--->printf系書式 とか。
移植するのはデータのみで、COBOLデータを Oracle などの DB に変換したら、あとはSQLでごりごりっとやるJavaプログラムを新たに設計する感じ。
データの移植も各社が出してるパッケージソフトを使ったり、Cで作ったりします。
すみません雑談でした。
なるほどー。 勉強になりました。 ありがとうございます。
Squeak Smalltalk で。#format: の引数として使える文字列を変換します。抜け落ちてしまう書式情報は配列で返させました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | | formatStr format out options idx |
formatStr := '%s, %s %d, %.2d:%.2d'.
format := formatStr readStream.
out := String new writeStream.
options := OrderedCollection new.
idx :=0.
[format atEnd] whileFalse: [
| opt |
out nextPutAll: (format upTo: $%).
opt := String new writeStream.
format atEnd ifFalse: [
format peek = $% ifTrue: [out nextPut: format next] ifFalse: [
[('-+ #0' includes: format peek)] whileTrue: [opt nextPut: format next].
format peek isDigit ifTrue: [
[format peek isDigit] whileTrue: [opt nextPut: format next]].
format peek == $. ifTrue: [
opt nextPut: format next.
[format peek isDigit] whileTrue: [opt nextPut: format next]].
('hL' includes: format peek) ifTrue: [opt nextPut: format next].
opt nextPut: format next].
options add: opt contents.
out nextPutAll: '{', (idx := idx + 1) printString, '}']].
^{out contents. options asArray} "=> #('{1}, {2} {3}, {4}:{5}' #('s' 's' 'd' '.2d' '.2d')) "
|
あくまでも書式の変換と考えて実装してみました。Cのprintf()関数ファミリーの書式をjava.util.Formatterの書式に変換します。C99の書式を受け付けるようにしましたが、Javaでは意味を持たないp, m変換には対応していません。書式の変換だけでは実現不可能な "*" による幅、精度の指定とn変換にも対応していません。
その代わり、"$" による引数の指定には対応しています。
対応しない書式を指定された場合でも例外にはしていません。java.util.Formatterは厳密に書式のチェックを行っているため、Formatterのチェックに任せたほうが得策と判断したためです。
#結果として、「長さ修飾子」を削っているだけです。:-)
尚、逆変換は行いません。引数の「長さ」によってCの書式は変わりますが、java.util.Formatterの書式は引数の長さに依存しません。従って引数の型が決まらなければCの書式は決まらないからです(そもそも、Cの型システムに強く依存したprintfファミリーの書式に変換する事は無意味と考えます)。
厳密な意味での変換を考えるのであれば、言語のトランスレータを作らなければ不可能でしょう。実用性を考えるなら対応する書式を制限するか、逆に必要性を充分満たすよう定義した書式を処理する独自Formatterを作るべきだと考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import java.util.regex.*;
public class Sample {
static final Pattern CFORMAT = Pattern.compile(
"(%\\P{Alpha}*)([hlLqjzt]*)([diouxXeEfFgGaAcCsS])");
public static String c2java(String fmt) {
return CFORMAT.matcher(fmt).replaceAll("$1$3");
}
public static void main(String[] args) {
System.out.println(c2java("%s: %#llX%%"));
System.out.println(c2java("%1$s, %3$d. %2$s, %4$d:%5$.2d"));
System.out.println(c2java("pi = %.5f"));
}
|
1 | def printf(format, *args): print format % args,
|
そのままですね
1 | printf("%d", 3)
|
この問題を解くには、「あなたの言語(今回はGroovy)のprintf系書式」が何かを考える必要があります。
GroovyにはもちろんJavaのprintfがありますが、これはGroovyらしい書式機能とは言えません。控えめに言ってもこれは「Javaのprintf系書式機能」でしょう。
ということで、Groovyらしい書式機能とは何かを真剣に考えまして、それはやっぱりクロージャだろう、ということで、C言語の書式を「対応する書式変換をあらわすクロージャ」に変換するプログラムを実装してみました。
printf ("書式指定", a,b,c)のとき、
Closure clos = format2closure("書式指定")
のように変換すると、clos.call(a,b,c)が書式変換の結果を返します。
逆変換はありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | def format2closure(fmt) {
return { Object...arglist->
def n = 0
fmt.replaceAll (/%(-?[0-9]*)([sd])/) {m0,m1,m2 ->
def value = arglist[n++].toString()
if (m1.size() != 0) {
def w = Integer.parseInt(m1)
if (w < 0) {
value = value.padRight(-w)
}
else if (w > 0) {
value = value.padLeft(w)
}
}
value
}
}
}
assert format2closure("AAA%10sBBB").call("abc") == "AAA abcBBB"
assert format2closure("AAA%-10sBBB").call("abc") == "AAAabc BBB"
assert format2closure("AAA%10dBBB").call(10) == "AAA 10BBB"
assert format2closure("AAA%-10dBBB").call(10) == "AAA10 BBB"
assert format2closure("%s,%d").call("a",1) == "a,1"
|





yamamoto
#9136()
Rating-10/12=-0.83
1 reply [ reply ]