MPS 2020.1 ヘルプ

正規表現

正規表現言語の概要

導入

正規表現言語(テキスト操作を大幅に簡素化する言語)は、現在最も使用されているドメイン固有の言語の 1 つです。ほとんどすべての開発者が少なくとも 1 回は使用しています。Perl や Python などの一部の言語には、サポートが組み込まれています。Java などの一部は、ライブラリを介して使用します。MPS の実装に使用する言語である Java には、正規表現の言語レベルのサポートがないため、DSL を実装するのが自然であったため、正規表現ライブラリの代わりに DSL を使用できます。この言語は、MPS 言語の良い例です。この概要を読んで、MPS で言語を作成および使用する方法を理解できるようになります。

サンプル

MPS が既にインストールされていると想定しています。
このドキュメントでは多くの例を使用しています。jetbrains.mps.regexp.examples ソリューションの正規表現言語プロジェクト(%MPS_HOME%/ platform / regexp)で見つけることができます。

worddav54fd8999f157b75ecce5d51ca9bff681

言語の概要

簡単な正規表現アプリケーションを見てみましょう。メールアドレスからユーザー名とドメイン名を取得したいとします。正規表現を使用してメールアドレスを分析することにより、ユーザー名とドメイン名を出力するコードを次に示します(この例は EmailExample クラスにあります)。

worddav6357b7cf6d2db641dc49ad95d2b20ce6

この match regexp ステートメントで使用される正規表現は、次のことを行います。最初に、1 つ以上の単語文字(\ w +)を読み取り、「ユーザー」変数に保存します。その後、「@」文字を読み取ります。次に、ピリオド(「.」文字)で区切られた単語のリストを読み取り、ドメイン変数(\ w +(。\ w +))に保存します。一致が見つかった場合、プログラムはユーザーとドメインを System.out に出力します。
この例の正規表現の構文ツリーは次のとおりです。
worddave83ccb1d63a90d30c3ac013f61241197

言語構造

MPS で言語を作成するとき、通常、その抽象構文を定義することから始めます。MPS の抽象構文は、言語構造と呼ばれます。これを行うには、構造言語を使用します。構造言語は、XML 言語に対応する XML スキーマ、または SQL に対応する DDL です。正規表現の言語構造を見てみましょう。

概要

MPS 正規表現言語には、いくつかの部分が含まれています。

  • 正規表現:正規表現を指定するために使用される概念。これらには、文字列リテラル、シンボルクラス、「or」および「sequence」正規表現の概念が含まれます。

  • BaseLanguage(BaseLanguage は、ジェネレーターのターゲット言語として MPS によって内部的に使用される Java に似た言語です)統合:この部分には、正規表現関連のコードを BaseLanguage に埋め込むために使用される概念が含まれます。例:MatchStatementReplaceStatementSplitStatement が含まれます。

  • 正規表現ライブラリのサポート。正規表現で作業するとき、再利用したいため、このタスクのための特別な概念を作成しました。

正規表現

言語のすべての正規表現の概念は、その構造モデルの「Regexp」フォルダーに配置されます。

worddavc3eea13ac43a8310f50d2743e3c07435

それらを詳細に検討しましょう。それらすべてに単一の基本概念があります:正規表現:
worddavb1c8d5e9a555626d4a7c6f326b4391ba

BaseConcept コンセプトから派生しています。すべての MPS の概念は、そこから派生しています。この概念には抽象概念プロパティもあります。つまり、言語で正規表現を定義するために使用されるのではなく、概念階層を形成するために作成されます。これは、Java クラスの「抽象」修飾子に似ています。
それから派生した概念を考えてみましょう。階層ビューで見ることができます。このビューを表示するには、コンセプト宣言で Ctrl + H を押します。正規表現コンセプトの場合、次のように表示されます。
worddav641bbd1e055f8a1257d7e09e8545a4b5

