MPS 2020.3 ヘルプ

BaseLanguage の拡張機能用のジェネレーターを実装する

この記事では、LValue 式で動作する BaseLanguage 拡張機能のジェネレーターを作成するための慣用的な方法を紹介します。BaseLanguage 式は、代入式の左側で使用できる場合は左辺値であると言います。このような式を導入する概念は、動作の側面でインスタンスメソッド Expression#isLValue() または静的メソッド Expression#lvalue() のいずれかをオーバーライドする必要があります。

この調査は 2 つの独立した言語で構成されており、どちらも BaseLanguage の小さな拡張です。ソースコードは、MPS ディストリビューションの BlReference サンプルプロジェクトにあります。

jetbrains.mps.baseLanguage.box

この言語では、新しい LValue 式を導入するカスタム BaseLanguage 拡張機能を生成する方法を示します。提示された手法は、smodel プロパティアクセスやカスタムコレクションのインデックスによる要素アクセスなどの式を生成するために使用できます。

BoxLanguage の拡張機能は、変数をラップし、それを読み書きする操作を提供する新しい "box" 型を導入します。言語全体は 3 つの概念で構成されています。

  • モデルの「ボックス」タイプを表す BoxType。

  • 新しい式で「ボックス」インスタンスを作成するために使用できる BoxCreator

  • Box_ValueOperation は、オペランドが「ボックス」タイプの場合にドット式の演算として使用できます。

Image2018 11 12 10 25 36

「value」操作を使用したドット式を LValue 式として扱うため、Box_ValueOperation の動作アスペクトで IOperation#lvalue() メソッドをオーバーライドします。このメソッドをオーバーライドすると、「値」演算を使用したドット式を代入の左側で使用できるようになります。さらに、この操作は、複合代入(+ =、-= など)、インクリメント操作とデクリメント操作で使用できます。最終的なゴールは、Box_ValueOperation コンセプトのジェネレーターを作成することです。

素朴なアプローチは、"value" 演算を含むドット式が集約され、それぞれのケースに対して適切な Java コードを生成するすべてのケースを手動で処理することです。ただし、処理するケースが多すぎるため、この方法では不十分です。また、このアプローチでは、式が BaseLanguage の独立した拡張からの式と集約されている場合は処理できません。

別のアプローチは、私たちの表現をもう一つの、すでに存在する LValue 式に変換することです。式を変換できる LValue 式は、Java 言語でいくつかあります。変数参照、フィールドアクセス操作、またはインデックス操作による配列の要素アクセスです。残念ながら、すべてのカスタム LValue 式を Java LValue 式に簡単に変換できるわけではありません。

たとえば、実行時に「box」タイプが getValuesetValue の 2 つのメソッドを提供する Box インターフェースで表される場合、「value」操作を Java LValue 式に変換することは困難です。この問題を克服するために、BaseLanguage の生成時の「一般的な左辺値式」の概念を使用できます。これに式を変換できます。

Genxx1

Image2018 11 12 11 36 58

「一般的な左辺値式」は、参照という 2 つの必須のロールで構成されます。前者のロールは元の LValue 式の型を指定し、後者のロールは型 jetbrains.mps.references.Reference <T> の式を指定します。この型は、評価された変数から値を読み取るための #get() と評価された変数に新しい値を割り当てるための #set() の 2 つのメソッドを提供します。

「一般的な左辺値式」には、値の 取得と値の割り当てという 2 つのオプションのロールが追加で含まれています。これらの式は、生成されたコードを単純化し、実行中に Reference <T> オブジェクトのメモリ割り当てを少なくするために使用されます。最初の式は、生成された式が左辺値の位置ではなく使用される場合に使用され、2 番目の式は、生成された式が割り当ての左側のロールにある場合にのみ使用されます。値の割り当て式で使用できる特別な「value」キーワードがあり、このキーワードは式で 1 回だけ使用する必要があることに注意してください。

jetbrains.mps.baseLanguage.date

このサンプル言語では、新しい式を導入したり、LValue 式を何らかのロールで集約した既存の式をカスタマイズしたりするカスタム BaseLanguage 拡張を生成する方法を示します。

日付 BaseLanguage の拡張は、新しい "date" 型と "date" インスタンスに対するいくつかの操作を紹介します。

Image2018 11 12 10 45 48

実行時に、「日付」タイプを java.time.LocalDate で表します。

「date」拡張子は、「date」および「int」インスタンスの plus 演算子をオーバーロードするため、この操作は、左側の部分式から評価される日付に日数(右側の部分式から評価される量)を追加します。生成時に、この plus 演算子を単純な静的メソッド呼び出しに変換します。

Image2018 11 12 10 59 49

Image2018 11 12 11 31 48

拡張機能の一貫性を保つために、plus 演算子をオーバーロードしたのと同様に、オーバーロードと代入式も必要です。plus 演算子plus 代入式の大きな違いは、plus 代入では、左側の部分式を左辺値にする必要があるため、この式を、計算値を割り当てることができる変数に評価する必要があることです。ただし、Java では、メソッド呼び出しを介して変数を渡すことができないため、式を単なるメソッド呼び出しに変換することはできません。

この問題に取り組むために、MPS は「@byRef」生成時間の概念を提供します。この式は LValue 式をラップし、生成中にそれを jetbrains.mps.references.Reference <T> 型の式に変換します。この型のインスタンスを指定すると、ラップされた LValue 式から評価される変数に対して get および set 操作を生成できます。

"@byRef" 式を使用すると、プラス代入式は次のようにして簡単に生成できます。

Image2018 11 12 11 33 15

Image2018 11 12 11 34 3

独立した拡張の構成

上記の 2 つの拡張子は互いに依存しません。どちらのジェネレーターも、BaseLanguage が提供する生成時概念を使用して設計されています。これらの生成時機能により、ジェネレーターはより一貫性のあるものになり、定型コードが少なくなります。それに加えて、示された方法でそのジェネレーターが設計されているエクステンションは、互いに依存関係を持たずに安全に互いに集約することができます。

Image2018 11 12 11 51 17