/***************************************************************************** * * match.cpp * * Highly-specialized depot path matching class * *****************************************************************************/ #include "sdview.h" Match::Match(LPCTSTR pszPattern) { Tokenizer tok(pszPattern); String str, strPath, strPats; while (tok.Token(str)) { if (MapToFullDepotPath(str, strPath)) { _AddPattern(strPath, strPats); } } _pszzPats = new TCHAR[strPats.Length()]; if (_pszzPats) { CopyMemory(_pszzPats, strPats, strPats.Length() * sizeof(TCHAR)); _pszEnd = _pszzPats + strPats.Length(); } } BOOL Match::Matches(LPCTSTR pszPath) { LPCTSTR pszPat; if (_pszzPats) { for (pszPat = _pszzPats; pszPat < _pszEnd; pszPat += lstrlen(pszPat) + 1) { if (_Matches(pszPat, pszPath)) { return TRUE; } } } return FALSE; } #define PAT_END ((TCHAR)0) #define PAT_START ((TCHAR)1) #define PAT_DOTS ((TCHAR)2) #define PAT_STAR ((TCHAR)3) #define PAT_BRANCH ((TCHAR)4) #define MAX_BACKTRACK 20 // totally arbitrary void Match::_AddPattern(LPTSTR pszPat, String& strPats) { CharLower(pszPat); // // "compile" the pattern by changing "..." to PAT_DOTS and // "%1" and "*" to PAT_STAR. // // Oh, and change "//depot/blah/" and "//depot/private/blah/" to // "//depot/" so we can track across branches. // LPTSTR pszIn, pszOut; int iWildcards = 0; int cSlashes = 0; TCHAR pat; pszIn = pszOut = pszPat; for (;;) { switch (*pszIn) { case TEXT('\r'): case TEXT('\n'): case TEXT('\0'): goto endcompile; case TEXT('.'): if (pszIn[1] == TEXT('.') && pszIn[2] == TEXT('.')) { pszIn += 3; pat = PAT_DOTS; goto L_wildcard; } else { goto L_default; } break; case TEXT('%'): if ((UINT)(pszIn[1] - TEXT('1')) < 9) { pszIn += 2; pat = PAT_STAR; goto L_wildcard; } else { goto L_default; } break; case TEXT('*'): pszIn++; pat = PAT_STAR; goto L_wildcard; L_wildcard: // // Collapse consecutive wildcards for perf. Otherwise // a search string of a****b will take exponential // time. // if (pszOut[-1] == pat) { // ** and ...... are the same as * and ... (respectively) // so just throw away the second wildcard. } else if (pszOut[-1] == (PAT_STAR + PAT_DOTS - pat)) { // ...* and *... are the same as "..." pszOut[-1] = PAT_DOTS; } else if (iWildcards++ < MAX_BACKTRACK) { // just a regular ol' wildcard *pszOut++ = pat; } else { *pszOut++ = PAT_DOTS; // Give up when the limit is reached goto endcompile; } break; case TEXT('/'): cSlashes++; if (cSlashes == 3) { if (StringBeginsWith(pszIn, TEXT("/private/"))) { // a private branch *pszOut++ = PAT_BRANCH; pszIn += 9; // length of "/private/" } else { // a main branch *pszOut++ = PAT_BRANCH; } // Skip over the branch name while (*pszIn != TEXT('/') && *pszIn != TEXT('\r') && *pszIn != TEXT('\n') && *pszIn != TEXT('\0')) { pszIn++; } } else { goto L_default; } break; L_default: default: *pszOut++ = *pszIn++; } } endcompile:; *pszOut++ = PAT_END; // Now add it to the list of patterns we care about strPats << PAT_START << Substring(pszPat, pszOut); } // // This is the fun part -- funky pattern matching. // // The pszPath is assumed to be of the form // // //depot/fully/qualified/path#n // // PAT_DOTS matches any string. // PAT_STAR matches any string not including slash. // // This code is adapted from code I wrote back in 1993 for the // Windows 95 Netware emulation layer. I've also seen it stolen // by Wininet. I guess good code never dies. Or maybe it's just // that pattern matching is hard. (I suspect the latter because // the Wininet folks stole the code and then adapted it incorrectly.) // BOOL Match::_Matches(LPCTSTR pszPat, LPCTSTR pszPath) { struct Backtrack { int iStart; int iEnd; }; Backtrack rgbt[MAX_BACKTRACK+1]; /* +1 for PAT_START fake-backtrack point */ Backtrack *pbt = rgbt; int i, j; /* i = index to pattern, j = index to target */ int m = lstrlen(pszPath); /* m = length of target */ int back; /* First available slot in backtrack array */ i = -1; /* Will be advanced to 0 */ j = 0; advance: ++i; switch (pszPat[i]) { case PAT_START: pbt->iEnd = 0; goto advance; case PAT_END: if (pszPath[j] == TEXT('#')) return TRUE; else goto retreat; case PAT_DOTS: pbt++; // this is a backtracking rule pbt->iStart = j; pbt->iEnd = j = m; goto advance; case PAT_STAR: pbt++; // this is a backtracking rule pbt->iStart = j; while (pszPath[j] != TEXT('/') && pszPath[j] != TEXT('#') && pszPath[j] != TEXT('\0')) { j++; } pbt->iEnd = j; goto advance; case PAT_BRANCH: // this is a non-backtracking rule if (pszPath[j] != TEXT('/')) goto retreat; if (StringBeginsWith(&pszPath[j], TEXT("/private/"))) { j += 8; } // Skip over the branch name do { j++; } while (pszPath[j] != TEXT('/') && pszPath[j] != TEXT('#') && pszPath[j] != TEXT('\0')); goto advance; default: if (pszPath[j] == pszPat[i]) { j++; goto advance; } else if (pszPath[j] >= TEXT('A') && pszPath[j] <= TEXT('Z') && pszPath[j] - TEXT('A') + TEXT('a') == pszPat[i]) { // I hate case-insensitivity j++; goto advance; } else goto retreat; } retreat: --i; switch (pszPat[i]) { case PAT_START: return FALSE; // cannot backtrack further case PAT_DOTS: case PAT_STAR: if (pbt->iStart == pbt->iEnd) { pbt--; goto retreat; } j = --pbt->iEnd; goto advance; default: goto retreat; } }