図形 - MPS 入門チュートリアル
MPS に不慣れですぐにそれを試してみたいなら、これはあなたのための正しいチュートリアルです。2 時間以内に、新しい言語とその言語を使用する機能コードを入手できます。このチュートリアルでは、最初から始め、安全で便利な道を歩むことで、新しい言語のコア要素を設計します。すぐにフィニッシュラインに到達するために、高度な概念、複雑な構成要素、暗いコーナーを避けます。最後に、MPS とは何か、MPS がどのような原則に基づいているのかを理解できます。
シートベルトを締めてください。高速に走ります。
前提条件
MPS チュートリアルへのファーストトラックの最初の部分を経験し、MPS 環境に精通し、言語とソリューションの概念を理解し、MPS 射影エディターを操作できることを前提としています。そうでない場合は、最初の 30 分程度を費やして検討してください。特に、これらのキーボードショートカットはあなたの生存の鍵です。
Ctrl+Space - 不完全な単語を補完したり、無効な(赤い)識別子を正しい(黒い)識別子に変える
Alt+Enter - 現在のエディター位置に適用可能な便利なオプションを含むメニューを表示する
Ctrl+Up - 選択したテキストの領域を拡大する
Ctrl+Down - 選択したテキストの領域を縮小する
タブ - エディターで編集可能な要素を移動します
Ctrl+Z - 元に戻す
また、MPS をインストールし、あなたの目の前で実行していると仮定します。始めることができます。
ゴール
グラフィック図形を指定するためのサンプル言語を実装します。この言語により、ユーザーは平らなキャンバスに視覚的な 2 次元形状をレイアウトすることができます。その後、定義は Java Swing アプリケーションに変換され、これによって画面上のレイアウトが視覚化されます。


この言語により、プログラマー以外の人は、Java、Swing、2D グラフィックス API の知識がなくても Java アプリケーションを構築できます。一方、言語デザイナーのロールを果たし、Java の知識に基づいてこのような使いやすい言語を準備します。UI プログラマーが現在手動で解決しなければならないいくつかのケースをカバーする言語とジェネレーターを提供することにより、UI プログラマーの作業を自動化します。
Java または Swing に精通していない場合でも、あまり早く心配する必要はありません。この可能性を想定し、注意深くガイドするチュートリアルを作成しました。簡単に通過することができるでしょう、見るでしょう。
新規プロジェクトを作成する
新しいプロジェクトから始めましょう。ウェルカム画面で、新しいプロジェクトの作成をクリックし、ウィザードに従います。


空の Language Definition と空の Solution を含む空のプロジェクトが得られます。

ソリューションはプログラムを保持します。これらのプログラムを作成するには、同じプロジェクトで定義された言語、またはインポートされた言語を使用します。このチュートリアルでは、まず言語を定義し、次にそれを使用して実行できるコードを作成します。私たちのために作成されたソリューションは、サンドボックスソリューションという特別な種類のソリューションです。これらのソリューションは主に、言語設計中に言語設計者 (つまり、あなた: ) によって使用され、専用ソリューションの安全な環境でさまざまなアイデアをすぐに試してテストすることで言語設計を反復的に進化させることを目的としています。デフォルトでは、サンドボックスソリューションは、コード生成後にコンパイルされた生成出力を取得しません。ただし、このチュートリアルでは、生成された Java コードをコンパイルして実行できることがメリットとなるため、ここで追加の構成手順を実行する必要があります。つまり、サンドボックスソリューションのコンパイルを有効にします。
論理ビューのサンドボックスソリューションを右クリックし、一番下にあるモジュールプロパティオプションを選択します。

「Java」タブで、上部のコンパイル設定領域を展開し、「MPS でコンパイルする」を選択します。

ここで OK を押してダイアログを閉じれば、次の冒険の準備が整います。
内部の言語とプログラム
まず、ここで先に進む前に知っておくべき少しの背景知識があります。これは、Shapes 言語を完全に実装すれば作成できるコードの一部です。

構築している言語は、個々のコマンドで構成され、それぞれが別々の行にあり、それぞれが描画する単一の形状を定義するペイント定義を許可する必要があります。私たちの言語は、そのような各コマンドをコンセプトでカバーする必要があります。概念は、言語の抽象構文、つまり許可された言語の論理構造のセットを定義します。プログラムは、これらの概念のインスタンスを保持する抽象構文木で構成されます。

上記の短いプログラムの AST は、抽象構文を示しています。ノードで構成されており、各ノードは、言語が定義する概念のインスタンスです。概念はプロパティ、子、参照を定義し、ノードは具体的な値を提供します。
ここでちょっとした理論をまとめると:
言語 は概念で構成されています
概念は、論理(抽象)要素をそのプロパティ、子、および参照とともに定義します
プログラム(ソリューション) は、ノードで構成される AST で構成されます
ノード は、プロパティ、子、およびそれらの概念の 参照に具体的な値を与える概念のインスタンスです。
グラフィック形状
私達の言語はかなり簡単になるでしょう。必要な概念はいくつかあります。
キャンバス - 絵画の定義全体を表し、すべてのシェイプを保持する最上位ノードを定義します
形状 - キャンバスに形状を描画するコマンドを表します。これは、円や正方形など、すべての具体的な形状に共通のスーパーコンセプトとして機能します。
円 - 円を描くコマンドを表します
正方形 - 正方形を描画するコマンドを表します
ColorReference-java.awt.Color で定義されたいくつかの事前定義された色の 1 つを表します
シェイプの概念から実践的な演習を開始します。オブジェクト指向プログラミングの場合と同様に、Concepts は相互に拡張できるため、スーパーコンセプトの機能を継承できます。便利にそれを継承することができるように私たちの言語のすべての形状は、色のプロパティが必要になるため、図形の概念は、色のプロパティを保持し、そのような一般的なスーパー概念として機能します。

言語の構造アスペクトを右クリックして、新しいコンセプトを作成します。エディターで新しいコンセプト定義が開きます。

Concept にはわかりやすい名前を付ける必要があります。この場合、Shape は問題なく機能します。形状と入力するだけです。MPS は名前がないことを示します。赤い <名前なし> フィールドです。

