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 キーワードの直後にそれを見ることができます)。ただし、必要に応じて別の直接親を指定することもできます。概念が別の概念を拡張する場合、概念は親からすべてのプロパティ、子、参照を継承します。
私たちのコンセプト電卓を名づけましょう。
インスタンスが root になることができるようになるまで Tab を押し、その値を true に設定するために Ctrl+Space を押します。直接 true と入力することもできます。どこでも Ctrl+Space を押す必要はありません。これにより、プロジェクトビューのルートノードを作成するメニュー項目を使用して電卓インスタンスを作成できます(電卓の概念自体の場合と同じ)。
電卓を名前で参照する必要があります。その中にプロパティ名を作成できます。しかし、そうするためのより良い方法(そして推奨される方法)があります。私たちの概念に INamedConcept インターフェースを実装させる必要があります。この概念インターフェースには、プロパティ名が 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 インスタンスの操作を開始できます。
最初の計算機を定義する
サンドボックスモデルをクリックし、新規作成メニューから電卓を選択します。
名前を入力できる簡単な電卓があります:
プロパティセルに名前を入力します。この例では、MyCalc という名前を使用しました。
ご覧のとおり、計算機のエディターは、エディターの観点で書いたものと非常によく似ています。
入力フィールド
次に、入力フィールドの概念を作成しましょう。このフィールドは、出力フィールドで INamedConcept を参照したいため、INamedConcept を実装します。ノードを参照するときは、完了メニューでノードを参照する必要があるため、ノードが必要です。INamedConcept インスタンスの場合、MPS はこれらのインスタンスの名前を認識するため、名前が正しく表示されます。INamedConcept 以外の概念を参照することは確かに可能ですが、INamedConcept はこれを行う最も簡単な方法です。
エディターを作成しましょう:
電卓に入力フィールドを含めるには、その構造とエディターを少し変更する必要があります。0..n カーディナリティを持つ Calculator への InputField 型の子を作成しましょう。これを行うには、子セクションにキャレットを置き、Enter または Insert を押します。その後、InputField をターゲットコンセプトとして指定し、子名を inputField に、カーディナリティを 0..n に設定します。
今度は cell という名前に新しい行を追加します。名前のセルにカーソルを合わせるとバルブが表示されます。バルブで呼び出せるアクションはインテンションと呼ばれます。さまざまな言語でそれらの多くがあります。入力方法がわからない場合は、Ctrl+Space を押すだけでなく、Alt+Enter を押して対応するインテンションを使用することもできます。今 Alt+Enter を押して、新しい行を追加インテンションを適用してください。
次に、エディターに入力フィールドの垂直リストを表示します。{name} ラベルの最後にある Enter を押して、Ctrl+Space を押します。そこに %inputField% を選択してください:
入力フィールドを垂直に配置します。すべてのセルの後に新しい行を追加する必要があります。これは、インテンションで行うことができます: Alt+Enter を押し、子供のための新しい行を追加するを選択します。
次に、左側のプロジェクトビューパネルの言語ノードのポップアップメニューを使用して言語を作ってみましょう(Ctrl/Cmd+F9 を押して実行することもできます)。そして、私たちのサンドボックスを見てみましょう:
エディターが更新されました。これで、新しい入力フィールドを追加できる名前の行にセルがあります。いくつかの入力フィールドを追加しましょう:
出力フィールド
出力フィールドのコンセプトを作成しましょう。入力フィールドでのみ値を参照したいため、プロパティ名を含める必要はありません(INamedConcept を実装する InputField と比較してください):
エディターを作成しましょう:
OutputField 型の子を計算コンセプトに追加しましょう:
今度はエディターを変更します。コピー / ペーストはここで使用できます。Ctrl+Up/Down を使用してセルを選択し、次に Ctrl+C/V をコピー / ペーストします。%inputField% コレクションの後にノードを貼り付けるには、閉じた " -)" 括弧で Ctrl+V を押します。inputField を outputField に置き換えます。
空のセルで入力フィールドと出力フィールドを分離しました。それを追加するには、inputField のセルの後ろにキャレットを置いて Enter を押し、定数を選択してから新しい行を追加インテンションを使用してこの定数の後に改行コードシンボルを追加します。
次に、言語を作成し(言語を右クリックするか Control/Cmd + F9 を押す)、サンドボックスの概念を見てみましょう。
出力フィールドを追加することはできますが、式の格納を宣言していないため、出力フィールドを入力することはできません。
式サポートの追加
MPS には、Java の MPS の BaseLanguage があります。新しい言語を作成するときには、それを拡張したり、基本言語の概念を再利用したりすることがよくあります。出力フィールドに式言語を再利用しましょう。これを行うには、言語を BaseLanguage に拡張する必要があります。MPS の言語拡張は、拡張言語の概念を使用して拡張できることを意味します。BaseLanguage の Expression コンセプトを使用したいため、拡張言語セクションに追加する必要があります。言語プロパティダイアログを開きます。
拡張言語のリストに BaseLanguage を追加しましょう。これを行うには、依存関係タブを選択し、追加ボタン()をクリックして jetbrains.mps.baseLanguage を選択します。次に、スコープのドロップダウンボックスから拡張を選択します。
BaseLanguage には式の概念が含まれています。"2"、"2 + 3"、"abc + abc" などの形式の式を表します。これは出力フィールド式に必要なものです。それを見てみましょう。Ctrl+N を押し、そこの BaseLanguage から Expression を選択してください:
表現自体は抽象的な概念です。それを見てみましょう:
式には独自のプロパティはありません。MPS でどのような表現があるのかを理解するために、その下位概念を見てみましょう。ポップアップメニューの階層でコンセプトを表示アクションを選択します。
ご覧のとおり、さまざまな言語でさまざまな表現があります。表現は非常に広く拡張されています:
OutputField に型式の子を追加しましょう:
それに応じてエディターを変更してください(ここでは Ctrl+Space を使用できます):
次に、言語を作成し(言語を右クリックするか Control/Cmd + F9 を押す)、取得した内容を確認します。
モデルの依存関係に BaseLanguage を追加する必要があるかもしれません:
これで、出力フィールドに式を入力できますが、残念ながらそこの入力フィールドを参照することはできません。
式概念の拡張
入力フィールドへの参照をサポートするには、独自の種類の Expression を作成する必要があります。それを "InputFieldReference" と名付けて、それを式に拡張しましょう。それを作りましょう:
ここに参考文献を追加しました。これには、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(); } }); } }
実行時にアプリケーションがどのように見えるかは次のとおりです。
さあ、それを実装しましょう。
ジェネレーターの実装
最初にメインクラスのスケルトンを作成しましょう。更新メソッド、メインメソッド、更新メソッドを呼び出す 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/Down ショートカットを使用)、ノードマクロの追加を選択してみましょう:
以下を取得します:
補完メニューからループを選択します。
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 から取り出された値で置き換える必要があります。つまり、対応する i 変数が LocalVar ラベルに格納されています。InputFieldReference のジェネレーターを作成するには、リダクションルールを定義する必要があります。削減ルールは、生成中にコピーされるすべてのノード(たとえば、$COPY_SRC$ macro)に適用されます。マッピング設定で対応するリダクションルールを作成しましょう:
テンプレートはあまり長くないため、インラインテンプレートにはコンテキストを使用します。補完リストの <コンテキスト付きインラインテンプレート> を選択してください。
InputNodeReference をローカル変数参照に変換するため、そのようなローカル変数参照を含む有効なコンテキストノードを作成する必要があります。BlockStatement をコンテキストノードとして使用しましょう:
この BlockStatement の中で、ローカル変数 i を定義し、それを参照する単純な式を作成しましょう:
このローカル変数 i への参照は、インラインテンプレートの結果として使用するため、テンプレートフラグメントとしてマークする必要があります。Alt+Enter を押して "Create Template Fragment" を選択してくださいインテンション:
次に、このローカル変数参照の参照マクロを作成しましょう:
正しい参照クエリを指定して、LocalVar から正しい i を取得します。
言語を再構築してみましょう(言語を右クリックして言語の再構築を選択してください)、サンドボックスモデルがどうなるかを見てみましょう。ご覧のとおり、MPS は必要なコードを正確に生成します。
入社給与プログラム
言語とそのジェネレーターを終了したら、給与プログラムに入ることができます。こちらで確認できます:
生成されたコードの実行
コードを実行するには、ソリューションモデルを作成する必要があります。
ソースは、ソリューションの source_gen ディレクトリにあります。(ファイルシステムビューに切り替えることを忘れないでください):
好きな Java IDE で実行できます。
言語の主要部分を終えました。今度は研磨をしましょう。
InputFieldReference のスコープの作成
サンドボックスモデルで別の電卓のルートを作成すると、最初の電卓から入力フィールドを参照することができますが、これは正しくありません。
それを修正しましょう。そのためには、InputFieldReference の概念の範囲を定義する必要があります。これを行うには、制約を定義します。InputFieldReference を開き、制約タブに移動して、それに対する制約ルートを作成します。スコープ解決の新しい階層(継承)メカニズムを使用します。このメカニズムは、ScopeProvider を実装する先祖にスコープ解決を委譲します。我々の InputFieldReference はこのように InputField ノードを検索し、それらのリストを構築するためにその先祖に頼ります。
参照制約を追加し、そのリンクとして field を選択します。
それでは、このリンクのスコープを作成しましょう。scope セクションに移動し、Ctrl+Space を押してください。InputFieldReference がリンクできる要素を解決する方法を指定する必要があります。
InputField を検索するときの InputFieldReference のスコープが継承されるように指定したら、電卓が ScopeProvider であることを示す必要があります。これは、電卓がその子孫として配置されているすべての InputFieldReferences のスコープを構築することを宣言していることを保証します。
このケースの電卓は、InputField の範囲を照会するたびに、その InputFields のすべてのリストを返す必要があります。電卓の振る舞いの面では、getScope() の方法(Ctrl+O)をオーバーライドします。
スコープが未解決のまま残っている場合は、それを含むモデル(Ctrl+R)をインポートする必要があります(jetbrains.mps.scope)。
機能をエンコードする必要があるため、BaseLanguage も必要です。ノードを照会するには、smodel 言語をインポートする必要があります。これらの言語は自動的にインポートされているはずです。そうでない場合は、Ctrl+L ショートカットを使用してインポートできます。
最後に、スコープ定義コードを完成させることができます。これは、本質的に電卓内のすべての入力フィールドを返します。
簡単なヒント: SimpleRoleScope クラスの使い方に注目してください。それは独自のカスタムスコープを構築するのを手助けすることができるいくつかのヘルパークラスの 1 つです。SimpleRoleScope(Ctrl+N)に移動し、それを含むパッケージ構造(Alt+F1)を開いてチェックしてください言語を作成した後(言語を右クリックして言語を作るを選択)、2 番目の電卓のフィールドにはアクセスできなくなります。
しかし、最初の電卓のフィールドにはまだアクセスできます。
InputFieldReference の型システムルールの作成
これで次のように入力できます:
このコードは、3 進演算子の条件の一部として使用されているため、width はブール値でなければならないため、正しくありません。このエラーを表示するには、私たちの概念の型システムルールを作成する必要があります。やってみましょう。InputFieldReference を開き、型システムタブに移動します。そこに InferenceRule を作成してください:
型システムエンジンは型方程式と不等式を操作します。方程式はどのノードの型が等しいかを指定します。方程式では、どのノードのタイプを互いのサブタイプにするかを指定します。参照の型が常に 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/Right を使用します。
コード補完がオプションのテキストエディターとは異なり、MPS はこの機能をそのまま使用できます。Ctrl+Space をほぼどこにでも押して、現在の位置にある可能なアイテムのリストを見ることができます。言語の機能を調べたい場合は、目的の場所でこのショートカットを使用し、補完メニューの内容を確認するのが最適な方法です。
MPS の一般的に使用されるもう 1 つの機能はインテンションです。インテンションは、エディター内のノードに適用できるアクションです。エディターにライトバルブが表示されている場合は、インテンションを適用することができます。Alt+Enter を押すと、バルブがメニューに展開され、目的のアクションを選択できます。
MPS では、このような要素が複数存在する場所に新しい要素を追加したいことがよくあります。例: メソッド本体に新しいステートメントを追加したり、クラス宣言の新しいメソッドを追加したりすることができます。これを行うには、Insert と Enter の 2 つのショートカットを使用できます。前者は、現在のアイテムの前に新しいアイテムを追加します。後者は、現在のアイテムの後に新しいアイテムを追加します。現在の項目を削除するには、Ctrl+Delete を押します。
関連ページ:
MPS へのファストトラック - 習得するための 10 のステップ
ようこそ ! このチュートリアルは、MPS にまったく慣れておらず、MPS の風景を見ながらのガイドツアーを好む開発者のために特別に設計されます。次に進むべき場所を示す明確なマークに従って、殴打された道を一度に 1 歩歩きます。情報は、より単純な概念からより複雑な概念へと進み、旅の終わりに MPS を理解し、あなたのプロジェクトでそれを効果的に使用できるようになるように構成されています。私達は MPS を学ぶことよりも世界に簡単な作業があることを認めます。言語設計は複雑な領域であり、射影編集は慣...
図形 - MPS 入門チュートリアル
MPS に不慣れですぐにそれを試してみたいなら、これはあなたのための正しいチュートリアルです。2 時間以内に、新しい言語とその言語を使用する機能コードを入手できます。このチュートリアルでは、最初から始め、安全で便利な道を歩むことで、新しい言語のコア要素を設計します。すぐにフィニッシュラインに到達するために、高度な概念、複雑な構成要素、暗いコーナーを避けます。最後に、MPS とは何か、MPS がどのような原則に基づいているのかを理解できます。では、シートベルトを締めてください。早く乗りに行きましょ...
スコープ
カスタム言語要素のスコープを定義する 2 つの方法、継承 (階層) と参照アプローチを見ていきます。実験用のテストベッドとして計算機のチュートリアル言語を選択しました。MPS ディストリビューションに付属のサンプルプロジェクトのセットに calculator-tutorial プロジェクトが含まれています。2 つの方法:すべての参照は許可されたターゲットのセットを知る必要があります。これにより、ユーザーが参照の値を入力しようとしているときはいつでも MPS が完了メニューに値を入力することができます...