challenge printf書式変換

C言語のprintf書式を、あなたの言語の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;
}
こっちは新たに書き起こしました。無駄に力作という点は相変わらずです。C++→printfはC++の規格票とにらめっこで分かる部分もありますが、iostreamを実装しようとしているわけではないので、適当にやり方を変えている部分もあったりします(規格票では、charの変換と数値型の変換は別個に扱われているが、このプログラムでは、charも数値型の1種として扱っているなど)。
  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 --> Java の移植と言っても、COBOLのプログラムをそのままJavaに翻訳するような無謀なことはしないと思いますよ。
移植するのはデータのみで、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"

Index

Feed

Other

Link

Pathtraq

loading...