正規表現
正規表現言語の概要
導入
正規表現言語(テキスト操作を大幅に簡素化する言語)は、現在最も使用されているドメイン固有の言語の 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+)) を読み取り、「user」変数に保存します。その後、「@」文字を読み取ります。次に、ピリオド (「.」文字) で区切られた単語のリストを読み取り、それをドメイン変数 (\w+(.\w+)).) に保存します。一致が見つかった場合、プログラムは user と domain を System.out に出力します。
この例の正規表現の構文ツリーは次のとおりです。
言語構造
MPS で言語を作成するとき、通常、その抽象構文を定義することから始めます。MPS の抽象構文は、言語構造と呼ばれます。これを行うには、構造言語を使用します。構造言語は、XML 言語に対応する XML スキーマ、または SQL に対応する DDL です。正規表現の言語構造を見てみましょう。
概要
MPS 正規表現言語には、いくつかの部分が含まれています。
正規表現: 正規表現を指定するために使用される概念。これらには、文字列リテラル、シンボルクラス、「or」および「sequence」正規表現の概念が含まれます。
BaseLanguage(BaseLanguage は Java に似た言語であり、ジェネレーターのターゲット言語として MPS によって内部的に使用されます)統合: この部分には、正規表現関連のコードを BaseLanguage に埋め込むために使用される概念が含まれます。例: それは MatchStatement、ReplaceStatement、および SplitStatement が含まれています。
正規表現ライブラリのサポート。正規表現で作業するとき、再利用したいため、このタスクのための特別な概念を作成しました。
正規表現
言語のすべての正規表現の概念は、その構造モデルの「Regexp」フォルダーに配置されます。
それらを詳細に検討しましょう。それらすべてに単一の基本概念があります: 正規表現:
これは、BaseConcept の概念から派生しています。すべての MPS の概念はそれから派生しています。この概念には、抽象概念プロパティもあります。これは、正規表現を定義するための言語では使用されない、概念階層を形成するために作成されることを意味します。これは、Java クラスの「abstract」修飾子に似ています。
それから派生した概念を考えてみましょう。それらは階層ビューで確認できます。コンセプト宣言で Ctrl + H を押すと、このビューを表示できます。正規表現の概念については、次のようになります。
StringRegexp は、テキストと照合できる任意の文字列を表します(このセクションで検討する正規表現のすべての例は、Regexps ルートノードにあります)。
その概念宣言を見てみましょう(概念のインスタンスがエディターで選択されているときに Ctrl + Shift + S を押すと、概念宣言にすばやく移動できます)。
宣言には、文字列型のプロパティ text があり、これはエディターに表示されるテキストを格納するために使用されます。また、この概念は概念プロパティ「alias」を宣言します。概念プロパティは単純なプロパティとは異なります。単純なプロパティは Java インスタンスフィールドに対応し、概念プロパティは Java 静的フィールドに対応します。概念プロパティ alias の値は、Ctrl + Space を押すと補完メニューに表示されます。
バイナリ正規表現は、2 つの異なる正規表現を 1 つに結合する正規表現を表すために作成されます。BinaryRegexp の概念は抽象として宣言されており、OrRegexp と SeqRegexp の 2 つの具体的なサブ概念があります。それらのインスタンスの例を次に示します。
その概念宣言は次のとおりです。
2 つのリンクを定義します。1 つは左側の部分を格納するためのもので、もう 1 つは右側の部分を格納するためのものです。「集約」という言葉は、このリンクの正規表現が宣言された概念インスタンスの一部になることを意味します。つまり、構文ツリーを見ると、親 BinaryRegexp の下に子正規表現があります。
ドット正規表現は、任意の文字に一致する正規表現を表します。LineEndRegexp は、行の終わりでのみ一致します。LineStartRegexp は、行の先頭でのみ一致します。ParensRegexp は、囲んでいる正規表現を読みやすくするために、他の正規表現をグループ化するために使用されます。
よく使われる記号のセットはたくさんありますが、入力するにはかなり冗長です。そこで、(A|B|CZ) の代わりに [A-Z] を入力できるようにする文字クラスがあります。文字クラスには、負と正の 2 種類があります。どちらも抽象 SymbolClassRegexp を拡張します。
これらの文字クラスの多くは複数の場所で使用されているため、PredefinedSymbolClassRegexp を使用すると、より簡単に参照できます。[A-Z] の代わりに「\w」と記述できます。
この概念は、次の方法で宣言されます。
ここに、参照ステレオタイプを持つ symbolClass リンク宣言があります(前述の集約もリンクステレオタイプです)。参照ステレオタイプとは、この概念のインスタンスに、参照されるノードが子として含まれないことを意味します。代わりに、参照されるノードはモデルの任意の場所に保存できます。また、抽象的な概念 UnaryRegexp から派生したさまざまな UnaryRegexp があります。+、* およびその他の正規表現操作が含まれます。
テキストを操作するときは、一致するものを覚えて、後で参照すると便利なことがよくあります。この作業を容易にするために、言及する前にマッチした文字列こと、それが一致する文字列を覚えて MatchParensRegexp、および MatchVariableReferenceRegexp を持っています。次のコードは、同じ xml タグのペアとその中にテキストが含まれているものと一致します。
BaseLanguage 統合
正規表現は、BaseLanguage コードに統合できない場合、少し役に立ちます。正規表現言語には、BaseLanguage で記述されたプログラムで正規表現関連の構造を記述できるようにする特別な概念があります。
BaseLanguage に新しい構造を追加する場合は、通常、BaseLanguage から Expression または Statement の概念を拡張します。Expression の概念は、「1+2」、「a == b」などの式を表します。Statement の概念は、「if() { }」、「while() { }」などの制御構造を表します。正規表現言語では、新しい式とステートメントの両方を作成します。
まず、式ではなくステートメントを見てみましょう。
MatchRegexpStatement は、指定された文字列が正規表現と一致するかどうかを確認する場合に使用されます(このセクションの例は、jetbrains.mps.regexp.examples モデルの BaseLanguageIntegration クラスにあります)。
ここには興味深い機能があります。MatchRegexpStatement ブロックで名前付き一致を参照できます。これらの一致変数は、正規表現言語で定義されている他のステートメントで機能します。
FindMatchStatement は、指定された文字列に指定された正規表現との一致が含まれているかどうかを確認します。これは MatchRegexpStatement に似ています。
ForEachMatchStatement を使用すると、指定された文字列内の指定された正規表現のすべての一致を反復処理できます。
文字列を操作する場合、正規表現のすべての一致を指定されたテキストに置き換えたいことがよくあります。正規表現言語では、ReplaceWithRegexpExpression を使用してこれを行うことができます。
文字列を正規表現で分割することも実用的です。例: 1 つ以上の空白記号で区切られた文字列の部分を抽出するには、次の SplitExpression を記述できます。
ブロック内の一致を参照する場合、MatchVariableReference の概念が使用されます。また、式の概念から派生しています。
ライブラリサポート
正規表現を扱うときは、その一部をさまざまな場所で使用したいと考えています。これらの再利用可能な正規表現を定義するために、特別な概念である Regexps があります。これには、0 個以上の名前付き正規表現が含まれています。
アクセサーリーモデル
多くの言語では、次の問題があります。非常によく似たエンティティがたくさんあり、この言語で記述されたすべてのモデルで使用できます(事前定義されたシンボルクラスの正規表現など)。そのようなすべてのエンティティの概念を作成できます。ただし、MPS にはより優れたソリューションがあります。アクセサーリモデルと呼ばれる特別なモデルを作成し、その中のすべてのエンティティを言語で宣言できます。
シンボルクラスを宣言するために使用される PredefinedSymbolClass の概念があります。また、これらのシンボルクラスを含む PredefinedSymbolClasses コンテナーの概念があります。正規表現言語のアクセサーリモデルを調べると、次のことがわかります。
エディター
概念構造を定義した後、通常はそのためのエディターを作成します。このタスクを実行するために、エディター言語を使用します。使い方はとても簡単なので、最も一般的な構成を考えてみましょう。
すべてのエディター関連のコードは、エディターモデルに配置されます。プロジェクトツリーの言語ノードにあります。
これが StringLiteralRegexp のエディターです:
これには、水平コレクション、その中に他の構成をグループ化するために使用できるコンテナー、インスタンスプロパティのエディターを含めるために使用される {text} が含まれています。
これが MatchVariableReferenceRegexp のエディターです:
これも水平コレクションで構成されていますが、今回は内部に豊富な構成要素のセットがあります。「(ref)」と「)」は定数であり、常に同じテキストが含まれます。「%match%->{name}」は、一致リンクのターゲットのプロパティ「name」を参照するために使用されます。
これが正規表現のエディターです:
これには、ネストされた水平コレクションを含む垂直コレクションが含まれます。また、"(> %Getting_started.xmlregexp% <)" 構造も含まれます。これは、ロール "regexp" のすべてのノードのエディターを含めるために使用されます。
スコープ
構造内で参照を宣言すると、それらに対するデフォルトの代替メニューができます。これらのデフォルトメニューには、現在のモデルとそのすべてのインポートされたモデル内の参照型のすべてのノードが含まれます。これが機能する場合もありますが、これらのメニューのスコープを絞り込む必要がある場合もあります (例: モデルのさまざまな部分に「name」という名前の一致変数が多数ある場合、これらの変数の Java スコープルールに従うことをお勧めします)。このタスクを処理するために、制約言語のスコープがあります。
スコープは、言語ノードの制約モデルに配置されます。
MatchVariableReference のスコープを考えてみましょう:
スコープは、参照セットハンドラー、スコープ条件 (「作成可能」というラベルが付けられている)、およびスコープコンストラクターで構成されます。通常、スコープコンストラクターのみが指定されます。スコープコンストラクターは、ISearchScope インターフェースを実装するオブジェクトを返す必要があります。通常、クラス SimpleSearchScope のインスタンスが返されます。このインスタンスには、ノードのリストを受け取るコンストラクターがあり、つまり、指定された場所で表示されるノードのリストを返します。
アクション
MPS のデフォルトのエディターは、非常に使いやすいものではありません。このデフォルトの動作を改善するために、アクション言語およびエディター言語とは異なる構成を使用できます。
テキストベースの言語でコードを入力する場合、通常は左から右に入力します。「2」から始めて、「2+」と入力し、最後に「2+2」と入力します。「右変換」と呼ばれるメカニズムを使用して、MPS でコードをこのように入力することもできます。
右変換アクションを定義するには、アクションモデルに右変換アクションルートを作成し、それにいくつかの右変換アクションを追加する必要があります。正規表現言語からの右変換アクションを考えてみましょう。このアクションは、1 つの正規表現を単項正規表現に変換します。つまり、"a" を "a+","a*" などに変換します (制約、エディター、構造と同様に、アクションモデルはプロジェクトツリーの言語ノードにあります)。
各権利変換には、適用可能な概念があります。このアクションを適用できる概念のタイプです。また、条件と最も重要な部分があります。それは、正しい変換メニューです。右変換メニューにはさまざまな種類があります。上の図のメニューは、非抽象 UnaryRegexp サブコンセプトごとに 1 つのメニュー項目を追加します。このメニュー部分のハンドラーは、式を単項式に変換します。
型システム
多くの言語には型システムがあります。モデルをモデルと照合することができ、編集エクスペリエンスを向上させ、ジェネレーターを簡素化するために使用できます。例: 特定の式のタイプがわかっている場合、どのメソッドをその式に適用できるかを計算できます。MPS には、HELGINS と呼ばれる型システム用の特別な言語があります。非常に単純な構造の言語では、それなしで生活することは可能ですが、複雑な言語がある場合、BaseLanguage と統合したい場合は、少なくとも BaseLanguage 統合の概念のために型システムを作成する必要があります。
HELGINS では、タイプは MPS ノードとして表されます。BaseLanguage のように型のサブ言語がある場合は、それを型チェックに使用できます。
正規表現言語のいくつかのルールを考えてみましょう。
このコードでは、文字列と呼ばれる型を定義します(文字列は、ここではメソッドのパラメーターの型、ローカル変数や他の場所で使用される BaseLanguage、から ClassifierType のインスタンスです)。そのために、GIVETYPE ステートメントを使用します。
より複雑なルールを見てみましょう。
このルールでは、FindMatchStatement で正規表現と照合する式が String 型のサブタイプである必要があります。これは、型方程式を指定して行います。記号 ":<=:" はサブタイプを示し、式 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 ベースの形式で保存します。ただし、モデルファイルを独自の形式でロードまたは保存したい場合があります。スタブモデルこれらは読み取り専用モデルであり、通常はライブラリコードを表します。たとえば、MPS を利用して既存の言語の 1 つを記述したいとします。たとえば、BaseLa...
データフロー
このクックブックでは、あなたの言語用のデータフローを設計する際の素早い答えとガイドラインを提供するべきです。型システムの詳細な説明については、ユーザーガイドのデータフローセクションを参照してください。値を読む:読み取り操作は、特定の値が読み取られることをデータフローエンジンに指示します。data flow builder for LocalVariableReference { (node)->void { if (node.isVariableDefinedInThisMethod()