StringRegexp は、テキストと照合できる任意の文字列を表します(このセクションで検討する正規表現の例はすべて、正規表現ルートノードにあります)。
worddav60b32b2dae9e635eaec0472fce900e33

その概念宣言を見てみましょう(概念のインスタンスがエディターで選択されているときに Ctrl + Shift + S を押すと、概念宣言にすばやく移動できます)。
worddavc394a4e587c8986c177d7a7e08981894

その宣言には、エディターに表示されるテキストを保存するために使用される文字列型のプロパティテキストがあります。また、このコンセプトは、コンセプトプロパティ「エイリアス」を宣言します。コンセプトプロパティは、単純なプロパティとは異なります。単純なプロパティは Java インスタンスフィールドに対応し、コンセプトプロパティは Java 静的フィールドに対応します。Ctrl + Space を押すと、コンセプトプロパティエイリアスの値が補完メニューに表示されます。
worddavf9496c8f18b0eb7b37640d05e82a8bf6

バイナリ正規表現は、2 つの異なる正規表現を 1 つに結合した正規表現を表すために作成されます。BinaryRegexp コンセプトは抽象として宣言され、2 つの具体的なサブコンセプト OrRegexpSeqRegexp があります。インスタンスの例を次に示します。
worddav293aaafcd17556c2b1f82ba50ef99148
worddava8acebee034eb28ab80b57e485b5aebd

その概念宣言は次のとおりです。
worddav37de03cf815e63b6dab3fbf5daf43f86

2 つのリンクを定義します。1 つは左の部分を保存し、もう 1 つは右の部分を保存します。「集約」という言葉は、このリンクの正規表現が宣言された概念インスタンスの一部になることを意味します。つまり、構文ツリーを見ると、親 BinaryRegexp に子の正規表現が表示されます:
worddav4ad0a0fbd213cbdb7725a1851aaf03bf

ドット regexp は、任意の文字に一致する正規表現を表します。LineEndRegexp は、行末でのみ一致します。LineStartRegexp は、行の先頭でのみ一致します。ParensRegexp は、囲む正規表現を読みやすくするために、他の正規表現をグループ化するために使用されます。
worddav24e21f44c149abcb3659dfd9d06e62aa
worddav642ced80a263561d8c469346025b4836
worddav94705c1fc94ecb5145b425d14f42afcf

頻繁に使用される記号のセットは多数ありますが、入力するのは非常に冗長です。(A | B | CZ)の代わりに [A-Z] を入力できる文字クラスがあります。ネガティブとポジティブの 2 種類があります。どちらも抽象 SymbolClassRegexp を拡張します。
worddav802b499d6a7435f7a89accadba5698b3
worddava11126417379b10b2d63b7b458305019

これらの文字クラスの多くは複数の場所で使用されているため、PredefinedSymbolClassRegexp を使用してより簡単に参照できます。[A-Z] の代わりに、「\ w」と書くことができます。
worddav133f90af8072c5fb9dfe57c7158c29a9

この概念は、次の方法で宣言されます。
worddav622ddc5598027e3c18f2fbd3f4bf8d24

ここには、参照ステレオタイプを持つ symbolClass リンク宣言があります(上記で説明した集約もリンクステレオタイプです)。参照ステレオタイプは、この概念のインスタンスには、参照ノードが子として含まれないことを意味します。代わりに、参照されるノードをモデル内の任意の場所に保存できます。
また、抽象概念 UnaryRegexp から派生した多くの異なる UnaryRegexps があります。+、*、その他の正規表現操作が含まれます。
worddav302fe26fe89dcd25ad86915228449271
worddav196774bebcf4832840530e8a598b777a

テキストで作業するとき、一致を覚えて、後で参照することがしばしば役立ちます。このタスクを容易にするために、一致する文字列を記憶する MatchParensRegexp と、以前に一致した文字列を参照する MatchVariableReferenceRegexp があります。次のコードは、同じ xml タグのペアとその中のテキストを照合します。
worddav480b88108c470d3412e1c3ca338bf95d

