MPS 2023.3 ヘルプ

共通言語パターン

この章では、MPS を学習する初心者が頻繁に遭遇する一般的な言語設計パターンについて説明します。これらは、MPS ユーザーがオンラインフォーラムで最も頻繁に確認する質問であることが長年にわたり判明しており、簡単に参照できるように回答を 1 か所にまとめることにしました。

言語パターン付きのサンプルプロジェクト

MPS にはサンプルプロジェクトがバンドルされています。ここで説明するパターンの多くは、languagePatterns サンプルプロジェクトに実装されています。これを MPS で開き (MPS のウェルカムスクリーンでサンプルプロジェクトを開くをクリック)、サンプルプロジェクトを参照するこの章のパターンの実際の実装を確認できます。

mps-patterns1.png

パターンは言語定義とサンドボックスソリューションの両方で仮想パッケージにまとめられ、個々のパターンを形成する要素を識別できます。

初期のヒント

命名の概念

多くの概念では、ノードの名前を保持するための文字列プロパティを提供する必要があります。概念の名前には特定の特別な性質があり、MPS によって特別な方法で認識および処理される必要があるため(たとえば、コード補完メニュー、プロジェクトビュー、ノードエクスプローラー、デバッガーツールウィンドウなどでノードにラベルを付けるため)、INamedConcept コンセプトインターフェースからプロパティを継承することをお勧めします。多くの概念とほとんどすべてのルート概念は、INamedConcept の 実装を宣言する必要があります。

有効な識別子

BaseLanguage を拡張していて、概念の名前が Java 識別子に直接変換される場合は、INamedConcept を拡張する IValidIdentifier 概念インターフェースから name プロパティを継承することを検討する必要があります。制約を通じて、名前が有効な Java 識別子の基準を満たしていることを確認します。

固有の名前

同じ概念のノードの特定のグループの名前の一意性を確保したいことがよくあります。制約または非型システムルールのいずれかを使用できます。非型システムルールでは、エラーメッセージをカスタマイズするオプションが提供され、自動的な問題解決のためにユーザーにクイックフィックスを提供することもできます。計算機チュートリアルの計算機InputField の名前の一意性チェックのサンプルは次のようになります。

checking rule check_InputField {   applicable for concept = InputField as inputField   overrides false   do {     if (inputField.parent : Calculator.inputField.any({~it => it.name :eq: inputField.name && it :ne: inputField; })) {   error "Duplicate name " + inputField.name -> inputField; }   }                                                                                                                                                                                    }                                                                                                                                                                                     

同じ制約を制約で指定すると、次のようになります。

concepts constraints InputField {    can be child <none>        can be parent <none>        can be ancestor <none>            property {name}      get:<default>      set:<default>      is valid:(propertyValue, node)->boolean {        node.parent : Calculator.inputField.where({~it => it.name :eq: propertyValue; }).size <= 1;      }            <<referent constraints>>        default scope      <no default scope>      }

いくつかの理由から、ここでは制約はおそらくあまり最適ではないことに注意してください。

  • クイックフィックスを指定することはできません

  • エラーメッセージはカスタマイズできません

  • 非型システムルールではエラーを示すために重複した名前にのみ下線を引いていますが、制約はモデルに無効な値が挿入されるのを防ぎ、無効な値を赤いフォントで表示します。

型システムエラーが表示されない

型システムはバックグラウンドで実行され、結果をエディターに配信するのが遅くなることがあります。エディターのすべてのエラーと警告メッセージをリフレッシュするために F5 を押してください。省電力モード設定がオフになっていることも確認してください。パワーセーフモードでは、型システムはユーザーからの明示的な要求に応じてのみ動作します(F5)。

コメントと TODO コメント

ほとんどの言語は、ユーザーがコードの一部として自由形式の説明テキストを記述できる、ある種のコメントをサポートしています。コメントをサポートするには、言語は次の概念を提供する必要があります。

  • 通常のコードが挿入される位置に収まるように、ステートメントのような概念を拡張します。

  • 文字列プロパティとして、または Jetbrains.mps.lang.text 言語の概念の一部 (Line など) として、テキストを保持します。

BaseLanguageSingleLineComment コンセプトは、詳細なコメントコンセプトの良い例です。

SingleLineComment defintion

ロボット Kaja 言語の CommentLine は、文字列プロパティにテキストを格納するカスタムコメントの例です。