言語のすべての図形のための共通のスーパー概念として、それ自体が、AST の中で直接使用されることはありません形状によってシェイプコンセプトを作成しました。、Shape のインスタンス(ノード)が作成できないことを明示的に示すために、抽象的な 形状マークされます。
今こそ Alt+Enter キーボードショートカットを練習する時です。
コンセプト宣言の一行目にあるバルブのマークが見えますか? (バルブを表示するには、キャレットを最初の行に移動する必要がある場合があります。これは少し恥ずかしがります。) バルブには、コードに対して実行できる内容のヒントが記載されたコンテキストメニューが隠されています。キャレットの位置。Alt+Enter を使用すると、キーボードからバルブメニュー ( インテンションメニューと呼びます) にアクセスできます。


Concept の名前に Alt+Enter キーボードショートカットを配置すると、コンテキストメニューが表示され、「abstract にする」インテンションを Concept に適用するオプションが表示されます。このオプションを選択すると、その概念は abstract としてマークされます。これで、Shape のすべてのサブ概念で共有されるプロパティ、子、参照を追加できるようになりましたが、学習曲線を平坦にするために、後回しにしておきます。
それが私たちの最初のコンセプトです ! やあ ! もう 1 つ追加する時間です。サークルはどうですか? 確かに、Structure アスペクトを右クリックして、別の Concept を作成し、それに名前を付けます -Circle。
円形
同じ手順に従って、言語の構造アスペクトを右クリックし、新しい概念を追加する必要があります。Circle という名前を付けます。

Circle は Shape から機能を継承する必要があるため、extends 句で than を示す必要があります。「BaseConcept」テキストを含むセルの先頭をポイントし、MPS で最も便利なキーボードショートカット (Control + Space) を押して、コード補完を呼び出します。MPS は、「BaseConcept」テキストの置換として適用できるオプションのリストを表示します。Circle を Shape に拡張するには、ここで Shape を選択する必要があります。

補完メニューに「図形」が表示されない場合は、キャレットが単語の最初の位置にないことが原因です。

キャレットの左側の文字は、これらの文字に一致するオプションのみを表示するフィルターとして使用されます。キャレットが右に行くほど、補完メニューに表示されるオプションが少なくなります。

左矢印を使用してキャレットを移動する必要があります。または、Ctrl + Space キーを再度押すと、MPS はキャレットを現在の単語の一番左に移動します。

これは私たちの言語のユーザーによって使用される最初の具体的な概念です。コード補完ダイアログでコンセプトに適切なテキスト表現を与え、ユーザーが「circle」と入力するたびに MPS が賢く Circle のインスタンスを作成できるようにするには、コンセプトにエイリアス「circle」を付ける必要があります。

各円は、画面上の座標と半径を指定する必要があります。これらの整数値を保持するプロパティを作成します。プロパティセクションに移動し、空の値のコレクションを表す「<< ... >>」シンボルにキャレットを置き、Enter を押します。これにより、空のプロパティが作成されます。


プロパティに「x」という名前を付け、タブを押してプロパティのタイプ「整数」を指定します。「integer」と入力しているときに Ctrl + Space キーボードショートカットを押すと、MPS によって型の名前が補完されます。


次に、"Y" と "radius" のプロパティを追加すると、最初の具体的な概念は完了です。
四角
ここで、正方形の概念を作成してみてください。Circle に対して行った手順を繰り返し、異なるプロパティを作成します。upperLeftX と upperLeftY を使用して左上隅の座標を保持し、その後にサイズを指定して正方形の辺の長さを指定します。最終的には、これに到達する必要があります。

最初の行で Shape の概念を拡張することを忘れないでください。完了メニュー(コントロール + スペース)が表示されている間も文字を入力でき、完了メニューで使用可能なエントリを除外するために使用されることに気付いたかもしれません。
キャンバス
2 つの形状を作成したら、それらすべてを絵画に保持するための概念の定義に進むことができます。シェイプで構成されるシーンを表す、Canvas と呼ばれる別のコンセプトを作成します。ユーザーは複数のシーン(Canvasses)を作成できます。これらのシーンは相互に独立しており、形状を共有しません。各キャンバスには、名前とそれに含まれるシェイプのリストが保持されます。
そのため、言語の構造アスペクトをもう一度右クリックして、新しい概念を作成し、Canvas という名前を付けます。

( タブを使用して) 実装セクションの "<none>" テキストに移動し、INamedConcept を指定します (入力するか、Control + Space を使用します)。INamedConcept は、Canvas が実装するコンセプトインターフェースです。コンセプトインターフェースは、コンセプトと同様に、新しい機能を実装するコンセプトに新しい機能を追加します。この場合、INamedConcept は Canvas を name プロパティで強化するため、Canvas インスタンス (Nodes と呼ばれる) には name プロパティがあり、ユーザーが簡単に区別できるようになります。


Canvas はペイントシーンを表し、それ自体は他の概念の一部ではないため(モデル内に親はありません)、インスタンスをルートにすることができることを示します。これにより、Canvas インスタンスを AST のルートにすることができます。
Canvasses が Shape を保持できることを示すために、Shape の子コレクションを作成します。もう一度、子セクションの「<< ... >>」セルで Enter キーを押し、子の名前と保持できるノードのタイプを入力します。コード補完ダイアログを表示するには、* * Control + Space キーのショートカットを忘れないでください。


このような概念定義で終わるべきです:

これで、ミニマル言語を使えるようにするのに十分な概念を作成しました。やるだけやってみよう。
初期の試乗
言語を使用する前に、言語を構築する必要があります。今後、言語定義を変更した後は、変更を有効にするために「再構築」プロセスを忘れずに繰り返してください。プロジェクトビューで最上位のノード ( 言語とソリューションを含むプロジェクト全体を表す最上位のルート) を右クリックし、プロジェクトの再ビルドを選択します。

ビルドが完了すると、サンドボックスソリューション内のモデルで言語を使用できるようになります。モデルを右クリックし、インスタンス化するルートコンセプトを選択して、新しいキャンバスを作成します。ルートコンセプトはこのメニューに表示されるものであり、モデル内の最上位レベルでインスタンス化できることに注意してください。

