...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

typedef invariant(ubyte)[] mbstring;

D言語は文字列を全面的にUnicodeとしているので,マルチバイト文字列の扱いがちょっぴり苦手だ.
Windowsコマンドプロンプトに文字列を出力するときはマルチバイト文字列に変換する必要がある.

//import std.windows.charset;
char* msg = toMBSz("へろーうぉろうど");
writefln("%.*s", msg);

また,D言語の文字列は Unicode 文字(char, wchar, dchar)の配列だ.

alias invariant(char)[] string;
alias invariant(wchar)[] wstring;
alias invariant(dchar)[] dstring;

つまり char[] を構成する要素の1つ分がちゃんと1文字分の char だ.
それなのにマルチバイト文字列は char* で扱わなければいけない.
char って UTF-8 の1文字を表すんじゃなかったの?
char* だったら UTF-8 の1文字へのポインタじゃないの?
・・・これはつまらない.


そこで,マルチバイト文字列をこのように定義してみた(マルチバイトの1文字を表す型を作って,その配列をマルチバイト文字列とする方法もありそうだ).

typedef invariant(ubyte)[] mbstring;

この型の周りにいくつかの関数を装飾して様子を見てみる.
以下それを利用する側のサンプルコード.

import std.string;
import std.stdio;
import stdex.mbstring;

void main()
{
    mbstring m = toMBS("abcあいう");
    string c = toUTF8(m);
    wstring w = toUTF16(m);
    dstring d = toUTF32(m);
    // まだ書式指定ができない
    stdex.mbstring.writefln("[%s]", c, 'X', w, 3.14, m, 123+456i, d, ["a","b"], true, "\n");
    // 現phobosでの出力結果と比較してみる
    std.stdio.writefln("[%s]", c, 'X', w, 3.14, m, 123+456i, d, ["a","b"], true, "\n");

    // おまけに文字数を見てみる
    std.stdio.writefln("abc"c.length, "abc"w.length, "abc"d.length, "/", toMBS("abc").length());
    // toMBS("abc").length と toMBS("abc").length() で結果が異なるのでこの擬似プロパティはないほうがいいかも
}

さらに stdex.mbstring モジュールの中身を以下に.

module stdex.mbstring;

import std.utf;
import std.string;
import std.c.string;
import std.c.windows.windows;

// multi byte string
typedef invariant(ubyte)[] mbstring;

/+  Convert to UTF8,16,32 from MBS
 +
 + codePage = is the number of the target codepage, or
 +   0 - ANSI,
 +   1 - OEM,
 +   2 - Mac
 +/
wstring toUTF16(mbstring s, uint codePage=0)
{
    wstring result;
    result.length = MultiByteToWideChar(codePage, 0, cast(char*)s, s.length, null, 0);
    MultiByteToWideChar(codePage, 0, cast(char*)s, s.length, cast(wchar*)result, result.length);
    return result;
}
string toUTF8(mbstring s)
{
    return std.utf.toUTF8(.toUTF16(s));
}
dstring toUTF32(mbstring s)
{
    return std.utf.toUTF32(.toUTF16(s));
}

/+ Convert to MBS from UTF8,16,32 and others
 +
 + codePage = is the number of the target codepage, or
 +   0 - ANSI,
 +   1 - OEM,
 +   2 - Mac
 +/
mbstring toMBS(T)(T arg, uint codePage=0)
{
    static if(is(T : string))
    {
        ubyte[] result;
        const(wchar*) ws = std.utf.toUTF16z(arg);
        result.length = WideCharToMultiByte(codePage, 0, ws, -1, null, 0, null, null);
        WideCharToMultiByte(codePage, 0, ws, -1, cast(char*)result, result.length, null, null);
        return cast(mbstring)result.idup;
    }
    else static if(is(T : mbstring))
    {
        return arg;
    }
    else
    {
        return .toMBS(std.string.format(arg));
    }
}

void writef(T...)(T t)
{
    foreach (arg; t)
    {
        printf("%s", .toMBS(arg).ptr);
    }
}

void writefln(T...)(T t)
{
    .writef(t, "\n");
}

uint length(mbstring s)
{
    return strlen(cast(char*)s);
}

実行結果:

[%s]abcあいうXabcあいう3.14abcあいう123+456iabcあいう[a,b]true

[abc縺ゅ>縺・]Xabc縺ゅ>縺・3.14[97 98 99 130 160 130 162 130 164 0]123+456iabc縺ゅ>縺・[a b]true

333/3

std.stdio.writef は mbstring を ubyte[] と見なしてしまうので,数字の羅列を出力してしまう.
また,Unicode 文字のまま出力するので文字化けしている.
そこで mbstring を文字列として出力し,Unicode 文字列 を引数に含んでいても文字化けしない writef を試作してみた.
ここから std.mbstring.writef で書式指定できるようにするにはどうしたらいいだろうか.
以前の std.stdio.writef の様に書式指定子の数だけ引数が消化されていくようにできたらいいのだが.
いっそ最初の引数は書式指定専用にしてしまってもいいのかな.

修正
  • alias は typedef のつもりだったので修正.