CommentLine defintion


何らかのパターンに一致するコメント (例: 「TODO」または「fixme」で始まる) は、MPS によって「TODO コメント」として扱われます。このようなコメントは TODO ツールウィンドウにリストされ、コミット時にも報告されます。

TODO tool window

カスタムコメントのコンセプトは TODO コメントにも参加できます。上記の両方のサンプルコメントの概念は、IGenericComment の概念を (直接的または間接的に) 拡張していることに注意してください。IGenericComment から継承された動作メソッドを使用すると、MPS の TODO ツールでコメントを検出できます。

IGenericComment behavior
  • getTextualRepresentation() メソッドは、コメントの文字列値を返します。すべての具体的なコメントの概念でオーバーライドする必要があります。

  • isTODOComment() メソッドは、コメントを TODO コメントと見なすかどうかを示します。IGenericComment から継承された実装は「TODO」、「FIX」、「FIXME」などのプレフィックスを検出します。

ユーザー DSL のコメント概念の例として、Kaja の CommentLine を見てみましょう。これは、IGenericComment から継承されたプレフィックス「TODO」、「FIX」、「FIXME」とともに、プレフィックス「IMPLEMENTATION」を TODO コメントとして扱います。

CommentLine behavior

エディターで TODO コメントのさまざまなスタイルをサポートするには、CommentLine に使用されるパターンに従います。

Comment style definition in the Kaja language

基本的に、すべてのコメントに「コメント」スタイルが適用されます。コメントが TODO コメントの場合は、代わりに TODO スタイルが適用されます。注: 「apply LINE_COMMENT」および「apply TODO」ステートメントは、対応する名前を持つスタイルキーパックエントリを参照します (したがって、ここでの「TODO」は、上で定義された TODO スタイル以外のものです)。スタイルパックエントリを使用する代わりに、色とフォントスタイルを直接指定することもできます。

正規表現

デフォルトでは、プロパティは整数ブール値文字列の 3 つのタイプのいずれかになります。よりカスタマイズされたデータ型が必要な場合は、データ型を定義し、正規表現によって許可される値を制限する必要があります。BaseLanguage は、このようにフロートタイプを定義します(敵の例)。FloatingPointConstant コンセプト Alt+Insert)を開き、プロパティ値を確認します。タイプは _FPNumber_String です。_FPNumber_String Ctrl+Space の定義に移動して、制約されたデータ型の例を確認します。

constrained string datatype: _FPNumber_String                                                                                           matching regexp: -?[0-9]+\\.[0-9]*([Ee][\\+\\-]?[0-9]+)?[dD]?

安全なオペレータと操作

BaseLanguage とそのコア拡張機能は、比較する前に null をチェックする、のリストと null のリストを区別する、「==」の代わりに equals を呼び出すなど、Java では正しく実行するためにより多くの労力を必要とする一般的な操作に対して、いくつかの便利なショートカットを提供します。最も一般的に役立つもののリストを以下に示します。

  • : 式: - null-safe が等しい

  • ねえ: - null セーフではない

  • 無効です

  • isNotNull

  • サイズ

  • isEmpty

  • isNotEmpty

基本パターン

コンテナー - コンポーネント

languagePatterns サンプルのコンテナーコンポーネント仮想パッケージとして示されています。

コンテナー(この場合は FruitPlate)に複数の種類(リンゴ、オレンジ)の要素(果物)が含まれる一般的なシナリオ。抽象概念(Fruit)がコンテナーのプレースホルダーとして使用され、具体的な下位概念(Apples、Oranges)が言語ユーザーによって明示的に提供されます。サブコンセプトは完全に異なる外観を持つことがあります。

mps-patterns2.png

ユーザーが少しの労力で Apple を Oranges に切り替えたり、元に戻したりできるようにする方法については、シームレス置換パターンも参照してください。現在の実装では、ユーザーは最初にノード全体(Apple または Orange)を選択する必要があり、その後、コード補完によって代替手段が提供されます。

patterns3.png

カスタマイズプレゼンテーション

languagePatterns サンプルのカスタムプレゼンテーション仮想パッケージとして示されています。

http://forum.jetbrains.com/thread/Meta-Programming-System-2138?message=Meta-Programming-System-2138-3(英語) での議論によって動機付けられたコンテナー内に保持されているコンポーネントは、ComponentUsages から参照されます。ただし、参照には、補完メニューとエディターの両方で、コンポーネントの名前とともにコンテナーの名前が含まれている必要があります。

