2024年5月30日木曜日

Introduction to the D language

 D は、強力で効率的な最新のプログラミング言語です。 複数のパラダイムを組み合わせて、ソフトウェア設計のまったく新しい世界を切り開きます。 デスクトップ アプリケーションと Web アプリケーションの両方の開発に使用され、将来的にはモバイルを含むターゲットになり、複数のプラットフォームで利用できます。 1 つまたは複数の C ファミリー言語の経験がある人なら誰でも知っているでしょう。 ただし、類似点の中にいくつかの相違点が隠されているため、他の言語の一般的なイディオムを適用しようとすると驚くことがあります。 D を独学で学習する場合、習得に時間がかかる可能性があります。 言語を最大限に活用し、慣用的な D プログラマーになるためには、D で考える方法を学ぶ必要があります。 D の知識をより迅速かつ簡単に次のレベルに引き上げることができます。 あなたの旅は、言語の味と、DMD (Digital Mars によって開発されたリファレンス D コンパイラー)、およびコミュニティが開発したビルド ユーティリティおよびパッケージ マネージャーである DUB を使用した D プログラムのコンパイルの基本から始まります。 次に、主要な言語機能の調査に着手します。 これは、組み込み型、条件文、ループ、および D プログラムのすべての基本的なビルディング ブロックを含む D の基礎から始まり、D のオブジェクト指向プログラミング サポートの調査が続きます。 これらの機能が、既に使い慣れている言語とどのように異なるかを学習します。 次は、コンパイル時関数評価や条件付きコンパイルなどの D のコンパイル時機能と、テンプレートを使用した汎用プログラミングです。 その後、範囲と関数型パイプライン プログラミングのより高度な機能について学習します。 D エクスペリエンスを向上させるために、次に D エコシステムのツアーに参加し、D を C と対話させる方法を学びます。最後に、vibe.d プロジェクトを使用した D Web 開発を見て、本をいくつかの便利なもので閉じます。 次に行く場所のアドバイス。

 

 

 

 

 

D プログラミング言語のコア機能に飛び込む前に、いくつかの基礎を築く必要があります。 この最初の章は、D の穏やかな紹介と、この本全体で使用する取引の必須ツールをインストールするためのガイドとして機能します。 ここに示したサンプル プログラムは、驚くほど驚くべきものではありませんが、他の言語にはない D の機能をいくつか示しています。 本書の中で開発されたサンプルプロジェクトもここで紹介され、その背後にある動機と実装される機能を見ていきます。 期待できる内容は次のとおりです。

 

 

 

 

Say hello to D: これは単純な D プログラムを調べ、D に関するヘルプをどこに求めればよいかをアドバイスします。
Digital Mars D コンパイラ: DMD のインストール方法と実行方法について説明します。
MovieMan の紹介: これは、この本全体で開発されたサンプル プロジェクトの 2 つのバージョンの紹介です。
DUB — D ビルド ツールおよびパッケージ マネージャー: DUB をインストールして、MovieMan の最初のバージョンをビルドするように構成する方法について説明します。

 

 

 

 

D プログラミング言語は、C ファミリーに属するマルチパラダイム言語です。 そのままで、手続き型、オブジェクト指向、ジェネリック、ジェネレーティブ、および関数型プログラミングの側面をサポートします。 それが OOP 言語または関数型プログラミング言語であるとか、特定のパラダイムに正確に分類できると言っているわけではありません。 D の哲学は、プログラマーが効率性、制御性、およびモデリング能力を持ちながら、すべての異なる部分がスムーズに連携できるようにするさまざまなツールを提供することです。 必要に応じてオブジェクト指向を使用しますが、コード ベースの他の領域での手続き型プログラミングや関数型プログラミングには使用しません。 多くの D プログラマーは、D の特定の機能を 1 つだけ切り離して考えると、D 言語を楽しく使用できるようにするものはないと言うでしょう。 むしろ、D コードを書き続けるのは、すべての部分の合計です。

 

