MPS 電卓言語チュートリアル
導入
このチュートリアルでは、MPS での言語デザインのさまざまな分野について説明します。単純なスタンドアロン言語の抽象的な構造を定義し、そのためのエディターを設計し、タイプを制限し、スコープを作成し、最後に Java コードを生成するジェネレーターを準備します。以前の MPS への露出に応じて、このチュートリアルでは完了までに約 1 日かかります。このチュートリアルでは、主に MPS を評価する必要のある言語デザイナーを対象とし、ベアボーンの例以外のものを見たいと思っています。Java の基礎知識はジェネレーターをより簡単に完成させるのに役立ちます。
前提条件
この経験から、このチュートリアル全体で不要なグリッチを回避したい場合は、MPS プロジェクティブエディターに精通する必要があることがわかります。Fast Track to MPS チュートリアルに従った場合は、十分な準備ができています。そうでない場合は、今すぐ実行してください。
MPS とプロジェクティブ編集の非常に基本的な手順を説明し、新しい情報を消化可能なチャンクで徐々に提供します。これにより、最終的にこの電卓チュートリアルに戻ったときに、チュートリアルを最大限に活用するための知識を得ることができます。
ゴール
このチュートリアルでは、電卓言語を作成します。この言語は、ある種の計算を記述するいくつかの単純なエンティティを定義します。これらのエンティティを計算機と呼びます。電卓には、入力値と出力値のセットがあり、さらに、使用可能な入力値を使用して出力値を計算するための 1 つ以上の数式を定義します。
人工的なユースケースを取り上げます。Java および PHP プロジェクトに費やした時間数を入力するだけで、収益をすばやく計算したい Java/PHP 開発者です。結果のアプリケーションは次のようになります(出力値は時間値から自動的に計算されます)。
ゴールは、開発者が次の 4 行のコードを書くだけで、このようなアプリケーションを作成できるようにすることです (「10」と「5」は、Java と PHP の対応する支払い率を示す定数です)。
calculator MySalary input PHP Hours input Java Hours output Java Hours*10 + PHP Hours*5
Swing ベースのアプリケーション全体は、これらの 4 行の DSL コードから生成されます。
主要なステップ
このチュートリアル
前述の構文を使用して電卓を実装するための言語を作成します。私たちの言語は、電卓、その構造仕様、関係、個々の行動を構成する基本的な論理概念を定義します。
電卓からスイングアプリケーションを構築するためのルールを定義するジェネレーターを作成します。
言語で電卓を実装します。
プロジェクトの作成
MPS のプロジェクトは、言語、ソリューション、その両方で構成されます。もう少し詳しく、どのように構造化されているかを見ることができます。
まず、MPS を開始します。
さまざまなオプションのウェルカム画面が表示されます。ここからサンプルをすばやく開き、ブラウジングのドキュメントなどを開始できます。
新しい言語を作成する予定なので、新しいプロジェクトを最初に作成する必要があります。
ファイル | 新規プロジェクトをクリックしてください。ウィザードが表示されます。
プロジェクトを「電卓」と名づけましょう。
次に、作成したい言語(DSL)の名前を付ける必要があります。
言語には、次の命名規則をお勧めします。この規則は、Java パッケージに使用される規則に似ています: companyName.meaningful.name。それでは、私たちの言語に jetbrains.mps.tutorial.calculator という名前を付けましょう。
さらに、サンドボックスソリューションの作成チェックボックスをオンにします。解決策は、指定された言語で書かれたモデルのセットです。このソリューションを使用して、新しく作成した言語をすぐにテストします。
理想的には、これらの言語を使用する言語とソリューション用に別々のプロジェクトを用意することになります。ただし、開発している言語用の「サンドボックス」ソリューションがプロジェクトに含まれていることを推奨します。これにより、サンプルアプリケーションを作成して新しい言語を即座にテストできます。
次に、OK をクリックします。
開いたプロジェクトビューで、言語記号()でマークされた jetbrains.mps.tutorial.calculator ツリーノードを展開します。
その上の "S" -marked ツリーノード()は私たちのソリューションを表し、モジュールプールノード()は参考のために利用可能なすべての言語とソリューションのコレクションを含んでいます。今後の章でこれらの項目について詳しく見ていきます。
まず言語ノードの構造を調べてみましょう。これは、MPS の背後にある基本的な考え方を理解するのにも役立ちます。
言語の子ノードは、ダイヤモンド形のアイコン「M」でマークされています。これらはアスペクトモデルと呼ばれます。プログラムが通常テキストファイルに保存される他の言語とは異なり、MPS ではモデル内に保存されます。各モデルはノードのツリーを表し、各ノードには親、子ノード、プロパティ、他のノードへの参照が含まれます。各ノードは、その概念によって定義されます。
言語を構成するアスペクトモデルは、言語の異なる特徴を表現しています(アイコンは色によって異なります)。今のところ、言語に必要な 4 つの基本的なアスペクトモデルを考えます:
構造 — 言語の構文を記述します
エディター — その言語で書かれたモデルがエディターでどのように見えるかを説明します
制約 — ノードに適切な名前、変数参照が指すことができる変数などを記述します。
型システム — ノードの型を計算する方法を説明します
電卓のコンセプト
言語に取り組んでみましょう。
まず、最上位の概念、この場合は計算機を作成する必要があります。この概念の各インスタンスには、入力フィールドと出力フィールドが含まれます。
コンセプトを作成するには、構造アスペクトモデルを右クリックして、新規 | 概念を選択します。
概念宣言がエディターで開きます。
概念宣言はノードのクラスを定義し、そのクラスのノードの構造を指定します。
ノードは、次の要素を持つことができます。これらの要素は、概念宣言の対応するセクションで定義されています。
プロパティは、ノード内にプリミティブ値を格納します。例: ノードの名前をプロパティとして定義できます。
参照は、他のノードへのリンクを格納します。例: ローカル変数への参照を格納するために使用できます。
子は、現在のノード内に物理的に含まれているノードなど、集約されたノードを格納します。例: メソッド宣言は、その戻り型と引数を集約します。私たちの場合、これらは入力フィールドと出力フィールドになります。
他のセクションは非常に高度なので、このチュートリアルでは説明しません。MPS のユーザーガイドでそれらについて読むことができます。
概念宣言は継承階層を形成します。デフォルトでは、すべての概念が BaseConcept 概念を拡張します(extends キーワードの直後に表示されます)。ただし、必要に応じて、代わりに別の直接の親を指定できます。概念が別の概念を拡張する場合、その親からすべてのプロパティ、子、参照を継承します。
それでは、コンセプトに Calculator という名前を付けましょう。
'instance can be root' プレースホルダーに到達するまで Tab を押し、Ctrl+Space を押してその値を true に設定します。true と直接入力することもできます。どこでも、Ctrl+Space を押す必要はありません。これにより、プロジェクトビューのルートノードを作成するメニュー項目から電卓インスタンスを作成できます(電卓の概念自体で行ったのと同じです)。
さらに、電卓を名前で参照する必要があります。その中にプロパティ名を作成できます。ただし、これを行うためのより良い方法 (および推奨される方法) があります。コンセプトに INamedConcept インターフェースを実装する必要があります。この概念インターフェースには、name というプロパティが 1 つだけ含まれています。MPS IDE はこのインターフェースを認識しており、それを実装する概念のより高度なサポートを可能にします。例: INamedConcept への参照を作成する場合、その名前が補完メニューに表示されます。または、プロジェクトビューでノードを探索すると、ツリーに INamedConcept の名前が表示されます。
implements キーワードの後の <なし> プレースホルダーにキャレットを置き、Ctrl+Space を押します (エディターの効果的な使用方法の詳細については、付録 A を参照してください)。
INamedConcept を選択し、Enter を押します。
ノードの構文を定義しました。今その側面を定義する必要があります。エディターを作ってみましょう。
電卓のエディターの作成
MPS エディターはテキストエディターのように見えますが、正確にはそうではありません。これは、構文木を直接操作する構造エディターです。
ほとんどすべての既存の言語がテキストベースである場合、構造エディターを使用したいと思う理由は不思議に思うかもしれません。テキストベースの言語は、拡張したいときまで有効です。テキストベースの言語には通常、パーサーがあります。構文解析を決定論的にするためには、文法を慎重に設計する必要があります。これは、言語拡張の場合にはほとんど不可能であることを証明します。言語を拡張する場合、文法を拡張する必要がありますが、他の拡張が何をするかわからないため、この文法が十分であるかどうかはわかりません。Java に金銭的価値のサポートを追加する 2 つの言語拡張を考えてみましょう。どちらも money キーワードを追加します。これらの 2 つの言語拡張を使用するコードを解析すると、money キーワードの解釈方法はわかりません。第 1 言語のキーワードまたは第 2 言語のキーワードとして解釈する必要がありますか? 構造エディターの場合、プログラムが中間テキストプレゼンテーションなしで構文木として直接格納される場合、そのような問題はありません。
MPS の構造エディターは、セルを使用してノードを表します。ノードと同様に、セルはツリーを形成します。セルにはいくつかの種類があります。
プロパティセルを使用してノードのプロパティを編集する
一定のセルは常に同じ値を示します
収集セルは、その内部の他のセルをレイアウトするために使用されます
電卓のエディターを次のように見せたい:
電卓名
このような設計を実装するには、次の操作を行います(Ctrl+Space をそのまま使用します)。
インデント収集セルを作成します。インデントの収集レイアウトは、セルのテキストを好きなようにします。
コレクション内に一定のセルとプロパティセルを作成します。
Calculator の概念に新しいエディターを定義するには、エディタータブの一番下の行でエディタータブを選択する必要があります。エディターなしラベルが付いた空のタブが表示されます。エディターのアスペクトはまだ定義されていません。この空のエディターペインをクリックして、支援メニューからコンセプトエディターを選択できます。
新しいエディターを作成する
ルートインデントコレクションセルを作成しましょう。Ctrl+Space を押して [ - there:
定数セルを作成しましょう:
その中に電卓を入力します。
次のように入力すると、この定数を 1 つのステップで入力できます。"calculator。" 記号は、そのセルを定数セルにします。
次に、(INamedConcept コンセプトインターフェースからの)name プロパティのプロパティセルが必要です。水平リストの最後に別のセルを挿入するには、電卓の単語の最後で Enter を押し、最後に {name} を選択します。
これで、基本が定義されたら、言語を構築してその概念のインスタンスを作成しようとします。言語を構築するには、プロジェクトビューのポップアップメニューから言語を作るアクションを選択してください。
コードが生成されたため、MPS モデルでこの概念のインスタンスを作成および編集できます。MPS は、おそらく最初のプロジェクト作成時にすでに空のソリューションモデルを作成しました。モデルがない場合は、jetbrains.mps.tutorial.calculator.sandbox に新しい MPS モデルを作成できます。プロジェクトツリーでサンドボックスノードを右クリックし、新規、新規の順に選択します。ポップアップメニューからモデル化します。
NewModel のモデル名フィールドに jetbrains.mps.tutorial.calculator.sandbox と入力しますダイアログ
そして OK ボタンを押します。このモデル内で jetbrains.mps.tutorial.calculator 言語を使用するには、表示されたモデル特性ダイアログの使用言語セクションにある + ボタンを押して jetbrains.mps.tutorial.calculator 言語を選択してインポートする必要があります。
これで、モデル特性ダイアログで OK を押して、最終的にサンドボックスモデルを作成し、そこでそこで Calculator インスタンスの操作を開始できます。
サンドボックスのコンパイルを有効にする
ために作成されたソリューションは、サンドボックスソリューションという特別な種類のソリューションです。これらのソリューションは主に、言語設計中に言語設計者 (つまり、あなた:) によって使用され、専用ソリューションの安全な環境でさまざまなアイデアをすぐに試してテストすることで、言語の設計を反復的に進化させることを目的としています。デフォルトでは、サンドボックスソリューションは、コード生成後にコンパイルされた生成出力を取得しません。ただし、このチュートリアルでは、生成された Java コードをコンパイルして実行できることがメリットとなるため、ここで追加の構成手順を実行する必要があります。つまり、サンドボックスソリューションのコンパイルを有効にします。
論理ビューのサンドボックスソリューションを右クリックし、一番下にあるモジュールプロパティオプションを選択します。
「Java」タブで、上部のコンパイル設定領域を展開し、「MPS でコンパイルする」を選択します。
ここで OK を押してダイアログを閉じれば、次の冒険の準備が整います。
最初の計算機を定義する
サンドボックスモデルをクリックし、新規作成メニューから電卓を選択します。
名前を入力できる簡単な電卓があります:
プロパティセルに名前を入力します。この例では、MyCalc という名前を使用しました。
ご覧のとおり、電卓のエディターは、エディターの側面で作成したものと非常によく似ています。
入力フィールド
次に、入力フィールドの概念を作成しましょう。このフィールドは、出力フィールドで参照するため、INamedConcept を実装します。ノードを参照するときに、完了メニューに表示する必要があるため、これが必要です。INamedConcept インスタンスの場合、MPS はこれらのインスタンスの名前を理解しているため、それらの名前は正しく表示されます。INamedConcept 以外の概念を参照することは確かに可能ですが、それを行う最も簡単な方法は INamedConcept です。
エディターを作成しましょう:
電卓に入力フィールドを含めるには、その構造とエディターを少し調整する必要があります。カーディナリティが 0..n の入力フィールドから電卓へのタイプの子を作成しましょう。これを行うには、子供セクションにキャレットを置き、Enter または Insert を押します。その後、ターゲットコンセプトとして InputField を指定し、子名を inputField に設定し、カーディナリティを 0..n に設定します。
次に、名前セルに新しい行を追加します。名前セルにキャレットを置くとバルブが表示されます。バルブで呼び出すことができるアクションはインテンションと呼ばれます。さまざまな言語でそれらがたくさんあります。入力方法がわからない場合は、Ctrl+Space を押すだけでなく、Alt+Enter を押して対応するインテンションを使用することもできます。今 Alt+Enter を押して、追加した新しいラインインテンションを適用します。
次に、エディターに入力フィールドの垂直リストを表示します。{name} ラベルの最後にある Enter を押して、Ctrl+Space を押します。そこに %inputField% を選択してください:
入力フィールドを垂直に配置する必要があります。すべてのセルの後に新しい行を追加する必要があります。これはインテンションで実行できます。Alt+Enter を押して子供用の改行を追加を選択します。
次に、左側のプロジェクトビュー パネルの言語ノードにあるポップアップメニューを使用して言語を作成し(Control/Cmd + F9 を押して作成することもできます)、サンドボックスを見てみましょう。
エディターが更新されました。これで、新しい入力フィールドを追加できる名前の行にセルがあります。いくつかの入力フィールドを追加しましょう:
出力フィールド
出力フィールドのコンセプトを作成しましょう。入力フィールドでのみ値を参照したいため、プロパティ名を含める必要はありません(INamedConcept を実装する InputField と比較してください):
エディターを作成しましょう:
OutputField 型の子を計算コンセプトに追加しましょう:
次に、エディターを変更します。ここでコピー / 貼り付けを使用できます。Ctrl+Up および Ctrl+Down を使用してセルを選択し、次に Ctrl+C および Ctrl+V を使用してコピー / 貼り付けします。%inputField% コレクションの後にノードを貼り付けるには、閉じ括弧 "-)" で Ctrl+V を押します。inputField を outputField に置き換えます。
入力フィールドと出力フィールドを空のセルで区切りました。これを追加するには、inputField のセルの後にキャレットを置き、Enter キーを押し、定数を選択し、新しい行を追加インテンションを使用してこの定数の後に改行コード文字を追加します。
それでは、言語を作成し ( 言語を右クリックするか、Ctrl+F9 を押します)、サンドボックスの概念を見てみましょう。
出力フィールドを追加することはできますが、式の格納を宣言していないため、出力フィールドを入力することはできません。
式サポートの追加
MPS には、Java の MPS の BaseLanguage があります。新しい言語を作成するときには、それを拡張したり、基本言語の概念を再利用したりすることがよくあります。出力フィールドに式言語を再利用しましょう。これを行うには、言語を BaseLanguage に拡張する必要があります。MPS の言語拡張は、拡張言語の概念を使用して拡張できることを意味します。BaseLanguage の Expression コンセプトを使用したいため、拡張言語セクションに追加する必要があります。言語プロパティダイアログを開きます。
BaseLanguage を拡張言語のリストに追加しましょう。これを行うには、依存関係タブを選択し、追加ボタン()をクリックして jetbrains.mps.baseLanguage を選択します。次に、スコープのドロップダウンから拡張を選択します。
BaseLanguage には Expression の概念が含まれています。これは、「2」、「2+3」、「abc+abc」などの形式の式を表します。これはまさに出力フィールド式に必要なものです。これを見てみましょう。Ctrl+N を押して、BaseLanguage から Expression を選択します。
表現自体は抽象的な概念です。それを見てみましょう:
式には独自のプロパティはありません。MPS でどのような表現があるのかを理解するために、その下位概念を見てみましょう。ポップアップメニューの階層でコンセプトを表示アクションを選択します。
ご覧のとおり、さまざまな言語でさまざまな表現があります。表現は非常に広く拡張されています:
OutputField に型式の子を追加しましょう:
それに応じてエディターを変更してください(ここでは Ctrl+Space を使用できます):
今(言語を右クリックまたは Control/Cmd を + F9 を押す)の言語を作ろうと、持っているものを見てみましょう:
モデルの依存関係に BaseLanguage を追加する必要があるかもしれません:
これで、出力フィールドに式を入力できますが、残念ながらそこの入力フィールドを参照することはできません。
式概念の拡張
入力フィールドへの参照をサポートするには、独自の種類の Expression を作成する必要があります。これを「InputFieldReference」と名付け、Expression を拡張します。作成してみましょう。
ここに参考文献を追加しました。これには、InputField と 1-cardinality の型があります。1 カーディナリティの参照が 1 つだけある概念は、スマートな参照と呼ばれ、エディターで特別なサポートを受けています。少し後でそれについてさらに学びます。これでエディターを作成しましょう。% フィールド % - > を選択してください:
一番右のセルの内側で、{name} を選択します。
フィールド参照によって参照されるノードの名前を表示するには、%field% - > {name} を使用します。
今私たちの言語を作って、持っているものを見てみましょう。ノードの入力フィールドを入力できるようになりました:
これはスマート参照のために可能です。これが彼らの仕組みです。スマートリファレンスである概念が現在の文脈で利用可能であるなら、MPS はそれが参照することができる可能なノードのリストを見て、そのような各ノードのために 1 つの補完項目を追加します。それが、完成メニューに幅、高さ、深さがある理由です。
ジェネレーターの作成
言語のジェネレーターを作成しましょう — Java で実装を生成したいと思います。MPS はすでに作成している可能性がありますが、作成されていない場合は、言語ポップアップメニューで新規→ジェネレーターメニュー項目を選択します。
ジェネレーター名を空のままにします。
言語には新しいジェネレーターが含まれています:
ジェネレーターのエントリポイントはマッピング設定です。どのノードが変換され、どのようにするかを指定します。
生成するものを決める
ジェネレーターの開発を始める前に、生成するコードの種類について考えなければなりません。次のようなものを生成したいかもしれません:
public class Sandbox extends JFrame { private JTextField myInput1 = new JTextField(); private JTextField myOutput = new JTextField(); private MyListener myListener = new MyListener(); public Sandbox() { setTitle("Sandbox"); setLayout(new GridLayout(0, 2)); myInput1.getDocument().addDocumentListener(myListener); add(new JLabel("Input 1")); add(myInput1); add(new JLabel("Output")); add(myOutput); update(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } private void update() { int i1 = 0; try { i1 = Integer.parseInt(myInput1.getText()); } catch (NumberFormatException e) { } myOutput.setText("" + (i1)); } private class MyListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { update(); } public void removeUpdate(DocumentEvent e) { update(); } public void changedUpdate(DocumentEvent e) { update(); } } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new Sandbox(); } }); } }
実行時にアプリケーションがどのように見えるかは次のとおりです。
さあ、それを実装しましょう。
ジェネレーターの実装
まず、メインクラスのスケルトンを作成しましょう。update メソッド、main メソッド、update メソッドを呼び出す DocumentListener を使用してクラスを作成する必要があります。新しいメニューから新しいクラスを選択しましょう。
そして、これを得るでしょう:
ご覧のとおり、MPS はクラスの先頭にルートテンプレートアノテーションを追加しました。ジェネレーターモデル内の非ジェネレーター言語ノードはすべてテンプレートとして扱われます。すべてのテンプレートには入力ノード、つまり生成に使用されるノードが必要です。この入力ノードのタイプを指定するには、ルートテンプレートアノテーションが必要です。Calculator に入力を設定しましょう:
クラスに名前をつけましょう:
次に、入力モデルの各電卓からこのテンプレートを使ってクラスを生成する、マッピングのルールを作成しましょう。これをマッピングに追加しましょう:
このルールは、入力モデルから各電卓 を取得し、それを電卓テンプレートに適用するように指示します。私たちの言語は、その後、我々のモデルから生成したテキストをプレビュー(言語を再構築する 言語を右クリックして選択します)再構築してみましょう:
生成された Java ファイルの名前が付けられた新しいエディタータブに出力が表示されます。
ご覧のように、出力には CalculatorImpl という名前のクラスが 1 つあります。
テンプレートの実装
ソースモデルで指定した Calculator ノードの名前をテンプレートに使用させましょう。クラス名にキャレットを置きます。Alt+Enter を押して、「プロパティマクロの追加」を選択します。
プロパティマクロを使用すると、入力ノードに依存するプロパティの値を指定できます。これをインスペクタに入力してみましょう:
ここで、言語を再度再構築し ( 言語を右クリックして言語の再構築を選択)、サンドボックスモデルから生成されたコードをプレビューします。Calculator インスタンスの名前に対応する正しい名前のクラスが表示されます。
残りのクラススケルトンをテンプレートに入力しましょう。そのためには、Java クラスに対応するモデルをインポートする必要があります。あるモデルが別のモデルをインポートすると、インポートモデルは別のモデルからノードを参照できます。Java との相互運用を簡素化するために、MPS は、クラスを持つ jar ファイルまたはフォルダーに対応するモデルを作成できます。これらのモデルは package.name@java_stub フォームの名前を持ち、モデルプロパティダイアログから追加できます。
javax.swing@java_stub、javax.swing.event@java_stub、java.awt@java_stub、javax.swing.text@java_stub モデルをテンプレートモデルにインポートしましょう:
JFrame からクラスを継承し、Calculator テンプレートに main メソッド、documentListener フィールド、update メソッドを作成しましょう。update メソッドを呼び出す前に作成する必要があることに注意してください。
次に、私たちのためにフレームを設定するコードを作成しましょう:
"Calculator" 文字列にプロパティマクロを置き、node.name をインスペクタに追加して、タイトルが電卓の名前を反映するようにします。
次に、入力フィールドごとに視覚的なテキストフィールドコンポーネントを作成しましょう。まず、JTextField 型の 1 つのテキストフィールドを追加する必要があります。
今度は、電卓の入力フィールドごとにそのようなテキストフィールドが作成されるようにします。フィールド宣言全体を選択して(Ctrl+Up ショートカットを使用)、ノードマクロの追加を選択してみましょう:
以下を取得します:
補完メニューからループを選択します。
Loop マクロを使用すると、マクロ内の構成要素を何度でも繰り返すことができます。つまり、インスペクタのコードによって返されるコレクション内の各ノードに対して 1 回です。
ループのインスペクタで次のように入力します:
これは、ソース電卓にある inputField ごとにクラス内にフィールドを持つことを意味します。
それぞれの視覚的なテキストフィールドに一意の名前を付けましょう。その名前にプロパティマクロを追加し、このコードをマクロの中に入力する必要があります:
このコードは、変数に inputField_a、inputField_b などの一意の名前を付けます。
outputFields でも同じことをする必要があります:
唯一の違いは、$LOOP$ に node.outputField を使用し、ベース名として "outputField" を使用することです。変更を MPS で利用できるようにするには、言語を作成します。
サンドボックスを作り、持っているものを見てみましょう:
ご覧のとおり、フィールドが生成されています。これらのフィールドをビジュアルフレームに追加します。次のように入力します。
基本的に、BlockStatement ({} でラップされたコード)を作成して、BlockStatement のコンテンツを単一の InputField に属する単一のユニットとして扱うことができるようにします。次に、各計算機の InputField に対してブロックを簡単に繰り返すことができます。ブロックとその中のコードを $LOOP$ で囲み、node.inputField をインスペクターの値として使用します。
JLabel のコンストラクターのパラメーターにプロパティマクロを追加して、ラベルが対応する入力フィールドの名前と同じテキストを持つようにします。
現在のフィールドが格納されているフィールド宣言への参照を作成すると状況がより複雑になります。inputField への参照から、現在の inputField から生成された対応する JTextField への参照を生成する必要があります。これを行うには、フィールド宣言にラベルを付け、このラベルを使用してフィールドを見つける必要があります。
マッピングに移動し、これを追加してください:
これで、元の InputFields によってインデックス付けされた生成された FieldDeclarations のストレージができました。これは、inputField ラベルが、InputField から生成された FieldDeclaration タイプのノードを参照することを意味します。次に、生成されたフィールドの LOOP マクロにラベルを追加しましょう。
次に、inputField への参照を、ラベルを使った参照を使って見つかった参照に変更する必要があります。これを行うには、フィールド参照に参照マクロを追加する必要があります。
インスペクタに移動し、次のように入力します。
:
2 番目の参照と同じ操作を行います:
それでは、言語を作成して(言語を右クリックし、言語の 再構築を選択)、サンドボックスモデルから生成されるものを見てみましょう。
ご覧のとおり、参照は正しく設定されています。
次に、出力フィールドで同じことを行いましょう。ここでも、初期化コードを作成し、$LOOP$ マクロで囲む必要があります。フィールド宣言のラベルを作成し、outputField の $LOOP$ に配置します。次に、このラベルを使用して参照を作成する必要があります。これらの変更を行った後、テンプレートのコードは次のようになります。
ここで残された唯一のもの、つまり結果を計算された値で更新するコードを実装しましょう。このようなことをしたいと思います:
public void update() { int i1 = 0; try { i1 = Integer.parseInt(myInput1.getText()); } catch (NumberFormatException e) { } myOutput.setText("" + (i1)); }
入力ノードごとに int ローカル変数を作成し、そのフィールドに値を代入します。そのような変数のループマクロを作成しましょう。";" $LOOP$ マクロの中にあります:
これらの変数のそれぞれに固有の名前を生成しましょう:
これらの変数を初期化しましょう。変数を参照するには、さらに別のラベルを作成する必要があります。
そして、それをローカル変数宣言に割り当てます。そのためには、ローカル変数を $MAP_SRC$ マクロで囲む必要があります。ほとんどの場合、このマクロは、参照したいノードにラベルを追加するためだけに使用され、他のマクロは関連付けられていません。
LocalVariableDeclarationStatement ではなく、LocalVariableDeclaration を必ず選択してください。これを確認するには、セミコロンを参照してください。それがマクロ角括弧の外側にある場合は、適切なノードを選択します。
これらのローカル変数を電卓の入力フィールドの値で適切に初期化するには、別の LOOP が必要です。まず、try ... catch ブロックを追加します。これを持っています:
LOOP マクロで囲みます:
ステートメント全体(終了セミコロンを含む行)が LOOP マクロでラップされていることを確認してください。次に、参照マクロを作成し、ラベルを使用して、これらの参照の現在のターゲット(たとえば、それぞれ LocalVariableDeclarations と InputFields)を見つけます。ローカル変数を使用する場合:
フィールド参照あり:
次に、出力値を、計算された出力を表すテキストフィールドに設定する必要があります。次のコードを入力します。
LOOP マクロで囲みます:
繰り返しになりますが、終了セミコロンを含むステートメント全体が LOOP マクロでラップされていることを確認してください。次に、参照マクロを outputField に追加します。
今度は、計算結果が表示されるようにテキストを設定する必要があります。そのため、null 値がある場所に出力フィールドの式を生成する必要があります。setText() メソッドの引数 null を削除し、"" +(null)と入力します。各ステップで Ctrl + Space を押します。出力コードが正しいことを確認するためにここに括弧を追加します。括弧がなく、式が 2 〜 3 の場合、出力コードは "" + 2 — 3 になりますが、これは正しくありません。一方、"" +(2 — 3)は正しいです。
null 値にノードマクロを追加し、$COPY_SRC$ マクロに変更します。$COPY_SRC$ macro はマクロのノードをインスペクタのコードで返されたノードのコピーで置き換えます。node.expression を返します。
残っているのは、InputFieldReference の処理だけです。まだジェネレーターはありません。参照を、参照された入力フィールドに対応する JTextField から取得した値に置き換える必要があります。基本的に、LocalVar ラベルに格納されている対応する i 変数。 InputFieldReference のジェネレーターを作成するには、削減ルールを定義する必要があります。削減ルールは、たとえば $COPY_SRC$ マクロで、生成中にコピーされるすべてのノードに適用されます。マッピング構成で対応する削減ルールを作成しましょう。
テンプレートはそれほど長くないため、コンテキスト付きのインラインテンプレートを使用します。補完リストから <インライン template with context> を選択しました。
InputNodeReference をローカル変数参照に変換するため、そのようなローカル変数参照を含む有効なコンテキストノードを作成する必要があります。BlockStatement をコンテキストノードとして使用しましょう:
この BlockStatement の中で、ローカル変数 i を定義し、それを参照する単純な式を作成しましょう:
このローカル変数 i への参照は、インラインテンプレートの結果として使用するものなので、テンプレートフラグメントとしてマークする必要があります。Alt+Enter を押して、「テンプレートフラグメントの作成」インテンションを選択します。
次に、このローカル変数参照の参照マクロを作成しましょう:
LocalVar から正しい i を取得するために、適切な参照クエリを指定します。
私たちの言語を再構築(言語を右クリックして、言語を再構築選択)とサンドボックスモデルと何が起こるかを見てみましょう。ご覧のとおり、MPS は必要なコードを正確に生成します。
入社給与プログラム
言語とそのジェネレーターを終了したら、給与プログラムに入ることができます。こちらで確認できます:
生成されたコードの実行
コードを実行するには、ソリューションモデルを作成する必要があります。
ソースは、ソリューションの source_gen ディレクトリにあります。(ファイルシステムビューに切り替えることを忘れないでください):
好きな Java IDE で実行できます。
言語の主要部分を終えました。今度は研磨をしましょう。
InputFieldReference のスコープの作成
サンドボックスモデルで別の電卓のルートを作成すると、最初の電卓から入力フィールドを参照することができますが、これは正しくありません。
修正しましょう。そのためには、InputFieldReference コンセプトのスコープを定義する必要があります。これを行うには、制約を定義します。InputFieldReference を 開き、制約タブに移動して、その制約ルートを作成します。スコープ解決の新しい階層(継承)メカニズムを使用します。このメカニズムは、スコープ解決を ScopeProvider を実装する祖先に委譲します。私たちは、このように InputFieldReference の InputField のための検索は、ノードとそれらのリストを構築するためにその祖先に依存しています。
指示対象制約を追加し、リンクとしてフィールドを選択します。
次に、このリンクのスコープを作成しましょう。スコープセクションに移動して、Ctrl+Space を押します。InputFieldReference がリンクできる要素を解決する方法を指定する必要があります。
の InputField を検索 InputFieldReference ためのスコープが継承されることを指定したら、電卓が ScopeProvider であることを示す必要があります。これにより、Calculator は、その子孫として配置されるすべての InputFieldReferences のスコープを構築する際に発言権を持つようになります。
ここで、Calculator の構造に移動し、ScopeProvider コンセプトインターフェースコンセプトが実装されていることを確認します。
この場合の電卓 は、InputField のスコープを照会するたびに、すべての InputField のリストを返す必要があります。電卓 の動作の側面では、Ctrl+O を getScope() メソッドでオーバーライドします。
スコープが未解決のままの場合は、スコープを含むモデル Ctrl+R (jetbrains.mps.scope)をインポートする必要があります。
一部の機能をエンコードする必要があるため、BaseLanguage も必要です。ノードを照会するには、smodel 言語をインポートする必要があります。これらの言語は自動的にインポートされているはずです。そうでない場合は、Ctrl+L ショートカットを使用してインポートできます。
最後に、スコープ定義コードを完成させることができます。これは、本質的に電卓内のすべての入力フィールドを返します。
簡単なヒント: SimpleRoleScope クラスの使用に注意してください。これは、独自のカスタムスコープの構築に役立ついくつかのヘルパークラスの 1 つです。SimpleRoleScope Control/Cmd + N に移動し、含まれているパッケージ構造 Alt+F1 を開いて、確認してください。
(言語を右クリックし、言語の作成 を選択します)言語を行った後、もはや二番目の最初の電卓からフィールドにアクセスすることはできません。
しかし、最初の電卓のフィールドにはまだアクセスできます。
InputFieldReference の型システムルールの作成
これで次のように入力できます:
このコードは、三項演算子の条件の一部として使用されるため、幅はブール値でなければならないため、正しくありません。このエラーを表示するには、コンセプトの型システムルールを作成する必要があります。やってみましょう。InputFieldReference を開き、型システムタブに移動します。そこで InferenceRule を作成します。
typesytem エンジンは、型方程式と不等式で動作します。方程式は、どのノードのタイプが等しくなければならないかを指定します。方程式で、どのノードのタイプを相互のサブタイプにするかを指定します。参照の型が常に int であることを示す方程式を追加する必要があります。方程式を定義しましょう。完了メニューで等号を選択します。
左側の typeOf を選択してください:
typeof の中に 'inputFieldReference' と入力してください:
方程式の右辺の <quotation> を選択します。
引用は、引用符の中にあるノードを値として取得するための特別な構文です。通常、作成したいノードは非常に複雑です。順次プロパティ / 子 / 参照割り当てを使用してノードを作成すると、エラーが発生しやすく、退屈な作業になります。引用はこのようなタスクを大幅に簡素化します。
その内部に IntegerType 型を入れます:
今度はルールが完成しました:
言語を作る(言語を右クリックし、言語の作成 を選択し)、エラーでコードを見てみましょう:
今すぐハイライトされています。Ctrl + F1 を押して、"type int is not type of Boolean" というエラーメッセージを表示することができます。
MPS エディターの使い方
テキストエディターのように見えますが、MPS エディターは正確には 1 つではありません。このため、テキストエディターを使用して作業するのとは異なります。このセクションでは、MPS エディターの操作の基本について説明します。
ナビゲーションは、テキストエディターと同様に機能します。矢印キー page up/down、および home/end を使用でき、それらはテキストエディターと同じように動作します。選択の動作は異なります。MPS にはそのようなものがないため、エディターで任意の間隔を選択することはできません。セルまたは隣接するセルのセットのみを選択できます。現在のセルの親を選択するには、Ctrl+Up を押します。現在のセルの子を選択するには、Ctrl+Down を押します。隣接するセルを選択する場合は、Shift+Left および Shift+Right を使用します。
コード補完がオプションのテキストエディターとは異なり、MPS はこの機能をそのまま使用できます。Ctrl+Space をほぼどこにでも押して、現在の位置にある可能なアイテムのリストを見ることができます。言語の機能を調べたい場合は、目的の場所でこのショートカットを使用し、補完メニューの内容を確認するのが最適な方法です。
MPS の一般的に使用されるもう 1 つの機能はインテンションです。インテンションは、エディター内のノードに適用できるアクションです。エディターにライトバルブが表示されている場合は、インテンションを適用することができます。Alt+Enter を押すと、バルブがメニューに展開され、目的のアクションを選択できます。
MPS では、そのような要素が複数存在する可能性のある場所に新しい要素を追加したいことがよくあります。例: メソッド本体に新しいステートメントを追加し、クラス宣言に新しいメソッドを追加する場合があります。これを行うには、Insert と Enter の 2 つのショートカットを使用できます。前者は、現在のアイテムの前に新しいアイテムを追加します。後者は、現在のアイテムの後に新しいアイテムを追加します。
関連ページ:
MPS へのファストトラック
ようこそ ! このチュートリアルは、MPS にまったく慣れておらず、MPS の風景を見ながらのガイドツアーを好む開発者のために特別に設計されます。次に進むべき場所を示す明確なマークに従って、殴打された道を一度に 1 歩歩きます。情報は、より単純な概念からより複雑な概念へと進み、旅の終わりに MPS を理解し、あなたのプロジェクトでそれを効果的に使用できるようになるように構成されています。私達は MPS を学ぶことよりも世界に簡単な作業があることを認めます。言語設計は複雑な領域であり、射影編集は慣...
図形
MPS に不慣れですぐにそれを試してみたいなら、これはあなたのための正しいチュートリアルです。2 時間以内に、新しい言語とその言語を使用する機能コードを入手できます。このチュートリアルでは、最初から始め、安全で便利な道を歩むことで、新しい言語のコア要素を設計します。すぐにフィニッシュラインに到達するために、高度な概念、複雑な構成要素、暗いコーナーを避けます。最後に、MPS とは何か、MPS がどのような原則に基づいているのかを理解できます。シートベルトを締めてください。高速に走ります。前提条件...
スコープ
カスタム言語要素のスコープを定義する 2 つの方法、つまり継承された(階層的)アプローチと参照アプローチについて見ていきます。実験のテストベッドとして計算機のチュートリアル言語を選択しました。MPS ディストリビューションに付属するサンプルプロジェクトのセットに含まれている電卓チュートリアルプロジェクトを見つけることができます。2 つの方法:すべての参照は許可されたターゲットのセットを知る必要があります。これにより、ユーザーが参照の値を入力しようとしているときはいつでも MPS が完了メニュー...