正規表現ライブラリ lei_jregex ドキュメント

1.0

2005年4月17日修正
2003年9月10日修正
2001年12月20日作成

Copyright © 2001-2005 LangEdge, Inc. All rights reserved.


目次

はじめに
ソースファイル構成
ビルド方法
ヘッダーファイル
名前空間
クラス・関数
日本語対応
簡単な使用例
パターンの仕様
正規表現の仕様
制限事項
ライセンス


はじめに

lei_jregex.dll (lei_jregex.so) は、有限会社ラング・エッジの作成した 正規表現ライブラリです。 Windows の動的リンクライブラリ(.dll) または Unixなどの共有ライブラリ (.so) の形式をとっています。 本ライブラリは、本稿末尾に記載したライセンス条件に従うかぎり、目的のいかんにかかわらず、 無料で使用したり再配布したりすることができます。

ShiftJIS および EUC コードを用いた日本語文字列に対応しています。 ShiftJIS の場合は、いわゆる半角カタカナも認識します。

正規表現エンジンの形態としては、非決定性有限オートマトン (NFA) 型です。 正規表現を NFA に変換した後、直接それを解釈しながら実行します。DFA 型に比べると、 実行速度では少々劣りますが、使用するメモリ量を抑えることができます。 また、基本的にバックトラックを行わないので、 ほとんどのパターンに対して安定した速度で動作します。

メタ文字を含まない単純なパターン (以降、キーワードと呼びます) の場合は、 Boyer-Moor-Horspool (BMH) アルゴリズムを用いて、高速に検索を行います。また、 キーワードを "|" で結合したパターン (複数キーワードパターン) の場合は、 Aho-Corasick (AC) + BMH アルゴリズムを用いた高速な検索が可能です。


ソースファイル構成

パッケージのルートディレクトリ (たとえば lei_libs_2/) を、以降、 $LEI_LIBS_ROOT と表記することにします。

$LEI_LIBS_ROOT の下には、以下のようなサブディレクトリが存在します。

他にユニットテストのフレームワークである cppunit も存在します。


ビルド方法

まず、boost ライブラリ をインストールしてください。インストールした boost ライブラリのルートディレクトリ (たとえば、boost_1_30_0) をインクルードパスに含めます。 VisualStudio なら、[ツール]-[オプション]-[ディレクトリ]で設定できます。

Windowsでのビルド

VisualStudio で、jregex\dll\jregex_dll.sln (または .dsw) を開き、ビルドしてください。 ライブラリは、build\win_xxxxx\bin\lei_jregex.dll に作成されます。 ("xxxxx" は、"debug" または "release")

UNIXでのビルド

unix上でビルドするためには、GNUMake が必要です。"which gmake" あるいは "which gnumake" を実行してプログラムが見つかれば、OK です。

カレントディレクトリを jregex に移動し、 ../unitmake release を実行します。

  $ cd jregex
  $ ../unitmake release

ライブラリは、build/Release/bin/lei_jregex.so に作成されます。

スタティックライブラリ

DLL/SO をビルドするときに、スタティックライブラリ (.lib または .a) も作成されます。ファイルは、それぞれ、

Windows …… build\win_xxxxx\lib\lei_jregex.lib
UNIXなど …… build/Release/lib/lei_jregex.a

となります。


ヘッダーファイル