他の C ファミリー言語から D に来る新しいユーザーは、見慣れた多くの言語を見つけるでしょう。 これは心強いものであり、D の機能のいくつかを学習する上で有利なスタートを切ることができます。 ただし、注意が必要です。 その親しみやすさを当然のことと考えて、D をより親しみやすい言語であるかのように書こうとするのは魅力的です。 ほとんどの場合、物事は期待どおりに機能します。 結局のところ、D は C ファミリーのメンバーです。 しかし場合によっては、予期しないコンパイラ エラーが発生したり、一部のコード ブロックが新しい D ユーザーが想定したとおりに動作していないことに気付くことがあります。 他の場合では、特に標準ライブラリを使用する場合に、読みやすさや保守性を改善できる、より慣用的な D アプローチがあります。

 

新しい言語を効果的に学ぶには、できる限り白紙の状態に近づける必要があります。 表面上は機能が他の言語と同じように見えるからといって、その機能が下で同じであるとは限りません。 その前提を前提として、この本の一貫したテーマは、D は C++、Java、C#、または D 以外の言語ではないということです。D コードを書くときは、D で考えるべきです。 D の機能を紹介するだけでなく、特定の機能が他の言語とどのように異なるかを明示的に示すことを目的としています。 そのようなスニペットを自分でエディターに入力することをお勧めします。特に、C++ が既に筋肉の記憶にある場合はそうです。 遭遇したスニペットを実装し、動作の違いを確認すると、独自の D プロジェクトで作業するときに、C++ ではなく D で考える可能性が高くなります。

 

 

 

このセクションでは、いくつかの言語とライブラリ機能を示す単純な D プログラムを紹介します。 次に、各機能の簡単な説明と、それがより詳細に説明されている章への参照を示します。 C に精通している人なら誰でもコードを簡単にたどることができるはずですが、珍しいと思われる機能がいくつかあるかもしれません。 わかりにくいと感じたとしても、今は心配しないでください。 各機能については、本書の後半で説明します。

 

この本のすべてのコード スニペットと例を実装する準備として、コマンド ラインから簡単に移動できる場所にディレクトリを作成することをお勧めします (C:\LearningD や ~/learningd など)。 このディレクトリにどのような名前を付けても、本書では $LEARNINGD と呼ばれます。 各章には独自のサブディレクトリが必要です。 このセクションの例は、$LEARNINGD/Chapter01/hello.d として保存する必要があります。

 

 

 

 

最初に理解しておくべきことは、すべての D ソース ファイルは、コンピューター上の単なるテキスト ファイルである以上の目的を持っているということです。 D ソース ファイルも D モジュールです。 ほとんどの D ユーザーは、ソース ファイルとモジュールという用語を同じ意味で使用します。 モジュールは、D プログラムのカプセル化のいくつかのレベルの 1 つです (カプセル化については、第 3 章「D によるオブジェクトのプログラミング」で詳しく説明します)。 hello.d という名前の D ソース ファイルをコンパイルすると、コンパイラはそれをメモリに読み込み、プログラムには hello という名前のモジュールが 1 つ含まれます。 これがデフォルトの動作です。 単純な 1 つのモジュールのプログラムを実装しているので、デフォルトをそのまま受け入れて、それをオーバーライドする方法を学ぶのを先延ばしにします。

 

 

 

import 宣言は、import キーワードの直後の名前で指定された特定のモジュールを検索し、現在のモジュールの特定のセクション内でそのシンボルの一部またはすべてを使用可能にするようにコンパイラに指示します。 ここで使用する、シンボルが指定されていない形式では、インポートされたモジュールで公開されているすべてのシンボルが利用可能になります。 宣言の場所によって、シンボルが使用できるセクションまたはスコープが決まります。 この場合、宣言はモジュール スコープ内にあり、特定のシンボルがリストされていないため、実質的な効果は、core.thread および std.stdio モジュールで公開されているすべてのシンボルが、hello モジュール全体で使用できることです。

 

