MPS 2019.2ヘルプ

正規表現

正規表現言語の概要

導入

正規表現言語(テキスト操作を大幅に簡素化する言語)は、現在最も使用されているドメイン固有の言語の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に埋め込むために使用される概念が含まれます。例:MatchStatementReplaceStatement、およびSplitStatementが含まれます。

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

正規表現

言語のすべての正規表現の概念は、その構造モデルの「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の学習に使用できます。

最終更新日: 2019年8月30日