Canvas インスタンスに名前を付けると、最初の絵ができあがります。言語で定義された概念を使用して、キャンバスに視覚的な形状を配置することができます。


ここでも、Control + Space は、ここで頻繁に使用するキーボードショートカットです。何を入力するか迷うたびに使用してください。コード補完ダイアログには、言語で作成した 2 種類の図形が表示されることに注意してください。



図形リストの最後の図形の末尾で Enter キーを押すと、追加の図形を挿入できます。この場合、これは「円」の終了シンボル「}」です。Control + Space を使用すると、図形の名前を入力する必要がなくなることに注意してください。


クール ! あなたの成功をきちんと祝っていることを願っています。では、これらのエディターを調整してコードが画面上で見栄えよくなるようにしましょう。
エディター
MPS は射影エディターを使用します。エディターの動作が予想とは少し異なることに気付いているかもしれません。テキストベースの言語とは異なり、MPS はコードをプレーンテキストとして表現することはありません。代わりに、MPS のコードは常に AST (抽象構文木) を意味します。
これには、言語設計、言語の合成可能性、解析不可能な表記法に関する大きな利点があります。これらの表記法については、MPS の資料で詳しく読むことができます。
ここでは、射影言語の編集の側面に焦点を当てる必要があります。プレーンテキストエディターは AST を確実に表すことができず、AST を直接編集することは非常に不便であるため、射影言語は言語の一部であるすべての概念のエディターを提供する必要があります。 1 つのコンセプトに対して複数の代替エディターを使用することもありますが、ここでのゴールではありません。MPS は、多くの場合、それを持たない Concepts のデフォルトエディターを提供するために優れた作業をすることができます。これは、言語のプロトタイピングに非常に適しています。ただし、エンドユーザーが便利に使用できるように、明示的なエディターの準備に時間をかける必要があります。
形状
Shape の 概念は抽象的な概念であるため、エディターは必要ありません。これはそのままにしておきます。
円形
Circle の場合、必要なすべてのプロパティを 1 行に適切に配置するエディターを作成できます。エディターでサークルコンセプトを開き、左下隅にある緑色の「+」ボタンをクリックして、「エディター」->「コンセプトエディター」を選択します。


Circle コンセプトの空のエディター定義を取得します。ここで物事を編集するには、Control + Space が非常に必要になることを忘れないでください。

MPS のコンセプトのエディターはビジュアルセルで構成されており、各セルは基礎となるコンセプトに属する情報の一部を表しています。ノードは AST 内で階層的に構成されているため、それらのエディターは画面上で構成され、サブノードのエディターはそれらの先祖のものの中にネストされています。
Circle の場合、すべてのプロパティ(x、y、radius)の値に加えて、それらの周囲の任意のテキストをすべて 1 行で表示したいと思います。
最初にこれらのセルのレイアウトを選択します。インデントレイアウトはここで問題なく機能します。、赤いセルで Control + Space を押して、リストから選択します。角括弧の後にマイナスシンボルを入力すると、検索を高速化できます。

行の先頭に配置される定数のテキストを入力するには、「circle」と入力します。

Enter キーを押して新しいセルを作成します。「x:」と入力して、次のセルに x プロパティの値が含まれていることをマークします。


もう一度 Enter キーを押して、新しいセルを作成します。新しいセルを追加するたびに最初に Enter キー を押してから、完了メニュー(コントロール + スペース)から挿入するセルの種類を選択することを忘れないでください。
次に、コード補完メニューから x プロパティを選択して、セルを適切なプロパティにバインドする必要があります。{x} と入力しないでください、Control + Space を使用して完了メニューを表示し、メニューから x プロパティを選択します。

これで、一定のテキストと y プロパティおよび radius プロパティの値を保持するセルを自分で挿入し続けることができます。Enter キーを押すと新しいセルが挿入され、Control + Space を押すとコード完了メニューが表示されることを忘れないでください。このようなエディターで終わるはずです。

四角
Square にはエディターも必要です。エディターで Square コンセプトを開き、左下隅にある「+」シンボルを押して、新しいコンセプトエディターを作成します。前のセクションの指示に従って、次のようにエディター定義を入力します。

確かに独自でこれをすることができます。
キャンバス
Canvas コンセプトのエディターは、画面上にある程度のスペースを与える必要のあるシェイプのコレクションを保持しているため、少し異なります。したがって、Canvas は複数の線にまたがって広がり、各線は Canvas の図形のコレクションから 1 つの図形を表示します。ただし、前と同じ方法で開始し、Canvas コンセプトを開き、「+」シンボルを押して新しいコンセプトエディター を作成し、インデントレイアウトを挿入し、テキストを入力して次のようにします。

次に、赤いセルを name プロパティにバインドします。プロパティは INamedConcept コンセプトインターフェースから継承されているため、完了メニュー(Control + Space)のさらに下にありますが、確かにそこにあります。

波括弧がそれをラップしているのが見える場合は、name プロパティが正しく挿入されていることを確認してください。(これらの波括弧を自分で挿入することは想定されていないことに注意してください。これらは、すべてのプロパティセルに対して MPS によって追加されます。)
次のセルは、Canvas に追加された図形のコレクションを保持します。ここでエディターを構成する方法に注意してください。Canvas は、個々のシェイプを編集する必要がある領域のみをマークし、その領域の使用方法をシェイプに任せます。
図形は垂直方向に整理する必要があるため、それぞれが 1 行になっているため、完了メニューから垂直方向のコレクションレイアウトを選択する必要があります。

ここに注意してください。縦とは異なるレイアウトを選択した場合、図形は上下に並んでいるためはなく、おそらく同じ行に並んでいるため、最も直感的に言語を使用することはできません。

赤いセルを Canvas の shapes 子コレクションにバインドする必要があります。


ここで、Canvas の名前にコレクションを配置するには、Alt+Enter ショートカット (または light-bulb シンボル) を使用して、インテンションポップアップメニューを表示し、新しい行に追加を選択する必要があります。