モジュール名 std.stdio を検討してください。 名前の各部分には定義された意味があります。 モジュール名という用語は完全な名前を指すために使用されますが、ドットの右側の部分 stdio は実際のモジュール名です。 ドットの左側の std は、パッケージの名前です。 パッケージは、モジュールを階層にグループ化するために使用されます。 モジュールは 1 つのパッケージにのみ属することができますが、そのパッケージは別のパッケージのサブパッケージになることができ、別のパッケージのサブパッケージになることもできます。 つまり、mylib.data.formats.json のように、ドットの左側に複数のパッケージ名を付けることができます。 ここでは、3 つのパッケージがあり、json というモジュールを参照しています。 format という名前のパッケージはデータのサブパッケージであり、mylib と呼ばれる最上位またはルート パッケージのサブパッケージです。

 

std および core パッケージは、すべての D コンパイラで利用できます。 前者は D 標準ライブラリである Phobos の一部であり、後者はランタイム ライブラリである DRuntime の一部です。 std.stdio は、標準入力 (stdin) からの読み取りや標準出力 (stdout) への書き込みなど、基本的なファイル I/O に必要なすべてのものを利用できるようにします。 モジュール core.thread は、新しいスレッドを作成し、現在のスレッドの実行に影響を与える機能を提供します。

 

 

 

 

 

 

すべての D プログラムには、main という名前の関数が必要です。 プログラムが最初に起動されると、制御はオペレーティング システムから C ランタイムに渡され、そこから D ランタイムに渡されます。 最後に、main が呼び出され、プログラムが制御を取得します。 これについては、Programming Objects the D Way でもう少し詳しく見ていきます。このとき、main が呼び出される前にコードを実行できることもわかります。

 

メイン関数を宣言するための 4 つの基本的な選択肢があります。 どちらを選択するかは、プログラムの要件に完全に依存します。

 

 

 

 

最初の 2 つのバージョンは、最終的に後の 2 つのバージョンと同等です。 コンパイラは、実行が成功したときに実際に 0 を返すことを保証します。 例外がスローされた場合、実行は失敗したと見なされます (例外は、D Way のオブジェクトのプログラミングで導入されています)。 この本のほとんどの例では、最初の署名だけで十分です。 後でいくつかのスニペットを除いて、コマンド ライン引数を解析しないので、文字列の配列を受け入れるフォームを省略できます。 また、終了時に OS に戻り値を渡す必要があるプログラムを作成していないため、int 戻り値を持つバージョンは必要ありません。

 

Windows プログラマーは、D が WinMain をどのように処理するのか疑問に思うかもしれません。 DRuntime は main しか認識しないため、WinMain がプログラムのエントリ ポイントとして使用される場合、通常 DRuntime によって実行されるすべての初期化を手動で処理する必要があります。 DRuntime については後で詳しく説明します。

 

コードに戻りましょう。 次の行は別のインポート宣言で、モジュールの上部にある 2 つの宣言とは異なります。

 

 

 

 

この宣言は関数内にあるため、スコープ付きインポートと呼ばれます。 スコープ付きインポートによって可視化されたシンボルは、宣言が行われたスコープ内でのみ可視化されます。 この場合、シンボルはメイン内でのみ表示されます。 ただし、この宣言には続きがあります。 コロンの後に iota と retro が続くことに注意してください。 インポート宣言では、コロンの後にコンマ区切りの記号のリストが続きます。これは、リストされた記号のみが表示されることを意味します。 この場合、std.range のシンボルは、iota と retro 以外の main に表示されません。 彼らが何をするかはすぐにわかります。

 

実際に画面に何かを表示する行の時間です。 そのために、便利で非常に柔軟な関数を std.stdio モジュールから呼び出します。

 

 

 

write("ご挨拶");
write 関数は、テキスト文字列を標準出力に出力する数少ない関数の 1 つです。 これは C 標準ライブラリ関数 puts に似ていますが、任意の型の任意の数の引数を取ることができるという点で異なります。 各引数は、関数に与えられた順序で出力され、その間にスペースは追加されません。 例えば:

 

write("Happy", 100, "誕生日おめでとう", "あなた!")
これにより、Happy 100th birthday to you! というテキストが出力されます。

 

次の行では、次の 3 つの項目を紹介します。

 

