なんなのなんなの
C言語で、Delphiから使うのを前提に組んだ謎の正規表現エンジン。
外部依存はWindows APIまたはlibc, iconvのみです。故にリンクさえできれば言語を問わず使えます。
Borland C++他、VC++Toolkitやmingw版gcc、Digital Mars C++でも動くようです。アーカイブにはC/C++、Delphi、DigitalMars D、GNAT(gcc)向けのインポートユニットを同梱しています。
文字コードは、バイト列、英語、Windowsロケール、UTF-8、UTF-16、SJIS, EUC-JPを混在可能です。
UnicodeはUCS-2まで、16ビットの範囲で対応です。
他の正規表現ライブラリには無い(と思われる)最大の特徴として、IsDBCSLeadByte/CompareStringを用いてマッチを行う、全半角や仮名の対応テーブル作るのめんどくさかったんだよ速度など知った事か無謀モードWindowsとの親和性が非常に高いモードを搭載しています。勿論UTF-8/16の時はCompareStringWを用います。
APIを用いるメリットとして、テーブルを一切抱えこむことなく全半角同一視やひらがなカタカナ同一視ができてしまいます。また文字コード決め打ちから逃れられますので、外国版Windowsでは、その国での正しい動作を期待できます。
逆にWindows以外では、mblenとかstrcollでそれっぽくしてはいますが、機能は制限されます。
アルゴリズムはNFA…かな(今だによくわかってない)。
使える表現
(?...: E) オプション
n | バイナリ列として扱う, オプションは何を併用しても無視 |
a | 全部半角文字と仮定, CompareStringに渡す時は言語を英語とする |
l | Windowsが使用している言語, 日本ならSJIS, 非Windowsではsetlocaleに従う…はず |
u | UTF-8, 4バイト以上の長さになる文字には未対応 |
w | UTF-16, サロゲートペア未対応 |
b | UTF-16 Big Endian |
s | SHIFT-JIS |
e | EUC-JP |
x+- | CompareString使用の有無(n,e時は無効), 非Windowsではそれっぽい関数で代替 |
i+- | 大小文字同一視(x-時は半角文字だけ) |
f+- | 全半角同一視(b+時は強制的にx+), 非Windowsでは無効 |
k+- | カタカナひらがな同一視(k+時は強制的にx+), 非Windowsでは無効 |
m+- | 複数行モード(.$^に作用) |
NUM | 括弧にマッチした箇所をコールバックで知らせる ex. (?1:...) |
表現
. | 全てに一致(m+時改行にはマッチしない) |
[A-Z] | 文字集合 |
[^A-Z] | 補集合 |
[~A-Z] | 〃 |
^ | 先頭(m+時改行の直後にもマッチする) |
% | 〃 |
$ | 末尾(m+時改行の直前にもマッチする) |
< | 単語頭 |
> | 単語末 |
=A | 現在位置から見て次に存在する文字がAに一致する |
~A | 現在位置から見て次に存在する文字がAに一致しない |
A* | 0..n |
A*! | 0..n 一度一致したら手放さない |
A*? | 0..n 最少 |
A+ | 1..n |
A+! | 1..n 一度一致したら手放さない |
A+? | 1..n 最少 |
A? | 0..1 |
A?! | 0..1 一度一致したら手放さない |
A?? | 0..1 最少 |
A|B | or |
\0 | #$00 |
\a | #$07 |
\b | #$08 BS |
\d | [0-9] |
\e | #$1B |
\f | #$0C FF |
\l | #$0A LF |
\n | #$0D#$0A?|#$0A |
\r | #$0D |
\s | [#$00-#$20] |
\t | #$09 H-TAB |
\v | #$0B V-TAB |
\w | [A-Za-z_0-9] |
\xHEX | 文字コード16進数 |
\z | #$1A EOF |
\SYM | 記号そのまま |
#NUM | 文字コード10進数 |
#$HEX | 文字コード16進数 |
"STR" | 文字列そのまま |
'NUM | アプリケーション定義のマッチング |
download
C言語からの使用例
Boostの正規表現が、なんかexeサイズが200kもBoostされるというわけわからん事実*1を知りましたので、対抗して(身の程知らずだな俺)サンプルソースあげておきます。
大御所と比較したら、それはもう機能的にも速度的にもダメダメですが、dyaregexpには20kに満たないというメリットがあります。
なお、サンプルのお題はどこかのパクリだったりしますが、気にしてはいけません。
//---------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#define __DYAREGEXP__NEXT__
#include <dyaregexp.h>
//---------------------------------------------------------------------------
void test(char const *pattern, char const *text, int first, int last_plus1)
{
regexp re;
target s;
char const *p;
char const *pre;
char const *match_first;
char const *match_last_plus1;
int text_len;
if(last_plus1 < 0) last_plus1 = strlen(text);
text_len = strlen(text);
if(last_plus1 > text_len) text_len = last_plus1;
s.start = text;
s.last_plus1 = text + text_len;
s.on_label = NULL;
s.on_app = NULL;
s.context = 0;
if(!regexp_initialize(&re, strlen(pattern), pattern, encoding_ascii, 0)){
putchar('!');
putchar('\n');
puts(pattern);
}else{
p = text;
pre = p;
match_first = NULL;
match_last_plus1 = NULL;
while(p < s.last_plus1){
if(regexp_match(&re, &s, &p)){
match_first = pre;
match_last_plus1 = p;
break;
}else{
p = regexp_next(re.e, p);
}
pre = p;
}
if(match_first == text + first && match_last_plus1 == text + last_plus1){
putchar('O');
}else{
putchar('X');
putchar('\n');
puts(pattern);
}
}
}
#pragma argsused
int main(int argc, char* argv[])
{
/* マッチした部分を、前後に#を付けた文字列で置換する */
char const ex[] = "A([1-9]*|[a-z]*)A";
char const tx[] = "A123A AaaaA A3b3A A9A";
char const *p = tx, *pre = tx, *i;
/* 結果はここに格納 */
char result[1024] = {0, }, *r_last = result;
/* 正規表現オブジェクトを用意 */
regexp re;
target s;
regexp_initialize(&re, strlen(ex), ex, encoding_ascii, 0);
/* パラメータ構造体 */
s.start = tx; /* 開始位置 */
s.last_plus1 = tx + strlen(tx); /* 終了位置 */
s.on_label = NULL; /* コールバック、今回は使わない */
s.on_app = NULL; /* コールバック、今回は使わない */
s.context = 0; /* コールバックに渡す引数、今回は使わない */
/* regexp_matchは一回分のマッチしか行わないので全部を読み切るまで繰り返す */
while(p < s.last_plus1){
if(regexp_match(&re, &s, &p)){
/* ヒットした場合は#で挟む */
*r_last = '#'; ++r_last;
for(i = pre; i < p; ++i){ *r_last = *i; ++r_last; }
*r_last = '#'; ++r_last;
}else{
/* はずれた場合は単に一文字進める */
*r_last = *p; ++r_last;
p = regexp_next(re.e, p);
}
pre = p;
}
*r_last = '\0';
/* オブジェクトを破棄 */
regexp_finalize(&re);
/* 出力 */
puts(result);
test(".", "A", 0, -1);
test("(?L:.*.*\xe3\x81\x82)", "あああ", 0, -1);
test("(?1:A)", "A", 0, -1);
test("(?LK+:\xe3\x81\x82)", "ア", 0, -1);
test("A|B|C", "B", 0, -1);
test("((A))B", "AB", 0, -1);
test("^ABC", "ABC", 0, -1);
test("<WORD>", "WORD", 0, -1);
test("[^A]", "B", 0, -1);
test("A=B.", "AB", 0, -1);
test("A*AB", "AAAAB", 0, -1);
test("\\w*", "Word_Excel", 0, -1);
test("\\n", "\r\n", 0, -1);
test("(?e:A)", "A", 0, -1);
test("(?w:AB)", (char const *)(wchar_t const *)(L"AB"), 0, 4);
test("(?w:\xe3\x81\x82)", (char const *)(wchar_t const *)(L"あ"), 0, 2);
test("(?s:\xe3\x81\x82)", "あ", 0, -1);
test("(?li+:A)", "a", 0, -1);
test("\\#[^#13#10]*!", "#a", 0, -1);
test("\\xd\\xa", "\r\n", 0, -1);
test("?lf+x+:A", "A", 0, -1);
test("?m:.*!\\nOK", "\rOK", 0, -1);
test("?m:\\n^OK", "\rOK", 0, -1);
test("?m:#13^OK", "\rOK", 0, -1);
test("(?e:A\xe3\x81\x82)", "A\xa4\xa2", 0, -1);
test("\"\\\"'1A", "\\A", 0, -1);
return 0;
}
//---------------------------------------------------------------------------
#A123A# #AaaaA# A3b3A #A9A# OOOOOOOOOOOOOOOOOOOOOOOOOO
D言語からの使用例
Borland C++とはOMF同士なのでそのままリンクできる…と思っていたら、Windows APIのエントリポイントの装飾が違ったので、Digital Mars C++でコンパイルし直す必要がありました。
D版はphobosのRegExpクラス互換メソッドを幾つか用意してます。
ただ、(phobosやBoostと異なり)見つかった位置を覚えてはいないため、位置情報が必要な場合は明示的に受け取ることになります。
import stream;
import dyaregexp;
int main()
{
// <で始まって>で終わる文字列にマッチする正規表現で検索
RegExp r = new RegExp(`\<[^>]+\>`, "s");
int f, l;
char[] s = "The HTML tag <title> means that ...";
r.search(s, f, l);
if(f >= 0){
stdout.writeLine("found (pos=" ~ toString(f) ~ ")");
stdout.writeLine(" ==> " ~ s[f..l]);
}
// マッチした部分を、前後に#を付けた文字列で置換する
RegExp r2 = new RegExp("A([1-9]*|[a-z]*)A", "s");
stdout.writeLine(r2.replace("A123A AaaaA A3b3A A9A", `#$&#`));
return 0;
}
found (pos=13) ==> <title> #A123A# #AaaaA# A3b3A #A9A#