誤って「新しい行の追加」を押した場合は、Ctrl+Z でその操作を元に戻してください。 最終的なエディター定義が得られます。

バージョンが違って見える場合は、いくつかの理由が考えられます。
補完メニューから name プロパティを挿入しました。「name」のみを入力した場合、セルは name プロパティにバインドされていない可能性が高く、使用時にテキスト「name」のみが表示されます。
「新しい行に追加」インテンションを設定します。誤って代わりに「新規行の追加」を選択した場合、図形セルは「ペイント ...」の最初の行ではなく、その隣に描画されます。
図形セルとは別のセルに「新しい行に追加」プロパティを設定すると、レイアウトも予想とは異なるものになります。
図形コレクションに対して垂直とは異なるレイアウトを選択しました。レイアウトが異なれば、使用するシンボルも異なります。垂直レイアウトでは「(>」が使用され、「(-」と「(/」はそれぞれインデントと水平レイアウトに属します。
これらすべてのケースで、元に戻す Ctrl+Z の繰り返しが役立ちます。
2 回目の実行
これで、プロジェクトを再度再構築し ( プロジェクトビューの最上位ノードを右クリック)、MyDrawing でサンドボックスコードを確認できます。

コードレイアウトがどのように変わったか参照してください。

これは同じコード(AST)ですが、画面上では編成が異なります。以前は波括弧をたくさん含んだ醜い木のようなテキストでしたが、今やコードは MPS に提供したエディター定義を反映しています。
ビューが異なる場合は、3 人のエディターに再度アクセスしてください。それらはかなり小さいため、必要に応じて削除してやり直してください。エディターアスペクトモデルのプロジェクトビューでエディター定義全体を見つけて削除を押すか、右クリックしてコンテキストメニューで削除を選択すると、エディター定義全体を削除できます。または、疑わしいコードをすべて削除するまで、エディター定義内の削除ボタンとバックスペースボタンを押し続けることもできます。
色指定
次に、Shape に戻り、色のサポートを追加する必要があります。Circle と Square はどちらも Shape を拡張するため、どちらも色を継承します。
言語のユーザーが事前定義された色のリストから形状の色を選択できるようにしたいため、テキストプロパティを使用して色の値を保持することはできません。代わりに、Shape への参照を追加し、この参照ポイントを事前定義された色定数の 1 つに設定します。事前定義された色を表すような定数の作成から始めましょう。
色の概念
1 つの方法は、MPS 列挙型を使用して色を定義することです。ただし、これではユーザーが独自の色を定義することはできません。すべての色は、Shapes 言語の一部となる列挙型で定義されます。代わりに、色に本格的な概念を使用し、その概念のノードが個々の色を定義します。これらのカラーノードは、言語の一部として(いわゆるアクセサーリモデル内で)定義することも、Canvas コンセプトのインスタンスの隣のユーザーモデルで直接定義することもできます。
まず、色定数を表す概念を作成します。これを Color と呼び、ルート化できるため、モデル内に配置できます。

エディターも必要です。

定義済みの色
次に、プロジェクトを再構築します。追加したばかりの色の概念をコンパイルして、いくつかの色を作成できるようにする必要があります。Color コンセプトの具体的なノードを提供する必要があります。これは、個々の色定数を表し、言語のユーザーが Canvasses から参照できるようにします。これにはアクセサーリーモデルを利用します。アクセサーリーモデルは、言語の一部になり、言語ユーザーに表示される任意のノードを保持する言語定義内のモデルです。
それで最初に、言語でアクセサーリーモデルを作成する必要があります。

モデルは名前を要求します。MPS でステレオタイプボックスが無効になっていない場合は、ステレオタイプボックスが空であることを確認してください。

Color の概念を宣言する図形言語は、アクセサーリモデルにインポートする必要がある唯一の言語です。

使用言語タブに切り替えます。空であるか、リストに残すことができる devkit が含まれています。プラスシンボルをクリックして、リストから Shapes 言語を選択します。その言語は、使用言語のリストに追加されている必要があります。
次に「詳細設定」タブに切り替えて、「生成しない」チェックボックスを選択します。

次に OK をクリックしてダイアログを閉じます。
色を作成する
これで、新しく作成したアクセサーリモデルに色定数を作成できるはずです。

新規メニューに色が表示されない場合は、言語を再構築するのを忘れている可能性があります。または、true に設定する必要がある Color コンセプトの can beroot プロパティを見逃しました。

依存関係への最初の接触
MPS の重要な知識は、依存関係とインポートされた言語を処理する方法です。モジュールまたはモデルの依存関係を表示するには、左側のプロジェクトビューパネル(パネルを開くには Alt+1)でその依存関係に移動する必要があります。Shapes 言語でそれを試してください。ツリーで選択します。

Alt+Enter を押すと (または右クリックしてモジュール / モデルのプロパティを選択すると)、モジュール / モデルのプロパティを含むダイアログが表示されます。依存関係タブには、現在のモジュール / モデルが依存するモジュール / モデル (Java のインポート、Ruby の require など) が表示されます。

ものはおそらく今空です。'+' ボタンを使用して要素を追加します。小さな検索ダイアログで、目的の依存関係の名前を数文字入力して検索を絞り込み、正しい依存関係を見つけたら Enter キーを押します。

使用言語セクションで、モジュール / モデルで使用できるようにする言語(構文)を指定します。

ここでは何も変更する必要はないため、OK ボタンをクリックして続けましょう。
色参照のための概念
私たちの言語は、コード内の形状の望ましい色を示す方法を必要としています。Shape は、図形言語のアクセサーリモデルの色定数の 1 つを参照している必要があります。MPS は、ユーザーが Shape の色を指定しようとするたびに、使用可能なすべての色定数を完了メニューに自動的に入力します。
構造アスペクトモデルで新しい概念を作成し、ColorReference という名前を付けます。

ColorReference は、単一の色定数(Color 概念のノード)を指す参照(AST 階層を通過するポインター)を保持します。
色を表示および編集するには、ColorReference のエディターも必要です。

参照は、参照する色定数の名前を表示するだけなので、コード補完メニューからターゲット を選択し、色定数の name プロパティがユーザーに表示するものであることを指定します。

次のようにエディターの定義を取得する必要があります。

形状を更新する
Shape の 概念は、Circle と Square の両方が継承するため、新しい ColorReference を配置するのに適した場所です。エディターでそれを開き、変更を加えます。

形状は、カラーサークルのためのエディターを定義するだけでなく、広場は、このように重複を避けるそれらのエディターでそのエディターコンポーネントを再利用することができるであろうエディターコンポーネントを定義することができます。
「+」シンボルを押して、新しいエディターコンポーネントを作成します。


エディターコンポーネントは、すでに使い慣れた言語を定義しているため、インデントレイアウトを簡単に指定し、定数テキストセルの後に color プロパティにバインドされたセルを定義できます。
エディターコンポーネントを埋め込む
これで、Shape 用に定義されたエディターコンポーネントが Circle および Square のエディターに追加されます。



エディターコンポーネントは、エディターのレイアウト内の別のセルになります。
3 回目の実行
今が言語を再構築する最適な時期です。プロジェクトビューで言語ノードを右クリックし、「言語の再構築」を選択します。
MyDrawing プログラムを開くと、赤の色の空のセルが表示されます。それらの中で Control + Space を試してみると、選択できる色のリストが表示されます。


これで、言語が完全に定義されました。キャンバスを作成し、その位置、サイズ、色を指定して、それに円と正方形を追加できます。それは、このような短期間でかなりの成果です。
まだ欠落しているのは、これらのプログラムを Java に変換することです。そのため、実行して、画面上にうまく描かれた形を見ることができます。続けるなら、すぐにそこにいるということにすぐに気づくでしょう。
生成プログラム
言語には、コンパイルして実行できるコードを生成できるように、ジェネレーターが必要です。DSL のターゲットとして BaseLanguage を選択します。BaseLanguage は MPS で配布される Java のコピーであるため、Java コンパイラーがバイナリにコンパイルするためのテキスト Java ソースに簡単に変換できます。そのターゲット言語の定義をプロジェクトにプラグインすれば、他のターゲットプラットフォームと言語を選択できる可能性があります。
ジェネレーターは非常に簡単で、いくつかのルールと単一のマッピング設定のみを必要とします。

言語はすでに空のジェネレーターのスケルトンを含んでいます。マッピング設定を開くことができます。これは、どのルールをいつ適用するかを指定します。ここで設定エントリを徐々に追加していきます。
これがジェネレーターの背後にある、実装するという考えです。
Canvas は Java クラスに変換されます。これにより、Java の JFrame クラスが拡張され、すべての形状が描画される JPanel が保持されます。
各 Shape は、Graphics オブジェクトのメソッド呼び出しに変換され、JPanel に形状を描画します。
ColorReference は、java.awt.Color クラスの適切な色定数への参照に変換されます。
Canvas のクラスから始めましょう。Canvas はルートの概念であるため、ルートマッピングロールに新しいエントリを追加する必要があります。Enter キーを押して、新しい空のルールを挿入します。

インテンション(Alt+Enter を使用して、ポップアップメニューから新しいルートテンプレートを選択できるようにする必要があります。
このルールは、Canvas ノードを Java クラスに置き換える必要があることを定義しています。テンプレートは、どのクラスで定義します。

ルートテンプレートは Java クラスを生成するはずなので、"class" を選ぶ必要があります。

これは、終了ルートマッピングルールです。map_Canvas は、ジェネレーターで作成されたルートテンプレートの名前です。変更できるように、開いてください。

まず、JDK モジュールに依存するようにジェネレーターモジュールの依存関係を設定する必要があります。この依存関係がなければ、サンドボックスを最終的にコンパイルすることはできません。
注 : Alt+Enter は、左側のプロジェクトビューで選択されたノードのプロパティを表示することを忘れないでください。

次に、ジェネレーターモデルは、以下に指定するように、java.swing と java.awt に依存する必要があります。これらの依存関係がないと、ジェネレーターテンプレートの実装に必要な Java Swing コードを入力できません。

依存関係を使用すると、生成された Java クラスの一部となる Java コードの入力を開始できます。次に、Canvas の値を使用してコードをパラメーター化し、ユーザーの意図を反映させます。
クラスは JFrame を拡張する必要があります。

次に、実行可能な Java クラスを取得する main メソッドを追加します。「psvm」ライブテンプレートを使用すると、メソッドをすばやく入力できます。


メソッド内で、map_Canvas をインスタンス化する必要があります。

フレームを初期化するメソッドも必要です。「method」と入力し、Control + Space を使用してメソッド定義を完了します。

メソッド Initialize() を呼び出し、メソッドが main から呼び出されていることを確認します。

すべての形状は JPanel に描画されるため、フィールドとして 1 つ追加する必要があります。


JPanel を少しカスタマイズできるように、匿名の内部クラスを使用していることに注意してください。
重要 : BaseLanguage で匿名の内部クラスを作成するには、new JPanel() の直後、最後のセミコロンの前にキャレットを配置します。次に、「{」(左波括弧) キーを押すと、MPS によって末尾の「}」シンボルが追加されます。次に、これらの「{」と「}」シンボルの間にカーソルを置き、パネルの匿名内部クラスにメソッドを追加します。
JPanel の PainComponent メソッドをオーバーライドします。これは、このメソッドを使用すると、Java で JPanel 上に図形を簡単に描画できるためです。キャレットが JPanel の匿名クラス本体の「{」と「}」シンボルの間にあるときに Ctrl+O を押して、JPanel のメソッドのオーバーライドダイアログを呼び出し、paintComponent メソッドを選択します。

スクリーンショットに示されているように、paintComponent() メソッドが JPanel の匿名内部クラス内に正しくネストされていることを確認してください。また、その名前が、PaintComponents ではなく、paintComponent であることを確認してください。
メソッドに次のコードを入力します。

ここで、initialize メソッドを入力すると、テンプレートの準備が整います。

テンプレートのパラメーター化
テンプレートのコードは現在、入力モデルの値を使用していません。コードをハードコードするだけなので、ユーザーモデルでどのような形状が作成されても、テンプレートによって生成されるコードは常に同じになります。これは変更する必要があります。テンプレートは入力モデルに反応する必要があり、生成されるコードは形状、サイズ、色を反映している必要があります。Canvas のプロパティと子は、マクロを介してテンプレートに挿入する必要があります。MPS は、次の 3 種類のマクロを提供します。
プロパティマクロ - 入力モデルからプロパティを挿入する
ノードマクロ - テンプレート内のノードを入力モデルのノードと置き換える
参照マクロ - 入力モデル内のノードを指すようにテンプレート内の参照を調整します
徐々にこれらすべてを使います。
まず、生成されたクラスの名前とフレームのタイトルを Canvas の名前でカスタマイズします。class- map_Canvas の名前にキャレットを置き、Alt+Enter を押します。


今すぐポップアップメニューから node.name プロパティマクロを選択します。

「map_Canvas」テキストはプロパティマクロでラップ (アノテーションが付けられ) され、name プロパティが Canvas の名前に変更されます。インスペクターパネル (Alt + 2) を使用して、プロパティマクロを入力または変更することもできます。現在、現在の Canvas の名前である node.name の値を返します。
注 : 通常、「インスペクター」ウィンドウには、エディターで選択されたエディターセルの追加詳細が表示されます。これは、エディターおよびジェネレーター定義言語で特に役立ちます。自分の言語にも活用できます。好奇心旺盛な人のためのメモ : インスペクター内のコードはどのようにインスペクションしますか ? 「インスペクターのためのインスペクター」はありませんが、セルがメインエディター内にあるかインスペクターウィンドウ内にあるかに関係なく、セル上で使用できるノードのインスペクションアクションがあります。このアクションは、右クリックのコンテキストメニューからトリガーします。

これで、「タイトル」テキストをラップして、フレームのタイトルをカスタマイズできます。Ctrl+Up キーショートカットを使用して、テキスト「タイトル」を 囲んでいる「」文字なしで選択し、Alt+Enter を使用して正しいプロパティマクロを挿入します。

コードは次のようになります。

図形を描く
このテンプレートは、図形を描画するコードが JPanel フィールドの PaintComponent メソッド内に配置されることを前提としています。ステートメント「System.out.println( "ここに描画");」すべての形状を描画する実際のコードのプレースホルダーとして機能します。COPY_SRC マクロを使用して、プレースホルダーステートメントを単一の図形を描画するステートメントに置き換えます。また、LOOP マクロを利用して、現在の Canvas で定義されているすべての図形に対してこれを繰り返します。
次に、Ctrl+Up キーショートカットを使用して、終了セミコロンを含むプレースホルダーステートメントを選択し、Alt+Enter を押して適切なノードマクロオプションを選択し、現在の Canvas のすべての子 Shape をループする LOOP マクロを挿入します。

繰り返しますが、インスペクターはバインディングコードを示しています。

LOOP マクロに赤で下線が引かれている場合は、インテンションを適用する前に行全体を選択しなかった可能性があります。元に戻し、セミコロンを含む行全体を選択して、LOOP マクロを追加インテンションを再度適用します。
LOOP マクロは「System.out.println("Draw here");」を繰り返します。node.shapes にリストされている各シェイプのステートメント。
ただし、「System.out.println("Draw here");」が必要です。ステートメントは、これらのそれぞれの形状を描画するコードに置き換えられます。COPY_SRC マクロはまさにそれを実現します。セミコロンを含む LOOP マクロ内のステートメント全体を再度選択し、Alt+Enter を押してノードマクロを選択してください。

COPY_SRC (Control + Space) と入力すると、「System.out.println("Draw here");」を置き換えるマクロが表示されます。LOOP マクロが提供するすべての形状の現在の形状を使用します。


COPY_SRC マクロが、イメージに表示されているように、セミコロンを含むステートメント全体をラップしていることを確認してください。そうでない場合は、元に戻し、ステートメント全体を選択して、COPY_SRC マクロを再度挿入します。
円を生成する
ここで Canvas を Java クラスに変換し、Shape を描画するコードを追加する場所も作成しました。シェイプ自体の実際の変換ルールを定義する時期が来ました。これにより、Circle シェイプの代わりに「graphics.drawCircle()」メソッドが生成されたコードに挿入されます。メインのマッピング設定を開き、「削減ルール」セクションに新しいエントリを追加する必要があります。

新しいテンプレートを作成する Alt+Enter :

新しい reduct_Circle テンプレートが左側のプロジェクトビューに表示されます。

Square の縮小ルールを作成することもできます。

reduce_Circle テンプレートを開きます。ここで、Circles を置き換える Java コードを指定する必要があります。Java コードは paintComponent メソッド内の map_Canvas に配置されることに注意してください。

まず、テンプレートをラップする BlockStatement を入力します。

同じ名前の paintComponent パラメーターのプレースホルダーとして、Graphics 型のローカル変数が必要になります。そしてまたもや BlockStatement。



Java で円を描くには、Graphics オブジェクトを使用して最初に色を設定し、その drawOval メソッドを呼び出します。以下のコードを入力してください。


次に、Ctrl+Up を使用して内部 BlockStatement を選択し、Alt+Enter (light-bulb) を押して、メニューからテンプレートフラグメントの作成を選択します。これにより、コードの選択したフラグメントが実際のテンプレートとしてマークされ、最終的に map_Canvas に配置されます。

パラメーター化
コードは、Circle ノードからの実際の値でパラメーター化する必要があります。

最初の値「10」は、Circle ノードの x 座標に置き換える必要があります。プロパティマクロがそれを行います。同様に、2 番目の値「10」は y 座標に置き換える必要があります。

3 番目と 4 番目の値「10」は両方とも円ノードの半径値に置き換える必要があります。

最後に、「Color.red」カラープレースホルダー参照を、Circle ノードのカラーリファレンスの実際のターゲットに置き換える必要があります。参照マクロを使用して参照を置き換えます。「赤い」単語にキャレットを置き、Alt+Enter を押します。

参照マクロは、インスペクターウィンドウで指定したノードへの参照で赤への参照を置き換えます。

参照 関数は、文字列値(参照する宣言の名前)または node <StaticFieldDeclaration> (StaticFieldDeclaration を表すノード)のいずれかを返します。これは、赤 自体が node <StaticFieldDeclaration> への参照であるためです。実際、java.awt.Color クラスのすべての色定数は StaticFieldDeclarations として宣言されています。
参照マクロ内のタスクは、Circle がその色の子として設定した色に対応する Color クラス内から StaticFieldDeclaration を取得することです。これはプログラムで行うため、適切なモジュールとモデルに依存関係を設定する必要があります。これにより、必要なコードを記述できるようになります。
まず、ジェネレーターモジュールは、その言語で宣言されている StaticFieldDeclaration の概念を参照できるようにするために、BaseLanguage に依存する必要があります。

次に、ジェネレーターモデルは、Shapes 言語の構造で定義された概念を参照できる必要があります。

これらの言語をインポートすると、Color クラス内で正しい静的フィールド宣言を検出するコードを入力できるようになります。

node-ptr/.../ 構成要素を使用すると、指定された名前で表される指定された概念のインポートされたモデル内のノードを取得できます。JDK には Color クラスが 1 つしかないため、node-ptr/Color/ として識別される参照は一意であり、モデルを Color クラスにポイントします。
注 : 完了メニューから正しい色要素を選択していることを確認してください。Shapes 言語の Color コンセプトではなく、java.awt.Color である必要があります。

ノードポインタは、ノードへの永続的な参照を表します。メモリ内の実際のノードへの参照を取得するには、モデルリポジトリでノードポインタを解決する必要があります。次のコードを使用してリポジトリを取得し、そこから Color ノードを解決します。

ダウンキャスト演算子を使用すると、基礎となる Java API にアクセスできます。現在、この場所にリポジトリを取得する唯一の方法です。

コレクション言語を使用すると、簡潔なクエリを完了することができます。

node は Circle コンセプトのインスタンスであるため、node.color はサークルの色への参照(ColorReference のインスタンス)であり、node.color.target はアクセサーリモデルの Color (Shapes.Color コンセプトのインスタンス)です。
簡単に言うと、クエリは、Circle に指定された色の名前と同じ名前の java.awt.Color クラスの静的フィールド宣言内の最初の静的フィールド宣言を検索します。
正方形を減らす
Circles のジェネレーターがどのように行われるかを模倣し始めます。reduce_Square テンプレートに次のコードを同じように指定します。
ヒント :BlockStatement を挿入することから始めます

「drawRect」に渡される値は、Square の upperLeftX、upperLeftY、および size プロパティを持つプロパティマクロに置き換える必要があります。

コードを生成
これでジェネレーターの定義が完了しました。言語を再構築する場合は、MyDrawing を開いて右クリックし、「生成されたテキストのプレビュー」を選択します。

JFrame を適切に初期化し、すべての形状を描画する、適切に構造化された Java コードを取得します。

コードが異なる場合は、ジェネレーターテンプレートの 1 つを調べましょう。おそらく、チュートリアルで示されているものとは異なります。おそらく、マクロが正しいコード部分にアタッチされていないか、インスペクターウィンドウで指定されたマクロの値がスクリーンショットのものと異なっている可能性があります。
コードがコンパイルされない場合は、以前に定義したように、ジェネレーターモジュールが JDK モジュールに依存していることを確認してください。
Squares のためのよりロバストな世代
テンプレートで graphics ローカル変数を処理する方法は完全に正しくありませんでした。map_Canvas、reduce_Circle、reduce_Square で同じになるように、変数の名前をおそらく楽観的に依存していました。これら 3 つのテンプレートの変数の名前が同じでない場合はどうなりますか? より堅牢なソリューションが必要です
サークルジェネレーターのセクションで前述したように、reduce_Square テンプレートを使用して、グラフィックスローカル変数を map_Canvas テンプレートが生成するグラフィックスパラメーターと適切に結び付けます。名前の一致に依存することは、それほど堅牢ではありません。
基本的に 3 つのステップを踏む必要があります。
作成したグラフィックパラメーターのストレージを定義する
グラフィックパラメーターを map_Canvas テンプレートに保存します
reduce_Square テンプレートで適切なグラフィックパラメーターを取得します
まず、マッピング構成でマッピングラベルを作成します。これは ParameterDeclarations のストレージになり、それぞれが生成元の Canvas によって識別されます。このマッピングラベルは、Canvas を ParameterDeclarations にマップする辞書のように考えることもできます。

graphicsParam マッピングラベルは、それらが属する Canvas によってマップされた ParameterDeclarations を格納します。
map_Canvas テンプレートは、グラフィックパラメーターをマッピングラベルに保存する必要があります。MAP_SRC マクロは、そのために活用でき、大きな成功を収めています。

パラメーター宣言(型を含む)を MAP_SRC マクロ(Alt+Enter、ノードマクロの選択、型 MAP_SRC_)でラップします。次に、_Inspector ウィンドウで、生成されたパラメーター宣言を格納するために使用するマッピングラベルを選択します。Canvas コンセプトのインスタンスである現在のソースノードは、マッピングラベルで生成されたグラフィックパラメーター宣言を識別するためのキーとして使用されます。
最後に、reduce_Square テンプレートのマッピングラベルからパラメーター宣言を取得する必要があります。名前の一致に依存しなくなったことを明確に示すために、変数にグラフィックスとは異なる名前を使用できます。サンプルでは g を使用します。

g 参照で Alt+Enter を実行し、参照マクロを選択すると、マッピングラベル から適切なグラフィックパラメーターを取得できます。

次に、2 番目の g 参照に対して参照マクロの作成を繰り返します。作成したばかりの g 変数参照にアタッチされた両方参照マクロは、参照の目的のターゲットを取得する方法の詳細をインスペクターで指定する必要があります。genContext オブジェクトは、現在の世代のセッションの便利なメソッドとプロパティへのアクセスを提供します。genContext で「ラベルと入力の出力の取得」操作を使用し、graphicsParam マッピングラベルと、現在生成されている Square を保持する Canvas を提供します。

これで、reduce_Circle テンプレートのグラフィックパラメーターの取得も複製できるようになりました。
コードを実行する
生成されたコードを見るのは良いことですが、実際にはコードが実行されているのを見る方が良いかもしれません。MPS は、生成された Java コードを簡単にコンパイルして実行できます。Canvas が実行可能な Java クラスに生成されるため、Canvas 自体を実行可能なクラス、つまり「メイン」クラスとして扱う必要があることを示すだけで済みます。Canvas に IMainClass インターフェースを実装させるだけで済み、残りは MPS が処理します。IMainClass インターフェースは、jet Brains.mps.execution.util 言語に由来するため、言語の依存関係のリストに追加し、スコープを拡張に設定する必要があります。

Alt+Enter を使用して、プロパティダイアログを取得します。言語を拡張としてマークする必要があることに注意してください。
Convas の概念では、IMainClass インターフェースを実装セクションに追加できるようになりました。

言語を再構築し、プロジェクトビューで MyDrawing を右クリックし、実行をクリックします。

あなたの努力の報酬としてあなたの絵が描かれた実行中の Java アプリケーションを手に入れるでしょう。

代替ジェネレーター - XML を生成する
xml などの宣言型言語でコードを生成するためにジェネレーターをどのように利用できるかを考えてみるために、ここでは xml を生成する Shapes 言語用の簡単なジェネレーターを紹介します。空のジェネレーターから始めます。Java テンプレートとルールはすべて削除されました。

まず、jetbrains.mps.core.xml 言語をインポートする必要があります(Control + L)。BaseLanguage が Java と同等の射影であるように、これは xml と同等の射影です。
ルートマッピングルールは、XML ファイルにキャンバス ES を変換するために作成する必要があります。


map_Canvas という名前のテンプレートが作成されます。

必要なコードを作成するためには、XML コードをテンプレートに入力する必要があります。


name 属性を挿入するには、「space」に続いて「name=」を入力します。

name 属性値の内容にプロパティマクロを設定する必要があります。


もう少し xml を挿入する必要があります。

すべての図形のプレースホルダーを作成します。

Ctrl+Up を使用して、プレースホルダーの xml 要素を選択します。

次に、COPY_SRCL マクロを挿入して、Canvas のすべての形状をループし、それらの縮小ルールをトリガーします。


生成された xml ファイルの名前 は、プロパティマクロを使用してカスタマイズすることもできます。


正方形と円の縮小ルールを作成する必要があります。


Circle と Square のテンプレートは、ルートとして XmlElement を保持する必要があります。

その後、xml テンプレートを完全に構築する必要があります。

テンプレートフラグメントは、xml コード全体の周囲に作成する必要があります。


TF シンボルはテンプレートフラグメントを示します。

プロパティマクロはテンプレートをパラメーター化するために使用されるべきです:

正しい値が使用され、整数から文字列に変換されるように、プロパティマクロをインスペクターでさらに指定する必要があります。

同じことが、Y XML 属性ごとに繰り返されなければなりません。
radius では、専用の xml 要素を使います。


そして、色の参照についても同様に要求します。

Square のテンプレートは非常に似ています。

ジェネレーターは 4 つのルートノードを保持しているはずです。

言語を再構築してサンドボックス用に生成されたコードをプレビューすると、次のような xml ファイルが表示されるはずです。

次に何をすべきか
おめでとうございます。MPS の入門チュートリアルはこれで完了です。
これで、言語に図形を追加して自分で続けることができます。ポイント、ライン、トライアングル、レクタングル、カラフルな塗りつぶしのある図形は、私たちの小さな言葉に加えて素晴らしいかもしれません。
MPS をさらに徹底的に理解したいのなら、詳細な計算機のチュートリアルを試すのがよいでしょう。これは多くの高度な概念を探り、コード生成、型システムとスコープについてさらに多くを教えるでしょう。
関連ページ:
MPS へのファストトラック
ようこそ ! このチュートリアルは、MPS にまったく慣れておらず、MPS の風景を見ながらのガイドツアーを好む開発者のために特別に設計されます。次に進むべき場所を示す明確なマークに従って、殴打された道を一度に 1 歩歩きます。情報は、より単純な概念からより複雑な概念へと進み、旅の終わりに MPS を理解し、あなたのプロジェクトでそれを効果的に使用できるようになるように構成されています。私達は MPS を学ぶことよりも世界に簡単な作業があることを認めます。言語設計は複雑な領域であり、射影編集は慣...
エディターの指示
MPS でコーディングしている場合、テキストエディターでの通常のコードの入力方法と MPS でのコードの編集方法にはいくつかの違いがあることに気づくでしょう。MPS では、射影エディターでコードを入力するときに AST を直接操作します。エディターはテキストを編集しているような錯覚を与えますが、それには限界があります。そのため、キャレットを配置できる場所と、その位置に入力できる内容が若干制限されます。信じているように、射影エディターは多くの分野で大きなメリットをもたらします。多少の慣れが必要です...
インタープリタークックブックの作成
バージョン 3.1 以降 MPS にバンドルされている Shapes サンプルプロジェクトを確認してください。エディターでいくつかの凝ったトリックを行うことができます。デフォルトでは、エディターは、キャンバス上に指定されたサイズと色の視覚的形状を描画するためのコマンドで構成されるプレーンコードを表示します。コードを Java に生成して実行することができます。これにより、上記のコードから生成されたばかりのアプリケーションを実行する新しい Java プロセスが起動します。ただし、MPS は Java...
MPS 電卓言語チュートリアル
導入:このチュートリアルでは、MPS での言語デザインのさまざまな分野について説明します。単純なスタンドアロン言語の抽象的な構造を定義し、そのためのエディターを設計し、タイプを制限し、スコープを作成し、最後に Java コードを生成するジェネレーターを準備します。以前の MPS への露出に応じて、このチュートリアルでは完了までに約 1 日かかります。このチュートリアルでは、主に MPS を評価する必要のある言語デザイナーを対象とし、ベアボーンの例以外のものを見たいと思っています。Java の基...