BaseLanguage 統合

正規表現は、BaseLanguage コードに統合できない場合、少し使い道があります。そのため、正規表現言語には、BaseLanguage で記述されたプログラムで正規表現関連の構造を記述できる特別な概念があります。
BaseLanguage に新しい構成要素を追加する場合、通常は BaseLanguage からまたはステートメントの概念を拡張します。コンセプトは、「1 + 2」、「a == b」などの式を表します。ステートメントコンセプトは、「if() {}」、「while() {}」などの制御構造を表します。正規表現言語では、新しい式とステートメントの両方を作成します。
最初にステートメントを見て、式を見てみましょう。
MatchRegexpStatement は、指定された文字列が正規表現と一致するかどうかを確認する場合に使用されます(このセクションの例は、jetbrains.mps.regexp.examples モデルの BaseLanguageIntegration クラスにあります)。

worddav7ca0d21a461360dc9e35c6d2732b1467

ここには興味深い機能があります。MatchRegexpStatement ブロックで名前付き一致を参照できます。これらの一致変数は、正規表現言語で定義されている他のステートメントで機能します。
FindMatchStatement は、指定された文字列に指定された正規表現の一致が含まれているかどうかを確認します。MatchRegexpStatement に似ています。
worddav49147cbc3846c222907f8ab55c1ed548

ForEachMatchStatement を使用すると、指定した文字列内の指定した正規表現のすべての一致を反復処理できます。
worddavb00b56a3a457bcfa3bd5c194d9f07a2a

文字列を操作するとき、正規表現のすべての一致を指定されたテキストで置き換えることがよくあります。正規表現言語では、ReplaceWithRegexpExpression を使用してこれを行うことができます。
worddav0324d85e995d77bce18cb1796cbb79a9

文字列を正規表現で分割することもしばしば実用的です。例:1 つ以上の空白記号で区切られた文字列の一部を抽出するには、この SplitExpression を記述できます。
worddav8693e20963fbd910cdc6f17e65500d23

ブロック内の一致を参照する場合、MatchVariableReference の概念が使用されます。また、コンセプトから派生しています。

ライブラリサポート

正規表現を使用する場合、それらのいくつかを多くの場所で使用したいと思います。これらの再利用可能な正規表現を定義するために、特別なコンセプト正規表現があります。0 個以上の名前付き正規表現が含まれています。

worddavb1831b4d7d4be636b5f539589a7abd8c

アクセサーリーモデル

多くの言語には、次の問題があります。非常によく似たエンティティが多数あり、この言語で作成された任意のモデルで使用できます(定義済みのシンボルクラスの正規表現など)。このようなエンティティごとにコンセプトを作成できます。しかし、MPS にはより優れたソリューションがあります。アクセサーリモデルと呼ばれる特別なモデルを作成し、言語でこれらのエンティティをすべて宣言できます。
シンボルクラスを宣言するために使用される PredefinedSymbolClass コンセプトがあります。また、これらのシンボルクラスを含む PredefinedSymbolClasses コンテナーコンセプトがあります。正規表現言語のアクセサーリモデルを見ると、次のように表示されます。

worddav26add762091c9a627c43395111298b8a

エディター

概念構造を定義した後、通常はそのためのエディターを作成します。このタスクを達成するために、エディター言語を使用します。使用するのは非常に簡単なので、最も一般的な構成要素を考えてみましょう。
すべてのエディター関連コードは、エディターモデルに配置されます。プロジェクトツリーの言語ノードにあります。

worddavc1136b6cec7f424fb0462579c99c0799

StringLiteralRegexp のエディターは次のとおりです。
worddav9af071cf04fc04a21f5fda7ba65302a4

これには、水平コレクション、その中の他の構成要素をグループ化するために使用できるコンテナー、およびインスタンスプロパティのエディターを含めるために使用される {text} が含まれています。
MatchVariableReferenceRegexp のエディターは次のとおりです。
worddavde451156ae66510919af6563b7264e39

