文
CやC++プログラマの方は、Dの文を非常に親しみやすく感じるでしょう。 いくつか面白い追加もあります。Statement: ; NonEmptyStatement ScopeBlockStatement NoScopeNonEmptyStatement: NonEmptyStatement BlockStatement NoScopeStatement: ; NonEmptyStatement BlockStatement NonEmptyOrScopeBlockStatement: NonEmptyStatement ScopeBlockStatement NonEmptyStatement: NonEmptyStatementNoCaseNoDefault CaseStatement DefaultStatement NonEmptyStatementNoCaseNoDefault: LabeledStatement ExpressionStatement DeclarationStatement IfStatement WhileStatement DoStatement ForStatement ForeachStatement SwitchStatement ContinueStatement BreakStatement ReturnStatement GotoStatement WithStatement SynchronizedStatement TryStatement ScopeGuardStatement ThrowStatement VolatileStatement AsmStatement PragmaStatement MixinStatement ConditionalStatement StaticAssert TemplateMixin
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 を記述した場合、 その名前の変数が宣言されて、 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 ) ScopeStatementwhile文は簡単なループを構成します。 まず Expression が評価されます。 この結果は、boolean であるかまたはbooleanに変換可能な型です。 結果がtrueならば内容文が実行され、 もう一度 Expression の評価へ戻ります。 このループは Expression がfalseへ評価されるまで続きます。
int i = 0; while (i < 10) { foo(i); i++; }break文はループを終了します。 continue文は、 直ちに Expression の評価へ処理を飛ばします。
Do-While 文
DoStatement: do ScopeStatement while ( Expression )do-while文は簡単なループを構成します。 ScopeStatement が実行されます。 その次に、boolean であるかまたはbooleanに変換可能な型をもつ Expression が評価されます。 trueならばまたループを繰り返します。 Expression がfalseへ評価されるまでループが続きます。
int i = 0; do { foo(i); } while (++i < 10);break文はループを終了します。 continue文は、 直ちに Expression の評価へ処理を飛ばします。
For 文
for文は、 initialization, test, increment の3つの節を持つループです。ForStatement: for (Initialize Testopt ; Incrementopt) ScopeStatement Initialize: ; NoScopeNonEmptyStatement Test: Expression Increment: Expression
まず Initializer が実行されます。 その次に、boolean であるかまたはbooleanに変換可能な型をもつ Test が 評価されます。これがtrueなら内容文が実行されます。その後、 Increment が実行され、次にまた Test の評価に行きます。 これがtrueならばまたループが繰り返されます。 Test がfalseになるまでこのループが続きます。
break文はループを終了します。 continue文は、 直ちに Increment の評価へ処理を飛ばします。
Initializer で変数が宣言されていれば、その変数のスコープは 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++) { }Initializer は省略可能です。Test も省略可能で、 その場合あたかも常にtrueと評価されるかのように扱われます。
Foreach 文
foreach文では、集成体の内容をループします。ForeachStatement: Foreach (ForeachTypeList ; Aggregate) NoScopeNonEmptyStatement 817a818 Foreach: foreach foreach_reverse ForeachTypeList: ForeachType ForeachType , ForeachTypeList ForeachType: ref BasicType Declarator BasicType Declarator ref Identifier 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"; // \u2260 を UTF-8 の3バイトで表現 foreach (dchar c; a) { writefln("a[] = %x", c); // 'a[] = 2260' を表示 } dchar[] b = "\u2260"; 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[char[]] a; // index型は char[], value型は double ... foreach (char[] s, double d; a) { writefln("a['%s'] = %g", s, d); }
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 が存在してもかまいません。 適切な物が、 foreach文の ForeachType と dg の型が合うように選択されます。関数の本体では、 そのオブジェクト内の要素を順にたどり、 それぞれを dg へ渡します。dg が 0 を返せば、 処理を次の要素へと進めます。dg が 0以外の値を返すと、 繰り返しを止めてその値をreturnする必要があります。 それ以外で、全ての要素をたどり終わったならば、 最後に0をreturnします。
例えば、二つの要素を含むコンテナクラスを考えてみましょう:
class Foo { uint array[2]; 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 にはできません。 index にはタプルのいくつ目の要素であるかが格納されます。
タプルが型のリストであった場合、 foreach文はそれぞれの型に対して一度ずつ実行され、 変数はその型へのaliasとなります。
import std.stdio; import std.typetuple; // TypeTupleテンプレート void main() { alias TypeTuple!(int, long, double) TL; foreach (T; TL) { writefln(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 = a.length + 10; // エラー a = b; // エラー } a = null; // ok
foreach からの break と continue
foreachの本体部分での break 文はループを終了します。continue 文は、 直ちに次の Expression の評価へ処理を飛ばします。
Switch 文
switch文は、 式の値に応じて case文のうちの一つへ処理を移します。SwitchStatement: switch ( Expression ) ScopeStatement CaseStatement: case ArgumentList : ScopeStatementList DefaultStatement: default : ScopeStatementList ScopeStatementList: StatementListNoCaseNoDefault StatementListNoCaseNoDefault: StatementNoCaseNoDefault StatementNoCaseNoDefault StatementListNoCaseNoDefault StatementNoCaseNoDefault: ; NonEmptyStatementNoCaseNoDefault ScopeBlockStatement
Expression が評価されます。 結果の型はT は整数型か、 char[], wchar[], dchar[] のいずれかです。結果はcaseの式の各々と 比較され、マッチしたcase文へ処理が移ります。
caseのArgumentListとは、 コンマで区切られた式のリストです。
どのcaseの式ともマッチしなかった場合、default文があれば、 処理はdefault文へ移ります。
更に default 文もなかった場合は、 std.switcherr.SwitchError 例外が送出されます。 これは、enumに新しい値を追加したときにswitch文を変更し忘れる、 などのよくあるバグを早く見つけられるようにするためです。 これはC/C++とは異なった動作です。
case の式は全て、 switch Expression の型Tへ暗黙に変換できるような 定数値か定数配列へ評価されなくてはなりません。
caseの式は全て異なる値へ評価される必要があります。二個以上の default 文を書くのは不正です。
ScopeStatementList は新しいスコープを作ります。
switch文に関連付けられたcase文やdefault文は、 ネストしたブロックの中にあっても構いません。 例えば次は許されます:
switch (i) { case 1: { case 2: } break; }
case文は続くcase文へと 'fall through' します。 break 文があれば、switch の BlockStatement を終了します。 例えば:
switch (i) { case 1: x = 3; case 2: x = 4; break; case 3,4,5: x = 5; break; }
i が 1 ならば x は 4 になります。
char[] name; ... switch (name) { case "fred": case "sally": ... }
コマンドラインスイッチの処理などへの応用では、 この構文によって、直接的でエラーの少ないコードが書けるようになります。 char, wchar, dchar のいずれの文字列も使用できます。
実装ノート: コンパイラのコード生成器は、 caseは実行頻度の高い順に並べられていると仮定して構いません。 プログラムの正しさの面ではこの仮定は無関係ですが、 パフォーマンスの面を考えると 意味があります。
Continue 文
ContinueStatement: continue; continue Identifier ;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; break Identifier ;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; return Expression ;返値を定めて、 現在の関数を終了します。 void以外の返値型を持つ関数ならば、 Expression が必要です。 Expression は暗黙の内に返値型へ変換されます。
void以外の返値型を持つ関数では、インラインアセンブラを含まない場合には、 最低でも1つのreturn, throw, あるいは assert(0) 文が必要です。
void 返値の関数であっても、return に Expression を指定することは可能です。この場合、Expression は評価されますが、何か返値を返すようになるわけではありません。 Expression に副作用が無く、返値型が void の場合は、そのコードは不正なものとされます。
関数が完全に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文を囲む最も内側のswitch文 のdefault文へ処理を移します。
三番目の形 goto case; では、 このgoto文を囲む最も内側のswitch文における、 直後のcase文へ処理を移します。
四番目の形 goto case Expression; では、 このgoto文を囲む最も内側のswitch文における、 Expressionとマッチするcase文へと処理を移します。
switch (x) { case 3: goto case; case 4: goto default; case 5: goto case 4; default: x = 4; break; }関連するfinally節は全て実行され、 同期オブジェクトは解放されます。
初期化を skip するようなgotoは不正です。
With 文
with文によって、 同じオブジェクトへの参照の繰り返しを簡単化できます。WithStatement: with ( Expression ) ScopeStatement with ( Symbol ) ScopeStatement with ( TemplateInstance ) ScopeStatementExpression はクラスの参照か、 構造体のインスタンスへと評価されるものを指定します。 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; と同じ }
Synchronize 文
synchronize文は、クリティカルセクションで他の文を包んで、 複数のスレッドが同時に文を実行することを禁止します。
SynchronizedStatement: synchronized ScopeStatement synchronized ( Expression ) ScopeStatement
同時に高々1つのスレッドのみが mutex を取得して ScopeStatement を実行することが許されます。
どの mutex によってこの動作が実現されるかは、Expression によって決まります。Expression が指定されなかった場合、 一つのsynchronized文につき一つグローバルなmutexが作成されます。 異なるsynchronized文は異なるグローバルmutexを使用します。
ScopeStatement が例外,goto,returnなどで終了した場合であっても、 ロックは解除されます。
例:
synchronized { ... }
これは標準的なクリティカルセクションの実装です。
Try Statement
例外処理は、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はObjectの派生クラスです。 型Tの派生クラスがthrowされた場合、 そのオブジェクトで v が初期化され、 catch文の中身が実行されます。
型Tだけが書かれ変数名vが無いときも、 catch文は実行されます。
もし CatchParameter の型T1が後ろに続く 型T2の Catch を隠していた場合、つまりT1がT2と同じ型か T2の基底クラスの場合は、エラーになります。
LastCatch は全ての例外をキャッチします。
FinallyStatement は、 try ScopeStatement がgoto,break,continue,return,例外, 正常終了のいずれで終わる場合も必ず実行されます。
FinallyStatement 内で例外が発生し、 その FinallyStatement の中でcatchされなかった場合、 新しい例外が現在処理中の例外に置き換わります:
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 second done
FinallyStatement から goto, break, continue, return などで脱出したり、gotoで中に飛び込んだりしてはいけません。
FinallyStatement の中には Catches を含むことはできません。 この制約は将来的には緩和される予定です。
Throw 文
例外を投げます。ThrowStatement: throw Expression ;Expression はオブジェクトの参照へと評価される必要があります。 そのオブジェクトが例外として送出されます。
throw new Exception("message");
スコープガード文
ScopeGuardStatement: scope(exit) NonEmptyOrScopeBlockStatement scope(success) NonEmptyOrScopeBlockStatement scope(failure) NonEmptyOrScopeBlockStatementScopeGuardStatement は、指定された NonEmptyOrScopeBlockStatement を、 この文自体が現れた箇所ではなく、 現在のスコープの終了時に実行します。 scope(exit) は、 スコープから通常終了するときと例外で抜ける時の両方でNonEmptyOrScopeBlockStatementを実行します。 scope(failure) は、 スコープを例外で抜ける時のみNonEmptyOrScopeBlockStatementを実行します。 scope(success) は、 スコープから通常終了するときのみNonEmptyOrScopeBlockStatementを実行します。
一つのスコープに複数の ScopeGuardStatement があった場合は、 ソースコード文字列上で現れた順番の逆順で実行されます。 そのスコープ終了時に破棄されるscopeインスタンスが存在する場合も、 それらのデストラクタも、ソースコード上の逆順で ScopeStatement の間に挟まって実行されます。
writef("1"); { writef("2"); scope(exit) writef("3"); scope(exit) writef("4"); writef("5"); } writefln();というプログラムは以下を出力します:
12543
{ scope(exit) writef("1"); scope(success) writef("2"); scope(exit) writef("3"); scope(success) writef("4"); } writefln();の場合はこうなります:
4321
class Foo { this() { writef("0"); } ~this() { writef("1"); } } try { scope(exit) writef("2"); scope(success) writef("3"); scope Foo f = new Foo(); scope(failure) writef("4"); throw new Exception("msg"); scope(exit) writef("5"); scope(success) writef("6"); scope(failure) writef("7"); } catch (Exception e) { } writefln();の時は:
0412scope(exit) と scope(success) 文は、 throw, goto, break, continue, return 文で終了したり、 goto で中に飛び込んだりしてはいけません。
Volatile 文
volatile文の境界をまたいだコードの移動は行われません。VolatileStatement: volatile Statement volatile ;Statement 文が評価されます。 この際、Statement の前のメモリ書き込みは全て、 Statement 内および後のメモリ読み込みよりも前に完了されます。 Statement より後のメモリ読み込みは全て、 Statement 内および前のメモリ書き込みが完了した後に実行されます。
volatile文はアトミック性は保証しません。 その用途には synchronized 文を使います。
Asm 文
インラインアセンブラはasm文によってサポートされます。AsmStatement: asm { } asm { AsmInstructionList } AsmInstructionList: AsmInstruction ; AsmInstruction ; AsmInstructionListasm文は、アセンブラ言語の命令を直接使うことを可能にします。 これによって、外部のアセンブラを用いずに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 { /* ... なんらかのworkaround ... */ }
意味的に連続している複数の AsmStatement 文の間には、 余計な命令 (レジスタの退避や復元等)がコンパイラによって勝手に挿入されることはありません。
pragma文
PragmaStatement: Pragma NoScopeStatement
mixin文
MixinStatement: mixin ( AssignExpression ) ;
AssignExpression は、 コンパイル時定数へと評価される文字列である必要があります。 Tその文字列の内容は正当な 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 }