patterns4.png
patterns6.png

さらに、スコーピングルールは、コンポーネントへの参照が 1 つだけ存在することを保証し、完了メニューがフィルタリングされて、すでに参照されているコンポーネントが提供されないようにする必要があります。

patterns5.png

宣言 - 参照

languagePatterns サンプルの宣言参照仮想パッケージとして示されています。

patterns8.png

宣言とそれらへの参照に関する典型的なパターン - イベントの歌手 は、パフォーマンスを使用してアジェンダに編成できます。さまざまな種類のパフォーマンスをご利用いただけます。

スコープ規則はそれを確実にします:

  • 現在のイベントにリストされている歌手だけがアジェンダに追加できます

  • 各歌手は、組み合わせられたパフォーマンスで一度だけリストされることができます

  • 便利なインテンション (IntroduceSinger) を使用すると、パフォーマンスで入力された文字列から歌手を作成できます (「使用箇所から作成」または「変数の導入」リファクタリングとも呼ばれます)。

流暢な編集

languagePatterns サンプルの流暢な編集仮想パッケージとして示されています。

patterns7.png

テキスト風の編集環境を作成し、Editor クックブック(エディタークックブック)に記載されている推奨事項の多くを実装する例。描画コマンド(線、長方形)を指定するための簡単な言語を実装しています。

  • Enter キーを押すと空行が挿入されます。子コレクションに指定されたデフォルトノードファクトリのおかげで空行はどこにでも配置できます

  • IDontSubstituteByDefault コンセプトインターフェースを実装しているため、完了メニューに空の行がオプションとして表示されません。

  • 空行に入力すると目的の項目が挿入されます (線または長方形)

  • コードブロックの本文が空の場合、キャレットは最初の行 (ヘッダーの隣) に配置され、この最初の行から編集を開始できます。

  • NodeFactories は、ノードが別のノードに置き換えられた場合に、新しいノードへの値の伝播を処理します

  • 左側の変換により、描画コマンドの左側にオプションの「線種」子を指定できます。

  • ラッパーを使用すると、希望する線のスタイルを空の行に入力することができ、中間の「IncompleteCommand 」が自動的に作成されます。

  • IncompleteCommand は、線または長方形のいずれかが入力されることを想定しており、目的の描画コマンドに即座に置き換えられます

  • draw コマンドはエイリアスとエディターを定義し、それらが同じ単語で始まるようにします。

  • draw コマンドの抽象スーパーコンセプトのエディターは、具象サブコンセプトによって再利用されます。

  • コマンドの左側に入力すると、許可されている接頭辞が表示されます。

  • 接頭辞(実線、点線)を削除しても、接頭辞のみが正しく削除されます。

オーバーライドエディターコンポーネント

languagePatterns サンプルの override-editor-component 仮想パッケージとして示されています。

サブコンセプト (Truck) でオーバーライドされるエディターコンポーネントの使用例。Car のエディターは、Car でも定義されているエディターコンポーネント CarProperties を使用します。サブコンセプト (Truck) は、独自のプロパティを含めるために、CarProperties エディターコンポーネントを TruckProperties エディターコンポーネントでオーバーライドします。Car のエディターは、Trucks にはエディターコンポーネントの Trucks バリアントを使用し、Cars には Car バリアントを使用します。

patterns9.png

シームレス置換

languagePatterns サンプルのシームレス置換仮想パッケージとして示されています。

patterns10.png

関連するさまざまなサブコンセプトをシームレスに切り替える (置換する) 例。リクエストには「説明」が含まれます。これは、文字列単純なフォーム、または複雑なフォームのいずれかです。補完メニューを使用すると、ユーザーは要求された説明タイプを選択できます。ユーザーが単にテキストを入力すると、「文字列」ベースの説明が「PickTheRightDescriptionType 」置換アクションで自動的に選択されます。各説明コンセプトのエディターの最初のセルは置換に敏感であるため (セルの「メニュー」プロパティを通じて設定)、補完メニューを使用して説明タイプを切り替えるオプションが提供されます。「Converters 」ノードファクトリには、説明情報の一部を保持し、それを新しくインスタンス化された説明コンセプトに伝播するコードが含まれています。

patterns11.png
patterns12.png

階層スコープ

