./index.html ../index.html

Ada視点でDに挑戦

D言語といえばK.INABA氏のD Memoがあまりにも有名で、そこでは C++ + Java + C# と書かれています。 僕なんぞが異を唱えるのは恐れ多いのですが…。

しかし、Adaを知っている目で見ると、どう見てもDはC++よりAdaに近い。 Adaは、ネイティブでGC装備という、D言語と同じような組み合わせの言語です。 そして、D言語の細かい機能は、一見C系を踏襲しているようですが、どう見てもPascal、Ada寄りです。

…というわけで、Pascal系好きの僕は、D言語の設計者の意向すら無視してD≒Ada+その他(Eiffel,Delphi Language)、という無謀な仮説を打ちたてるのであります。 では、Adaの視点で、超巨大なんでもかんでも詰め込み言語Dを見ていきましょう。

本当のところは、いつものように僕がD言語に挑むページです。 ただ、被らないように趣向を変えてみたまで…。 あくまで僕がD言語で色々実験したりした過程を残したものですので、これでD言語を勉強してやろうなどと思ったら、果てしなき遠まわりになることは保証しましょう。

インストール

Dはまだα版だったりします。 DMの配布ページからWin32に関係のありそうなものを落としてきて、展開して、PATHを通しておしまい。 コンパイル済みのコンパイラ好きよ。

動作確認

C:\Program Files\DM\dmd\samples\d>..\..\bin\shell all.sh
shell 1.00
\dmd\bin\dmd hello
Error: '\dmd\bin\dmd' not found

all.shに書かれているパスを書き直してもう一度。

C:\Program Files\DM\dmd\samples\d>..\..\bin\shell all.sh
shell 1.00
..\..\..\dmd\bin\dmd hello
Error: Error reading file 'object.d'


--- errorlevel 1

…むう?

僕という人間は、標準インストールパスを無視してなんでもProgram Filesに放り込むので、よくこういう目に遭います。 SC.iniはきちんと相対パスで書かれているので…想像。 ファイル名の半角空白!どうだ? そういう時はProgra~1で絶対パス指定だぁ! (勘、と書きたいが、毎回同じような目に遭っているので…)

[Version]
version=7.51 Build 020

[Environment]
LIB="%@P%\..\lib";\dm\lib
old_DFLAGS="-I%@P%\..\src\phobos"
DFLAGS="-IC:\Progra~1\dm\dmd\src\phobos"
LINKCMD=%@P%\..\..\dm\bin\link.exe
C:\Program Files\DM\dmd\samples\d>shell all.sh
shell 1.00
..\..\..\dmd\bin\dmd hello
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe hello,,,user32+kernel32/noi;

hello
hello world
args.length = 1
args[0] = 'C:\Program Files\DM\dmd\samples\d\hello.exe'

del hello.obj hello.exe hello.map
..\..\..\dmd\bin\dmd sieve
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe sieve,,,user32+kernel32/noi;

sieve
10 iterations

1899 primes
..\..\..\dmd\bin\dmd pi
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe pi,,,user32+kernel32/noi;

pi 1000
pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062
86208998628034825342117067982148086513282306647093844609550582231725359408128481
11745028410270193852110555964462294895493038196442881097566593344612847564823378
67831652712019091456485669234603486104543266482133936072602491412737245870066063
15588174881520920962829254091715364367892590360011330530548820466521384146951941
51160943305727036575959195309218611738193261179310511854807446237996274956735188
57527248912279381830119491298336733624406566430860213949463952247371907021798609
43702770539217176293176752384674818467669405132000568127145263560827785771342757
78960917363717872146844090122495343014654958537105079227968925892354201995611212
90219608640344181598136297747713099605187072113499999983729780499510597317328160
96318595024459455346908302642522308253344685035261931188171010003137838752886587
53320838142061717766914730359825349042875546873115956286388235378759375195778185
77805321712268066130019278766111959092164201884
1 seconds to compute 1000 digits of pi

..\..\..\dmd\bin\dmd dhry
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe dhry,,,user32+kernel32/noi;

dhry

Dhrystone Benchmark, Version 2.1 (Language: D)

Please give the number of runs through the benchmark:
Execution starts, 10000000 runs through Dhrystone
Execution ends

Final values of the variables used in the benchmark:

Int_Glob:            5
        should be:   5
Bool_Glob:           1
        should be:   1
Ch_1_Glob:           A
        should be:   A
Ch_2_Glob:           B
        should be:   B
Arr_1_Glob[8]:       7
        should be:   7
Arr_2_Glob[8][7]:    10000010
        should be:   Number_Of_Runs + 10
Ptr_Glob.
  Ptr_Comp:          8854096
        should be:   (implementation-dependent)
  Discr:             0
        should be:   0
  Enum_Comp:         2
        should be:   2
  Int_Comp:          17
        should be:   17
  Str_Comp:          DHRYSTONE PROGRAM, SOME STRING
        should be:   DHRYSTONE PROGRAM, SOME STRING
Next_Ptr_Glob.
  Ptr_Comp:          8854096
        should be:   (implementation-dependent), same as above
  Discr:             0
        should be:   0
  Enum_Comp:         1
        should be:   1
  Int_Comp:          18
        should be:   18
  Str_Comp:          DHRYSTONE PROGRAM, SOME STRING
        should be:   DHRYSTONE PROGRAM, SOME STRING
Int_1_Loc:           5
        should be:   5
Int_2_Loc:           13
        should be:   13
Int_3_Loc:           7
        should be:   7
Enum_Loc:            1
        should be:   1
Str_1_Loc:           DHRYSTONE PROGRAM, 1'ST STRING
        should be:   DHRYSTONE PROGRAM, 1'ST STRING
Str_2_Loc:           DHRYSTONE PROGRAM, 2'ND STRING
        should be:   DHRYSTONE PROGRAM, 2'ND STRING

Register option selected?  NO
Microseconds for one run through Dhrystone:     1.4
Dhrystones per Second:                        724165.4
VAX MIPS rating =    412.160


..\..\..\dmd\bin\dmd wc
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe wc,,,user32+kernel32/noi;

wc wc.d
   lines   words   bytes file
      50     102     919 wc.d

wc foo
   lines   words   bytes file
Error: foo: file not found

..\..\..\dmd\bin\dmd wc2
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe wc2,,,user32+kernel32/noi;

wc2 wc2.d
   lines   words   bytes file
      73     182    1443 wc2.d
--------------------------------------
 10 total
  6 n
  1 Z
  1 d
 12 cnt
  6 lu
  5 length
  1 main
  1 bytes
  6 inword
  1 stdio
  1 read
  3 for
  1 z
  1 return
  1 lines
  7 input
  5 printf
  2 import
  4 wstart
  9 i
  1 A
  2 else
  2 s
  3 file
 11 int
  6 j
  5 dictionary
  7 if
  1 a
  1 cast
  7 l
  1 words
 17 c
 10 char
  7 w
  8 word
  5 args
  4 keys

del wc2.obj wc2.exe
..\..\..\dmd\bin\dmd hello2.html
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe hello2,,,user32+kernel32/noi;

hello2
hello world
args.length = 1
args[0] = 'C:\Program Files\DM\dmd\samples\d\hello2.exe'

del hello2.obj hello2.exe
..\..\..\dmd\bin\dmd -c dserver -release

..\..\..\dmd\bin\dmd -c chello

..\..\..\dmd\bin\dmd dserver.obj chello.obj uuid.lib ole32.lib advapi32.lib kern
el32.lib user32.lib dserver.def -L/map
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe dserver+chello,,,uuid.lib+ole3
2.lib+advapi32.lib+kernel32.lib+user32.lib+user32+kernel32,dserver.def/noi/map;

..\..\..\dmd\bin\dmd dclient  ole32.lib uuid.lib
C:\Program Files\DM\dmd\bin\..\..\dm\bin\link.exe dclient,,,ole32.lib+uuid.lib+u
ser32+kernel32/noi;


C:\Program Files\DM\dmd\samples\d>

動いた動いたバンザーイ。 こんなところで躓くことが可能な奴は普通居ねえよなあ…でも、こういう隙間情報を書ける自分に、少し誇りを持ってたりするのが僕の複雑なところで…。

他には、make.exeがBorlandのと被ったり、様々な僕しか引っかからないような罠が沢山。

Dig

C:\DMDにインストールしていないとmakeできない。 まあ、それはいいとして、binに実行ファイルを放りこもうとします。 Boostもそうですけど、そういうライブラリ嫌いですね。 没。

DIDE

この程度ならまだお気に入りのエディタを使った方がいいでしょう。 ついでに全角文字を入力すると普通に落ちたので、没。

※この文章は、2003-07-25時点の情報です。 なお、DIDEはUnicode化の作業が進められています。

とりあえずサンプルソースを眺めてみる

import c.stdio;

int main(char[][] args)
{
    printf("hello world\n");
    printf("args.length = %d\n", args.length);
    for (int i = 0; i < args.length; i++)
        printf("args[%d] = '%s'\n", i, (char *)args[i]);
    return 0;
}

Cのライブラリがそのまま利用できるらしい、というわけでも無いようです。 ...\dmd\src\phobos\c\stdio.dに、extern (C):付きでずらずらあります。 というわけで、単に、Delphiの{$LINK }と同じような次元の話らしいです。 D独自のライブラリもあるっぽいのでprintfはとりあえず無視。

前々から、最初はHello Worldではなく1+1の方がいいと思ってるのですが、実践する機会に恵まれないですな…。 ともかく、このソースからわかることは、こんなとこでしょう。

というわけで、Hello Worldを見る限りは、import文がJavaらしく見えること、配列はJava的だが、それをそのまま文字列に使っているところがAda的、ぐらいで、構文そのものはクラシックなC言語にしか見えませんです。

さて、気になった点を検証していくことにしましょう。

モジュール

少し弄った結果。

