文
CやC++プログラマの方は、Dの文を非常に親しみやすく感じるでしょう。 いくつか面白い追加もあります。Statement:
;
NonEmptyStatement
ScopeBlockStatement
NoScopeNonEmptyStatement:
NonEmptyStatement
BlockStatement
NoScopeStatement:
;
NonEmptyStatement
BlockStatement
NonEmptyOrScopeBlockStatement:
NonEmptyStatement
ScopeBlockStatement
NonEmptyStatement:
NonEmptyStatementNoCaseNoDefault
CaseStatement
CaseRangeStatement
DefaultStatement
NonEmptyStatementNoCaseNoDefault:
LabeledStatement
ExpressionStatement
DeclarationStatement
IfStatement
WhileStatement
DoStatement
ForStatement
ForeachStatement
SwitchStatement
FinalSwitchStatement
ContinueStatement
BreakStatement
ReturnStatement
GotoStatement
WithStatement
SynchronizedStatement
TryStatement
ScopeGuardStatement
ThrowStatement
AsmStatement
PragmaStatement
MixinStatement
ForeachRangeStatement
ConditionalStatement
StaticAssert
TemplateMixin
ImportDeclaration
Statement の文法と Declaration の文法の間の曖昧性は、 すべて、Declaration を優先するという形で解決されます。 Statement として解釈させたい場合は、 括弧を使うことで Statement としてしか構文解析できないように書いて下さい。
スコープ文
ScopeStatement:
NonEmptyStatement
BlockStatement
文法定義でスコープ文(ScopeStatement)とされた箇所では NonEmptyStatement か BlockStatement を書くことができ、ローカルシンボルのスコープが新しく導入されます。
新しいスコープが導入されるとは言っても、 ローカルシンボルの宣言が、同じ関数の他の宣言を隠す (shadowする) ことは許されていません。
void func1(int x) {
int x; // 不正。x は引数 x を隠す
int y;
{ int y; } // 不正。 y は外側のスコープの y を隠す
void delegate() dg;
dg = { int y; }; // ok。この y は同じ関数の中ではない
struct S {
int y; // ok。この y はメンバ変数。ローカルシンボルでない
}
{ int z; }
{ int z; } // ok。この z は他の z を隠さない
{ int t; }
{ t++; } // 不正。t は未定義
}
これは、 複雑な関数で意図せず宣言が他の宣言を隠すことに起因するバグを避けるための仕様です。 ひとつの関数内では、ローカル名は重ならないようにすべきでしょう。
スコープブロック文
ScopeBlockStatement:
BlockStatement
文法定義でスコープブロック文 (ScopeBlockStatement) とされた箇所には BlockStatement のみが記述でき、新しいスコープが導入されます。
ラベル付き文
文にはラベルを付けられます。 ラベルとは、文の前に置く識別子です。
LabeledStatement:
Identifier : NoScopeStatement
空文を含めて任意の文がラベル付け可能で、 goto文の飛び先とできます。 continue 文や break 文の飛び先ともなり得ます。
ラベルの名前空間は、 宣言や変数、型などとは独立しています。 しかしそれにもかかわらず、ローカルの宣言と同じ名前のラベルは使用できません。 ラベル名の有効範囲は、そのラベルのある関数本体のです。 名前空間のネストは行われません。つまり、 ブロックの中のラベルは、外からでもアクセスできます。
ブロック文
BlockStatement:
{ }
{ StatementList }
StatementList:
Statement
Statement StatementList
ブロック文とは、{ } で囲まれた文の並びのことです。 テキスト上での順序と同じ順で、各文が実行されます。
式文
ExpressionStatement:
Expression ;
式が評価されます。
副作用のない式、 例えば (x + x), は式文としては不正です。 そのような文が必要な場合は、 void へとキャストすることで正当なコードになります。
int x;
x++; // ok
x; // 不正
1+1; // 不正
cast(void)(x + x); // ok
宣言文
変数や型を宣言します。DeclarationStatement:
Declaration
宣言文の例です:
int a; // a を 型 int と宣言し、0 に初期化
struct S { } // 構造体 s の宣言
alias int myint;
if 文
if文では、文を条件付きで実行します。IfStatement:
if ( IfCondition ) ThenStatement
if ( IfCondition ) ThenStatement else ElseStatement
IfCondition:
Expression
auto Identifier = Expression
BasicType Declarator = Expression
ThenStatement:
ScopeStatement
ElseStatement:
ScopeStatement
Expression が評価されます。この結果は、boolean
であるかまたはbooleanに変換可能な型です。
結果がtrueならば ThenStatement が実行され、そうでなければ ElseStatement
が実行されます。
'ぶら下がりelse' の問題については、elseは一番近いifと結合する、 と定められています。
auto Identifier を記述した場合、 その名前の変数が宣言されて、 Expression の型と値が割り当てられてます。変数のスコープは、 初期化時から ThenStatement の終了時までです。
Declarator iを記述した場合、 その名前の変数が宣言されて、 Expression の型と値が割り当てられてます。変数のスコープは、 初期化時から ThenStatement の終了時までです。
import std.regexp;
...
if (auto m = std.regexp.search("abcdef", "b(c)d"))
{
writefln("[%s]", m.pre); // [a] を表示
writefln("[%s]", m.post); // [ef] を表示
writefln("[%s]", m.match(0)); // [bcd] を表示
writefln("[%s]", m.match(1)); // [c] を表示
writefln("[%s]", m.match(2)); // [] を表示
}
else
{
writefln(m.post); // エラー。m は未定義
}
writefln(m.pre); // エラー。m は未定義
while 文
WhileStatement:
while ( Expression ) ScopeStatement
while文は簡単なループを構成します。
まず Expression が評価されます。
この結果は、boolean
であるかまたはbooleanに変換可能な型です。結果がtrueならば ScopeStatement が実行され、
もう一度 Expression の評価へ戻ります。
このループは
Expression がfalseへ評価されるまで続きます。
int i = 0;
while (i < 10) {
foo(i);
i++;
}
BreakStatement はループを終了します。
ContinueStatement は、
直ちに Expression の評価へ処理を飛ばします。
do 文
DoStatement:
do ScopeStatement while ( Expression ) ;
do 文は簡単なループを構成します。
ScopeStatement が実行されます。
その次に、boolean
であるかまたはbooleanに変換可能な型をもつ
Expression が評価されます。
trueならばまたループを繰り返します。
Expression がfalseへ評価されるまでループが続きます。
int i = 0;
do {
foo(i);
} while (++i < 10);
BreakStatement はループを終了します。
ContinueStatement は、
直ちに Expression の評価へ処理を飛ばします。
for 文
for文は、 initialization, test, increment の3つの節を持つループです。ForStatement:
for (Initialize Testopt ; Incrementopt) ScopeStatement
Initialize:
;
NoScopeNonEmptyStatement
Test:
Expression
Increment:
Expression
まず Initialize が実行されます。 その次に、boolean であるかまたはbooleanに変換可能な型をもつ Test が評価されます。これがtrueなら内容文が実行されます。その後、 Increment が実行され、次にまた Test の評価に行きます。 これがtrueならばまたループが繰り返されます。 Test がfalseになるまでこのループが続きます。
BreakStatement はループを終了します。 ContinueStatement は、 直ちに Increment の評価へ処理を飛ばします。
ForStatement は新しいスコープを導入します。 Initialize で変数が宣言されていれば、その変数のスコープは for文の本体の最後まで拡大されます。例
for (int i = 0; i < 10; i++)
foo(i);
これは次と同等です。
{
int i;
for (i = 0; i < 10; i++)
foo(i);
}
ループの中身を空にはできません:
for (int i = 0; i < 10; i++)
; // 不正
代わりに次のように書きます:
for (int i = 0; i < 10; i++)
{
}
Initialize は省略可能です。 Test も省略可能で、
その場合あたかも常にtrueと評価されるかのように扱われます。
foreach 文
foreach文では、集成体の内容をループします。ForeachStatement:
Foreach (ForeachTypeList ; Aggregate) NoScopeNonEmptyStatement
Foreach:
foreach
foreach_reverse
ForeachTypeList:
ForeachType
ForeachType , ForeachTypeList
ForeachType:
refopt BasicType Declarator
refopt Identifier
Aggregate:
Expression
Aggregate がまず評価されます。 その結果は、静的配列, 動的配列, 連想配列, 構造体, クラス, デリゲート, タプル のいずれかの型を持つ式でなければなりません。次に、 集成体の各要素につき一度ずつ、NoScopeNonEmptyStatement が実行されます。 毎回、 ForeachTypeList で宣言した変数にその集成体の要素がコピーされます。 変数が ref だった場合、参照となります。
集成体はループ中は不変でなければなりません。つまり、 ループ本体の NoScopeNonEmptyStatement で要素を追加したり削除することは禁止されています。
配列に対する foreach
式部分が静的/動的配列な場合、 1つか2つの変数を宣言することが可能です。 変数が1つの時は、valueと呼ばれ、 配列の要素が一つずつ格納されます。 変数の型は、下で示す特別な場合を除き、 配列の内容と合致しなければなりません。 2つの変数が宣言されていると、一つ目が index、二つ目が value と呼ばれます。 index 変数は int か uint または size_t 型とし、 ref 指定はできません。 配列要素のインデックスが格納されます。
char[] a;
...
foreach (int i, char c; a)
{
writefln("a[%d] = '%c'", i, c);
}
foreach では、 配列の要素は0番目から始まって 最大の添字を持つ要素で終わる順番で処理されます。 foreach_reverse では、 配列要素が逆順で渡されます。
文字の配列に対する foreach
式部分が char, wchar, dchar の静的/動的配列である場合、 value の型は char, wchar, dcharのいずれにもできます。 これを用いることで、 どの種類のユニコード文字配列も、任意の種類のユニコード文字型へとデコードすることができます:
char[] a = "\xE2\x89\xA0".dup; // \u2260 を UTF-8 の3バイトで表現
foreach (dchar c; a)
{
writefln("a[] = %x", c); // 'a[] = 2260' を表示
}
dchar[] b = "\u2260"d.dup;
foreach (char c; b)
{
writef("%x, ", c); // 'e2, 89, a0' を表示
}
集成体が文字列リテラルのときは、 char, wchar, dchar 配列としてアクセス可能です:
void test() {
foreach (char c; "ab") {
writefln("'%s'", c);
}
foreach (wchar w; "xy") {
writefln("'%s'", w);
}
}
出力は次のようになります:
'a'
'b'
'x'
'y'
連想配列に対する foreach
式部分が連想配列な場合、 1つか2つの変数を宣言することが可能です。 変数が1つの時はその変数は value と呼ばれ、 配列の要素が一つずつ格納されます。 変数の型は配列の内容と合致しなければなりません。 2つの変数が宣言されていると、一つ目が index、二つ目が value と呼ばれます。 index 変数の型は連想配列のインデックス型と合致させます。 ref 指定はできません。配列要素のインデックスが格納されます。 foreach で要素が列挙される順番は決まっていません。 また、連想配列に対する foreach_reverse は不正です。
double[string] a; // index 型は string, value 型は double
...
foreach (string s, double d; a)
{
writefln("a['%s'] = %g", s, d);
}
レンジを表す構造体やクラスに対する foreach
以下のプロパティの定義された構造体やクラスは レンジと呼ばれ、foreachでループすることが可能です:
プロパティ | 用途 |
---|---|
.empty | もう要素がない時に true を返す |
.front | レンジの最左端の要素を返す |
.back | レンジの最右端の要素を返す |
メソッド | 用途 |
---|---|
.popFront() | レンジの左端をひとつ右に動かす |
.popBack() | レンジの右端をひとつ左に動かす |
つまり:
foreach (e; range) { ... }
このコードは以下のように変形されます:
for (auto __r = range; !__r.empty; __r.popFront())
{
auto e = __r.front;
...
}
同様に:
foreach_reverse (e; range) { ... }
このコードは以下のように変形されます:
for (auto __r = range; !__r.empty; __r.popBack())
{
auto e = __r.back;
...
}
レンジとしてのプロパティが存在しない場合は、代わりに opApply メソッドが使用されます。
opApply を持つ構造体やクラスに対する foreach
構造体やクラスオブジェクトについては、 foreach の順序は特別な opApply メンバ関数で定義されます。 foreach_reverse の動作は opApplyReverse メンバ関数の定義によります。 対応する foreach 文を使用するためには、 型ごとにこれらの特別な関数をユーザー定義する必要があります。 これらの関数の型は次の通りです:
int opApply(int delegate(ref Type [, ...]) dg);
int opApplyReverse(int delegate(ref Type [, ...]) dg);
Type は foreach で Identifier の宣言に使われた Type と一致するものです。複数の ForeachType は、 opApply や opApplyReverse へ渡されるdelegateの複数の Type に対応します。 複数個の opApply や opApplyReverse が存在してもかまいません。 適切な物が、 ForeachStatement の ForeachType と dg の型が合うように選択されます。関数の本体では、 そのオブジェクト内の要素を順にたどり、 それぞれを dg へ渡します。dg が 0 を返せば、 処理を次の要素へと進めます。 dg が 0以外の値を返すと、 繰り返しを止めてその値をreturnする必要があります。 それ以外で、全ての要素をたどり終わったならば、最後に0をreturnします。
例えば、二つの要素を含むコンテナクラスを考えてみましょう:
class Foo {
uint[2] array;
int opApply(int delegate(ref uint) dg)
{
int result = 0;
for (int i = 0; i < array.length; i++)
{
result = dg(array[i]);
if (result)
break;
}
return result;
}
}
これを使った例は次のようになるでしょう:
void test() {
Foo a = new Foo();
a.array[0] = 73;
a.array[1] = 82;
foreach (uint u; a)
{
writefln("%d", u);
}
}
出力はこうです:
73
82
デリゲートに対する foreach
Aggregate には、 opApply と同じ型のデリゲートを指定することも可能です。これによって、 一つのクラスや構造体に 多くのループ方法を名前付きで共存させることができます。
タプルに対する foreach
集成体としてタプルを指定した場合は、 一つか二つの変数を宣言できます。一つの場合、 その値にはタプルの要素が一つずつセットされます。 変数の型を指定した場合、 タプルの中身と合う型でなければなりません。 型が指定されていないときは、 変数の型はタプルの要素の型になり、繰り返し毎に変化します。 二つ変数を宣言した場合、 一つめが index、 二つめが value となります。index は int 型か uint 型のどちらかで、ref にはできません。 タプルのいくつ目の要素であるかが格納されます。
タプルが型のリストであった場合、 foreach文はそれぞれの型に対して一度ずつ実行され、 変数はその型へのaliasとなります。
import std.stdio;
import std.typetuple; // TypeTupleテンプレート
void main() {
alias TypeTuple!(int, long, double) TL;
foreach (T; TL)
{
writeln(typeid(T));
}
}
この出力は:
int
long
double
foreach の ref パラメータ
元の要素を書き換えるためには、ref が使えます。
void test() {
static uint[2] a = [7, 8];
foreach (ref uint u; a)
{
u++;
}
foreach (uint u; a)
{
writefln("%d", u);
}
}
出力:
8
9
ref はインデックス値には適用できません。
ForeachType に型指定がなかった場合は、 Aggregate の型から自動で推論されます。
foreach の制限事項
foreachの繰り返しの最中に、 集成体自身をサイズ変更や再割り当て、 解放や再代入、破棄などすることはできません。
int[] a;
int[] b;
foreach (int i; a)
{
a = null; // エラー
a.length += 10; // エラー
a = b; // エラー
}
a = null; // ok
foreach range 文
foreach range 文は指定された範囲の値をループします。ForeachRangeStatement:
Foreach (ForeachType ; LwrExpression .. UprExpression ) ScopeStatement
LwrExpression:
Expression
UprExpression:
Expression
ForeachType では変数を明示的な型指定付きで宣言するか、 LwrExpression と UprExpression から暗黙に推論される型で宣言します。 n を UprExpression - LwrExpression としたとき、ScopeStatement は n 回実行されます。 もし UprExpression が LwrExpression 以下であったならば、 ScopeStatement の実行回数はゼロになります。 Foreach が foreach のとき、変数はまず LwrExpression に設定され、本体を1回実行するたびにその後に1ずつ増えていきます。 Foreach が foreach_reverse の時は、変数はまず UprExpression に設定され、本体実行の前に1ずつ減っていきます。 ScopeStatement の実行回数に関係なく、 LwrExpression と UprExpression はどちらもちょうど1回だけ実行されます。
import std.stdio;
int foo() {
write("foo");
return 10;
}
void main() {
foreach (i; 0 .. foo())
{
write(i);
}
}
出力:
foo0123456789
foreach からの break と continue による脱出
foreach 内での BreakStatement はforeachのループを終了し、 ContinueStatement はすぐに制御を次の要素に移します。
switch 文
switch文は、 式の値に応じて case文のうちの一つへ処理を移します。SwitchStatement:
switch ( Expression ) ScopeStatement
CaseStatement:
case ArgumentList : ScopeStatementList
CaseRangeStatement:
case FirstExp : .. case LastExp : ScopeStatementList
FirstExp:
AssignExpression
LastExp:
AssignExpression
DefaultStatement:
default : ScopeStatementList
ScopeStatementList:
StatementListNoCaseNoDefault
StatementListNoCaseNoDefault:
StatementNoCaseNoDefault
StatementNoCaseNoDefault StatementListNoCaseNoDefault
StatementNoCaseNoDefault:
;
NonEmptyStatementNoCaseNoDefault
ScopeBlockStatement
Expression が評価されます。 結果の型 T は整数型か、 char[], wchar[], dchar[]. のいずれかです。結果はcaseの式の各々と 比較され、マッチしたcase文へ処理が移ります。
case の ArgumentList は、 コンマで区切られた式のリストです。
CaseRangeStatement は、 FirstExp から LastExp までの case ラベルを並べるための省略記法です。
どのcaseの式ともマッチしなかった場合、default文があれば、 処理はdefault文へ移ります。
switch 文には必ず default 節が必要です。
caseの式は、定数値か定数配列、または実行時初期化された 整数型の const/immutable 変数でなければなりません。 また、switch の Expression の型へと暗黙変換可能である必要があります。
case の式は全て異なる値へと評価される必要があります。 const/immutable 変数には必ず全て違う変数を指定します。 値が同じだった場合、 その値をもつ最初のcase節に制御が渡ります。 default 節は必ずちょうど一個書かなければいけません。
ScopeStatementList は新しいスコープを作ります。
switch文に関連付けられたcase文やdefault文は、 ネストしたブロックの中にあっても構いません。 例えば次は許されます:
switch (i) {
case 1:
{
case 2:
}
break;
}
ScopeStatementList は空であるか、または最後の case 以外、 ContinueStatement, BreakStatement, ReturnStatement, GotoStatement, ThrowStatement , assert(0) のいずれかで終える必要があります。 これは C言語の間違いやすい暗黙の fall-through から決別するための処置です。 fall-throught を明示するには goto case; を使います。
int number;
string message;
switch (number)
{
default: // 正しい: 'throw' で終わる
throw new Exception("unknown number");
case 3: // 正しい: 'break' で終わる ('switch' を抜けます)
message ~= "three ";
break;
case 4: // 正しい: 'continue' で終わる (周囲のループを continue します)
message ~= "four ";
continue;
case 5: // 正しい: 'goto' で終わる (次の case への明示 fall-through)
message ~= "five ";
goto case;
case 6: // エラー: 暗黙の fall-through
message ~= "six ";
case 1: // 正しい: case の中身が空
case 2: // 正しい: これはswitch文の最後のcase
message = "one or two";
}
break 文は switch の BlockStatement を抜けます。
文字列もswitch文で使用できます。 例えば:
char[] name;
...
switch (name) {
case "fred":
case "sally":
...
}
コマンドラインスイッチの処理などへの応用では、 この構文によって、直接的でエラーの少ないコードが書けるようになります。 char, wchar, dchar 文字列のいずれも使用できます。
実装ノート: コンパイラのコード生成器は、 caseは実行頻度の高い順に並べられていると仮定して構いません。 プログラムの正しさの面ではこの仮定は無関係ですが、 パフォーマンスの面を考えると 意味があります。
final switch 文
FinalSwitchStatement:
final switch ( Expression ) ScopeStatement
final switch 文は基本的には switch 文ですが、 以下の違いがあります:
- DefaultStatement は使用できない
- CaseRangeStatement は使用できない
- switch の Expression がenum型の場合、 全てのenumメンバが CaseStatement に現れなければならない。
- case 式に、 実行時に決まる値を書くことはできない
continue 文
ContinueStatement:
continue Identifieropt ;
continue文はループの現在の処理を中断し、
ループの先頭へ処理を戻します。
continueは、最内周のwhile,for,foreach,doループにはたらきます。
for文のincrement節は実行されます。
continue の後ろに Identifier が続いている時は、 その Identifier はcontinue文を囲うループのラベルでなければならず、 continue文によってそのループの先頭へ処理を戻します。 そのラベルの付いたループが存在しなければエラーです。
関連するfinally節は全て実行され、 同期オブジェクトは解放されます。
注: finally節でreturn,throw文やfinallyの外へのgoto文が実行された場合、 continueの目的地へは処理は到達しません。
for (i = 0; i < 10; i++)
{
if (foo(i))
continue;
bar();
}
break 文
BreakStatement:
break Identifieropt ;
break文は、そのbreak文を含む文を終了します。
breakは最内周のwhile,for,foreach,do,switch文を終了させ、
それらの次にある文へ処理を移します。
break の後ろに Identifier が続いている時は、その Identifier はbreak文を囲うループやswitchのラベルでなければならず、 break文によってそのループ等を終了します。 そのラベルの付いたループ等が存在しなければエラーです。
関連するfinally節は全て実行され、 同期オブジェクトは解放されます。
注: finally節でreturn,throw文やfinallyの外へのgoto文が実行された場合、 breakの目的地へは処理は到達しません。
for (i = 0; i < 10; i++)
{
if (foo(i))
break;
}
return 文
ReturnStatement:
return Expressionopt ;
返値を定めて、
現在の関数を終了します。
void以外の返値型を持つ関数ならば、
Expression が必要です。
Expression
は暗黙の内に返値型へ変換されます。
void以外の返値型を持つ関数では、インラインアセンブラを含まない場合、 最低でも1つのreturn, throw, あるいは assert(0) 文が必要です。
関数が完全にreturnする前に、 scope記憶クラスを持つオブジェクトが破棄され、 finally節は実行され、 scope(exit) 文が実行され、 scope(success) 文も実行され、 同期オブジェクトは 解放されます。
finally節でfinally節を抜けるgoto文やthrow文、return文が実行された場合、 関数からは戻りません。
out事後条件 (契約プログラミング 参照) がある場合は、 Expression の評価後、 関数が処理を戻す前にout節が実行されます。
int foo(int x)
{
return x + 3;
}
goto 文
GotoStatement:
goto Identifier ;
goto default ;
goto case ;
goto case Expression ;
ラベル Identifier
の付いた文へ処理を移します。
if (foo)
goto L1;
x = 3;
L1:
x++;
二番目の形 goto default; では、
このgoto文を囲む最も内側の SwitchStatement
DefaultStatement へ処理を移します。
三番目の形 goto case; では、 このgoto文を囲む最も内側の SwitchStatement における、 直後の CaseStatement へ処理を移します。
四番目の形 goto case Expression; では、 このgoto文を囲む最も内側の SwitchStatement における、 Expression とマッチする CaseStatement へ処理を移します。
switch (x)
{
case 3:
goto case;
case 4:
goto default;
case 5:
goto case 4;
default:
x = 4;
break;
}
関連するfinally節は全て実行され、
同期オブジェクトは解放されます。
初期化をスキップするような GotoStatement は不正です。
with 文
with文によって、 同じオブジェクトへの参照の繰り返しを簡単化できます。WithStatement:
with ( Expression ) ScopeStatement
with ( Symbol ) ScopeStatement
with ( TemplateInstance ) ScopeStatement
Expression はクラスの参照か、
構造体のインスタンスへと評価されるものを指定します。
WithStatement の本体では、シンボルを探す名前空間として、
まずそのオブジェクトへの参照が用いられるようになります。次のようなwith文
with (expression)
{
...
ident;
}
は、意味的に次と同値です:
{
Object tmp;
tmp = expression;
...
tmp.ident;
}
Expression は一度しか実行されません。 with文では、this や super の指す対象は変わりません。
Symbol には、スコープかまたは TemplateInstance を指定し、 対応するスコープからシンボルが検索されるようになります。 例えば:
struct Foo {
alias int Y;
}
...
Y y; // エラー、Y が未定義
with (Foo) {
Y y; // Foo.Y y; と同じ
}
ローカルシンボルを同じ識別子で隠してしまうような、 with 式の使い方は禁止されています。 これは、オブジェクトの宣言に新しいメンバが追加された場合に、 意図せずコードの動作を変えてしまうような危険を避けるためです。
struct S {
float x;
}
void main() {
int x;
S s;
with (s) {
x++; // エラー。x の宣言を隠している
}
}
synchronized 文
synchronize文は、クリティカルセクションで他の文を包んで、 複数のスレッドが同時に文を実行することを禁止します。
SynchronizedStatement:
synchronized ScopeStatement
synchronized ( Expression ) ScopeStatement
同時に高々1つのスレッドのみが ScopeStatement を実行することが許されます。
どの mutex によってこの動作が実現されるかは、Expression. によって決まります。Expression が指定されなかった場合、 一つのsynchronized文につき一つグローバルなmutexが作成されます。 異なるsynchronized文は異なるグローバルmutexを使用します。
Expression がある場合は、 オブジェクトへの参照か、Interface の場合は Interface を実装するオブジェクトへとキャストで評価されます。 使用される mutex はそのオブジェクトのインスタンス毎に用意されるもので、 同じインスタンスを参照する全てのsynchronized文で共有されます。
ScopeStatement が例外,goto,returnなどで終了した場合であっても、 ロックは解除されます。
例:
synchronized { ... }
これは標準的なクリティカルセクションの実装です。
synchronized 文は再帰ロックに対応しています。つまり、 synchronized でラップされた関数が自分自身を再帰呼び出しすることは可能で、 期待した通りの動作になります: mutex は再帰の回数分のロックとアンロックを行います。
try 文
例外処理は、try-catch-finally 文で行われます。TryStatement:
try ScopeStatement Catches
try ScopeStatement Catches FinallyStatement
try ScopeStatement FinallyStatement
Catches:
LastCatch
Catch
Catch Catches
LastCatch:
catch NoScopeNonEmptyStatement
Catch:
catch ( CatchParameter ) NoScopeNonEmptyStatement
CatchParameter:
BasicType Identifier
FinallyStatement:
finally NoScopeNonEmptyStatement
CatchParameter は型 T の変数 v を宣言します。 型 T は Throwable またはその派生クラスです。 型 T の派生クラスがthrowされた場合、 そのオブジェクトで v が初期化され、 catch文の中身が実行されます。
型Tだけが書かれ変数名vが無いときも、 catch文は実行されます。
もし CatchParameter の型T1が後ろに続く 型T2の Catch を隠していた場合、つまりT1がT2と同じ型か T2の基底クラスの場合は、エラーになります。
LastCatch は全ての例外をキャッチします。
The FinallyStatement は、 try ScopeStatement がgoto,break,continue,return,例外, 正常終了のいずれで終わる場合も必ず実行されます。
例外が FinallyStatement の中で発生し、元の例外が catch されるより前に catch されなかった場合、Throwable の next メンバによって前の例外にチェインされます。 注意点として、他の多くのプログラミング言語と違って、 D では新しい例外が古い例外を置き換えることはありません。 代わりに、新しい例外は最初のれいがいによって引き起こされた '連鎖ダメージ' と見なされます。 キャッチすべきは元々の例外の方で、そちらをキャッチすると、 連鎖した例外もそこから取得できます。
Error から派生したオブジェクトがthrowされたときの扱いは異なります。 これらの例外は通常のチェインメカニズムを迂回し、 Error のみがcatch可能となるように処理されます。 それ以降に発生した例外のリストに加え Error には、迂回が起こったときには、 一番最初に発生した元々の例外へのポインタも含まれます。 これによって、例外階層全体が手に入ることになります。
import std.stdio;
int main() {
try {
try {
throw new Exception("first");
}
finally {
writefln("finally");
throw new Exception("second");
}
}
catch(Exception e) {
writefln("catch %s", e.msg);
}
writefln("done");
return 0;
}
これは次のような表示になります:
finally
catch first
done
FinallyStatement から goto, break, continue, return などで脱出したり、gotoで中に飛び込んだりしてはいけません。
FinallyStatement の中には Catches を含むことはできません。 この制約は将来的には緩和される予定です。
throw 文
例外を投げます。ThrowStatement:
throw Expression ;
Expression は Throwable 型の参照へと評価される必要があります。
そのオブジェクトが例外として送出されます。
throw new Exception("message");
スコープガード文
ScopeGuardStatement:
scope(exit) NonEmptyOrScopeBlockStatement
scope(success) NonEmptyOrScopeBlockStatement
scope(failure) NonEmptyOrScopeBlockStatement
ScopeGuardStatement は、指定された NonEmptyOrScopeBlockStatement を、
この文自体が現れた箇所ではなく、
現在のスコープの終了時に実行します。
scope(exit) は、スコープから通常終了するときと例外で抜ける時の両方で
NonEmptyOrScopeBlockStatement を実行します。
scope(failure) は、スコープを例外で抜ける時のみ
NonEmptyOrScopeBlockStatement を実行します。
scope(success) は、スコープから通常終了するときのみ
NonEmptyOrScopeBlockStatement を実行します。
一つのスコープに複数の ScopeGuardStatement があった場合は、 ソースコード文字列上で現れた順番の逆順で実行されます。 そのスコープ終了時に破棄されるscopeインスタンスが存在する場合も、 それらのデストラクタも、ソースコード上の逆順で ScopeGuardStatement の間に挟まって実行されます。
write("1"); {
write("2");
scope(exit) write("3");
scope(exit) write("4");
write("5");
}
writeln();
というプログラムは以下を出力します:
12543
{
scope(exit) write("1");
scope(success) write("2");
scope(exit) write("3");
scope(success) write("4");
}
writeln();
の場合はこうなります:
4321
class Foo {
this() { write("0"); }
~this() { write("1"); }
}
try {
scope(exit) write("2");
scope(success) write("3");
scope Foo f = new Foo();
scope(failure) write("4");
throw new Exception("msg");
scope(exit) write("5");
scope(success) write("6");
scope(failure) write("7");
}
catch (Exception e) {
}
writeln();
の時は:
0412
scope(exit) と scope(success) 文は、
throw, goto, break, continue, return 文で終了したり、
goto で中に飛び込んだりしてはいけません。
asm 文
インラインアセンブラはasm文によってサポートされます。AsmStatement:
asm { }
asm { AsmInstructionList }
AsmInstructionList:
AsmInstruction ;
AsmInstruction ; AsmInstructionList
asm文は、アセンブラ言語の命令を直接使うことを可能にします。
これによって、外部のアセンブラを用いずにCPUの特殊命令の
実行などが簡単に実現できます。
関数の呼び出し規約やスタックの処理などは、
Dコンパイラが行います。
命令のフォーマットは、 CPUの命令セットに大きく依存しますので、 実装ごとに定義 されています。 しかし、フォーマットは次の規約に従っています:
- D言語と同じトークンを使う。
- コメント形式はDのコメント形式と合わせる。
- アセンブラ命令は行末ではなく ; で終わるものとする。
例えばIntelプラットホームでは:
int x = 3;
asm {
mov EAX,x; // xをロードしてEAXレジスタに入れる
}
インラインアセンブラによってハードウェアの直接制御も可能です:
int gethardware() {
asm {
mov EAX, dword ptr 0x1234;
}
}
Dの実装によっては、例えばDからCへのトランスレータなどでは、
インラインアセンブラは意味をなしません。この場合は、
インラインアセンブラが実装されている必要があります。これを確かめるには、version文を使うことができます:
version (D_InlineAsm_X86)
{
asm {
...
}
}
else
{
/* ... なんらかの代替案 ... */
}
意味的に連続している AsmStatement 文の間には、 余計な命令 (レジスタの退避や復元等)がコンパイラによって勝手に挿入されることはありません。
pragma 文
PragmaStatement:
Pragma NoScopeStatement
mixin 文
MixinStatement:
mixin ( AssignExpression ) ;
AssignExpression にはコンパイル時に定数文字列に評価される式を指定します。 文字列の中身は文法的に妥当な StatementList でなければならず、そのような文の列としてコンパイルされます。
import std.stdio;
void main() {
int j;
mixin("
int x = 3;
for (int i = 0; i < 3; i++)
writefln(x + i, ++j);
"); // ok
const char[] s = "int y;";
mixin(s); // ok
y = 4; // ok, mixin が y を宣言している
char[] t = "y = 3;";
mixin(t); // エラー、t はコンパイル時評価できない
mixin("y =") 4; // エラー、文字列は完全な文でないといけない
mixin("y =" ~ "4;"); // ok
}