本ライブラリを使用するには、$LEI_LIBS_ROOT をインクルードパスに含めた上で、 langedge/jre_util.hpp というヘッダーファイルをインクルードします。 (これはさらにいくつか langedge/*.hpp をインクルードします)

DLLではなく、スタティックな正規表現ライブラリを使用する場合は、 jre_util.hpp をインクルードする前に USE_LEI_JREGEX_STATIC_LIBRARY マクロを定義しておく必要があります。


名前空間

本ライブラリの提供するクラスや関数は、すべて、 langedge::jregex という名前空間の中で定義されています。 (DLLからエクスポートされている関数 CreateInstance() および SetDefaultCharcode() を除く)


クラス・関数

lei_jregex.dll は、以下のクラスや関数を提供しています。
langedge::jregex::JRegexIF 正規表現の実装クラスへのインタフェース
langedge::jregex::JREMatchRange パターンにマッチした文字列の範囲情報を保持しているクラス
CreateInstance() 正規表現インスタンス(オブジェクト)を生成して、そのインタフェースポインタを返すファクトリ関数 (DLL からエクスポートされる)。後述の JRegexLoader のメンバ関数からも呼び出せる。
SetDefaultCharcode() 正規表現オブジェクトを生成する際に 文字コードフラグの指定が無かった場合のデフォルトの文字コードを設定する (DLL からエクスポートされる)。後述の JRegexLoader のメンバ関数からも呼び出せる。

また、jre_util.hpp は、以下のクラスや関数を提供しています。
langedge::jregex::JRegexLoader 正規表現DLLのローダークラス。 DLLからエクスポートされている関数を呼び出すメンバ関数を定義している。
langedge::jregex::TheJRegexLoader グローバルな正規表現DLLローダーオブジェクトを生成するクラス。 同一の正規表現DLLローダーオブジェクトを返す関数 TheJRegexLoader::getLoader() を定義する。
langedge::jregex::JRECache パターンと、それをコンパイルした正規表現オブジェクトとのペアを保存しておくクラス。 正規表現キャッシュ。 後で同じパターンが呼ばれたとき、再コンパイルせずに前回使用したオブジェクトを返す
langedge::jregex::JREMatcher 正規表現キャッシュをラップする。パターンマッチを実行した結果として、 ( ) で指定されるサブパターンにマッチした部分文字列を保持しているクラス
langedge::jregex::TheJRECache グローバルな正規表現キャッシュオブジェクトを生成するクラス。 同一の正規表現キャッシュオブジェクトを返す関数 TheJRECache::getCache() を定義する。
langedge::jregex::JRegMatch グローバルな正規表現キャッシュオブジェクトを利用することにより、 オブジェクト操作を背後に隠して正規表現マッチングを行うことができる関数
langedge::jregex::JRegSubst JRegMatch と同様だが、正規表現置換を行うことができる関数

単純なプログラムで正規表現のマッチングや置換を行いたいなら、グローバル関数である langedge::jregex::JRegMatch()langedge::jregex::JRegSubst() あるいは langedge::jregex::JREMatcher を使用すると良いでしょう。

速度が要求されたり、もう少し複雑な処理をする場合は langedge::jregex::JRECache (あるいはグローバルな langedge::jregex::TheJRECache) を使うとよいでしょう。

langedge::jregex::JRECache や 正規表現オブジェクト (langedge::jregex::JRegexIF の実装) は複数スレッドによる共有をサポートしていません。マルチスレッド環境で使用する場合は、 それらのオブジェクトを単一のスレッドのみで使用するように注意してください。 (別スレッドでは別のオブジェクトを生成して使用する)


日本語対応

本ライブラリは、ShiftJIS および EUC 文字コードを用いた日本語文字列に対応しています。 たとえば、正規表現の . は日本語の1文字にマッチします。あるいは、 ?+ などのクロージャの直前に日本語文字がある場合、 その1文字が繰り返しの対象となります。 ShiftJIS の場合は、いわゆる半角カタカナも認識されます。

デフォルトでは、Windows系は ShiftJIS を使用し、それ以外のプラットフォームでは EUC を使用します。プラットフォームにかかわらず特定の文字コードを使用したい場合は、 langedge::jregex::JRegexLoader::setDefaultCharcode() によってデフォルトの文字コードを設定することができます。

デフォルトの文字コードにかかわらず、正規表現オブジェクトごとに文字コードを指定するには、 ファクトリ関数やコンストラクタ、グローバルなマッチング関数、

などの引数として、動作指定フラグに langedge::jregex::JREGEX_SJIS または langedge::jregex::JREGEX_EUC をセットしたものを渡します。

ファクトリ関数やコンストラクタで文字コードを指定すると、 その後のパターンコンパイルやマッチングでもその文字コードが使用されます。


簡単な使用例

前準備

#include "langedge/win_support.hpp"

#include <iostream>
#include <string>
using namespace std;

// 正規表現のインタフェースを宣言しているファイル
#include "langedge/jre_util.hpp"

// release() メソッドを持つインタフェース用のスマートポインタ
#include "langedge/remote_ptr.hpp"

using langedge::jregex::JRegexLoader;
using langedge::jregex::TheJRegexLoader;
using langedge::jregex::JRegexIF;
using langedge::jregex::JREMatchRange;
using langedge::jregex::JRECache;
using langedge::jregex::JREMatcher;
using langedge::jregex::JRegMatch;
using langedge::jregex::JRegSubst;
using langedge::RemotePtr;

// DLL_PATH を定義
#ifndef NDEBUG
#define DLL_PATH "..\\..\\..\\build\\win_debug\\bin\\lei_jregex.dll"
#else
#define DLL_PATH "..\\..\\..\\build\\win_release\\bin\\lei_jregex.dll"
#endif

// 正規表現DLLのパスを設定しておく
void jregex_dll_path()
{
    TheJRegexLoader::dllPath( DLL_PATH );
}

以下は、 langedge::jregex::JRegexLoaderlangedge::jregex::JRegexIFlangedge::jregex::JREMatchRange を使用する例です。

// langedge::jregex::JRegexLoader と langedge::jregex::JRegexIF を使用する例
void jregex_sample_1()
{
    // DLLローダ
    JRegexLoader& jreLoader = TheJRegexLoader::getLoader();

    // パターンから正規表現インスタンス(オブジェクト)を作成
    RemotePtr<JRegexIF> pjre( jreLoader.createInstance( "foo" ) );

    if (pjre->succeeded()) {
        // パターンコンパイルに成功
        if (pjre->regexec( "a foo bar", 0 )) {
            // パターンマッチに成功

            // パターンにマッチした範囲を得る
            JREMatchRange rm = pjre->getMatchedRange( 0 );
            cout << "match: [" << rm.begin << " - " << rm.end << "]" << endl;
        }
    }

    // 正規表現オブジェクトは、スマートポインタの破棄時に release() が呼ばれて
    // 自動的に解放される
}

実行結果は次のようになります。

match: [2 - 5]

以下は、 langedge::jregex::JRECache を使用する例です。

// langedge::jregex::JRECache を使用する
void jregex_sample_2()
{
    // DLLローダ
    JRegexLoader& jreLoader = TheJRegexLoader::getLoader();

    // 正規表現キャッシュを作成
    JRECache reCache( &jreLoader );

    // とあるパターンと文字列に対してパターンマッチを実行する
    // C++ ソースで、直接パターン文字列を記述する場合は、
    // \s を指定するのに \\s と書くことに注意
    JRegexIF* pReg = reCache.regMatch( "foo\\s+bar", "hoge foo bar fuga!" );
    if ( pReg ) {
        cout << "begin: "  << pReg->getMatchedRange(0).begin    << endl;
        cout << "end: "    << pReg->getMatchedRange(0).end      << endl;
        cout << "length: " << pReg->getMatchedRange(0).length() << endl;
    }
    // pReg を delete または release してはいけない!

    // とあるパターンと文字列に対してパターン置換を実行する
    string result;
    pReg = reCache.regSubst( "/foo/bar/ig", "foo Foo FOO!", result );
    if ( pReg ) {
        cout << "result: " << result << endl;
    }
    // pReg を delete または release してはいけない!
}

実行結果は次のようになります。

begin: 5
end: 12
length: 7
result: bar bar bar!

以下は、 langedge::jregex::JREMatcher を使用する例です。

// langedge::jregex::JREMatcher を使用する例
void jregex_sample_3()
{
    // DLLローダ
    JRegexLoader& jreLoader = TheJRegexLoader::getLoader();

    // 正規表現マッチャーを作成
    JREMatcher matcher(&jreLoader);

    // とあるパターンと文字列に対してパターンマッチを実行する
    // C++ ソースで、直接パターン文字列を記述する場合は、
    // \w を指定するのに \\w と書くことに注意
    if (matcher.match( "f\\w+o", "hoge foo bar fuga!" )) {
        cout << "match: " << matcher[0] << endl;
    }

    // 文字によるカッコ指定
    if (matcher.match( "({a}f\\w+o)\\s+bar", "hoge foo bar fuga!" )) {
        cout << "match[0]: " << matcher[0] << endl;
        cout << "match[a]: " << matcher['a'] << endl;
    }
}

int main()
{
    jregex_dll_path();
    jregex_sample_1();
    jregex_sample_2();
    jregex_sample_3();

    return 0;
}

実行結果は次のようになります。

match: foo
match[0]: foo bar
match[a]: foo

以下は、 langedge::jregex::JRegMatchlangedge::jregex::JRegSubst を使用する例です。

// langedge::jregex::JRegMatch と langedge::jregex::JRegSubst を使用する例
void jregex_sample_4()
{
    // あらかじめ、 TheJRegexLoader::dllPath() を呼んで、
    // 正規表現DLLのパスを設定しておく必要がある。
    // 本例では、main() の先頭で jregex_dll_path() を呼んでいる。

    // とあるパターンと文字列に対してパターンマッチを実行する
    JRegexIF* pReg = JRegMatch( "foo\\s+bar", "hoge foo bar fuga!" );
    if ( pReg ) {
        cout << "begin: "  << pReg->getMatchedRange(0).begin    << endl;
        cout << "end: "    << pReg->getMatchedRange(0).end      << endl;
        cout << "length: " << pReg->getMatchedRange(0).length() << endl;
    }
    // pReg を delete または release してはいけない!

    // とあるパターンと文字列に対してパターン置換を実行する
    string result;
    pReg = JRegSubst( "/foo/bar/ig", "foo Foo FOO!", result );
    if ( pReg ) {
        cout << "result: " << result << endl;
    }
    // pReg を delete または release してはいけない!
}

実行結果は次のようになります。

begin: 5
end: 12
length: 7
result: bar bar bar!


パターンの仕様

langedge::jregex::Regexp::regcomp() などの関数に引数として与えることのできるパターンの仕様は以下のとおりです。

  1. 正規表現文字列
  2. デリミタ 正規表現文字列 デリミタ [フラグ]
  3. デリミタ 正規表現文字列 デリミタ 置換文字列 デリミタ [フラグ]

デリミタを使用する場合は、引数の動作指定フラグに langedge::jregex::Regexp::REG_DELIM を指定しておく必要があります。 この場合、パターンの先頭文字をデリミタとみなします。 デリミタには任意の文字を使用することができますが、 パターン内におけるデリミタ文字は統一されていなければなりません。

例:

    regcomp( "/foo.*bar/", 0, RegExp::REG_DELIM );  /* / がデリミタ */
    regcomp( ":^...$:", 0, RegExp::REG_DELIM );     /* : がデリミタ */

置換文字列を含むパターンの場合は、引数の動作指定フラグに langedge::jregex::Regexp::REG_SUBST を指定しておく必要があります。 langedge::jregex::Regexp::REG_SUBSTlangedge::jregex::Regexp::REG_DELIM の指定も含意しています。

例:

    regcomp( "/foo/FOO/", 0, RegExp::REG_SUBST );  /* / がデリミタ */

デリミタが有効になっている場合、パターンの末尾に動作指定フラグを記述することができます。 現在サポートされているフラグは次のとおりです。

  1. i …… 英字の大文字・小文字の違いを無視する
  2. g …… マッチングや置換をターゲット文字列の全体にわたって繰り返す
  3. m …… 複数行モード。^$ が文字列中の任意の改行文字にマッチする
  4. p …… カッコで囲まれたサブパターンにマッチする範囲情報を保持する

例: 大文字・小文字の違いを無視して「fo*o」というパターンを検索し、かつ、 「fo*」にマッチした範囲を保持する

    regcomp( "/(fo*)o/ip", 0, RegExp::REG_DELIM );


正規表現の仕様

本ライブラリでサポートしている正規表現の一覧です。
【アンカー類】 (アンカーは、文字と文字の間にマッチする)
^ 行頭にマッチ
$ 行末にマッチ。行末が \n ならば、その \n の前の位置にマッチ
\A 文字列の先頭にマッチ
\Z 文字列の末尾にマッチ。文字列末が \n ならば、その \n の前の位置にマッチ
\b 単語境界 (単語構成文字と非単語構成文字との間) にマッチする
\B 単語境界以外の文字間にマッチする
(?= pattern ) 文字列を先読みして、pattern にマッチするかどうかを調べる。 もしマッチすれば、その先読み開始位置にマッチ (肯定先読み)
(?! pattern ) 文字列を先読みして、pattern にマッチするかどうかを調べる。 もしマッチしなければ、その先読み開始位置にマッチ (否定先読み)
【アトム類】 (アトムは、1文字にマッチする)
通常文字 メタ文字 (^, $, \, ., [, ], (, ), ?, *, +, {, }) 以外の通常文字は、その文字自体にマッチ。 2バイトの日本語文字や1バイトの半角カナ文字も1文字として扱われる
. 改行以外の任意の1文字にマッチ
\a アラート文字 (0x07) にマッチ
\f フォームフィード文字 (0x0c) にマッチ
\n 改行文字 (0x0a) にマッチ
\r 復帰文字 (0x0d) にマッチ
\t タブ文字 (0x09) にマッチ
\v 垂直タブ文字 (0x0b) にマッチ
\nnn nnn (n は 0〜7) の8進数でコードが表わされる文字にマッチ
\xnn nn (n は 0〜9, a-z) の16進数でコードが表わされる文字にマッチ
\d '0' から '9' までの数字にマッチ
\D '0' から '9' 以外の文字にマッチ
\w 単語構成文字、すなわち、英字、数字およびアンダースコアにマッチ
\W 単語構成文字以外の文字にマッチ
\s 空白文字 (ブランク、タブ、復帰、改行) にマッチ
\S 空白文字以外の文字にマッチ
[ string ] string に含まれる任意の1文字にマッチ。日本語文字も1文字として扱われる。 string 内には、上記の \ によるエスケープ表現および x-y による文字範囲指定が可能である。 ., (, ), ?, *, +, {, }, $ および先頭以外の ^ はメタ文字としての意味を失い、通常の文字の扱いになる
[^ string ] string に含まれない任意の1文字にマッチ。 string の解釈は、上と同様
【連接】
x y ... z 各表現 xy、...、z のそれぞれへのマッチが 連続している部分にマッチ
【選択】
x | y | ... | z 各表現 xx、...、または z のいずれかにマッチ
【カッコ・リファレンス類】
( pattern ) カッコ内の表現 pattern をひとまとめにする。 また、カッコの出現順に対して、1 から順に番号をふっておき、 後で '\数字' という形式のリファレンスを使うことができる。 また、RegExp::getMatchedRange()JREMatcher::operator[] の引数としてカッコの番号を与えることにより、 カッコ内のパターンにマッチした範囲を得ることができる
({n} pattern ) 表現 pattern をひとまとめにして、カッコに n (n は、'a'から 'z') という名前を付ける。 後で '${名前}' という形式のリファレンスを使うことができる。 また、RegExp::getMatchedRange()JREMatcher::operator[] の引数としてカッコの名前 ('a' 〜 'z') を与えることにより、 カッコ内のパターンにマッチした範囲を得ることができる
\1 ... \9 n (= 1...9) 番目のカッコにマッチしている文字列へのリファレンス
${a} ... ${z} 名前 ('a' ... 'z') が定義されているカッコにマッチしている文字列へのリファレンス
【繰り返し指定】 (クロージャ)
? 直前の表現の 0 〜 1回の繰り返しにマッチ。可能なら1回のマッチのほうを優先
* 直前の表現の 0回以上の繰り返しにマッチ。可能な限り最大回数のマッチを優先
+ 直前の表現の 1回以上の繰り返しにマッチ。可能な限り最大回数のマッチを優先
{n,m} 直前の表現の n回以上、m回 (m ≦ 100) 以下の繰り返しにマッチ。 可能な限り最大回数のマッチを優先
{n,} 直前の表現の n回 (n ≦ 100) 以上の繰り返しにマッチ。 可能な限り最大回数のマッチを優先
{n} 直前の表現の n回 (n ≦ 100) の繰り返しにマッチ
【最短マッチ指定】
? 上記の繰り返し指定の直後に記述する。 繰り返し指定は、通常、繰り返し回数が最大になるようなマッチが優先されるが、 この指定があると、繰り返し回数が最小になるようなマッチが優先される。

2つ以上の表現が結合する場合の優先順位は、高いほうから並べると、以下のようになります。

  1. カッコ
  2. 最短マッチ指定
  3. 繰り返し指定
  4. 連接
  5. 選択


制限事項

カッコのマッチング
デフォルトでは、マッチング速度を上げるために、 パターン中に含まれるカッコによって指定されるサブパターンにマッチした 部分文字列の範囲情報を保持しません。つまり、langedge::jregex::Regexp::regexec などを実行した結果として直接得られるのは、 パターン全体にマッチしていてる部分文字列の範囲情報だけです。

パターンマッチ後に、langedge::jregex::Regexp::getMatchedRange などの関数に 0 以外の引数を渡してサブパターンにマッチしている部分文字列の範囲情報を得ようとすると、 その範囲情報の保持を行うモード (すなわち langedge::jregex::Regexp::REG_PAREN が指定されたモード) で、自動的に再マッチングが実行されます。

もし、サブパターンにマッチした部分文字列の情報を使うことがあらかじめわかっているのであれば、 最初から langedge::jregex::Regexp::REG_PAREN を指定してマッチングを実行することをおすすめします。

{} による繰り返し回数指定
{n, m} を使用して繰り返し回数を指定すると、 内部的には、
x x … x (x (x (…)?)?)?
└ n個 ┘└─ m-n個 ─┘

という形に展開してしまいます。したがって、m としてあまり大きな数を指定すると、 多大なメモリを使ってしまうことになる可能性があるので、現在、 最大値を 100 に制限してあります。

後方参照

  1. \1 や ${a} による後方参照 (バックリファレンス) は、(?= )(?! ) の先読みの中では使用することができません。

  1. 後方参照で指示されているカッコでは、バックトラックが発生します (バックトラックが発生するのは、これと先読みだけです)。 特に、
    (……).*(……).*\1…\2
    

のように、後方参照で指定示されているカッコが複数あって、 その間に .* のような可変長文字列にマッチする表現がある場合は、 バックトラックによる試行回数が膨大な数になる可能性があります。 ご注意ください。

ゼロ幅の表現に対する繰り返し指定
アンカー類や a* のように幅が 0 (になりうる) 表現に対して 繰り返し指定を行うことはできません。

例:

(a*)+

このようなパターンをコンパイルしようとすると、エラーが返ります。


ライセンス

用語の定義
当社 有限会社ラング・エッジ
本パッケージ 正規表現ライブラリ lei_jregex を構成する全ファイル (ライブラリ、ソース、ドキュメントなどすべて) を含む集合体
当該諸行為 本パッケージの全部または一部を使用・複製・修正・配布する行為の全部または一部
当該諸権利 当該諸行為を行う権利
当該利用者 本ライセンスに記述した全条項を遵守することに同意した個人または組織

当社は、当該利用者に対して、本パッケージに関する当該諸権利を許諾します。 当該諸権利の行使に際して、当該利用者は当社を含むいかなる個人あるいは組織に対しても 対価を支払う義務はありません。 以下の条項のすべてを遵守することに同意しない個人または組織に対しては、 当該諸権利を許諾しません。

  1. 本ライセンスにある文言を一切修正しないこと。
  2. 本パッケージに含まれるすべての著作権表示を一切修正しないこと (ソースコードの修正者の著作権表示を追加することは禁止しない)。
  3. lei_jregex.dll (.so) のみを配布する場合を除き、 本パッケージの全部または一部を配布する場合には、 本ライセンスを含むドキュメントを添付すること。
  4. 本パッケージに対する当該諸行為を行う場合に、当社に対し一切の問い合わせを行わないこと。
  5. [免責] 当該諸行為の結果として、いかなる種類の損失、損害または不利益が発生しても、 当社およびその社員ないし従業員がその責を一切負わないことに同意し、かつ、 当社およびその社員ないし従業員にその責を一切負わせないこと。



正規表現ライブラリ lei_jregexに対してWed May 11 15:29:31 2005に生成されました。  doxygen 1.4.2