識別子をモジュール名で装飾できた(MODULE.NAME)ので#includeとは明らかに違います。 ならば、と、装飾しても、importを書かないとエラーになりました。 明示が必要なこの方式は、依存関係を明確にでき、同じディレクトリに変なファイルを置いていても影響しないので僕好み。 (Ada、Pascalに近い)

とりあえず~似とか関係無く、#includeを廃し、他のファイルを勝手に見ないimportの仕様に乾杯。 これだけでもC/C++/Java/C#/Eiffelと比べて+500点。(単位は謎) いや~、C系で唯一まともなモジュール機構を持った言語じゃないかと思えてきましたよ。

…+500点は僕の想像でしかないので、リファレンスをあたります。

何々、ソースコードはUTF-8らしいですね。 コメントが /* */ /+ +/ // の三種類。 ブロックコメントが複数あるとDelphiみたく (* { } *) といったネストが可能なので気持ち便利です。 ぎゃー、C言語最強の危険仕様、8進数が残ってやがるー、嘘ー、嫌だーっ! (←さっそく関係無いとこ読んでるし)

えーと、The packages correspond to directory names in the source file path とありますので、やっぱモジュールの階層化イコールディレクトリの様です。 気軽にファイルを移動できないので僕は好きじゃないのですが、まあ考え方次第ですし、その気になればコンパイラに-Iオプションを大量に渡せばいいのでOK。

importの方は、import foo, bar;と書ける様です。 ますますもって(Pascalの)usesですな。 モジュール名がそのまま名前空間名になったり、デフォルトで装飾が不要だったり、似ています。

static変数の初期化は、importの依存関係順。 破棄は逆順。 というわけで循環参照は禁止。 ここまでくるとPascalのunit以外の何者でもないです。 initializationが見つかりませんでしたが、グローバルスコープでauto変数を宣言したら、意図した順番でコンストラクタが走ってくれそうな気がします。 static thisをモジュールレベルに置くと、依存関係に従って動作してくれます。 (僕にとってはDelphiから組み方を変えなくていいので非常に嬉しい仕様です)

階層化の他に一点だけ違いを挙げるなら、Pascalだと後からusesしたのが優先ですが、Dだと識別子が被った場合エラーの様です。割と便利にトリックとして使える仕様だったのですが…ここは安全側に倒したDを誉めるべきでしょうね。

名前空間の装飾の方は、モジュール名の他に"."で始められて、一番外側を指せるようです。