何らかの参照を定義する場合、通常は参照のスコープを制限して、参照から特定の場所または「距離」にあるノードのみを指すようにします。さらに、定義によって、参照から離れた同じ名前の他の参照が非表示になることがあります。たとえば、パラメーター宣言またはフィールドを隠す Java のローカル変数を考えてみてください。MPS では、参照を定義するときに、言語定義の制約の側面で参照のスコープを指定します。階層スコープの場合は、参照で継承されたスコープ型を使用し、コードコンテナー (クラス、メソッド、ブロックステートメントなど) に ScopeProvider 概念インターフェースを実装させます。その getScope() メソッドで、参照が指す特定の種類の候補ターゲットを取得するロジックを実装します。詳細な方法については、スコープドキュメントを参照してください。

自分の参照用に DotExpression を拡張する

languagePatterns サンプル(MPS 3.3 以降にバンドルされている)の dotexpression 仮想パッケージとして示されています。

「ドット」表記による要素の参照解除は、多くの言語で非常に一般的な方法です。BaseLanguage、Java の「ドット」演算子を模倣する DotExpression コンセプトを提供しており、BaseLanguage の式を拡張している言語でこれを活用できます。検証式で検証する必要がある複数の住所を含む単純なフォームを想定してみましょう。式は、検証式に値を含めるために、各住所の番地と郵便番号を参照する必要があります。

patterns13.png

各アドレスは、アドレスコンセプトのノードによって表されます。コード補完メニューに「kind」プロパティが表示されるように、プレゼンテーションもカスタマイズされていることに注意してください。

patterns14.png
patterns15.png

AddressReference の概念により、検証セクションの BaseLanguage 式内からアドレスを参照できます。

patterns16.png

すべての BaseLanguage 式は、"dot" を追加するとすぐに DotExpression に変換され、左のオペランドは元の式になります。残るのは DotExpression の右側で、operation と呼ばれます。Operations は IOperation 概念インターフェースを実装します。ストリート用と郵便番号用の 2 つの操作が必要になるため、これらに共通の抽象スーパー概念から始めます。

patterns17.png

具体的な操作では、意味のある別名と型システム規則のみを指定して、周囲の式全体に正しく参加するようにします。

patterns18.png

関連ページ:

エディタークックブック

このドキュメントは、上級言語設計者を対象としており、MPS エディターに関する最も一般的な質問に対する回答を提供します。エディターのドキュメントを読むのを好むかもしれません。それはサブジェクトに関する徹底的な情報を含みます。エディター定義を作成する方法:MPS がエディターをデザインするために言語デザイナーに提供する DSL は、セルの概念に基づいて構築されています。言語設計者は、エディターセルを組み合わせて、表記法の望ましい最終レイアウトを反映するように画面上に配置します。画面の右下隅にある...

スコープ

カスタム言語要素のスコープを定義する 2 つの方法、つまり継承された(階層的)アプローチと参照アプローチについて見ていきます。実験のテストベッドとして計算機のチュートリアル言語を選択しました。MPS ディストリビューションに付属するサンプルプロジェクトのセットに含まれている電卓チュートリアルプロジェクトを見つけることができます。2 つの方法:すべての参照は許可されたターゲットのセットを知る必要があります。これにより、ユーザーが参照の値を入力しようとしているときはいつでも MPS が完了メニュー...

ノードをコメントアウトする

MPS でノードをコメントアウトするための一般的なサポート:MPS は、モデル内のノードをコメントアウトする汎用的な方法を提供します。以前のバージョンでは、この機能は、ノード属性または専用のコメントノードのいずれかを使用して、すべての言語で個別に実装する必要がありました。MPS 3.3 以降、コメントアウトされたノードに関する情報は、一般的な方法でモデルに保存されます。smodel 言語は、デフォルトでコメントアウトされたノードを無視するため、クエリでコメントアウトされたノードを明示的に除外す...

ハウツー MPS Make フレームワークへの統合

ファセットを構築:概要基本的に他のビルドシステムや make システムと同様に、MPS make はアーティファクトをビルドするために一連のステップ、つまりターゲットを実行します。必要な make ステップのグローバルな順序付けは、各ビルドターゲットに指定された相対的な優先順位から導き出されます(ターゲット A は B の前に実行し、B は C の前に実行する必要があるため、グローバル順序は A、B、C です)。完全なビルドプロセスでは、モデルのテキストへの生成、これらのモデルのコンパイル、サ...