MPS 2024.3 ヘルプ

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

この記事では、LValue 式で動作する BaseLanguage 拡張機能のジェネレーターを記述する慣用的な方法を紹介します。BaseLanguage 式は、代入式の左側で使用できる場合、lvalue であると言います。このような式を導入する概念は、動作の側面でインスタンスメソッド 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.png

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

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

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

たとえば、実行時に「ボックス」型が getValuesetValue の 2 つのメソッドを提供する Box インターフェースで表現される場合、「値」操作を Java LValue 式に変換するのは困難です。この問題を克服するには、BaseLanguage の生成時概念である「汎用 lvalue 式」を使用して、式を次のように変換します。

genxx1.png
image2018-11-12-11-36-58.png

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

「汎用 lvalue 式」には、さらに 2 つのオプションのロール、get valueassignment value が含まれます。これらの式は、生成されたコードを簡素化し、実行中に Reference<T> オブジェクトのメモリ割り当てを減らすために使用されます。最初の式は、生成された式が lvalue の位置で使用されていない場合に使用され、2 番目の式は、生成された式が割り当ての左側のロールにある場合にのみ使用されます。assign value 式では特別な "value" キーワードが使用可能であり、このキーワードは式で 1 回だけ使用する必要があることに注意してください。

jetbrains.mps.baseLanguage.date

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

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

image2018-11-12-10-45-48.png

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

「date」拡張機能は、「date」および「int」インスタンスのプラス演算子をオーバーロードし、この演算によって、左のサブ式から評価される日付に日数 (その量は右のサブ式から評価されます) が追加されます。生成時に、このプラス演算子を単純な静的メソッド呼び出しに変換します。

image2018-11-12-10-59-49.png
image2018-11-12-11-31-48.png

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

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

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

image2018-11-12-11-33-15.png
image2018-11-12-11-34-3.png

独立した拡張の構成

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

image2018-11-12-11-51-17.png