また、水平方向のコレクションで構成されていますが、今回はその中に豊富な構造のセットがあります。「(ref」および「)」は定数であり、常に同じテキストが含まれます。「%match%-> {name}」は、一致リンクのターゲットのプロパティ「name」を参照するために使用されます。
正規表現のエディターは次のとおりです。
worddav79e6df4b7ef387a833fa176607b56cd2

ネストされた水平コレクションを持つ垂直コレクションが含まれます。また、「(>%regexp%<)」構成要素が含まれています。ロール「regexp」のすべてのノードのエディターを含めるために使用されます。

スコープ

構造体で参照を宣言した後、それらのデフォルトの代替メニューがあります。これらのデフォルトメニューには、現在のモデルとインポートされたすべてのモデルの参照型のすべてのノードが含まれます。時には機能しますが、時にはこれらのメニューの範囲を絞り込む必要があります(例:モデルのさまざまな部分に「名前」という名前の一致変数がたくさんある場合、これらの Java スコープ規則に従うことをお勧めしますこのタスクを処理するために、制約言語のスコープがあります。
スコープは、言語ノードの制約モデルに配置されます。

worddavaeb22e88a30788c4c39df5434c19093d

MatchVariableReference のスコープを考えてみましょう。
worddavc378d6ee860a2581a4e9dbdc23fae9d8

スコープは、リファレントセットハンドラー、スコープ条件( "can create" というラベル)、およびスコープコンストラクターで構成されます。通常、スコープコンストラクターのみが指定されます。スコープコンストラクターは、ISearchScope インターフェースを実装するオブジェクトを返す必要があります。通常、クラス SimpleSearchScope のインスタンスが返されます。ノードのリストを取得するコンストラクターがあります。つまり、指定された場所に表示されるノードのリストを返します。

アクション

MPS のデフォルトのエディターは、あまり使いやすくありません。このデフォルトの動作を改善するために、アクション言語やエディター言語とは異なる構文を使用できます。
テキストベースの言語でコードを入力する場合、通常は左から右に入力します。「2」から始めて「2+」と入力し、最後に「2+2」と入力します。「右変換」 と呼ばれるメカニズムを使用して、この方法で MPS にコードを入力することも可能です。
正しい変換アクションを定義するには、アクションモデルに正しい変換アクションルートを作成し、それに正しい変換アクションを追加する必要があります。ある正規表現を単項正規表現に変換する、つまり「a」を「a+」、「a*」などに変換する正規表現言語からの正しい変換アクションについて考えてみましょう (制約、エディター、構造など、プロジェクトツリーの言語ノードにアクションモデルがあります。):

worddavced409801e815e076620cb81669d28df

各適切な変換には、適用可能な概念(このアクションを適用できる概念の種類)があります。また、条件と最も重要な部分、つまり正しい変換メニューがあります。さまざまなタイプの適切な変換メニューがあります。上の図のメニューは、非抽象 UnaryRegexp サブコンセプトごとに 1 つのメニュー項目を追加します。このメニューパーツのハンドラーは、式を単項式に変換します。

型システム

多くの言語には型システムがあります。これを使用してモデルをチェックすることができ、編集エクスペリエンスの向上とジェネレーターの簡素化に使用できます。例:特定の式のタイプがわかっている場合は、それに適用できるメソッドを計算できます。MPS には、HELGINS と呼ばれる型システム用の特別な言語があります。非常に単純な構造の言語では、それなしで生活することも可能ですが、複雑な言語がある場合や、BaseLanguage と統合する場合は、少なくとも BaseLanguage 統合の概念のために型システムを作成する必要があります。
HELGINS では、タイプは MPS ノードとして表されます。そのため、BaseLanguage のように型のサブ言語がある場合、それを型チェックに使用できます。
正規表現言語のルールをいくつか考えてみましょう。

worddavfbd103e572784a29f920f075b0b64b55

このコードでは、文字列と呼ばれる型を定義します(文字列は BaseLanguage の ClassifierType のインスタンスで、メソッドパラメーターの型、ローカル変数、その他の場所で使用されます)。そのためには、GIVETYPE ステートメントを使用します。
より複雑なルールを見てみましょう。
worddav1a274d8e26272ef9d52b6c7f7458bfff

このルールでは、FindMatchStatement の正規表現と照合する表現が文字列タイプのサブタイプである必要があります。これを行うには、型方程式を指定します。記号「:<=:」はサブタイプを示します。expression TYPEOF は、括弧内の式のタイプを示します。
型を計算するために、HELGINS は高度なアルゴリズムを使用して時間を大幅に節約します。型が計算される順序を心配する必要はありません。しなければならないのは、入力規則で型方程式を指定することだけです。HELGINS はそれらをあなたのために解決します。
もちろん、私たちの言語のルールは非常にシンプルです。HELGINS についてさらに知りたい場合は、BaseLanguage やモデル言語などの言語のルールを調べる必要があります。

生成プログラム

MPS で作成されたほとんどすべての言語にはジェネレーターがあります。MPS のジェネレーターは、高レベル言語コードを低レベル言語のコードに変換します。ジェネレーターの重要なコンポーネントは、マッピング構成です。言語をどうするかを教えてくれます。
正規表現言語のマッピング構成を考えてみましょう。

worddavf77db0d024ca925203b56a14931eb487

これには、1 つのマッピングルールと複数の削減ルールが含まれます。各ルールには適用可能な概念があります。この概念の各インスタンスに対して、ルールが適用されます。マッピングルールは、各アプリケーションに新しいルートノードを作成します。削減ルールは、適用されたノードを新しいノードに置き換えます。各ルールには、出力ノードの作成に使用されるテンプレートが関連付けられています。
そのようなテンプレートのインスタンスを見てみましょう。
worddavc1fd76f76c7cffb01f1f0e716bd309a7

テンプレートには、マクロとテンプレートフラグメントを含む MPS コードが含まれています。
テンプレートフラグメント外のコードは生成中には使用されず、テンプレートフラグメント内のコードのコンテキストを作成するためにのみ使用されます。例:コードが node というパラメーターを持つメソッド内に配置されることがわかっている場合、そのようなパラメーターを持つテンプレートフラグメントの周囲にメソッドを作成できます。生成中に、MPS はインテンションを認識し、この変数は自動的に解決されます。
マクロは、コードの可変部分を指定するために使用されます。たとえば、上の図の変数マッチャーにはプロパティマクロがあります。このプロパティマクロはこの変数の一意の名前を生成するため、ネストされた一致ブロックを使用できます。MPS にはさまざまな種類のマクロがあります。さまざまな種類のノードマクロ、プロパティマクロ、参照マクロです。これらの概念はすべて、jetbrains.mps.TLBase 言語で宣言されています。

参考文献

正規表現言語を見てきました。多くの MPS 言語開発機能を使用しますが、もちろんすべてではありません。MPS の使用方法を学ぶ最良の方法は、ベース言語やブートストラップ言語などの別の言語を調べることです。MPS には、MPS の動作を理解するために使用できるツールがいくつかあります。
それらの 1 つは、使用箇所の検索です。エディターのポップアップメニューから使用箇所の検索を選択するか、エディター内のノードで Alt + F7 を押すことで呼び出すことができます。

worddav49f451933a7d937ef0e3fc0fdcb6f70f

2 つ目は、概念インスタンスの検索です。概念に出くわし、その使用方法がわからない場合、それを学習する最善の方法は、そのインスタンスを見つけて、それらのインスタンスが何をするかを理解することです。
worddav8b182bc2907aa0f4261f0c1176d60061

MPS ディストリビューションには、%MPS_HOME%/ help フォルダーにドキュメントシステムも含まれています。そのうちのいくつかは古く、一部はかなり不完全ですが、MPS の学習に使用できます。