Index: trunk/sakura_core/CBregexp.h =================================================================== --- trunk/sakura_core/CBregexp.h (リビジョン 1922) +++ trunk/sakura_core/CBregexp.h (作業コピー) @@ -263,9 +263,10 @@ const char *m_szTarget; //!< 対象文字列へのポインタ char m_szMsg[80]; //!< BREGEXPからのメッセージを保持する struct SyntaxInfo { - SyntaxInfo() : lookBehindIsAvailable( false ), nestedRawBracketIsDisallowed( false ) {} + SyntaxInfo() : lookBehindIsAvailable( false ), nestedRawBracketIsDisallowed( false ), qeEscapeIsAvailable( false ) {} bool lookBehindIsAvailable; //!< 正規表現ライブラリが戻り読みをサポートするなら真。 bool nestedRawBracketIsDisallowed; //!< 正規表現ライブラリが文字集合の中で、POSIXブラケット以外のエスケープされていない [ を禁じていたら真。 + bool qeEscapeIsAvailable; //!<正規表現ライブラリが \Q...\Eをサポートしていたら真。 } m_checkedSyntax; // 静的メンバ変数 static const char m_tmpBuf[2]; //!< ダミー文字列 Index: trunk/sakura_core/CBregexp.cpp =================================================================== --- trunk/sakura_core/CBregexp.cpp (リビジョン 1922) +++ trunk/sakura_core/CBregexp.cpp (作業コピー) @@ -349,8 +349,8 @@ } /*! @brief CBregexp::MakePattern()の代替。 - * エスケープされておらず、文字集合の中にもない . を [^\r\n] に置換する。 - * エスケープされておらず、文字集合の中にもない $ を (?CheckPattern( szSearch ); const bool nestedRawBracketIsDisallowed = this->m_checkedSyntax.nestedRawBracketIsDisallowed; + const bool qeEscapeIsAvailable = this->m_checkedSyntax.qeEscapeIsAvailable; const char szDotAlternative[] = "[^\\r\\n]"; const char szDollarAlternative[] = "(?state]; - if( acceptChar && acceptChar == ch ) { - // 特定の文字を食べて次の状態へ。 - this->state = State( state + 1 ); - return true; - } else if( this->state == Escaped || this->state == SmallC ) { - // 何でも一文字消費して終了。 - this->state = None; - return true; - } - return false; // 関係ない文字だった。 - } - } seq; + enum State { + DEF = 0, /* DEFULT 一番外側 */ + D_E, /* DEFAULT_ESCAPED 一番外側で \の次 */ + D_C, /* DEFAULT_SMALL_C 一番外側で \cの次 */ + CHA, /* CHARSET 文字クラスの中 */ + C_E, /* CHARSET_ESCAPED 文字クラスの中で \の次 */ + C_C, /* CHARSET_SMALL_C 文字クラスの中で \cの次 */ + QEE, /* QEESCAPE \Q...\Eの中 */ + Q_E, /* QEESCAPE_ESCAPED \Q...\Eの中で \の次 */ + NUMBER_OF_STATE, + _EC = -1, /* ENTER CHARCLASS charsetLevelをインクリメントして CHAか DEFへ */ + _XC = -2, /* EXIT CHARCLASS charsetLevelをデクリメントして CHAか DEFへ */ + _DT = -3, /* DOT (特殊文字としての)ドットを置き換える */ + _DL = -4, /* DOLLAR (特殊文字としての)ドルを置き換える */ + _QE = -5 /* ENTER QEESCAPE OR NOT \Q...\Eがサポートされていれば QEEへ、でなければ DEFへ */ + }; + enum CharClass { + OTHER = 0, + DOT, /* . */ + DOLLAR, /* $ */ + SMALLC, /* c */ + LARGEQ, /* Q */ + LARGEE, /* E */ + LBRCKT, /* [ */ + RBRCKT, /* ] */ + ESCAPE, /* \ */ + NUMBER_OF_CHARCLASS + }; + static const State state_transition_table[NUMBER_OF_STATE][NUMBER_OF_CHARCLASS] = { + /* OTHER DOT DOLLAR SMALLC LARGEQ LARGEE LBRCKT RBRCKT ESCAPE*/ + /* DEF */ {DEF, _DT, _DL, DEF, DEF, DEF, _EC, DEF, D_E}, + /* D_E */ {DEF, DEF, DEF, D_C, _QE, DEF, DEF, DEF, DEF}, + /* D_C */ {DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF}, + /* CHA */ {CHA, CHA, CHA, CHA, CHA, CHA, _EC, _XC, C_E}, + /* C_E */ {CHA, CHA, CHA, C_C, CHA, CHA, CHA, CHA, CHA}, + /* C_C */ {CHA, CHA, CHA, CHA, CHA, CHA, CHA, CHA, CHA}, + /* QEE */ {QEE, QEE, QEE, QEE, QEE, QEE, QEE, QEE, Q_E}, + /* Q_E */ {QEE, QEE, QEE, QEE, QEE, DEF, QEE, QEE, Q_E} + }; + + State state = DEF; int charsetLevel = 0; // ブラケットの深さ。POSIXブラケット表現など、エスケープされていない [] が入れ子になることがある。 const char *left = szSearch, *right = szSearch; for( ; *right; right += Charcode::GuessCharLen_sjis( reinterpret_cast( right ) ) ) { - if( seq.EatCharacter( *right ) ) { - // (ブラケットの内外で有効な)エスケープシークエンス( \X, \cX )の一部だった。 - } else if( *right == '[' ) { - ++charsetLevel; - } else if( *right == ']' ) { - if( 0 < charsetLevel ) { // 文字集合の外のエスケープされていない ] は合法なので考慮する。 + const char ch = *right; + const CharClass charClass = + ch == '.' ? DOT: + ch == '$' ? DOLLAR: + ch == 'c' ? SMALLC: + ch == 'Q' ? LARGEQ: + ch == 'E' ? LARGEE: + ch == '[' ? LBRCKT: + ch == ']' ? RBRCKT: + ch == '\\' ? ESCAPE: + OTHER; + const State nextState = state_transition_table[state][charClass]; + if(0 <= nextState) { + state = nextState; + } else switch(nextState) { + case _EC: // ENTER CHARSET + charsetLevel += 1; + state = CHA; + break; + case _XC: // EXIT CHARSET charsetLevel -= nestedRawBracketIsDisallowed ? 1 : charsetLevel; - } - } else { - if( *right == '.' && charsetLevel == 0 ) { + state = 0 < charsetLevel ? CHA : DEF; + break; + case _DT: // DOT(match anything) strModifiedSearch.append( left, right - left ); left = right + 1; strModifiedSearch.append( szDotAlternative ); - } else if( *right == '$' && charsetLevel == 0 ) { + break; + case _DL: // DOLLAR(match end of line) strModifiedSearch.append( left, right - left ); left = right + 1; strModifiedSearch.append( szDollarAlternative ); - } + break; + case _QE: // ENTER QEESCAPE OR NOT + state = qeEscapeIsAvailable ? QEE : DEF; + break; + default: // バグ。enum Stateに見逃しがある。 + break; } } strModifiedSearch.append( left, right + 1 - left ); // right + 1 は '\0' の次を指す(明示的に '\0' をコピー)。 - return this->MakePatternSub( strModifiedSearch.c_str(), szReplace, "", nOption ); + return this->MakePatternSub( strModifiedSearch.data(), szReplace, "", nOption ); } //! 正規表現ライブラリがサポートする文法をチェックする。 void CBregexp::CheckSupportedSyntax() { BREGEXP* pBREGEXP = 0; - const char szTarget[] = ""; + const char szTarget[] = "$"; char szErrMsg[128] = ""; // 戻り読みチェック @@ -446,6 +488,12 @@ const char szNestedRawBracket[] = "m/[[]/"; this->m_checkedSyntax.nestedRawBracketIsDisallowed = this->BMatch( szNestedRawBracket, szTarget, szTarget + 1, &pBREGEXP, szErrMsg ) < 0; + // \Q...\Eが有効か調べる。 + szErrMsg[0] = '\0'; + const char szQEEscape[] = "m/\\Q$\\E/"; + this->m_checkedSyntax.qeEscapeIsAvailable = 0 < this->BMatch( szQEEscape, szTarget, szTarget + 1, &pBREGEXP, szErrMsg ) + && pBREGEXP->startp[0] == szTarget; + if( pBREGEXP ) { this->BRegfree( pBREGEXP ); } Index: trunk2/sakura_core/CBregexp.cpp =================================================================== --- trunk2/sakura_core/CBregexp.cpp (リビジョン 1922) +++ trunk2/sakura_core/CBregexp.cpp (作業コピー) @@ -280,8 +280,8 @@ /*! CBregexp::MakePattern()の代替。 - * エスケープされておらず、文字集合の中にもない . を [^\r\n] に置換する。 - * エスケープされておらず、文字集合の中にもない $ を (?state]; - if( acceptChar && acceptChar == ch ) { - // 特定の文字を食べて次の状態へ。 - this->state = State( state + 1 ); - return true; - } else if( this->state == Escaped || this->state == SmallC ) { - // 何でも一文字消費して終了。 - this->state = None; - return true; - } - return false; // 関係ない文字だった。 - } - } seq; + enum State { + DEF = 0, /* DEFULT 一番外側 */ + D_E, /* DEFAULT_ESCAPED 一番外側で \の次 */ + D_C, /* DEFAULT_SMALL_C 一番外側で \cの次 */ + CHA, /* CHARSET 文字クラスの中 */ + C_E, /* CHARSET_ESCAPED 文字クラスの中で \の次 */ + C_C, /* CHARSET_SMALL_C 文字クラスの中で \cの次 */ + QEE, /* QEESCAPE \Q...\Eの中 */ + Q_E, /* QEESCAPE_ESCAPED \Q...\Eの中で \の次 */ + NUMBER_OF_STATE, + _EC = -1, /* ENTER CHARCLASS charsetLevelをインクリメントして CHAか DEFへ */ + _XC = -2, /* EXIT CHARCLASS charsetLevelをデクリメントして CHAか DEFへ */ + _DT = -3, /* DOT (特殊文字としての)ドットを置き換える */ + _DL = -4, /* DOLLAR (特殊文字としての)ドルを置き換える */ + }; + enum CharClass { + OTHER = 0, + DOT, /* . */ + DOLLAR, /* $ */ + SMALLC, /* c */ + LARGEQ, /* Q */ + LARGEE, /* E */ + LBRCKT, /* [ */ + RBRCKT, /* ] */ + ESCAPE, /* \ */ + NUMBER_OF_CHARCLASS + }; + static const State state_transition_table[NUMBER_OF_STATE][NUMBER_OF_CHARCLASS] = { + /* OTHER DOT DOLLAR SMALLC LARGEQ LARGEE LBRCKT RBRCKT ESCAPE*/ + /* DEF */ {DEF, _DT, _DL, DEF, DEF, DEF, _EC, DEF, D_E}, + /* D_E */ {DEF, DEF, DEF, D_C, QEE, DEF, DEF, DEF, DEF}, + /* D_C */ {DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF}, + /* CHA */ {CHA, CHA, CHA, CHA, CHA, CHA, _EC, _XC, C_E}, + /* C_E */ {CHA, CHA, CHA, C_C, CHA, CHA, CHA, CHA, CHA}, + /* C_C */ {CHA, CHA, CHA, CHA, CHA, CHA, CHA, CHA, CHA}, + /* QEE */ {QEE, QEE, QEE, QEE, QEE, QEE, QEE, QEE, Q_E}, + /* Q_E */ {QEE, QEE, QEE, QEE, QEE, DEF, QEE, QEE, Q_E} + }; + State state = DEF; int charsetLevel = 0; // ブラケットの深さ。POSIXブラケット表現など、エスケープされていない [] が入れ子になることがある。 const wchar_t *left = szSearch, *right = szSearch; for( ; *right; ++right ) { // CNativeW::GetSizeOfChar()は使わなくてもいいかな? - if( seq.EatCharacter( *right ) ) { - // (ブラケットの内外で有効な)エスケープシークエンス( \X, \cX )の一部だった。 - } else if( *right == L'[' ) { // 鬼車では文字集合の中の [ は POSIXブラケット表現などの意味を持つので、必ずエスケープされている。これはエスケープされていないので、無条件でレベルを増す。 - ++charsetLevel; - } else if( *right == L']' ) { - if( 0 < charsetLevel ) { // 文字集合の外のエスケープされていない ] は合法なので考慮する。 - charsetLevel -= nestedBracketIsAllowed ? 1 : charsetLevel; - } - } else { - if( *right == L'.' && charsetLevel == 0 ) { + const wchar_t ch = *right; + const CharClass charClass = + ch == L'.' ? DOT: + ch == L'$' ? DOLLAR: + ch == L'c' ? SMALLC: + ch == L'Q' ? LARGEQ: + ch == L'E' ? LARGEE: + ch == L'[' ? LBRCKT: + ch == L']' ? RBRCKT: + ch == L'\\' ? ESCAPE: + OTHER; + const State nextState = state_transition_table[state][charClass]; + if(0 <= nextState) { + state = nextState; + } else switch(nextState) { + case _EC: // ENTER CHARSET + charsetLevel += 1; + state = CHA; + break; + case _XC: // EXIT CHARSET + charsetLevel -= 1; + state = 0 < charsetLevel ? CHA : DEF; + break; + case _DT: // DOT(match anything) strModifiedSearch.append( left, right ); left = right + 1; strModifiedSearch.append( szDotAlternative ); - } else if( *right == L'$' && charsetLevel == 0 ) { + break; + case _DL: // DOLLAR(match end of line) strModifiedSearch.append( left, right ); left = right + 1; strModifiedSearch.append( szDollarAlternative ); - } + break; + default: // バグ。enum Stateに見逃しがある。 + break; } } strModifiedSearch.append( left, right + 1 ); // right + 1 は '\0' の次を指す(明示的に '\0' をコピー)。 - return this->MakePatternSub( strModifiedSearch.c_str(), szReplace, L"", nOption ); + return this->MakePatternSub( strModifiedSearch.data(), szReplace, L"", nOption ); }