foreach(num; iota(1, 4).retro) {

 

foreach ループは、範囲を反復処理するために使用できるループ構造です。 iota は、数値の範囲 (この場合は 1 から 3) を返す関数です。 retro は、範囲を入力として取り、元の範囲の要素を逆順で含む新しい範囲を返す関数です。 この行の最終的な結果は、数値 3、2、1 を反復するループです。 foreach ループについては、第 2 章「D の基礎を使用した基盤の構築」で説明されています。 第 6 章「範囲を理解する」全体は、D の不可欠な部分である範囲の説明に専念しています。iota 関数と retro 関数の両方については、第 7 章「アルゴリズムと範囲を使用した関数パイプラインの構成」で説明されています。

 

ここで、 iota(1, 4).retro は retro(iota(1, 4)) と同じであることに注意してください。 前者の構文は、Uniform Function Call Syntax (UFCS) と呼ばれる機能により可能です。 関数 func と関数引数 arg が与えられると、func(arg) は arg.func() として記述できます。 UFCS の詳細については、Chapter 2, Building a Foundation with D Fundamentals を参照してください。

 

次は foreach ループの 3 行です。

 

 

 

 

writef("%s...", 数値);
stdout.flush();
Thread.sleep(1.seconds);

 

writef 関数は、書式設定されたテキスト文字列を標準出力に出力する write のバリエーションです。 これは、C 標準ライブラリ関数の printf に似ています。 with .stdout は File と呼ばれる型のグローバル インスタンスであり、どちらも std.stdio で宣言されています。

 

ファイル ハンドルに書き込むとき、オペレーティング システムは効率のためにテキストを内部的にバッファリングします。 通常、ハンドルがコンソールまたは端末に属している場合、ライン バッファリングが有効になります。 これは、改行文字が出力ストリームに出力されるときにバッファがフラッシュされることを意味します。 この例では、flush を呼び出すと、バッファが手動でフラッシュされ、1 秒あたり 1 つの数値が出力されるという効果が得られます。 そうしないと、ループが終了し、writeln への最初の呼び出しが実行された後に、すべてが一度に出力されます。 この効果は、プロセスの実行を 1 秒間一時停止させる Thread.sleep の呼び出しによって調整されます。

 

Thread.sleep への呼び出しは UFCS を使用していないことに注意してください。 スレッドはクラスであり、スリープは静的メンバー関数です。 ただし、1.seconds は UFCS を使用します。 関数 seconds は、core.time という名前のランタイム モジュールで宣言されています。 このモジュールは、すべてのシンボルが表示されるように、core.thread によって間接的にインポートされます。 1.seconds は seconds(1) と同じです (関数呼び出しの括弧はオプションの場合があります)。 この関数は、sleep が現在のスレッドを一時停止する時間を決定するために使用する Duration 型のインスタンスを返します。 パブリック インポートと関数呼び出しの構文については、Chapter 2, Building a Foundation with D Fundamentals で説明しています。 クラスとメンバー関数は、Chapter 3, Programming Objects the D Way で紹介されています。

 

 

 

 

writeln();
writeln("ハローワールド!");

 

writeln 関数は write と同じですが、出力に改行文字を追加する機能が 1 つあります。 ここでは、2 回呼び出します。 最初の呼び出しは、ループで書き込まれたテキストに改行を追加し、2 番目の呼び出しは挨拶を出力します。 これは writeln("\nHello world!") のように 1 行に要約できます。 writefln と呼ばれるこの関数のフォーマット バージョンもあることに注意してください。

 

このプログラムが期待どおりに動作することを確認するには、コンパイルして実行する必要があります。 その方法については、この章の後半で説明します。

 

助けを求める
D との旅では、必然的に支援が必要になります。 経験豊富な D ユーザーが、経験の浅い D ユーザーからの質問に答えたり、熱心なプログラマーが行うことで知られているように、言語機能について激しい議論を交わしている主要なオンライン ロケールがいくつかあります。

 

新しい D ユーザーが最初に見るべき場所は http://forum.dlang.org/ です。 これは、URL が示すような自己完結型のフォーラムではなく、Digital Mars が管理するニュースグループ サーバーへの Web インターフェイスです。 なぜ D フォーラムの投稿を編集または削除できないのか疑問に思ったことがある場合は、これが理由です。 新規ユーザーを対象としたフォーラムは digitalmars.D.learn であり、プロジェクトと主要なニュースの発表は digitalmars.D.announce で行われます。また、digitalmars.D では、言語の状態とその将来について目撃したり、議論に参加したりできます。 方向。 D とそのエコシステムに慣れてくると、他のフォーラムに興味を持つようになるかもしれません。

 

 

 

 

DFeed と呼ばれる Web インターフェイスは、Vladimir Panteleev というアクティブなコミュニティ メンバーによって D で開発されました。 DFeed のソースは https://github.com/CyberShadow/DFeed にあります。

 

Web インターフェースでそれができない場合は、フォーラムにアクセスするための他のオプションがあります。 プライマリ バックエンドがニュースグループであることを考えると、news.digitalmars.com のニュースグループ リーダーにアカウントを設定し、興味のあるニュースグループを選択できます。 または、ブラウザで http://lists.puremagic.com/mailman/listinfo にアクセスし、メーリング リスト インターフェイスを介して関心のあるフォーラムを購読することもできます。 繰り返しになりますが、メーリング リストは集合的にニュースグループの代替インターフェイスであり、完全に独立したエンティティではありません。

 

D コミュニティは一般的に、digitalmars.D.learn フォーラムで質問をする人に親切でフレンドリーです。 D フォーラムで質問することを躊躇してはいけません。 経験豊富なユーザーは定期的に立ち寄り、最も基本的な質問に喜んで答えます。 また、#D IRC チャネルでアイドリングしている多数の D ユーザーを見つけることができます。 IRC クライアントをお持ちの場合、#D は http://freenode.net/ にあります。 そこにいる誰でも D に関するあなたの質問に答えることができます。私はあまり IRC ユーザーではありませんでしたが、時々 #D に立ち寄っています。 私が近くにいるときはいつでも、この本や私の他の D プロジェクトに関する質問に喜んで答えます。 私は通常、IRC では aldacron というハンドル名で、フォーラムでは本名で見つかります。

 

 

 

 

Digital Mars D コンパイラ
DMD は、D プログラミング言語のリファレンス コンパイラです。 Walter Bright によって作成され、少数の有能なボランティアの助けを借りて、彼によって現在も維持されています。 D コンパイラはこれだけではありません。 GDC は GNU Compiler Collection (GCC) の上に構築され、LDC は LLVM ツールチェーンを使用します。 どちらのコンパイラも、D コミュニティのメンバーによって作成され、引き続き維持されています。 より多くの D コードを作成すると、DMD のコンパイル時間が非常に高速であることがわかりますが、GDC と LDC は高度に最適化された実行可能ファイルを生成し、パフォーマンスが向上する傾向があります (ただし、コンパイル速度の点では決して遅いわけではありません)。 このため、D プログラマーが新しいプロジェクトの開発中に DMD を使用してコンパイル時間を短縮し、他のコンパイラのいずれかを使用して最終リリースをビルドし、より高度な最適化の恩恵を受けることは珍しくありません。 とはいえ、この本では DMD だけに焦点を当てます。 コード スニペットとサンプル プロジェクトは、GDC または LDC で正常にコンパイルされるはずですが、テキスト内のコンパイラ固有の指示は DMD 用になります。

 

DMD のインストールに関して複雑なことは何もありません。 これを実現する前に、DMD の仕組みについていくつか理解しておくことが重要です。

 

 

 

 

フロントエンド、バックエンド、リンカー
D の初期の開発中に Walter が確立した主な目標の 1 つは、D が C とバイナリ互換でなければならないということでした。基本的に、これは、C ソース ファイルを C コンパイラでコンパイルし、その出力を次のプログラムに結合できることを意味します。 D で書かれ、D コンパイラでコンパイルされたもの、またはその逆。 この目標を達成するには、D コンパイラからの出力を C ツールチェーンが理解できる形式にする必要があります。 コンパイラ テクノロジの詳細な説明は、この本の範囲をはるかに超えていますが、DMD をより効果的に機能させるためには、コンパイラ テクノロジのごく一部について最小限の理解が必要です。

 

コンパイラには通常、連携して最終的な出力を作成する 2 つの主要なコンポーネント (フロントエンドとバックエンド) があります。 フロントエンドは特定の言語に直接関連付けられています。 ソースコードを入力として受け取り、出力として中間の言語に依存しない形式に変換します。 バックエンドは特定のプラットフォームに関連付けられています。 フロントエンドから変換されたコードを入力として受け取り、通常はオブジェクト ファイルの形式でマシン コードを出力として生成します。 オブジェクト ファイルが作成されると、最終的に次の 2 つのいずれかが行われます。それらを最終的な実行可能ファイルにリンクするツールに渡されるか、ライブラリにパックするツールに渡されます。 前者のツールはリンカーと呼ばれ、後者はライブラリアンまたはアーカイバーと呼ばれますが、1 つのツールで両方のタスクを実行することも可能です。

 

Walter が C とのバイナリ互換性という目標を達成するために、可能であれば既存のツールチェーンを利用することを選択しました。 このようにして、彼は実際に D ソース コードを処理する部分であるフロントエンドにほとんどの労力を費やし、残りは十分に試行錯誤された既存のツールに任せることができました。 彼はすでに C および C++ コンパイラ ビジネス (彼が所有する会社である Digital Mars は、Digital Mars C および C++ コンパイラ (DMC) を配布しています) に携わっていたので、ハード ドライブに独自の既存のバックエンドとリンカーを置いていました。 適切に、彼は D フロントエンドの実装を開始し、それを DMC バックエンドに接続して DMD を作成しました。 Digital Mars C(++) ツールチェーンは Windows 固有であるため、DMD を追加のプラットフォームに移植するときは、他のオプションを考慮する必要がありました。 Walter の解決策は、バックエンドを変更して各ターゲット プラットフォームに適切な出力を生成し、コンパイラに各プラットフォームのシステム リンカとライブラリアンを使用して最終的なバイナリを生成させることでした。

 

POSIX システムで DMD を実行するのは簡単なプロセスです。 コンパイラはシステム ツールチェーンを使用して、サポートする各システムで最終的な出力を作成するため、競合することなくスムーズに動作する傾向があります。 これには、32 ビットと 64 ビットの両方の出力のサポートが含まれます。 Windows の話はそれほど楽観的ではありません。

 

 

 

 

この本では、POSIX は、100% POSIX 認定されているかどうかに関係なく、DMD によってサポートされる Windows 以外の Unix に似たシステムを説明するために使用されます。 これには、Linux、Mac OS X、およびさまざまな BSD が含まれます。

 

問題は、Digital Mars リンカの OPTLINK が古いことです。 ほとんどの最新の C および C++ ツールチェーンと互換性のないオブジェクト ファイル形式を使用します。 この問題については、第 9 章「D と C の接続」で詳しく説明します。これは、C と対話する際に理解することが重要であるためです。OPTLINK に関するもう 1 つのポイントは、32 ビット出力のみをサポートすることです。 64 ビットのサポートは、Microsoft ツールチェーンが理解できる形式でオブジェクト ファイルを生成する機能を DMD に与えることで、Windows に実装されました。

 

このアプローチの主な利点は、少なくとも -m64 コマンド ライン スイッチを使用して 64 ビット バイナリをコンパイルする場合に、Windows でのオブジェクト ファイル形式の競合による煩わしさを解消できることです。 欠点は、DMD を使用して 64 ビット バイナリをコンパイルするには、MS ツールを含む Windows SDK のバージョン、または Microsoft Visual Studio の非 Express バージョンのいずれかをインストールする必要があることです。 DMD は、Windows での 64 ビット開発用の完全に自己完結型のディストリビューションになることはできません。 DMD 2.067 以降、デフォルトの Digital Mars ツールチェーンの代わりに Microsoft ツールを使用して、コマンド ライン スイッチ -m32mscoff を DMD に渡すことで 32 ビット バイナリを生成することもできます。

 

</p

 

 

コマンド ラインで DMD を使用して単純な D プログラムをコンパイルするのは難しいことではありませんが、プロジェクトが大きくなるにつれて、少し手に負えなくなることがあります。 複雑な D プログラムは多数のモジュールで構成され、複数のサードパーティ ライブラリに依存する場合があります。 ユーザーが D プロジェクトをコンパイルするために取ったアプローチは多数あります。 Makefile や、CMake や SCons など、D を何らかの形でサポートする既存のツールを使用している人もいれば、独自のビルド ツールをゼロから作成している人もいます。 数年間、ほとんどのプロジェクトで D で記述されたカスタム ビルド スクリプトを使用していました。

 

以前は、D コードをビルドするための非常に多くの異なるアプローチがあることは、特にそれぞれが異なるビルド システムを持つ複数のライブラリを使用する場合に、D ライブラリのユーザーにとって少し面倒なことでした。 私が管理しているライブラリのユーザーから、自分のワークフローに合わせてさまざまなビルド システムのサポートを追加するように依頼されることがよくありましたが、そのようなサポートを学習、実装、および維持すると、すでに不足している自由時間を削ってしまうため、私は非常に気が進まなかったのです。 ありがたいことに、これは D コミュニティが置き去りにしてきた 1 つの増大する苦痛です。 このセクションでは、急速に D エコシステムの中心となっている DUB について紹介します。

 

Sönke Ludwig によって作成された DUB は、D プロジェクトを構築するための統一されたアプローチを提供します。 これを容易にするために、配布プラットフォームとしても機能します。 D ライブラリを持っている人は誰でも、DUB 構成を作成し、DUB レジストリに登録することで、それを世界と共有できます。 その後、DUB ユーザーは、プロジェクトの DUB 構成ファイルに 1 行を追加することで、ライブラリを使用できます。 また、オープン ソースの実行可能ファイルを簡単に配布するためにも使用できます。 潜在的なユーザーは、1 つの DUB コマンドを使用して、D プログラムをダウンロード、コンパイル、および実行できます。

 

第 8 章「D の広い世界を探る」では、D エコシステムのツアーに参加しながら、DUB レジストリを調べ、独自のプロジェクトで DUB 対応ライブラリを使用する方法を確認します。 次に、MovieMan 専用の DUB 構成をセットアップしますが、作成する新しいプロジェクトにも同じ手順を適用できます。

 

 

 

 

D はシステム プログラミング言語です。 その焦点は、C および C++ のパワーと高性能を、Ruby や Python などの最新言語のプログラマーの生産性と組み合わせることにあります。 品質保証、文書化、管理、移植性、および信頼性のニーズに特別な注意が払われています。

 

 

 

 

これは、D プログラミング言語で書かれた単純な電卓用のパーサーおよびスキャナー プログラムです。 このプログラムは、足し算、引き算、掛け算、割り算などの基本的な算術演算を実行できます。

 

このプログラムは、Bison を使用してパーサー コードを生成し、電卓の文法を定義します。 PLUS、MINUS、STAR、SLASH、LPAR、RPAR、EOL、NUM など、パーサーが使用するトークンを定義します。 また、演算子の優先順位と式の文法規則も定義します。

 

このプログラムには、入力範囲を取得してトークンをスキャンする「calcLexer」という名前のスキャナー関数が含まれています。 最初のスペースをスキップし、数字と個々の文字 (演算子や括弧など) を識別します。 スキャナーがエラーを検出すると、それを標準エラーに報告します。

 

メイン関数は、スキャナーのインスタンスを作成し、スキャナーを使用してパーサーのインスタンスを作成し、スキャナーを使用してパーサーにトークンを提供します。 環境変数 YYDEBUG が設定されている場合、プログラムはデバッグ情報を出力します。

 

 

/* Parser and scanner for calc in D. -*- D -*-

 

Copyright (C) 2018-2021 Free Software Foundation, Inc.

 

This file is part of Bison, the GNU Compiler Compiler.

 

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

 

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

 

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */

 

%language "D"

 

%define api.parser.class {Calc}
%define api.push-pull push
%define api.token.constructor
%define api.value.type union
%define parse.error detailed
%define parse.trace

 

%locations

 

/* Bison Declarations */
%token PLUS "+"
MINUS "-"
STAR "*"
SLASH "/"
LPAR "("
RPAR ")"
EOL "end of line"
%token NUM "number"
%type exp
%printer { yyo.write($$); }

 

%left "-" "+"
%left "*" "/"
%precedence UNARY /* unary operators */

 

/* Grammar follows */
%%
input:
line
| input line
;

 

line:
EOL
| exp EOL { writeln ($exp); }
| error EOL { yyerrok(); }
;

 

exp:
NUM { $$ = $1; }
| exp "+" exp { $$ = $1 + $3; }
| exp "-" exp { $$ = $1 - $3; }
| exp "*" exp { $$ = $1 * $3; }
| exp "/" exp { $$ = $1 / $3; }
| "+" exp %prec UNARY { $$ = $2; }
| "-" exp %prec UNARY { $$ = -$2; }
| "(" exp ")" { $$ = $2; }
;

 

%%
import std.range.primitives;
import std.stdio;

 

auto calcLexer(R)(R range)
if (isInputRange!R && is(ElementType!R : dchar))
{
return new CalcLexer!R(range);
}

 

auto calcLexer(File f)
{
import std.algorithm : map, joiner;
import std.utf : byDchar;

 

return f.byChunk(1024) // avoid making a syscall roundtrip per char
.map!(chunk => cast(char[]) chunk) // because byChunk returns ubyte[]
.joiner // combine chunks into a single virtual range of char
.calcLexer; // forward to other overload
}

 

class CalcLexer(R) : Lexer
if (isInputRange!R && is(ElementType!R : dchar))
{
R input;

 

this(R r) { input = r; }

 

Location location;

 

// Should be a local in main, shared with %parse-param.
int exit_status = 0;

 

void yyerror(const Location loc, string s)
{
exit_status = 1;
stderr.writeln(loc.toString(), ": ", s);
}

 

Symbol yylex()
{
import std.uni : isWhite, isNumber;

 

// Skip initial spaces
while (!input.empty && input.front != '\n' && isWhite(input.front))
{
location.end.column++;
input.popFront;
}
location.step();

 

if (input.empty)
return Symbol.YYEOF(location);

 

// Numbers.
if (input.front.isNumber)
{
import std.compiler : version_minor;
static if (version_minor >= 95)
{
// from Dlang v2.095.0 onwards std.conv.parse reports
// the number of consumed characters
import std.typecons : Flag, Yes;
import std.conv : parse;
auto parsed = parse!(int, R, Yes.doCount)(input);
int ival = parsed.data;
location.end.column += cast(int) parsed.count;
}
else
{
auto copy = input;
import std.conv : parse;
int ival = input.parse!int;
while (!input.empty && copy.front != input.front)
{
location.end.column++;
copy.popFront;
}
}
return Symbol.NUM(ival, location);
}

 

// Individual characters
auto ch = input.front;
input.popFront;
location.end.column++;
switch (ch)
{
case '+': return Symbol.PLUS(location);
case '-': return Symbol.MINUS(location);
case '*': return Symbol.STAR(location);
case '/': return Symbol.SLASH(location);
case '(': return Symbol.LPAR(location);
case ')': return Symbol.RPAR(location);
case '\n':
{
location.end.line++;
location.end.column = 1;
return Symbol.EOL(location);
}
default: assert(0);
}
}
}

 

int main()
{
auto l = calcLexer(stdin);
auto p = new Calc(l);
import core.stdc.stdlib : getenv;
if (getenv("YYDEBUG"))
p.setDebugLevel(1);
int status;
do {
status = p.pushParse(l.yylex());
} while (status == PUSH_MORE);
return l.exit_status;
}

 https://dlang.org/spec/spec.html

0 件のコメント: