正規表現
正規表現言語の概要
導入
正規表現言語(テキスト操作を大幅に簡素化する言語)は、現在最も使用されているドメイン固有の言語の 1 つです。ほとんどすべての開発者が少なくとも 1 回は使用しています。Perl や Python などの一部の言語には、サポートが組み込まれています。Java などの一部は、ライブラリを介して使用します。MPS の実装に使用する言語である Java には、正規表現の言語レベルのサポートがないため、DSL を実装するのが自然であったため、正規表現ライブラリの代わりに DSL を使用できます。この言語は、MPS 言語の良い例です。この概要を読んで、MPS で言語を作成および使用する方法を理解できるようになります。
サンプル
MPS が既にインストールされていると想定しています。
このドキュメントでは多くの例を使用しています。jetbrains.mps.regexp.examples ソリューションの正規表現言語プロジェクト(%MPS_HOME%/ platform / regexp)で見つけることができます。
言語の概要
簡単な正規表現アプリケーションを見てみましょう。メールアドレスからユーザー名とドメイン名を取得したいとします。正規表現を使用してメールアドレスを分析することにより、ユーザー名とドメイン名を出力するコードを次に示します(この例は EmailExample クラスにあります)。
この match regexp ステートメントで使用される正規表現は、次のことを行います。最初に、1 つ以上の単語文字(\ w +)を読み取り、「ユーザー」変数に保存します。その後、「@」文字を読み取ります。次に、ピリオド(「.」文字)で区切られた単語のリストを読み取り、ドメイン変数(\ w +(。\ w +))に保存します。一致が見つかった場合、プログラムはユーザーとドメインを System.out に出力します。
この例の正規表現の構文ツリーは次のとおりです。
言語構造
MPS で言語を作成するとき、通常、その抽象構文を定義することから始めます。MPS の抽象構文は、言語構造と呼ばれます。これを行うには、構造言語を使用します。構造言語は、XML 言語に対応する XML スキーマ、または SQL に対応する DDL です。正規表現の言語構造を見てみましょう。
概要
MPS 正規表現言語には、いくつかの部分が含まれています。
正規表現:正規表現を指定するために使用される概念。これらには、文字列リテラル、シンボルクラス、「or」および「sequence」正規表現の概念が含まれます。
BaseLanguage(BaseLanguage は、ジェネレーターのターゲット言語として MPS によって内部的に使用される Java に似た言語です)統合:この部分には、正規表現関連のコードを BaseLanguage に埋め込むために使用される概念が含まれます。例:MatchStatement、ReplaceStatement、SplitStatement が含まれます。
正規表現ライブラリのサポート。正規表現で作業するとき、再利用したいため、このタスクのための特別な概念を作成しました。
正規表現
言語のすべての正規表現の概念は、その構造モデルの「Regexp」フォルダーに配置されます。
それらを詳細に検討しましょう。それらすべてに単一の基本概念があります:正規表現:
BaseConcept コンセプトから派生しています。すべての MPS の概念は、そこから派生しています。この概念には抽象概念プロパティもあります。つまり、言語で正規表現を定義するために使用されるのではなく、概念階層を形成するために作成されます。これは、Java クラスの「抽象」修飾子に似ています。
それから派生した概念を考えてみましょう。階層ビューで見ることができます。このビューを表示するには、コンセプト宣言で Ctrl + H を押します。正規表現コンセプトの場合、次のように表示されます。
StringRegexp は、テキストと照合できる任意の文字列を表します(このセクションで検討する正規表現の例はすべて、正規表現ルートノードにあります)。
その概念宣言を見てみましょう(概念のインスタンスがエディターで選択されているときに Ctrl + Shift + S を押すと、概念宣言にすばやく移動できます)。
その宣言には、エディターに表示されるテキストを保存するために使用される文字列型のプロパティテキストがあります。また、このコンセプトは、コンセプトプロパティ「エイリアス」を宣言します。コンセプトプロパティは、単純なプロパティとは異なります。単純なプロパティは Java インスタンスフィールドに対応し、コンセプトプロパティは Java 静的フィールドに対応します。Ctrl + Space を押すと、コンセプトプロパティエイリアスの値が補完メニューに表示されます。
バイナリ正規表現は、2 つの異なる正規表現を 1 つに結合した正規表現を表すために作成されます。BinaryRegexp コンセプトは抽象として宣言され、2 つの具体的なサブコンセプト OrRegexp と SeqRegexp があります。インスタンスの例を次に示します。
その概念宣言は次のとおりです。
2 つのリンクを定義します。1 つは左の部分を保存し、もう 1 つは右の部分を保存します。「集約」という言葉は、このリンクの正規表現が宣言された概念インスタンスの一部になることを意味します。つまり、構文ツリーを見ると、親 BinaryRegexp に子の正規表現が表示されます:
ドット regexp は、任意の文字に一致する正規表現を表します。LineEndRegexp は、行末でのみ一致します。LineStartRegexp は、行の先頭でのみ一致します。ParensRegexp は、囲む正規表現を読みやすくするために、他の正規表現をグループ化するために使用されます。
頻繁に使用される記号のセットは多数ありますが、入力するのは非常に冗長です。(A | B | CZ)の代わりに [A-Z] を入力できる文字クラスがあります。ネガティブとポジティブの 2 種類があります。どちらも抽象 SymbolClassRegexp を拡張します。
これらの文字クラスの多くは複数の場所で使用されているため、PredefinedSymbolClassRegexp を使用してより簡単に参照できます。[A-Z] の代わりに、「\ w」と書くことができます。
この概念は、次の方法で宣言されます。
ここには、参照ステレオタイプを持つ symbolClass リンク宣言があります(上記で説明した集約もリンクステレオタイプです)。参照ステレオタイプは、この概念のインスタンスには、参照ノードが子として含まれないことを意味します。代わりに、参照されるノードをモデル内の任意の場所に保存できます。
また、抽象概念 UnaryRegexp から派生した多くの異なる UnaryRegexps があります。+、*、その他の正規表現操作が含まれます。
テキストで作業するとき、一致を覚えて、後で参照することがしばしば役立ちます。このタスクを容易にするために、一致する文字列を記憶する MatchParensRegexp と、以前に一致した文字列を参照する MatchVariableReferenceRegexp があります。次のコードは、同じ xml タグのペアとその中のテキストを照合します。
BaseLanguage 統合
正規表現は、BaseLanguage コードに統合できない場合、少し使い道があります。そのため、正規表現言語には、BaseLanguage で記述されたプログラムで正規表現関連の構造を記述できる特別な概念があります。
BaseLanguage に新しい構成要素を追加する場合、通常は BaseLanguage から式またはステートメントの概念を拡張します。式コンセプトは、「1 + 2」、「a == b」などの式を表します。ステートメントコンセプトは、「if() {}」、「while() {}」などの制御構造を表します。正規表現言語では、新しい式とステートメントの両方を作成します。
最初にステートメントを見て、式を見てみましょう。
MatchRegexpStatement は、指定された文字列が正規表現と一致するかどうかを確認する場合に使用されます(このセクションの例は、jetbrains.mps.regexp.examples モデルの BaseLanguageIntegration クラスにあります)。
ここには興味深い機能があります。MatchRegexpStatement ブロックで名前付き一致を参照できます。これらの一致変数は、正規表現言語で定義されている他のステートメントで機能します。
FindMatchStatement は、指定された文字列に指定された正規表現の一致が含まれているかどうかを確認します。MatchRegexpStatement に似ています。
ForEachMatchStatement を使用すると、指定した文字列内の指定した正規表現のすべての一致を反復処理できます。
文字列を操作するとき、正規表現のすべての一致を指定されたテキストで置き換えることがよくあります。正規表現言語では、ReplaceWithRegexpExpression を使用してこれを行うことができます。
文字列を正規表現で分割することもしばしば実用的です。例:1 つ以上の空白記号で区切られた文字列の一部を抽出するには、この SplitExpression を記述できます。
ブロック内の一致を参照する場合、MatchVariableReference の概念が使用されます。また、式コンセプトから派生しています。
ライブラリサポート
正規表現を使用する場合、それらのいくつかを多くの場所で使用したいと思います。これらの再利用可能な正規表現を定義するために、特別なコンセプト正規表現があります。0 個以上の名前付き正規表現が含まれています。
アクセサーリーモデル
多くの言語には、次の問題があります。非常によく似たエンティティが多数あり、この言語で作成された任意のモデルで使用できます(定義済みのシンボルクラスの正規表現など)。このようなエンティティごとにコンセプトを作成できます。しかし、MPS にはより優れたソリューションがあります。アクセサーリモデルと呼ばれる特別なモデルを作成し、言語でこれらのエンティティをすべて宣言できます。
シンボルクラスを宣言するために使用される PredefinedSymbolClass コンセプトがあります。また、これらのシンボルクラスを含む PredefinedSymbolClasses コンテナーコンセプトがあります。正規表現言語のアクセサーリモデルを見ると、次のように表示されます。
エディター
概念構造を定義した後、通常はそのためのエディターを作成します。このタスクを達成するために、エディター言語を使用します。使用するのは非常に簡単なので、最も一般的な構成要素を考えてみましょう。
すべてのエディター関連コードは、エディターモデルに配置されます。プロジェクトツリーの言語ノードにあります。
StringLiteralRegexp のエディターは次のとおりです。
これには、水平コレクション、その中の他の構成要素をグループ化するために使用できるコンテナー、およびインスタンスプロパティのエディターを含めるために使用される {text} が含まれています。
MatchVariableReferenceRegexp のエディターは次のとおりです。
また、水平方向のコレクションで構成されていますが、今回はその中に豊富な構造のセットがあります。「(ref」および「)」は定数であり、常に同じテキストが含まれます。「%match%-> {name}」は、一致リンクのターゲットのプロパティ「name」を参照するために使用されます。
正規表現のエディターは次のとおりです。
ネストされた水平コレクションを持つ垂直コレクションが含まれます。また、「(>%regexp%<)」構成要素が含まれています。ロール「regexp」のすべてのノードのエディターを含めるために使用されます。
スコープ
構造体で参照を宣言した後、それらのデフォルトの代替メニューがあります。これらのデフォルトメニューには、現在のモデルとインポートされたすべてのモデルの参照型のすべてのノードが含まれます。時には機能しますが、時にはこれらのメニューの範囲を絞り込む必要があります(例:モデルのさまざまな部分に「名前」という名前の一致変数がたくさんある場合、これらの Java スコープ規則に従うことをお勧めしますこのタスクを処理するために、制約言語のスコープがあります。
スコープは、言語ノードの制約モデルに配置されます。
MatchVariableReference のスコープを考えてみましょう。
スコープは、リファレントセットハンドラー、スコープ条件( "can create" というラベル)、およびスコープコンストラクターで構成されます。通常、スコープコンストラクターのみが指定されます。スコープコンストラクターは、ISearchScope インターフェースを実装するオブジェクトを返す必要があります。通常、クラス SimpleSearchScope のインスタンスが返されます。ノードのリストを取得するコンストラクターがあります。つまり、指定された場所に表示されるノードのリストを返します。
アクション
MPS のデフォルトのエディターは、あまり使いやすくありません。このデフォルトの動作を改善するために、アクション言語やエディター言語とは異なる構文を使用できます。
テキストベースの言語でコードを入力する場合、通常は左から右に入力します。「2」から始めて「2+」と入力し、最後に「2+2」と入力します。「右変換」 と呼ばれるメカニズムを使用して、この方法で MPS にコードを入力することも可能です。
正しい変換アクションを定義するには、アクションモデルに正しい変換アクションルートを作成し、それに正しい変換アクションを追加する必要があります。ある正規表現を単項正規表現に変換する、つまり「a」を「a+」、「a*」などに変換する正規表現言語からの正しい変換アクションについて考えてみましょう (制約、エディター、構造など、プロジェクトツリーの言語ノードにアクションモデルがあります。):
各適切な変換には、適用可能な概念(このアクションを適用できる概念の種類)があります。また、条件と最も重要な部分、つまり正しい変換メニューがあります。さまざまなタイプの適切な変換メニューがあります。上の図のメニューは、非抽象 UnaryRegexp サブコンセプトごとに 1 つのメニュー項目を追加します。このメニューパーツのハンドラーは、式を単項式に変換します。
型システム
多くの言語には型システムがあります。これを使用してモデルをチェックすることができ、編集エクスペリエンスの向上とジェネレーターの簡素化に使用できます。例:特定の式のタイプがわかっている場合は、それに適用できるメソッドを計算できます。MPS には、HELGINS と呼ばれる型システム用の特別な言語があります。非常に単純な構造の言語では、それなしで生活することも可能ですが、複雑な言語がある場合や、BaseLanguage と統合する場合は、少なくとも BaseLanguage 統合の概念のために型システムを作成する必要があります。
HELGINS では、タイプは MPS ノードとして表されます。そのため、BaseLanguage のように型のサブ言語がある場合、それを型チェックに使用できます。
正規表現言語のルールをいくつか考えてみましょう。
このコードでは、文字列と呼ばれる型を定義します(文字列は BaseLanguage の ClassifierType のインスタンスで、メソッドパラメーターの型、ローカル変数、その他の場所で使用されます)。そのためには、GIVETYPE ステートメントを使用します。
より複雑なルールを見てみましょう。
このルールでは、FindMatchStatement の正規表現と照合する表現が文字列タイプのサブタイプである必要があります。これを行うには、型方程式を指定します。記号「:<=:」はサブタイプを示します。expression TYPEOF は、括弧内の式のタイプを示します。
型を計算するために、HELGINS は高度なアルゴリズムを使用して時間を大幅に節約します。型が計算される順序を心配する必要はありません。しなければならないのは、入力規則で型方程式を指定することだけです。HELGINS はそれらをあなたのために解決します。
もちろん、私たちの言語のルールは非常にシンプルです。HELGINS についてさらに知りたい場合は、BaseLanguage やモデル言語などの言語のルールを調べる必要があります。
生成プログラム
MPS で作成されたほとんどすべての言語にはジェネレーターがあります。MPS のジェネレーターは、高レベル言語コードを低レベル言語のコードに変換します。ジェネレーターの重要なコンポーネントは、マッピング構成です。言語をどうするかを教えてくれます。
正規表現言語のマッピング構成を考えてみましょう。
これには、1 つのマッピングルールと複数の削減ルールが含まれます。各ルールには適用可能な概念があります。この概念の各インスタンスに対して、ルールが適用されます。マッピングルールは、各アプリケーションに新しいルートノードを作成します。削減ルールは、適用されたノードを新しいノードに置き換えます。各ルールには、出力ノードの作成に使用されるテンプレートが関連付けられています。
そのようなテンプレートのインスタンスを見てみましょう。
テンプレートには、マクロとテンプレートフラグメントを含む MPS コードが含まれています。
テンプレートフラグメント外のコードは生成中には使用されず、テンプレートフラグメント内のコードのコンテキストを作成するためにのみ使用されます。例:コードが node というパラメーターを持つメソッド内に配置されることがわかっている場合、そのようなパラメーターを持つテンプレートフラグメントの周囲にメソッドを作成できます。生成中に、MPS はインテンションを認識し、この変数は自動的に解決されます。
マクロは、コードの可変部分を指定するために使用されます。たとえば、上の図の変数マッチャーにはプロパティマクロがあります。このプロパティマクロはこの変数の一意の名前を生成するため、ネストされた一致ブロックを使用できます。MPS にはさまざまな種類のマクロがあります。さまざまな種類のノードマクロ、プロパティマクロ、参照マクロです。これらの概念はすべて、jetbrains.mps.TLBase 言語で宣言されています。
参考文献
正規表現言語を見てきました。多くの MPS 言語開発機能を使用しますが、もちろんすべてではありません。MPS の使用方法を学ぶ最良の方法は、ベース言語やブートストラップ言語などの別の言語を調べることです。MPS には、MPS の動作を理解するために使用できるツールがいくつかあります。
それらの 1 つは、使用箇所の検索です。エディターのポップアップメニューから使用箇所の検索を選択するか、エディター内のノードで Alt + F7 を押すことで呼び出すことができます。
2 つ目は、概念インスタンスの検索です。概念に出くわし、その使用方法がわからない場合、それを学習する最善の方法は、そのインスタンスを見つけて、それらのインスタンスが何をするかを理解することです。
MPS ディストリビューションには、%MPS_HOME%/ help フォルダーにドキュメントシステムも含まれています。そのうちのいくつかは古く、一部はかなり不完全ですが、MPS の学習に使用できます。
関連ページ:

カスタム持続クックブック
このドキュメントでは、MPSにバンドルされているxmlPersistenceサンプルを使用して、独自の永続化形式を定義、デプロイ、使用する方法を説明します。カスタム永続性とは何ですか? :MPS は通常、モデルを独自の XML ベースの形式で保存します。ただし、独自の形式でモデルファイルをロードまた...

データフロー
このクックブックでは、あなたの言語用のデータフローを設計する際の素早い答えとガイドラインを提供するべきです。型システムの詳細な説明については、ユーザーガイドのデータフローセクションを参照してください。値を読む読み取り操作は、特定の値が読み取られることをデータフローエンジンに指示します。data fl...