あらら、リファレンスを読んだら、+500点が+950点ぐらいになっちゃいましたよ。 (というか僕の知ってる言語で一番素晴らしいモジュール化機構かも…流石後発、というか、絶対設計者Pascal意識していいとこ取りしたでしょこれは。営業の手前、嫌われがちなPascal系言語を避けてC++だのC#だの言ってるだけで)

追記。 何故かprintfだけはデフォルトで使えます。 (α版故?それともobject.dにprintfの定義があるから?) object.dは常に自動的にimportされるからです。

なお、publicスコープに書いたimportは連鎖します。 使う側に見せたくないモジュールは、privateスコープでimportしましょう。

モジュール内private

単にprivate:でいいようです。

import mod;

int main()
{
  execute();
  return 0;
}
module mod;
import stream;

private:

public:

void execute()
{
  stdout.writeLine('OK');
}

何度でもprivate/publicを切り替えられるので、C/C++やAdaのようなヘッダー分離型、もしくはPascalのようなinterface/implementation節分離型と異なり、関連する記述が泣き別れになりません。 こういうの待ってたんですよ。 あぁ、Delphiでも何回もinterface/implementationを切り替えられるようにならないかなぁ。 (Java/C#方式の全部にアクセス制御を明記するやりかたは純粋に書くのも読むのもメンドイので却下)

配列

宣言順

リファレンスを読んでます…大英断を見ました。

array (0..3, 0..10) of Integer が、int[10][3]になります! array (0..3) of access all array (0..10) of Integer int[10]*[3]です。 完全にPascal系と逆順、型の装飾は右から順番にかかると割り切った様子。

ちなみに C/C++では、int (*A[3])[10]; ですね。ポインタ検定試験の頻出問題。 C/C++だと変数名の後ろで要素数を宣言したため、逆順にならずに済んだのですが、その代わりというかこっちの問題の方が大きいのですが、型情報が変数名を挟んで分散し、さらには変数宣言に括弧が要るので、ややこしくなると変数宣言と関数宣言を取り違えるコンパイラもあるようです。

これに対し、型の後ろに[]を持ってきたのがJavaですが、まともに右からかかるとするなら要素数が逆順になる問題を、配列を参照型にして誤魔化しました。 C#も同様。

これに対しDは、固定長配列も使えます。 逃げずに、真っ向からこの構文では逆順になる!と訴えてくれました。 これは嬉しいです。 Dでは逆順になってしまいましたが、きっとEではきちんと前から[3]*[10]intになることが期待できる大英断です。

動的配列

要素数を書かないことで、動的配列になります。 a.length = n;という具合にlengthプロパティに代入することで長さが変わります。 Java、C#同様、a = new int[n]のような書き方もできるようです。 いまひとつ参照型なのか値型なのか曖昧。

動的配列の場合気になるのがコピーオンライトの有無です。 Delphi Languageの動的配列は、Javaの配列を意識してしまったのかコピーオンライトされません。 stringはされるのに…。

ついでにAdaのunconstrained arrayを説明しておきますと、初期化時のみ長さを決定できます。 つまりは配列宣言の要素数のところに変数が書けます。 C言語(99年規格)の動的配列もこの仕様じゃありませんでしたっけね? この方法は、allocaで実現できるため、mallocが必要な、好きな時に長さを変えられる動的配列よりも効率がいいです。 反面、何度も長さを変えたい時は、長さを変える都度newしてアクセス型(ポインタ。Adaのアクセス型は不完全な型でも指すことが可能)に保持しておくなどの工夫が要ります。 文字列も同様で、別途可変長文字列としてUnbounded_Stringのようなものが用意されています。

なんかbackground-imageが泣き出しそうな展開が続いてますが…とりあえずDの動的配列はコピーオンライトされるのでしょうか? 調べてみます。

import stream;
int main(char[][] args)
{
  char[] a,b;
  a.length = 3;
  a[0] = '1';
  a[1] = '2';
  a[2] = '3';
  b = a;
  b[1] = 'b';
  stdout.writeLine(a);
  stdout.writeLine(b);
  return 0;
}
1b3
1b3

…。 されてません。コピーオンライト。 普通の発想だと、連動してはまずいと思うのですが…。 大真面目に、「配列を指すポインタ」が使える言語で配列を参照型に近づける意味はどこに!? Delphi4以来の長年の疑問が、ここに来て脹れ上がりました。

import stream;
int main(char[][] args)
{
  char[3] a = "123";
  char[] b;
  b = a;
  b[1] = 'b';
  stdout.writeLine(a);
  stdout.writeLine(b);
  return 0;
}
1b3
1b3

固定長配列から動的配列へ代入できます。 これは嬉しい点です。 …が、ここでも連動!?何故!?

動的配列は完全にポインタか? 固定長配列同士で代入しようとすると、cannot change reference to static array 'b'と言われます。 …本当に本当の様です。 リファレンスを読んだ結果、動的配列はポインタと長さの組、と思っていい様です。 lengthへの代入は、新たな配列をnewして今まであった中身をコピー、のようです。

文字列

Dの文字列は文字の配列です。 長さは配列長であり、C言語のルーチンに渡さない限り'\0'では終わりません。 この点はAdaに似てます。

import stream;
int main(char[][] args)
{
  stdout.writeLine("ABCD\0EFG");
  return 0;
}
ABCD EFG

関係無いですが、クリップボードのCF_TEXTは'\0'で終わるので、コマンドプロンプトから結果をコピーしてきたらEFGが消えてました…。

C言語のルーチンに渡す時はchar*へキャストできます。 ちなみにAdaならA(A'First)'Accessのように先頭要素のアクセス値を渡しましたが、同様にして&A[0]でも型はchar*になるのでこれでもいいと思われます。

import C.stdio;
int main(char[][] args)
{
  printf("args.length = %d\n", args.length);
  for (int i = 0; i < args.length; i++)
    printf("args[%d] = '%s'\n", i, &args[i][0]);
  return 0;
}
...>hello kazu !
args.length = 3
args[0] = '...\hello.exe'
args[1] = 'kazu'
args[2] = '!'

うん。 問題無いですね。

…後から考えれば、a = &a[0] ってC言語では当然の読み替えだった気がする…と思って、&も[0]もキャストも何も付けずに、args[i]だけで試したのですが、コンパイルは通るのですが(printfの引数は型チェックされないので当然か)、何故かアクセスバイオレーションが出ます。 それから、文字列の最後に"\0"が付いていることは期待できませんので、%sで表示してしまっている上のコードは全部危険ですね。argsに限ってはランタイムが付けてくれているのかも知れませんが。

添え字範囲

さて、僕が、Dを見て、一番最初にAdaだと思ったところをやります。

import stream;
int main(char[][] args)
{
  char[10] a = "0123456789";
  stdout.writeLine(a[1..4]);
  stdout.writeLine(a[0..1] ~ a[8..9]);
  return 0;
}
123
08

+をoverloadせず、専用の配列結合演算子があること。(Adaは&) 添え字に範囲を書くことで、配列の部分を抜き出せること。

Adaとの違いとしては、[1..4]と書いたら、Adaなら(1,2,3,4)番目の要素が対象になるのですが、Dの場合(1,2,3)番目の要素が対象になるようです。 最後が含まれない。 C系らしいですけどね。

import stream;
int main(char[][] args)
{
  char[] a = "0123456789";
  char[] b;
  b = a[3..8];
  b[3] = 'a';
  stdout.writeLine(a);
  stdout.writeLine(b);
  return 0;
}
012345a789
345a7

範囲を抜き出した時も連動します。 Adaでunconstrained arrayをprocedureのin outパラメータに渡した時の動作に近いですね。 Delphiの動的配列ですと、Copyで部分を抜いてきた場合、連動しませんから。 連動するにしても、徹底して連動させる姿勢は、感服します。 ここまで徹底してくれるなら、もう連動でいいやー、みたいな。

複写

連動を断ち切りたい時は.dupでコピーが得られます。 最初、括弧無しでメソッドが呼べるのかと勘違いして試して見ましたが、呼べませんでした。 dupはプロパティの様です。 (プロパティの読み出しにコピーという動作を割り当てるセンスは疑問…GCあるからいいのか)

class

とにかく試してみました。

import stream;

class Base
{
  abstract void execute();
}

alias Base function() Create;
alias char[] ID;

class A : Base
{
  void execute() { stdout.writeLine('xxx'); };
  const ID id = 'A0001';
  static Base create() { return new A(); }
}

class B : Base
{
  void execute() { stdout.writeLine('yyy'); };
  const ID id = 'B0001';
  static Base create() { return new B(); }
}

struct Table
{
  Create create;
  ID id;
}

Table[] table = [{&A.create, A.id}, {&B.create, B.id}];

int main()
{
  for(int i = 0; i < table.length; ++i)
  {
    Base obj = table[i].create();
    stdout.writeString(table[i].id);
    obj.execute();
  }
  return 0;
}
A0001xxx
B0001yyy

classは参照型、structは値型。 デフォルトはpublic:、ついでにconstはstaticのようです。 ひとつ大きな点として、private:と書いても、それはObject Pascalのprivateスコープを意味します。 同じファイル内なら全部見える、というやつ。 クラス外にあるものをクラス中で宣言しなければいけない厄介なfriendを消せる、手軽な仕様です。 (background-imageをアテナに変えた方がいいような気がしてきた…)

あと配列定数が [ ] で囲む様になっています。 配列や構造体に要素名を付けての初期化も可能で、その辺はAda的。 C99的とも言う。 (ex. [0: {create: &A.create, id: A.id}, 1: {create: &B.create, id: B.id}] )

classの前にabstractと書くと全メソッドabstractのクラスに、finalと書くと効果無しです。 (final class A … から継承できる) α版だからかどうかは知りません。

命名規則は、型名は大文字からPascal風、モジュール、メソッドや変数名は小文字からJava風。 僕はC系ではC++のkomoji_lowerが好きだったのですけどね…。 ただし、標準ライブラリを見てもかなりいい加減なので、絶対とは程遠いようです。

で…まあ、なんつーか、Try&Errorしてたのですが、コンパイラのエラーメッセージとか、いかにもα版ですね。 改善に期待したいです。

それから、functionとdelegateは非互換でした。 functionはグローバル関数やstaticメソッド、delegateは非スタティックメソッドや関数内関数が対象です。 delegateの方が幅広い概念なのに、全部delegateにできません。 僕が書いたQueenですら解決していたのに、情けないぞDigital Mars。(←おいこら)

※この文章は、2003-07-27時点の情報です。

ラベル付きbreakがあります。

import stream;

int main()
{
  LABEL: for(;;)
  {
    stdout.writeLine("↓");

    for(int i = 0; i < 99; ++i)
      if(i == 50)
        break LABEL;
        
    stdout.writeLine("↑");
  }

  return 0;
}

全角文字を使ってみたのですが、ソースコードをマニュアル通りBOM付きUTF-8にしたら、化けました。 SHIFT-JISですと上手くいきます。 多分ライブラリが未熟なせいと思いますが…。

集合

bit配列はパックされる様子なので、どこまでPascalの集合型を真似られるか実験してみましょう。 AdaはBoolean配列にpragma packで指示すれば、集合型をほとんど完全に実現できます。 DもAda同様文字列を素の配列そのままで表現できるほど配列が強力な言語。 ならば…。

import stream, windows;
extern(Windows) int IsDBCSLeadByte(char c);

int main()
{
  bit[char] lead_bytes;
  stdout.writeLine(toString(lead_bytes.size));
  for(int i = 0; i <= 255; ++i)
    if(IsDBCSLeadByte(i) != 0) lead_bytes[i] = true;
  //char[] s = stdin.readLine();
  char[] s = 'abcdeあいうえお';
  while(s != '')
  {
    if(s[0] in lead_bytes)
    {
      stdout.writeLine(s[0..2]);
      s = s[2..s.length];
    }
    else
    {
      stdout.writeLine(s[0..1]);
      s = s[1..s.length];
    }
  }
  const bit[256] a = [(char)'A': 1, (char)'B': 1, (char)'C': 1];
  const bit[256] b = [(char)'A': 1, (char)'C': 1, (char)'E': 1];
  stdout.writeLine(toString(a.size));
  for(int i = 'A'; i <= 'Z'; ++i)
    stdout.writeString(toString(a[i]));
  stdout.writeLine('');
  for(int i = 'A'; i <= 'Z'; ++i)
    stdout.writeString(toString(b[i]));
  stdout.writeLine('');
  bit[256] c;
  c[] = a[] && b[];
  for(int i = 'A'; i <= 'Z'; ++i)
    stdout.writeString(toString(c[i]));
  
  return 0;
}
8 ←連想配列のサイズは8
a
b
c
d
e
あ
い
う
え
お
35 ←256/8で32のはずだけど…それになんで奇数?
11100000000000000000000000
10101000000000000000000000
11111111111111111111111111 ←???集合演算は構文上通るけど動かない

というわけで、非常に惜しい結果に終わりました。 (四日目にしていきなりキワモノ実験に走ったとか言わないでね…)

直接関係無いですけど、readLineで全角文字を入力しようとするとエラーになります。 やはり入出力系は中途半端らしい。

契約

あまり好きな話でもないのですが、避けては通れないので一応書いておきます。

Eiffelが走りっぽいとあちらこちらで囁かれていますので、特に疑うことも無く信じています。 でもEiffelは昔少し試したのですが、OOPに徹した結果使いにくくなったJavaという印象。 (実際には逆。Javaが基本的なところはEiffelを模倣しつつ色々妥協した)

EiffelのDBCは、この辺に和訳があります。

掻い摘まんでてきとーに説明しますと、要するに言語組み込みのAssert文が色々な形で書けるわけです。 単なるAssertですとvirtual関数の中に書いたりするとoverride先で無視されたりしますが、構文の一部にされているのでそんな心配は無かったり。 もちろんコンパイルオプションでON/OFF可能。

class ...
feature
  ...
  METHOD : RESULT_TYPE is
  require
    CHECK_NAME : BOOLEAN_EXPRESSION;
    ...
  do
    ...
  ensure
    CHECK_NAME : BOOLEAN_EXPRESSION;
    ...
  end
  ...
invariant
  BOOLEAN_EXPRESSION;
  ...
end

invariantは制御文にも付けられたりします。 variantってのもありますが、ループ文が終了することを保証するためのものですので、ここでは触れません。(PascalやAdaのfor文のように、最初にループ範囲を決め打ってしまうってだけの話です)

class ...
{
  ...
  RESULT_TYPE METHOD()
  in
  {
    ...
  }
  out(result)
  {
    ...
  }
  body
  {
    ...
  }
  ...
  invariant()
  {
    ...
  }
}

Dで書くとこうなります。 Eiffelは返値はResult変数に代入しますが、DはC系の伝統でreturnで返しますので、事後条件のところでは引数の様にして返値を受け取っています。 (ラベル付きbreakを導入したところで、いい加減returnやめればいいのに) 後は、式の羅列ではなく、文を書くようになっています。 実際にはAssertを並べることになりますが、デバッグログを出力することもできる寸法です。 invariantもメソッドになっています。 Eiffelは式に名前を付けて、多分どの式が失敗したのかとか情報を得たり、式ごとにON/OFFしたりもできたようなできなかったような…(曖昧)…その辺は、文が書けるのだからいいようにやれ、ということでしょうね。

Assertを実行するタイミングが、従来はプログラムの流れに依存していたのが、確実に与えられるようになった、それだけ。 (理論とかは僕の手に余りますが、機能だけ見ればそういうことでしょ?)

template

Dのtemplateはグループ単位です。 Adaのpackage単位genericとよく似てます。 is <>のぶんだけAdaのほうが柔軟性はありますが、特殊化ができるぶんDのほうが高機能です。 特殊化ができるのでメタプログラミングができます、といってもC++ほど大したことはできません。

template factorial(int x)
{
  instance factorial(x - 1) m;
  int value () { return x * m.value(); }
}

template factorial(int x : 1)
{
  int value () { return 1; }
}

int main()
{
  instance factorial(5) fact;
  printf("%d", fact.value());
  return 0;
}

いきなりこんな例かよ…ってのはさておき、const int value = ... にしますと、怒られました。 templateのconstはconstとして見てくれないようです。

template factorial(int x)
{
  instance factorial(x - 1) m;
  enum {value = x * m.value}
}

template factorial(int x : 1)
{
  enum {value = 1}
}

int main()
{
  instance factorial(5) fact;
  printf("%d", fact.value);
  return 0;
}

enumならOK。 無名のenumも使えるようです。 こんなとこまでC++を真似なくても、constを完全にコンパイル時に評価してくれればそれでいいのに…と思いますが、templateにはconstじゃなくてenum!ってのはDでも有効な様子。

8月12日の出来事

冒頭で紹介したK.INABAさんが、リファレンスの日本語訳を作ってくださってます。 ありがたく活用しましょう。 …はいいのですが、「日本語訳について」のところ、何故このページにリンクが!?

コンパイラ本体もバージョンアップされてます。 …はいいのですが、文字列リテラルがシングルクオーテーション(')からバッククオーテーション(`)に変更されてるぎゃーっ! charと文字列が同じ記法というのは、char*まで考えたら無理があるケースがあるのは、Pascalで度々遭遇してますから、やっぱ別にしよう、ってのは理解できなくもないですし、@とか付けられなかっただけマシといえばマシなんですが… 僕はprintfを除きほぼすべて(')で書いてきましたから、書き直しが大変でした。

Version 0.69

文字列リテラルがシングルクオーテーション(')からバッククオーテーション(`)に変更されました。 charと文字列が同じ記法というのは、char*まで考えたら無理があるケースがあるのは、Pascalで度々遭遇してますから、やっぱ別にしよう、ってのは理解できなくもないですし、@とか付けられなかっただけマシといえばマシなんですが… 僕はprintfを除きほぼすべて(')で書いてきましたから、書き直しが大変でした。

D言語でもコルーチン

まず、こっちを先に読んでください

D言語にはtemplateがあるので、値を汎用Pointer型で受け渡すような真似は不要です。 GCもあるので、interfaceで参照カウンタなんてのも不要です。 delegateが関数内関数を指せるので、EBPの受け渡しも不要です。 ああDって便利だな。 あと、KAISA_Progressionはprogression_of_differencesに直してあります。 ああ恥ずかしい。

import windows, stream, string;

/* API declaration */
extern(Windows)
{
  alias size_t SIZE_T;
  alias VOID function(PVOID lpParameter) LPFIBER_START_ROUTINE;

  LPVOID CreateFiber(
    SIZE_T dwStackSize,                   // initial stack size
    LPFIBER_START_ROUTINE lpStartAddress, // fiber function
    LPVOID lpParameter                    // fiber argument
  );
  
  LPVOID ConvertThreadToFiber(
    LPVOID lpParameter  // fiber data for new fiber
  );
  
  VOID SwitchToFiber(
    LPVOID lpFiber  // fiber to schedule
  );
  
  VOID DeleteFiber(
    LPVOID lpFiber   // pointer to the fiber to delete
  );
}

/* coroutine */
template coroutine(Input, Output)
{
  alias void delegate(Output result) Coreturn;
  alias void delegate(Input argument, Coreturn coreturn) Procedure;

  class Abort : Exception
  {
    this() { super(``); }
  };

  class Coroutine : Object
  {
    this(Procedure procedure, Input argument)
    {
      this.procedure = procedure;
      this.argument = argument;
      this.fiber = CreateFiber(0, win_fiber, cast(void*)(this));
      this.caller = ConvertThreadToFiber(null);
      this.abort = false;
    }
    
    ~this()
    {
      abort = true;
      SwitchToFiber(fiber);
      DeleteFiber(fiber);
    }
    
    Output execute()
    {
      SwitchToFiber(fiber);
      return result;
    }
    
  private:
    Procedure procedure;
    Input argument;
    Output result;
    void* fiber;
    void* caller;
    bit abort;

    void coreturn(Output result)
    {
      this.result = result;
      SwitchToFiber(caller);
      if(abort) throw new Abort();
    }
  }

  extern(Windows) void win_fiber(void *data)
  {
    Coroutine self = cast(Coroutine)(data);
    try{
      self.procedure(self.argument, self.coreturn);
    }catch(Abort){
      ;
    }
    for(;;){
      SwitchToFiber(self.caller);
    }
  }
}

/* test */
int main(char[][] args)
{
  instance coroutine(int, int) integer_progression;
  
  void progression_of_differences
    (int initial, integer_progression.Coreturn coreturn)
  {
    int value = initial;
    for(;;){
      coreturn(value);
      value *= 3;
    }
  }
  
  integer_progression.Procedure p = progression_of_differences;
  integer_progression.Coroutine c = new integer_progression.Coroutine(p, 1);
  for(int i = 1; i <= 10; ++i){
    stdout.writeLine(toString(c.execute()));
  }
  
  return 0;
}

追記: ファイバーのスタックは、GCに引っかからないため、上記コードは、危険です。 実際無理に使うなら、ローカル変数を使ってはダメです…といっても、with文なんかも内部的にローカル変 数なんだろーなー。 くわばらくわばら。

動的配列とstruct

struct a
{
  a[] f;
}

…いやね、Delphiだとレコード型宣言が終了しないと動的配列の要素にすら使えないものですからね。 つくづくDっていいなあ、と。

Rubyモドキ

import stream;

class Foo
{
  char[] file_name;
  this(char[] file_name) { this.file_name = file_name; }

  int apply(int delegate(inout File f) block)
  {
    auto File file = new File(file_name);
    return block(file);
  }
}

int main()
{
  foreach(inout File f; new Foo(`a.d`)){
    stdout.writeLine(f.readLine());
  }
  return 0;
}

foreachでRubyのマネ。 autoがあるから要らないってわかっていても、やはり一度はやってみたいとうずうず。

delegateが返すbreakを意味するマジックナンバーってなんでしょうね。 未調査ですが、勘だけで書くと、ラベル付きbreak/continueのための飛び先アドレスじゃないかな。 僕が作るならforeach中からの脱出は例外で実装しますけどね。 あれ?delegate中で例外を投げたら…?

よし、調査だ。

import stream;

class Foo
{
  char[] file_name;
  this(char[] file_name){ this.file_name = file_name; }

  int apply(int delegate(inout File f) block)
  {
    try{
      auto File file = new File(file_name);
      int magic = block(file);
      printf("通るか?\n");
      return magic;
    }finally{
      printf("ふぁいなりー\n");
    }
  }
}

int main()
{
  try{
    foreach(inout File f; new Foo(`a.d`)){
      stdout.writeLine(f.readLine());
      //throw new Exception(`だー`);
      break;
    }
  }catch(Exception){
    printf("受け止め\n");
  }
  return 0;
}

break

...>a
import stream;
通るか?
ふぁいなりー

throw

...>a
import stream;
ふぁいなりー
受け止め

なにはともあれ例外も有効みたいで良かった良かった。

version 0.73

プロパティー!!

これで(予告されていた)欲しい言語機能は全て出揃った。 後はIDEを待つのみです。

現状デバッグシンボル付けてもTurboDebuggerではシンボル見えないし…。 やはりここはDigitalMars Dが発売されるしか!

プロパティに紛れて、嬉しい機能が追加されています。

class A
{
  int prop() { return 1; }
  void prop(int value) { printf("%d", value); }
}

int func()
{
  return 3;
}

int main()
{
  A a = new A();
  int v = a.prop;
  a.prop = v;
  int c = func;
  a.prop = func;
  return 0;
}

ふふ、わかりませんか? 引数が無ければ括弧が無くても関数が呼べるのです!

Pascal系に慣れてると空の括弧をタイプするのは正直ややめんどくさかったですからね。 定数←→関数←→プロパティの使う側での気にしなくてOK度がぐんと上がりました。 こうなると.dupも気にならなくなりますね。 Dですとtemplateがあるので、これは構文糖以上の意味があります。 AdaやEiffelでも、定数と関数の使う側での互換は強調されてた記憶が。 特にEiffelでは、スーパークラスの定数をサブクラスで関数でオーバーライドするようなこともできたような記憶があります。 (記憶が、と書いてあるのは、間違ってるかも知れませんよ~という意味です^^)

余波で、アドレスが欲しい時は&演算子必須になってました。

version 0.74

Walter様が、Unicode識別子使用可能で日本語使えるぜ!(意訳)と息巻いておられるのですが、ソースコードはSHIFT-JISにしておかないと文字列リテラルがUTF-8化されてしまいprintfやstdout.writeLineで化けますので、意味無いですな。

ええ。ええ。そうですよ。 どーせ僕は、相手が日本語読めないと知ってるからこそこんな口が叩ける小心者ですよ。 フォーラムの方に、拙い英語で報告しときましたから、勘弁してつかーさい。

version 0.75

ライブラリが階層化されました!

ついでに、しつこく主張しただけあって、ライブラリ中のほとんどのimportがprivate化されました。 標準以外のwindows.dを自然に併用できたり、stdoutがstreamとc.stdioで衝突しなくなったりと、色々嬉しいです。 一部漏れ(random.dとthread.dがプラットフォーム用のモジュールをpublic importしている)を見つけましたが、報告済みです。 GREPかけただけで見つかるようなの、どうです?ねえ?

あと、foreachで複数の引数を返せるのと。 メソッド名が apply から opApply になっています。

ソースコードをUTF-8にしたときの日本語の文字化けは改善されず。 SHIFT-JISをUnicodeに変換する方法を聞いてたのはなんだったのだろう。

メタプログラミング

ニュースグループで書式化出力が話題になっているようなので、それっぽいのを作ろうとして、全然違うものができてしまったという話。

template filling(tchar, int args : 0)
{
  struct Value
  {
    tchar[] str;

    instance filling(tchar, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
  }
  struct Start
  {
    private alias Value V; //avoid for compiling error
    static Value div(tchar[] s)
    {
      V result;
      result.str = s;
      return result;
    }
  }
}

template filling(tchar, int args : 1)
{
  struct Value
  {
    tchar[] str;

    instance filling(tchar, args - 1) predecessor;
    predecessor.Value* pred;
    
    instance filling(tchar, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
    
    tchar[] or(tchar[] s)
    {
      tchar[] result = new tchar[pred.str.length + s.length + str.length];
      result[0..pred.str.length] = pred.str[];
      result[pred.str.length..pred.str.length + s.length] = s[];
      result[pred.str.length + s.length..result.length] = str[];
      return result;
    }

    tchar[] build(tchar[][1] a)
    {
      return pred.str ~ a[0] ~ str;
    }
  }
}

template filling(tchar, int args)
{
  struct Value
  {
    tchar[] str;

    instance filling(tchar, args - 1) predecessor;
    predecessor.Value* pred;
    
    instance filling(tchar, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
    
    instance filled(tchar, Value, args, args - 1) made;
    made.Value or(tchar[] s)
    {
      made.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }

    tchar[] build(tchar[][args] a)
    {
      tchar[][args - 1] b;
      b[] = a[0..args - 1];
      return pred.build(b) ~ a[args - 1] ~ str;
    }
  }
}

template filling(tchar, int args : 10)
{
  struct Value
  {
    tchar[] str;
    
    instance filling(tchar, args - 1) predecessor;
    predecessor.Value* pred;

    instance filled(tchar, Value, args, args - 1) made;
    made.Value or(tchar[] s)
    {
      made.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }

    tchar[] build(tchar[][args] a)
    {
      tchar[][args - 1] b;
      b[] = a[0..args - 1];
      return pred.build(b) ~ a[args - 1] ~ str;
    }
  }
}

template filled(tchar, predecessor, int args, int rest : 1)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;
  
    tchar[] or(tchar[] s)
    {
      tchar[][2] a;
      a[0] = str;
      a[1] = s;
      return pred.build(a);
    }
  }
}

template filled(tchar, predecessor, int args, int rest)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;

    instance filled(tchar, Value, args, rest - 1) next;
    next.Value or(tchar[] s)
    {
      next.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
    
    tchar[] build(tchar[][rest] a)
    {
      tchar[][rest + 1] b;
      b[0] = str;
      b[1..rest + 1] = a[];
      return pred.build(b);
    }
  }
}

instance filling(char, 0) string_filling;
instance filling(wchar, 0) wstring_filling;

alias string_filling.Start F;

unittest
{
  printf("- string fill\n");
  assert((F/"A"%"C" | "B")  == "ABC");
  //printf("%.*s\n", F/"A"%"C"%"E" | "B" | "D");
  assert((F/"A"%"C"%"E" | "B" | "D")  == "ABCDE");
  //printf("%.*s\n", F/"A"%"C"%"E"%"G" | "B" | "D" | "F");
  assert((F/"A"%"C"%"E"%"G" | "B" | "D" | "F")  == "ABCDEFG");
}

要するに文字列の連結をしているだけですが、書いた順では無くて、%演算子で連結した所へ、後から挿入する形です。

文字列連結として用意された~演算子を避けたのは、二項演算子の結合強度で選んだからです。 特に%にはprintfへのメタファーがありますし。 開始に用いる演算子は、%と同じ結合強度を持つ/*のどちらにするかで悩みましたが、結局見た目で/採用。 |は、&&+=の類を除けば、最弱の演算子なので、選択です。

C++で良く不適切な演算子オーバーロードといわれる<<よりも、更に不適切ですね。

何故こんなのを書いてみたかと申しますと、単に型と個数がチェックされるprintfが欲しかったからです。 printfのあまり言われないメリットとして、値が展開される場所には直接式を書かずに、%とそれに続く数文字だけが置かれるため、全体としてどういう文字列になるかの見通しが良いことがあると思います。 なので、これは、書式化はさておき、printfの見通しの良さだけを、タイプセーフにやろうというtemplateです。

効率は無視ですが、最後に文字列を連結している箇所以外はヒープへのメモリ割り当てがされないように書きましたし、メタプログラミングの効果で制御文が全く無いため、インライン展開されれば、ふつーに順番に~で連結するのと比べて、無駄な配列操作が入る程度でそう大差無いはず…。

書き終わってから、お、これ、Adaでもできないか!?と、少し嬉しくなったのですが、Adaは強型に加えて他の名前空間の演算子オーバーロードを使用するためにuse type宣言が必要なので、すげー面倒なことになりそう。 上のコードでは上限10にしてますが、結局テンプレートのインスタンスが…えーと…11(filling)+45(filled)で56個必要なわけで、それ全てにuseを書く羽目になりそうな予感。 SGLでも感じた事ですが、言語が厳密過ぎるとメタプログラミングは逆に難しくなりますね…。

〃改良版

改良版

template filling(tchar, predecessor, int args : 0)
{
  struct Value
  {
    tchar[] str;

    instance filling(tchar, Value, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
  }
  struct Start
  {
    private alias Value V; //avoid for compiling error
    static Value div(tchar[] s)
    {
      V result;
      result.str = s;
      return result;
    }
  }
}

template filling(tchar, predecessor, int args : 1)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;
    
    instance filling(tchar, Value, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
    
    tchar[] or(tchar[] s)
    {
      tchar[] result = new tchar[pred.str.length + s.length + str.length];
      result[0..pred.str.length] = pred.str[];
      result[pred.str.length..pred.str.length + s.length] = s[];
      result[pred.str.length + s.length..result.length] = str[];
      return result;
    }
  }
}

template filling(tchar, predecessor, int args)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;
    
    instance filling(tchar, Value, args + 1) more;
    more.Value mod(tchar[] s)
    {
      more.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
    
    instance filled(tchar, args, Value, args - 1) made;
    made.Value or(tchar[] s)
    {
      made.Value result;
      result.str = s;
      result.pred = this;
      result.last = this;
      return result;
    }
  }
}

template filling(tchar, predecessor, int args : 10)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;

    instance filled(tchar, args, Value, args - 1) made;
    made.Value or(tchar[] s)
    {
      made.Value result;
      result.str = s;
      result.pred = this;
      return result;
    }
  }
}

template filled(tchar, int args, predecessor, int rest : 0)
{
  struct Value
  {
    tchar[] str;
    predecessor* pred;
  }
}

template filled(tchar, int args, predecessor, int rest : 1)
{
  struct Value
  {
    instance filled(tchar, args, Value, 0) next;

    tchar[] str;
    predecessor* pred;
    void* last;
  
    instance builder(tchar, args) b;
    tchar[] or(tchar[] s)
    {
      next.Value n;
      n.str = s;
      n.pred = this;
      return b.build(last, &n);
    }
  }
}

template filled(tchar, int args, predecessor, int rest)
{
  struct Value
  {
    instance filled(tchar, args, Value, rest - 1) next;

    tchar[] str;
    predecessor* pred;
    void* last;

    next.Value or(tchar[] s)
    {
      next.Value result;
      result.str = s;
      result.pred = this;
      result.last = last;
      return result;
    }
  }
}

template builder(tchar, int args : 1)
{
  struct filling_image 
  {
    tchar[] str;
    filling_image* pred;
  }

  tchar[] build(void* o, void* i)
  {
    return (cast(filling_image*)(o)).pred.str ~ 
      (cast(filling_image*)(i)).str ~ 
      (cast(filling_image*)(o)).str;
  }
}

template builder(tchar, int args)
{
  struct filling_image 
  {
    tchar[] str;
    filling_image* pred;
  }

  instance builder(tchar, args - 1) b;

  tchar[] build(void* o, void* i)
  {
    //printf("%p,%p,", o, i);
    return 
      b.build(
        (cast(filling_image*)(o)).pred, 
        (cast(filling_image*)(i)).pred
      ) ~
      (cast(filling_image*)(i)).str ~ 
      (cast(filling_image*)(o)).str;
  }
}

instance filling(char, void, 0) string_filling;
instance filling(wchar, void, 0) wstring_filling;

何をつまらん事を長々と、とお思いでしょうが、改良版です。 配列で渡すのをやめたので少し効率いいはず。 buildの引数がvoid*なのは、実につまらん理由なのですが、テンプレートのインスタンスが大量に生成されるのを防ぐため…。 filledのValueのlastがvoid*なのは、テンプレート引数で型を渡してくると、コンパイラが内部エラー吐きやがるため…。 dmd、まだまだα版です。

困ったのは、structのメンバとしてaliasとか書いても、それを受け取った先で使えない事…。 評価のタイミングがよくわからんです。

11月23日の出来事

attohttpDにwindows.h翻訳が使われました…実際にアプリで使用されたのは初めてなのですげー嬉しいです。

bit[]を無理やりuintにキャストしてビットフィールド(兼Pascalの集合型、AdaのBoolean配列)として使えるか使えないかが微妙なところ。実験を重ねてから報告します。

version 0.76

importのprivate化の0.75での漏れ修正と、演算子オーバーロードがopXXXに統一されたのと。

それと、ニュースグループの方で予告は無かったと記憶しているのですが、is演算子が新設されました。 D言語、ますますPascal系に近くなってきております。 …残念な事に、ObjectPascalそのままに型の判定には使えないようですけどね。 あくまで===と同じ意味。

delete

どっかでGC装備なのに明示的に解放もできるAdaみたいなのは珍しい、と書いた記憶があるのですが…どこだったかな? まあいいか。

明示的なdeleteが動いてるっぽいので、載せときます。 (読み直したらリファレンスにも書かれてました)

class A
{
  ~this()
  {
    printf("die.\n");
  }
}

int main()
{
  A a = new A();
  printf("before delete.\n");
  delete a;
  printf("after delete.\n");

  return 0;
}
...>dele
before delete.
die.
after delete.

AdaのUnchecked_Deallocationのサンプルも併記しようかとも一瞬思ったのですが、面倒なので割愛します。

そういや、連想配列の要素削除もdeleteでしたね…。

int main()
{
  A[int] a;
  a[0] = new A();
  printf("before delete.\n");
  delete a[0];
  printf("after delete.\n");

  return 0;
}
...>dele
before delete.
after delete.
die.

連想配列の要素をdeleteしても、参照先はその場では解放されませんね。 覚えてなきゃ後々ハマりそうな仕様です。 僕なんかは、.sortとか要らんもの付けるぐらいなら、(どうせ用意するなら昇順降順、比較関数ぐらいオプションで渡させてくれれば便利なんですが)連想配列に.removeとか付けて区別すればいいのに、なんて思ってしまいますけれども…。

集合再び

こんな感じか!?

template bit_set(_Element)
{
  //alias instance minimal(0, (1 << (_Element.max - _Element.min + 1)) - 1).T Int_Set;
  alias uint Int_Set; //don't think greater than 32 bit

  struct T
  {
    bit[_Element.max - _Element.min + 1] set;

    bit has(_Element e)
    {
      return set[e - _Element.min];
    }

    void include(_Element e)
    {
      set[e - _Element.min] = true;
    }

    void exclude(_Element e)
    {
      set[e - _Element.min] = false;
    }
    
    int opApply(int delegate(inout _Element) block)
    {
      _Element i = _Element.min;
      for(;;){
        if(set[i - _Element.min]){
          int result = block(i);
          if(result) return result;
        }
        if(i == _Element.max) break;
        ++i;
      }
      return 0;
    }
    
    T opAnd(T r)
    {
      T result;
      *cast(Int_Set*)(&result.set) = value() & r.value();
      return result;
    }
    
    T opOr(T r)
    {
      T result;
      *cast(Int_Set*)(&result.set) = value() | r.value();
      return result;
    }

    Int_Set value()
    {
      return *(cast(Int_Set*)&set);
    }
  }
}

unittest
{
  printf("- bit_set\n");
  enum E { e1, e2, e3, e4, e5, e6, e7, e8 }
  instance bit_set(E) eset;
  alias eset.Int_Set eset_i_t;
  eset.T s;
  s.include(E.e1);
  assert(s.has(E.e1));
  assert(s.value == 1);
  s.include(E.e3);
  assert(s.value == 5);
  eset.T t;
  t.include(E.e3);
  t.include(E.e5);
  eset.T r = s & t;
  assert(r.value == 4);
  r = s | t;
  assert(r.value == 21);
}

minimalというのは指定範囲を格納可能な最小の整数型を返すテンプレートです。 centもまだ使えない事ですし、Pascal同様にするならば、charの集合が格納可能な256ビット整数型が欲しい所ですが、とりあえず32ビット以上は考えないことにして、割愛。 幸いにして、というか、データサイズ無駄だろ、というか、bitの固定長配列には、何故か後ろに4バイトの無駄なゼロ領域が確保されるみたい(eset.T.size == 5!)ですので、今のとこuint決め打ちでも問題ないと…。 いつかは通用しなくなりそうですが…。

多倍長計算を自前で、と言ってもこの場合、andとorしか不要なので、instance bit_set(char)に対応しても、かなり楽な筈というかforで回すだけなので、また今度改良します。

opApplyは、Cのforをそのまま使うと、_Elementがビット幅を限界まで使う型の場合終わらなくなりますので、こんな風にしてます。 PascalやAdaのfor文の実装の真似、です。 突入チェックを省いてますけど。

ごめんなさい。 上記動作はdmdのバグでした。 よって、↑はバグに依存したコードです。 さらに、素のビット配列は、ビット演算子を使えるようになるそうなので、こんなラッパーは不要です。

overload

procedure Overload is

  package A is
    procedure Proc(A: Integer);
  end;

  package body A is
    procedure Proc(A: Integer) is
    begin
      null;
    end;
  end;

  package B is
    procedure Proc(A: String);
  end;

  package body B is
    procedure Proc(A: String) is
    begin
      null;
    end;
  end;

  use A;
  use B;
begin
  Proc(0);
  Proc("");
end Overload;
D:\Programming\Tests\Ada\overload>gnatmake overload.adb
gcc -c overload.adb
gnatbind -x overload.ali
gnatlink overload.ali

あー、コンパイル出来てしまいましたなー。

…何がやりたかったかといいますと、Dで、モジュールを超えたオーバーロードは、明示的にprivate aliasでも書かない限り、関数以外の識別子が被った時同様に衝突エラーなのですが、Adaはどーなってたっけなー、と。

尤も、Adaの場合、明示的にuse宣言が必要ということもあるので、プログラマが使うぞと言ってんだから使えないのはおかしいだろー、な感じかもしれませんね…。 あと、Adaは、演算子と通常関数を構文以外で区別していないので、これがダメだと演算子のオーバーロードが事実上使えなくなる、というのもあるかもしれません。

それにしても、Dは、随分安全側に倒してあるなー、って感じです。 うん、頼もしい。 std.stringに知らないうちにformat関数なんて追加された時も、検知してくれましたし。 (それまで自分でformatって名前の関数を作ってました。仕様が微妙に違ってて、コンパイラがこういう振る舞いじゃなきゃえらい目に遭ってたかも)

知らない人がいたらいけないので一応書いておきますと、

private alias module_A.func func;
private alias module_B.func func;

のように、異なるモジュールから同じ名前でalias宣言した場合、オーバーロードを有効にできます。

12月15日の出来事

経緯は省きますが、ここ数日、委譲の話に漬かってたわけですが…絶妙のタイミングと言うしか! 朝から意味不明にひとり部屋の中でへらへら笑っては踊り続けてます。

ところで、interfaceからのダウンキャスト、やっぱバグでした。 調査を怠ってはいけませんね…。 詳細はD Wayにて…混乱させてごめんなさいです。

文字コード

DのコンパイラはUnicode文字列/識別子を理解する→ソースコードはUnicodeで書く→文字列リテラルもUnicode→char[]に格納した場合はUTF-8→*.A系Windows APIは地域依存の文字コード(日本ならSJIS)を受け取る→直接渡せない

まずUTF-16にして.*W系APIに挑戦して、エラー返されたらWideCharToMultiByteでMBCS化して.*Aに渡すべしです。 素で渡されて来たchar[]をそのまま.*Aに渡すのは、厳禁なのですが、非常に多くの方々がやられてるどころかPhobosでも全部そうなってしまっているのが痛いところで…

…どうも(Walter様も含めてMBCS=UTF8圏の方々は)よくわかっておられないようですので、何かの形でこの注意事項を投稿したいのだが…説明し切る自信が無い!!

リスト

D Wayのほうで、GCを利用した関数型言語風のimmutableな単方向リンクリストのほうが向いているんじゃないか?と書きました。 で…書いてみました。

//---- Haskell-like-linked-list --------

//version = pure;

template list(_Element)
{

  alias _Element Element;

  struct T
  {
    Item* item;
    
    Element opIndex(int index)
    {
      version(pure){
        assert(item != null);
        if(index == 0){
          return item.value;
        }else{
          return item.next.opIndex(index - 1);
        }
      }else{
        int rest = index;
        T result = *this;
        while(rest > 0){
          assert(result.item != null);
          result = result.item.next;
          --rest;
        }
        assert(result.item != null);
        return result.item.value;
      }
    }
    
    int length()
    {
      version(pure){
        if(item == null){
          return 0;
        }else{
          return item.next.length + 1;
        }
      }else{
        int result = 0;
        for(T i = *this; i.item != null; i = i.item.next){
          ++result;
        }
        return result;
      }
    }
  }

  struct Item
  {
    T next;
    _Element value;
  }
  
  T invalid(){ return T.init; }
  
  T make(Element e1)
  {
    T result;
    result.item = new Item;
    result.item.value = e1;
    return result;
  }
  
  T make(Element[] es)
  {
    if(es.length == 0){
      return T.init;
    }else{
      T result;
      result.item = new Item;
      result.item.value = es[0];
      result.item.next = make(es[1..es.length]);
      return result;
    }
  }
  
  T make(Element e, T next)
  {
    T result;
    result.item = new Item;
    result.item.value = e;
    result.item.next = next;
    return result;
  }
  
  T findl(T list, Element e)
  {
    version(pure){
      if(list.item == null){
        return T.init;
      }else if(list.item.value == e){
        return list;
      }else{
        return findl(list.item.next, e);
      }
    }else{
      for(T i = list; i.item != null; i = i.item.next){
        if(i.item.value == e){
          return i;
        }
      }
      return T.init;
    }
  }

  //Haskell-like functions

  Element head(T list)
  in{
    assert(list.item != null);
  }
  body{
    return list.item.value;
  }
  
  Element last(T list)
  in{
    assert(list.item != null);
  }
  body{
    T i = list;
    while(i.item.next.item != null){ i = i.item.next; }
    return i.item.value;
  }
  
  T tail(T list)
  in{
    assert(list.item != null);
  }
  body{
    return list.item.next;
  }
  
  T take(int count, T a)
  {
    if(count > 0){
      assert(a.item != null);
      return make(a.item.value, take(count - 1, a.item.next));
    }else{
      return T.init;
    }
  }

  T drop(int count, T a)
  {
    version(pure){
      if(count > 0){
        assert(a.item != null);
        return drop(count - 1, a.item.next);
      }else{
        return T.init;
      }
    }else{
      T result = a;
      int rest = count;
      while(rest > 0){
        assert(a.item != null);
        a = a.item.next;
        --rest;
      }
      return result;
    }
  }

  T takeWhile(bit delegate(Element i) block, T a)
  {
    if(a.item != null && block(a.item.value)){
      return make(a.item.value, takeWhile(block, a.item.next));
    }else{
      return T.init;
    }
  }
  
  Element foldl(Element delegate(Element x, Element y) block, Element initial, T a)
  {
    version(pure){
      if(a.item == null){
        return initial;
      }else{
        return foldl(block, block(initial, a.item.value), a.item.next);
      }
    }else{
      Element result = initial;
      for(T i = a; i.item != null; i = i.item.next){
        result = block(result, i.item.value);
      }
      return result;
    }
  }

  Element foldr(Element delegate(Element x, Element y) block, Element initial, T a)
  {
    version(pure){
      if(a.item == null){
        return initial;
      }else{
        return block(a.item.value, foldr(block, initial, a.item.next));
      }
    }else{
      int count = a.length;
      Element** work = (Element**)alloca(count * (Element*).size);
      T current = a;
      for(int i = 0; i < count; ++i){
        work[i] = &current.item.value;
        current = current.item.next;
      }
      Element result = initial;
      for(int i = count - 1; i >= 0; --i){
        result = block(*work[i], result);
      }
      return result;
    }
  }
  
  T map(Element delegate(Element x) block, T a)
  {
    if(a.item != null){
      return make(block(a.item.value), map(block, a.item.next));
    }else{
      return T.init;
    }
  }
}

理想としては、(パフォーマンスとか無視で)version = pureのみにしたいのですが… そうすると、実体化したらコンパイラが落ちます。 template内の再帰呼出しは必ず落ちる、というわけでもなく、落ちる組み合わせと落ちない組み合わせがあって、よくわからんです。

とりあえずinstance list(char);が成功する所まで持っていったのがこのコードです。

コンパイラがこんな状態じゃ、とても実用には供さない…ってのが最近の感想です。 DirectX8ヘッダーがあればゲームとかも作ってみたいのですが。 (DirectX9が動くような豪勢なグラボは持ってないから&SDLやOpenGLは(真面目に)使ったことがないから)

12月27日の出来事

このページの主張崩壊。 Walter様、Ada経験無いのか…。

…。

C言語ラインタイムのコードページ

文字コード問題の続きです。 Phobosについては直るのを待つしかないとして…それからどうすりゃいいのよ、という話。 WindowsですとWideCharToMultiByteで事足りますが、それですとWindows依存になっちゃいます。

Hauke Dudenさん曰く、The C runtime library provides functions to convert from wide char to the local code page and vice versa. We can use those for conversions of this kind. だそうです。

DはCのライブラリを抱え込んでいるので、外部宣言さえすれば、そのまま使えます。 …正直、CRTのロケール関数なんか、使うのは初めてなのですが。

extern(C){
  enum { LC_ALL = 0 }
  char* setlocale(int, char*);
  int mbstowcs(wchar*, char*, int);
  int wcstombs(char*, wchar*, int);
}

で、まあ、こんな感じで。

typedef char[] mstring;

mstring toMBS(wchar[] s)
{
  mstring result;
  version(Win32){
    result.length = WideCharToMultiByte(0, 0, s, s.length, null, 0, null, null);
    WideCharToMultiByte(0, 0, s, s.length, result, result.length, null, null);
  }else{
    char* o = setlocale(LC_ALL, null);
    setlocale(LC_ALL, "");
    result.length = wcstombs(null, s, 0);
    wcstombs(result, s, result.length);
    setlocale(LC_ALL, o);
  }
  return result;
}

setlocaleは、nullを渡すと現在の設定が、空文字列を渡すとOSの言語設定(GetUserDefaultLangID)が使われます。 デフォルトは"C"です…意味はよくわかりませんが、この状態では全角文字を変換しようとするとエラーリターンしてくるので、きっとASCII文字セットでしょう。 なので、プログラムの先頭でsetlocale(LC_ALL, "");するか、↑のように、SelectObject風に切り替えるかする必要があります。

Win32だけ特別扱いする必要は無いのですが、どうせwcstombsはsetlocaleで指定したコードページを第一引数にWideCharToMultiByteを呼んでいるだけに決まってるので…。(未確認)

mstringにstrongly typedefを使っているのは、UTF-8のchar[]とオーバーロードやなんかで区別するためです。

ところで、WideCharToMultiByteですが、長さを明示的に与えている時ですら、途中に'\0'を含むとそれ以降を見てくれない気がするのですが、気のせいでしょうか? …つくづく、ゼロ終端文字列って癌だなあ、と。

追記…やねうらおさんのBBSでの話題ですが…setlocaleはマルチスレッド非対応っぽいです…。 ええと…。 ここだけsynchronizedで囲っても…ねえ…。 setlocaleはmainの先頭で一回だけ実行する、って事で…?

Version 0.77

あけましておめでとうございます。

久しぶりのバージョンアップ! …中身はこれから調べていきます。

W版APIがちらほら…9x切り捨てって解釈でいいのかな? その割にはstd.streamがそのままだったりするのは、やっぱ理解してもらって無いのでしょうか。 ひょっとしたら僕が下手にまずい説明したからかも知れませんけど…。

ビット配列のサイズは直ってました。 演算子はまだ使えません。

caseに値を並べられるのは嬉しい追加機能。 範囲も欲しいな。 otherwise書かないとエラーになるPascalもあった気がするなあ…など、思い出したように追記。

それから、typeofとか、templateの引数にtemplateを渡すとか、トリック用の機能が増えています。 …が、前に書いたF/"A"%"C" | "B"のやつ、コンパイルできなくなってます!? 仕様の変更なら構わないのですが、opOrが認識され無かったり、リンカがエラー吐いたり…

追記: Richter's book?

Richter's bookなるものに敗北しました。

YTはレベルが下がった
英語の自信が下がった
やる気が失せた

い、今まで、お、同じことを、な、何人の人が、何度書いたと…

まあ、ようやくWalterさんが認識してくれたみたいで、その点は嬉しいのですが…。

WYSIWYG文字列

WYSIWYG文字列にコントロールコードを混ぜる時は、Delphiの#みたく、`one`\n`two`のように書けるんですねえ…。 今まで知りませんでした。 これで憎きエスケープ記法とおさらばできます。 D偉い。

Version 0.78

どうでもいいようなバグFixばっかです。

それとは無関係ですが、char[]について。 length = 0によって空文字列にした場合でも、char[]bitの暗黙の変換はきちんとfalseになってくれているみたいです。 部分文字列か…失礼。

Version 0.79

templateのバグが直った事により、暫く使用不可能になっていた文字列挿入テンプレート復活! 再帰的なテンプレートが全滅してたみたいですね。

それ以外は…これから見ます。

Version 0.79 & windows.h for D

dmd 0.79は、>>>16がextern(D)の時だけ正常に動かないバグが直ってました。 そんなわけでHIWORDの定義も、回避策から>>>16に戻してあります。

今考えてるのは、A版CHARの定義を、alias char CHAR;からtypedef char CHAR;にするかどうか。 間違えてUTF-8のまま渡してしまうミスを防ぐため…なんですが、躊躇している理由は、インポートライブラリごときが個別にtypedefを使ってしまうと、ライブラリ間で、MBCS同士なのに渡せないなんてことも起こり得るかなあ…と。

言語側か、object.dあたりで、ニュースグループで時々言われるlcharが定義されれば、それを使うのですが…。

stdout.writeString

以前ニュースグループにも投稿したコードですが、stdoutやstderrを非Ascii文字を出力可能にするもの。 忘れてしまう前にここにも載せておきます。

class Console : File
{
  this(HANDLE _handle, FileMode _mode)
  {
    super(_handle, _mode);
  }
  
  override void writeString(char[] s)
  {
    if(s.length > 0){
      DWORD written;
      wchar[] w = toUTF16(s);
      if(WriteConsoleW(handle, &w[0], w.length, &written, null) == FALSE){
        mstring m = toMBS(w);
        super.writeString(m); // for 9x and redirect
      }
    }
  }
  
  override void write(char[] s)
  {
    super.write(s.length);
    writeExact(&s[0], s.length * char.size);
  }
  
  static this()
  {
    std.stream.stdout = new Console(std.stream.stdout.handle(), FileMode.Out);
    std.stream.stderr = new Console(std.stream.stderr.handle(), FileMode.Out);
  }
}

どうも、日本人コミュニティのD言語熱が冷えて来ている気がするのは気のせいでしょうか? 私も、あまり本腰を入れる気がしなくなってきています。 言語的には奇麗に書けてハッピーなのですが、dllとexeでGCを共有するみたいな、「泥臭いけれど無いと困る機能」が決定的に不足している感じなんですよね…現状。 少なくとも、ビット配列の演算子が正しく実装されるまで、当分お休み…。

DIDEのエンコーディング

DIDEにluaが搭載されたので、onFileOpen.luaに以下のコードを書き足せば日本語も使える、と、Charlesさんが教えてくださったのですが、

local s = CMainFrame:GetActiveScintilla()

if ( s ~= nil ) then
  s:SetCodePage(SC_CP_UTF8);
end

そもそも、onFileOpen.luaが動作していない(while 1 do endとか書いてもフリーズしない)ように思えます。 上手く行った人おりませんか? (日記にコメントフォームが欲しい…)

私が後ろ向きでした

迂闊な事書けないなあー。

しかし、これは私が後ろ向きでした。 言語機能に無くても、VirtualProtectでも何でも使って、無理やり実現するべきですよねっ(それも違

Version 0.80

とか書いてると0.80リリース。

いいこと

悪いこと

ざっと見た限りですが。

Version 0.81

…そう思ったその日に、Dもバージョンアップして致命的だったprotectedのバグがFix。

最近、言語に振り回されっぱなしです。

HTMLフィルタプロキシその1

別にattohttpDが、Proxomitronのような動作をサポートすると予告しつつ動きがないから、というわけでは無いのですが、DでProxomitronモドキを作りました。 …単にProxomitronのフィルタ定義が一般的な正規表現とかけ離れているのが使い辛くて、なにかいい代替品ないかなー、と考えていただけなのですが。 一応、使えない処理系とか、ウィンドウをすべて閉じるみたいな一発ツールを除けば、私がD言語で初めて作った実用アプリって事になります。

ビルドにはdyaregexpとdyayamlwindows.h for Dが必要です… って全部自前なのが少し悲しかったりしますが… 特にbogoYAMLぐらい使わせてもらっても良かったんじゃないかと思いつつ、Dには折角関数内関数へのdelegateがありますので、Delphiで培った極悪テクニックを流用してみたかったんです。 具体的にはpoo.dのio_setting関数です。

RFCなんて読まずに作ってますし、IE6でしか確かめてませんし、それもHTTP1.1を使うオプションがOFFでなければ動作しません。 (何年も前に学科でやらされた実験を除いて)ソケットを使ったのは初めてですので、色々馬鹿な事をしている気はしますが、適当に直していただけるなり御教授いただけるなりスルーしてくれるなりしていただければ幸いです。

それから、W版のAPIを用いましたのでWindowsのNT系でなければ動作しません。

使い方は、ブラウザのプロクシ設定をlocalhost:8080にするだけです。

最後になりましたが、Proxomitronの使い方からサーバーになるにはbindが必要なんてことから終始馬鹿なことを聞きまくった私に、色々教えていただいたCyberXに多大な感謝と敬意を表します。

HTMLフィルタプロキシその2

色々不具合がありましたので、細々修正しました。

一番大きいのが、Filter構造体が奇数バイトなため、GCに引っかかってしまって正規表現オブジェクトが回収されてしまう時があったって事です。 align(4)とか付けても何も変わらず、bitをuintにして解決しました。

VC++のデバッグ情報じゃなくて、OMF同士なのですから*.tds吐いてくれないかなあ…。 そうすればWinDbgじゃなくてC++BuilderXでデバッグできるのに。

それにしても反応早っ。 Win32&IEの事しか考えずに作ったものを移植というのも…というのは考えない事にして、手元にLinux環境が無いので試せないのが残念です。

ちなみに attohttpD は Vathix 氏の socket が標準入りしたらもう一度いじろうと思っていたのでした。

知りませんでした。 std.socketができたら書き直します…。

ちと気になったこととして、 thread 殺してないからか close してないかかはわかりませんけど終了してからしばらく listen 状態を続けることと、少々遅いかな?、ということがありました。

遅いのはともかく、listenしているスレッドは終了させるようにしました。 closeは多分GCがデストラクタを呼んでくれるでしょう…。 (未検証)

あと、よくある機能としてはRefererやUser-Agentの偽装ですね。 気が向いた時にやります。

HTMLフィルタプロキシその3

ahiruさんも反応されておられたのですね。 気付いてませんでしたというかそもそもahiruさんの日記は読んで無かったというか…申し訳ない。

でも、D言語をやっておられる方々の日記やサイトを片っ端から全部読まなければならない義務は流石に無いと思うので、こうなるとトラックバックが欲しいなあ…

やっぱりはてなか!? 選択肢ははてなダイアリーしか無いのか!?

返信が届いて無い可能性がありますのでここにも書いておきますと、今回のケースはalignでは無理です。 配列の要素はどうあがいてもアライメントされないみたい。 仕様かどうかは不明。

HTMLフィルタプロキシその4

アップローダーCGIなんかですと、一気に何百kbものデータを送るわけですね。 で、一括してsendしてしまうと…AirH"が耐えられない事が判明。 OS巻き込んでフリーズします。 (;_;

1kbづつ小出しで送るようにしたら、使えるようになりました。

そろそろスタートアップに入れても使えるレベル…か?

HTMLフィルタプロキシその5

まず、風邪ひいて頭回らないので、この文章だってきっと支離滅裂になってるはずですと、断っておきます。

めでたくpoo用済み万歳とか言ってみるテスト。 私が作ると、自分の環境しか考えないものができあがりますしね。

poo, それでも、最近は常駐させてますので、ちょくちょくFixしてます。 単純な作りのはずなのに何故かProxomitronよりもメモリ食うし… あまりGCに頼らないようにして、破棄して問題ないとわかりきったメモリはこまめにdeleteするべきか…? ああそうそう、Proxomitronomitronってのはあんまり過ぎますので正式名称募集中。 どうせ自分の環境しか考えて無い以上は非公開化ってのも選択肢としてありですが、windows.h for Dを作っておきながらそれを使ったアプリを一本も公開して無いのはどうかと思いますので当面置いておきます。 TODOとしては、shinichiro.hさんが折角作ってくださったLinux対応の取り込みと、HTTP/1.1対応とがあるのですが、今のところどっちもやる気無し。 ごめんなさい。

ついでにその下の記事に言及してみると、gdcがWin32をサポートしてくれたら、WinDbgからおさらばできて嬉しいのですけどね…。 あと、理想の言語としては絶対ノー。 そうですねー…、Adaにoverrideを明示する構文と、委譲機能込みでinterfaceが導入されればほぼ理想なのですが、米国防総省にそんなこと到底望めませんし。(米国防総省が規格を決めているわけではないよーな気もかすかにしますが、調べる気力が無いので忘れましょう) DelphiがC#2.0の機能(templateと関数内関数へのポインタ)を取り込んだ時にまだWin32版がバージョンアップされ続けていれば(implementsが生き続けていれば)、ほぼ理想になっちゃいそうなのが最近の期待です。 Dは…委譲が付けば機能的には満足なのですが、中途半端にCの構文を尊重しているため前二者と比べ圧倒的に見辛いという難点が…いや、Cの構文を継承しているのはDにはメリットと信じます。 信じますけど、Cの構文ってのは私の理想からは遥か彼方に…。 ちなみにGCは、あるならあるで書きやすくなってハッピー、無いなら無いで完成品のメモリ使用量が減ってハッピー。ですので無関係。暴言?

じゃあなんでDなんてやってるのかと言われそうですが、趣味コンパイラを書くのに向いてそうだとか、Delphiのネイティブ版が滅びた時の保険とか、templateで遊びたい時もあるとか、他にも色々ある/あったのですが、最近は、こうやって開発途上のマイナー言語に参加してなんやかややりとりするのが楽しいから、というのが一番の理由になりつつあるのでした。

その趣味コンパイラ、最近また続きを書きたい欲が出てきたのですが、風邪ひいて頭回らないのでコーディングする気力がないのが歯痒い。 気力があったとしても、素のPascal程度の機能も満足に実装できないレベルの自分が尚更歯痒い。 あ、こんな文脈で書いてしまって誤解を招きそうですが、見境無く夢想はしてますが、夢想している仕様が実現できたらそれが貴様の理想か?と聞かれればノーですハイ。

attohttpdの最新版を落としてみた後の追記

フィルタの記述がuri,search,replaceってコンパチですか?

あー、どーしよう。 こっちもD言語付属の正規表現に切り替えた方が、フィルタを共通で使えて便利ですよねえ。

Web上には、英語&UTF-8, SJIS, EUC-JPがどれも高い頻度で混在してて、文字コード自動判別なんてIE見てれば確実にはできないのは明らかな訳で、さらにサーバーが返してきたりHTMLに埋めこまれたりしているcharsetなんて嘘が混じってたりしますし、とすればプロキシ側でコード変換はやりたくないなあ→拙作の奴は複数文字コード対応してるからまあこれで…と安直に考えてた(自作のを使いたい欲求も含む)のですが、実際に置換したいのはAscii文字ばっかりで、幸いUTF-16のHTMLなんて見かけませんし、SJISのTrail Byteも、間にタグが挟まる事でまず引っかかることはないと断言できそうですし、Dの正規表現でも良かったじゃん…と。

それはまた今度考えるとして、今まで作ったフィルタを晒しておきます。 仕様の違いとしては、uriが向こう様は部分一致なのに対しこっちは正規表現なのと、それからsearchもそうですが正規表現自体の仕様がかなり違う事ですね…。 Dの正規表現はオプション切り替えが無いので、"?m-"なんてやってるのは改行を意識した記述に変えないといけないのと、Dの正規表現は括弧が全て記憶されるのと…あと、"*?"って使えるのでしょうか?

  -
    uri: http\:\/\/.*\.aaacafe\.ne\.jp\/.*
    search: ?m-:\<\!\-\- VC active \-\-\>.*?\<\!\-\- vc active \-\-\>
    replace: <!--//-->
  -
    uri: http\:\/\/www\.finito\-jp\.com\/.*
    search: \<img (?1:[^>]*?) src\=\"http\:\/\/ad\.a8\.net\/.*?"\>
    replace: <!--//-->
  -
    uri: http\:\/\/.*\.hp\.infoseek\.co\.jp\/.*
    search: ?m-:\<\!\-\- START_AD_Banner \-\-\>.*?\<\!\-\- END_AD_Banner \-\-\>
    replace: <!--//-->
  -
    uri: http\:\/\/www\.geocities\.jp\/.*
    search: ?m-:\<\!\-\- text below generated by server. PLEASE REMOVE \-\-\>.*$
    replace: <!--//-->
  -
    uri: http\:\/\/www\.geocities\.co\.jp\/.*
    search: ?m-:\<\!\-\-\*\/GeoGuide\/\*\-\-\>.*?\<\!\-\-\*\/GeoGuide\/\*\-\-\>
    replace: <!--//-->
  -
    uri: http\:\/\/forbes.com\/infoimaging\/.*
    search: ?m-:\<\!\-\- AD \-\-\>.*?\<\!\-\- \/AD \-\-\>
    replace: <!--//-->
  -
    uri: http\:\/\/www[0-9]*\.tok2\.com\/home\/.*
    search: ?m-:\<\!\-\- TOK2_TOP \-\-\>.*\<\!\-\- TOK2_TOP_END \-\-\>|\<\!\-\- TOK2_UNDER\-\-\>.*(?1:\<\/BODY\>)
    replace: <!--//-->$1

HTMLフィルタプロキシその6

中途半端に目が覚めたのでattohttpdのメインルーチンとアイコンだけpooのものにすげ替えてみた。 終了処理を一切すっ飛ばしてますが、まあ9xじゃあるまいし問題ないでしょう…。

あと poo のソースをいただいたのをマージしました。

なんて書かれていたので飛び上がってソースを落とさせていただいたのですが、私が何か勘違いしているのか何も変わって無いように見えましたので、取りあえずここに置いてあるファイルによる差し替えがattohttpd1.3でも通用することは確認。

このためだけにBash@Cygwin起動するの面倒だなー。 幸いにして?VC++こそ入ってませんが、私のHDD上ではBorlandとDigitalMarsとMinGWとCygwinのmake.exeが競合しています。 どれもこれも微妙に違うのはなんとかならないのでしょうか…。

それよりもattohttpdが全然Proxy動作してくれません…。 虫がよすぎたかな…。 ひょっとしたらひょっとして、お互いに、自分の環境でしか動かないものを作っている予感。(^^

IEで.tar.*を落とすと、.tar.tarになってしまうので、content-typeの差し替え機能はpooにもつけようかな…。