| /******************************************************************** |
| * COPYRIGHT: |
| * Copyright (c) 2002-2004, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ********************************************************************/ |
| |
| // |
| // regextst.cpp |
| // |
| // ICU Regular Expressions test, part of intltest. |
| // |
| |
| #include "intltest.h" |
| #if !UCONFIG_NO_REGULAR_EXPRESSIONS |
| |
| #include "unicode/regex.h" |
| #include "unicode/uchar.h" |
| #include "unicode/ucnv.h" |
| #include "regextst.h" |
| #include "uvector.h" |
| #include "charstr.h" |
| #include "util.h" |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // Test class boilerplate |
| // |
| //--------------------------------------------------------------------------- |
| RegexTest::RegexTest() |
| { |
| }; |
| |
| |
| RegexTest::~RegexTest() |
| { |
| }; |
| |
| |
| |
| void RegexTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) |
| { |
| if (exec) logln("TestSuite RegexTest: "); |
| switch (index) { |
| |
| case 0: name = "Basic"; |
| if (exec) Basic(); |
| break; |
| case 1: name = "API_Match"; |
| if (exec) API_Match(); |
| break; |
| case 2: name = "API_Replace"; |
| if (exec) API_Replace(); |
| break; |
| case 3: name = "API_Pattern"; |
| if (exec) API_Pattern(); |
| break; |
| case 4: name = "Extended"; |
| if (exec) Extended(); |
| break; |
| case 5: name = "Errors"; |
| if (exec) Errors(); |
| break; |
| case 6: name = "PerlTests"; |
| if (exec) PerlTests(); |
| break; |
| |
| |
| default: name = ""; |
| break; //needed to end loop |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // Error Checking / Reporting macros used in all of the tests. |
| // |
| //--------------------------------------------------------------------------- |
| #define REGEX_CHECK_STATUS {if (U_FAILURE(status)) {errln("RegexTest failure at line %d. status=%s\n", \ |
| __LINE__, u_errorName(status)); return;}} |
| |
| #define REGEX_ASSERT(expr) {if ((expr)==FALSE) {errln("RegexTest failure at line %d.\n", __LINE__);};} |
| |
| #define REGEX_ASSERT_FAIL(expr, errcode) {UErrorCode status=U_ZERO_ERROR; (expr);\ |
| if (status!=errcode) {errln("RegexTest failure at line %d. Expected status=%s, got %s\n", \ |
| __LINE__, u_errorName(errcode), u_errorName(status));};} |
| |
| #define REGEX_CHECK_STATUS_L(line) {if (U_FAILURE(status)) {errln( \ |
| "RegexTest failure at line %d, from %d. status=%d\n",__LINE__, (line), status); }} |
| |
| #define REGEX_ASSERT_L(expr, line) {if ((expr)==FALSE) { \ |
| errln("RegexTest failure at line %d, from %d.", __LINE__, (line)); return;}} |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // REGEX_TESTLM Macro + invocation function to simplify writing quick tests |
| // for the LookingAt() and Match() functions. |
| // |
| // usage: |
| // REGEX_TESTLM("pattern", "input text", lookingAt expected, matches expected); |
| // |
| // The expected results are UBool - TRUE or FALSE. |
| // The input text is unescaped. The pattern is not. |
| // |
| // |
| //--------------------------------------------------------------------------- |
| |
| #define REGEX_TESTLM(pat, text, looking, match) doRegexLMTest(pat, text, looking, match, __LINE__); |
| |
| UBool RegexTest::doRegexLMTest(const char *pat, const char *text, UBool looking, UBool match, int line) { |
| const UnicodeString pattern(pat); |
| const UnicodeString inputText(text); |
| UErrorCode status = U_ZERO_ERROR; |
| UParseError pe; |
| RegexPattern *REPattern = NULL; |
| RegexMatcher *REMatcher = NULL; |
| UBool retVal = TRUE; |
| |
| UnicodeString patString(pat); |
| REPattern = RegexPattern::compile(patString, 0, pe, status); |
| if (U_FAILURE(status)) { |
| errln("RegexTest failure in RegexPattern::compile() at line %d. Status = %s\n", |
| line, u_errorName(status)); |
| return FALSE; |
| } |
| if (line==376) { RegexPatternDump(REPattern);} |
| |
| UnicodeString inputString(inputText); |
| UnicodeString unEscapedInput = inputString.unescape(); |
| REMatcher = REPattern->matcher(unEscapedInput, status); |
| if (U_FAILURE(status)) { |
| errln("RegexTest failure in REPattern::matcher() at line %d. Status = %s\n", |
| line, u_errorName(status)); |
| return FALSE; |
| } |
| |
| UBool actualmatch; |
| actualmatch = REMatcher->lookingAt(status); |
| if (U_FAILURE(status)) { |
| errln("RegexTest failure in lookingAt() at line %d. Status = %s\n", |
| line, u_errorName(status)); |
| retVal = FALSE; |
| } |
| if (actualmatch != looking) { |
| errln("RegexTest: wrong return from lookingAt() at line %d.\n", line); |
| retVal = FALSE; |
| } |
| |
| status = U_ZERO_ERROR; |
| actualmatch = REMatcher->matches(status); |
| if (U_FAILURE(status)) { |
| errln("RegexTest failure in matches() at line %d. Status = %s\n", |
| line, u_errorName(status)); |
| retVal = FALSE; |
| } |
| if (actualmatch != match) { |
| errln("RegexTest: wrong return from matches() at line %d.\n", line); |
| retVal = FALSE; |
| } |
| |
| if (retVal == FALSE) { |
| RegexPatternDump(REPattern); |
| } |
| |
| delete REPattern; |
| delete REMatcher; |
| return retVal; |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // regex_find(pattern, inputString, lineNumber) |
| // |
| // function to simplify writing tests regex tests. |
| // |
| // The input text is unescaped. The pattern is not. |
| // The input text is marked with the expected match positions |
| // <0>text <1> more text </1> </0> |
| // The <n> </n> tags are removed before trying the match. |
| // The tags mark the start and end of the match and of any capture groups. |
| // |
| // |
| //--------------------------------------------------------------------------- |
| |
| |
| // Set a value into a UVector at position specified by a decimal number in |
| // a UnicodeString. This is a utility function needed by the actual test function, |
| // which follows. |
| static void set(UVector &vec, int val, UnicodeString index) { |
| UErrorCode status=U_ZERO_ERROR; |
| int idx = 0; |
| for (int i=0; i<index.length(); i++) { |
| int d=u_charDigitValue(index.charAt(i)); |
| if (d<0) {return;} |
| idx = idx*10 + d; |
| } |
| while (vec.size()<idx+1) {vec.addElement(-1, status);} |
| vec.setElementAt(val, idx); |
| } |
| |
| void RegexTest::regex_find(const UnicodeString &pattern, |
| const UnicodeString &flags, |
| const UnicodeString &inputString, |
| int line) { |
| UnicodeString unEscapedInput; |
| UnicodeString deTaggedInput; |
| |
| UErrorCode status = U_ZERO_ERROR; |
| UParseError pe; |
| RegexPattern *parsePat = NULL; |
| RegexMatcher *parseMatcher = NULL; |
| RegexPattern *callerPattern = NULL; |
| RegexMatcher *matcher = NULL; |
| UVector groupStarts(status); |
| UVector groupEnds(status); |
| UBool isMatch = FALSE; |
| UBool failed = FALSE; |
| int numFinds; |
| int i; |
| |
| // |
| // Compile the caller's pattern |
| // |
| uint32_t bflags = 0; |
| if (flags.indexOf((UChar)0x69) >= 0) { // 'i' flag |
| bflags |= UREGEX_CASE_INSENSITIVE; |
| } |
| if (flags.indexOf((UChar)0x78) >= 0) { // 'x' flag |
| bflags |= UREGEX_COMMENTS; |
| } |
| if (flags.indexOf((UChar)0x73) >= 0) { // 's' flag |
| bflags |= UREGEX_DOTALL; |
| } |
| if (flags.indexOf((UChar)0x6d) >= 0) { // 'm' flag |
| bflags |= UREGEX_MULTILINE; |
| } |
| |
| |
| callerPattern = RegexPattern::compile(pattern, bflags, pe, status); |
| if (status != U_ZERO_ERROR) { |
| #if UCONFIG_NO_BREAK_ITERATION==1 |
| // 'v' test flag means that the test pattern should not compile if ICU was configured |
| // to not include break iteration. RBBI is needed for Unicode word boundaries. |
| if (flags.indexOf((UChar)0x76) >= 0 /*'v'*/ && status == U_UNSUPPORTED_ERROR) { |
| goto cleanupAndReturn; |
| } |
| #endif |
| errln("Line %d: error %s compiling pattern.", line, u_errorName(status)); |
| goto cleanupAndReturn; |
| } |
| |
| if (flags.indexOf((UChar)'d') >= 0) { |
| RegexPatternDump(callerPattern); |
| } |
| |
| // |
| // Number of times find() should be called on the test string, default to 1 |
| // |
| numFinds = 1; |
| for (i=2; i<=9; i++) { |
| if (flags.indexOf((UChar)(0x30 + i)) >= 0) { // digit flag |
| if (numFinds != 1) { |
| errln("Line %d: more than one digit flag. Scanning %d.", line, i); |
| goto cleanupAndReturn; |
| } |
| numFinds = i; |
| } |
| } |
| |
| // |
| // Find the tags in the input data, remove them, and record the group boundary |
| // positions. |
| // |
| parsePat = RegexPattern::compile("<(/?)([0-9]+)>", 0, pe, status); |
| REGEX_CHECK_STATUS_L(line); |
| |
| unEscapedInput = inputString.unescape(); |
| parseMatcher = parsePat->matcher(unEscapedInput, status); |
| REGEX_CHECK_STATUS_L(line); |
| while(parseMatcher->find()) { |
| parseMatcher->appendReplacement(deTaggedInput, "", status); |
| REGEX_CHECK_STATUS; |
| UnicodeString groupNum = parseMatcher->group(2, status); |
| if (parseMatcher->group(1, status) == "/") { |
| // close tag |
| set(groupEnds, deTaggedInput.length(), groupNum); |
| } else { |
| set(groupStarts, deTaggedInput.length(), groupNum); |
| } |
| } |
| parseMatcher->appendTail(deTaggedInput); |
| REGEX_ASSERT_L(groupStarts.size() == groupEnds.size(), line); |
| |
| |
| // |
| // Do a find on the de-tagged input using the caller's pattern |
| // |
| matcher = callerPattern->matcher(deTaggedInput, status); |
| REGEX_CHECK_STATUS_L(line); |
| if (flags.indexOf((UChar)'t') >= 0) { |
| matcher->setTrace(TRUE); |
| } |
| |
| for (i=0; i<numFinds; i++) { |
| isMatch = matcher->find(); |
| } |
| matcher->setTrace(FALSE); |
| |
| // |
| // Match up the groups from the find() with the groups from the tags |
| // |
| |
| // number of tags should match number of groups from find operation. |
| // matcher->groupCount does not include group 0, the entire match, hence the +1. |
| // G option in test means that capture group data is not available in the |
| // expected results, so the check needs to be suppressed. |
| if (isMatch == FALSE && groupStarts.size() != 0) { |
| errln("Error at line %d: Match expected, but none found.\n", line); |
| failed = TRUE; |
| goto cleanupAndReturn; |
| } |
| |
| if (flags.indexOf((UChar)0x47 /*G*/) >= 0) { |
| // Only check for match / no match. Don't check capture groups. |
| if (isMatch && groupStarts.size() == 0) { |
| errln("Error at line %d: No match expected, but one found.\n", line); |
| failed = TRUE; |
| } |
| goto cleanupAndReturn; |
| } |
| |
| for (i=0; i<=matcher->groupCount(); i++) { |
| int32_t expectedStart = (i >= groupStarts.size()? -1 : groupStarts.elementAti(i)); |
| if (matcher->start(i, status) != expectedStart) { |
| errln("Error at line %d: incorrect start position for group %d. Expected %d, got %d", |
| line, i, expectedStart, matcher->start(i, status)); |
| failed = TRUE; |
| goto cleanupAndReturn; // Good chance of subsequent bogus errors. Stop now. |
| } |
| int32_t expectedEnd = (i >= groupEnds.size()? -1 : groupEnds.elementAti(i)); |
| if (matcher->end(i, status) != expectedEnd) { |
| errln("Error at line %d: incorrect end position for group %d. Expected %d, got %d", |
| line, i, expectedEnd, matcher->end(i, status)); |
| failed = TRUE; |
| // Error on end position; keep going; real error is probably yet to come as group |
| // end positions work from end of the input data towards the front. |
| } |
| } |
| if ( matcher->groupCount()+1 < groupStarts.size()) { |
| errln("Error at line %d: Expected %d capture groups, found %d.", |
| line, groupStarts.size()-1, matcher->groupCount()); |
| failed = TRUE; |
| } |
| |
| cleanupAndReturn: |
| if (failed) { |
| errln("\"%s\" %s \"%s\"", (const char *)CharString(pattern, 0), |
| (const char *)CharString(flags, 0), |
| (const char *)CharString(inputString, 0)); |
| // callerPattern->dump(); |
| } |
| delete parseMatcher; |
| delete parsePat; |
| delete matcher; |
| delete callerPattern; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // REGEX_ERR Macro + invocation function to simplify writing tests |
| // regex tests for incorrect patterns |
| // |
| // usage: |
| // REGEX_ERR("pattern", expected error line, column, expected status); |
| // |
| //--------------------------------------------------------------------------- |
| #define REGEX_ERR(pat, line, col, status) regex_err(pat, line, col, status, __LINE__); |
| |
| void RegexTest::regex_err(const char *pat, int32_t errLine, int32_t errCol, |
| UErrorCode expectedStatus, int line) { |
| UnicodeString pattern(pat); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| UParseError pe; |
| RegexPattern *callerPattern = NULL; |
| |
| // |
| // Compile the caller's pattern |
| // |
| UnicodeString patString(pat); |
| callerPattern = RegexPattern::compile(patString, 0, pe, status); |
| if (status != expectedStatus) { |
| errln("Line %d: unexpected error %s compiling pattern.", line, u_errorName(status)); |
| } else { |
| if (status != U_ZERO_ERROR) { |
| if (pe.line != errLine || pe.offset != errCol) { |
| errln("Line %d: incorrect line/offset from UParseError. Expected %d/%d; got %d/%d.\n", |
| line, errLine, errCol, pe.line, pe.offset); |
| } |
| } |
| } |
| |
| delete callerPattern; |
| } |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // Basic Check for basic functionality of regex pattern matching. |
| // Avoid the use of REGEX_FIND test macro, which has |
| // substantial dependencies on basic Regex functionality. |
| // |
| //--------------------------------------------------------------------------- |
| void RegexTest::Basic() { |
| |
| |
| // |
| // Debug - slide failing test cases early |
| // |
| #if 0 |
| { |
| // REGEX_TESTLM("a\N{LATIN SMALL LETTER B}c", "abc", FALSE, FALSE); |
| UParseError pe; |
| UErrorCode status = U_ZERO_ERROR; |
| RegexPattern::compile("^(?:a?b?)*$", 0, pe, status); |
| // REGEX_FIND("(?>(abc{2,4}?))(c*)", "<0>ab<1>cc</1><2>ccc</2></0>ddd"); |
| // REGEX_FIND("(X([abc=X]+)+X)|(y[abc=]+)", "=XX===================="); |
| } |
| exit(1); |
| #endif |
| |
| |
| // |
| // Pattern with parentheses |
| // |
| REGEX_TESTLM("st(abc)ring", "stabcring thing", TRUE, FALSE); |
| REGEX_TESTLM("st(abc)ring", "stabcring", TRUE, TRUE); |
| REGEX_TESTLM("st(abc)ring", "stabcrung", FALSE, FALSE); |
| |
| // |
| // Patterns with * |
| // |
| REGEX_TESTLM("st(abc)*ring", "string", TRUE, TRUE); |
| REGEX_TESTLM("st(abc)*ring", "stabcring", TRUE, TRUE); |
| REGEX_TESTLM("st(abc)*ring", "stabcabcring", TRUE, TRUE); |
| REGEX_TESTLM("st(abc)*ring", "stabcabcdring", FALSE, FALSE); |
| REGEX_TESTLM("st(abc)*ring", "stabcabcabcring etc.", TRUE, FALSE); |
| |
| REGEX_TESTLM("a*", "", TRUE, TRUE); |
| REGEX_TESTLM("a*", "b", TRUE, FALSE); |
| |
| |
| // |
| // Patterns with "." |
| // |
| REGEX_TESTLM(".", "abc", TRUE, FALSE); |
| REGEX_TESTLM("...", "abc", TRUE, TRUE); |
| REGEX_TESTLM("....", "abc", FALSE, FALSE); |
| REGEX_TESTLM(".*", "abcxyz123", TRUE, TRUE); |
| REGEX_TESTLM("ab.*xyz", "abcdefghij", FALSE, FALSE); |
| REGEX_TESTLM("ab.*xyz", "abcdefg...wxyz", TRUE, TRUE); |
| REGEX_TESTLM("ab.*xyz", "abcde...wxyz...abc..xyz", TRUE, TRUE); |
| REGEX_TESTLM("ab.*xyz", "abcde...wxyz...abc..xyz...", TRUE, FALSE); |
| |
| // |
| // Patterns with * applied to chars at end of literal string |
| // |
| REGEX_TESTLM("abc*", "ab", TRUE, TRUE); |
| REGEX_TESTLM("abc*", "abccccc", TRUE, TRUE); |
| |
| // |
| // Supplemental chars match as single chars, not a pair of surrogates. |
| // |
| REGEX_TESTLM(".", "\\U00011000", TRUE, TRUE); |
| REGEX_TESTLM("...", "\\U00011000x\\U00012002", TRUE, TRUE); |
| REGEX_TESTLM("...", "\\U00011000x\\U00012002y", TRUE, FALSE); |
| |
| |
| // |
| // UnicodeSets in the pattern |
| // |
| REGEX_TESTLM("[1-6]", "1", TRUE, TRUE); |
| REGEX_TESTLM("[1-6]", "3", TRUE, TRUE); |
| REGEX_TESTLM("[1-6]", "7", FALSE, FALSE); |
| REGEX_TESTLM("a[1-6]", "a3", TRUE, TRUE); |
| REGEX_TESTLM("a[1-6]", "a3", TRUE, TRUE); |
| REGEX_TESTLM("a[1-6]b", "a3b", TRUE, TRUE); |
| |
| REGEX_TESTLM("a[0-9]*b", "a123b", TRUE, TRUE); |
| REGEX_TESTLM("a[0-9]*b", "abc", TRUE, FALSE); |
| REGEX_TESTLM("[\\p{Nd}]*", "123456", TRUE, TRUE); |
| REGEX_TESTLM("[\\p{Nd}]*", "a123456", TRUE, FALSE); // note that * matches 0 occurences. |
| REGEX_TESTLM("[a][b][[:Zs:]]*", "ab ", TRUE, TRUE); |
| |
| // |
| // OR operator in patterns |
| // |
| REGEX_TESTLM("(a|b)", "a", TRUE, TRUE); |
| REGEX_TESTLM("(a|b)", "b", TRUE, TRUE); |
| REGEX_TESTLM("(a|b)", "c", FALSE, FALSE); |
| REGEX_TESTLM("a|b", "b", TRUE, TRUE); |
| |
| REGEX_TESTLM("(a|b|c)*", "aabcaaccbcabc", TRUE, TRUE); |
| REGEX_TESTLM("(a|b|c)*", "aabcaaccbcabdc", TRUE, FALSE); |
| REGEX_TESTLM("(a(b|c|d)(x|y|z)*|123)", "ac", TRUE, TRUE); |
| REGEX_TESTLM("(a(b|c|d)(x|y|z)*|123)", "123", TRUE, TRUE); |
| REGEX_TESTLM("(a|(1|2)*)(b|c|d)(x|y|z)*|123", "123", TRUE, TRUE); |
| REGEX_TESTLM("(a|(1|2)*)(b|c|d)(x|y|z)*|123", "222211111czzzzw", TRUE, FALSE); |
| |
| // |
| // + |
| // |
| REGEX_TESTLM("ab+", "abbc", TRUE, FALSE); |
| REGEX_TESTLM("ab+c", "ac", FALSE, FALSE); |
| REGEX_TESTLM("b+", "", FALSE, FALSE); |
| REGEX_TESTLM("(abc|def)+", "defabc", TRUE, TRUE); |
| REGEX_TESTLM(".+y", "zippity dooy dah ", TRUE, FALSE); |
| REGEX_TESTLM(".+y", "zippity dooy", TRUE, TRUE); |
| |
| // |
| // ? |
| // |
| REGEX_TESTLM("ab?", "ab", TRUE, TRUE); |
| REGEX_TESTLM("ab?", "a", TRUE, TRUE); |
| REGEX_TESTLM("ab?", "ac", TRUE, FALSE); |
| REGEX_TESTLM("ab?", "abb", TRUE, FALSE); |
| REGEX_TESTLM("a(b|c)?d", "abd", TRUE, TRUE); |
| REGEX_TESTLM("a(b|c)?d", "acd", TRUE, TRUE); |
| REGEX_TESTLM("a(b|c)?d", "ad", TRUE, TRUE); |
| REGEX_TESTLM("a(b|c)?d", "abcd", FALSE, FALSE); |
| REGEX_TESTLM("a(b|c)?d", "ab", FALSE, FALSE); |
| |
| // |
| // Escape sequences that become single literal chars, handled internally |
| // by ICU's Unescape. |
| // |
| |
| // REGEX_TESTLM("\101\142", "Ab", TRUE, TRUE); // Octal TODO: not implemented yet. |
| REGEX_TESTLM("\\a", "\\u0007", TRUE, TRUE); // BEL |
| REGEX_TESTLM("\\cL", "\\u000c", TRUE, TRUE); // Control-L |
| REGEX_TESTLM("\\e", "\\u001b", TRUE, TRUE); // Escape |
| REGEX_TESTLM("\\f", "\\u000c", TRUE, TRUE); // Form Feed |
| REGEX_TESTLM("\\n", "\\u000a", TRUE, TRUE); // new line |
| REGEX_TESTLM("\\r", "\\u000d", TRUE, TRUE); // CR |
| REGEX_TESTLM("\\t", "\\u0009", TRUE, TRUE); // Tab |
| REGEX_TESTLM("\\u1234", "\\u1234", TRUE, TRUE); |
| REGEX_TESTLM("\\U00001234", "\\u1234", TRUE, TRUE); |
| |
| REGEX_TESTLM(".*\\Ax", "xyz", TRUE, FALSE); // \A matches only at the beginning of input |
| REGEX_TESTLM(".*\\Ax", " xyz", FALSE, FALSE); // \A matches only at the beginning of input |
| |
| // Escape of special chars in patterns |
| REGEX_TESTLM("\\\\\\|\\(\\)\\[\\{\\~\\$\\*\\+\\?\\.", "\\\\|()[{~$*+?.", TRUE, TRUE); |
| |
| |
| }; |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // API_Match Test that the API for class RegexMatcher |
| // is present and nominally working, but excluding functions |
| // implementing replace operations. |
| // |
| //--------------------------------------------------------------------------- |
| void RegexTest::API_Match() { |
| UParseError pe; |
| UErrorCode status=U_ZERO_ERROR; |
| int32_t flags = 0; |
| |
| // |
| // Debug - slide failing test cases early |
| // |
| #if 0 |
| { |
| } |
| return; |
| #endif |
| |
| // |
| // Simple pattern compilation |
| // |
| { |
| UnicodeString re("abc"); |
| RegexPattern *pat2; |
| pat2 = RegexPattern::compile(re, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| |
| UnicodeString inStr1 = "abcdef this is a test"; |
| UnicodeString instr2 = "not abc"; |
| UnicodeString empty = ""; |
| |
| |
| // |
| // Matcher creation and reset. |
| // |
| RegexMatcher *m1 = pat2->matcher(inStr1, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(m1->lookingAt(status) == TRUE); |
| REGEX_ASSERT(m1->input() == inStr1); |
| m1->reset(instr2); |
| REGEX_ASSERT(m1->lookingAt(status) == FALSE); |
| REGEX_ASSERT(m1->input() == instr2); |
| m1->reset(inStr1); |
| REGEX_ASSERT(m1->input() == inStr1); |
| REGEX_ASSERT(m1->lookingAt(status) == TRUE); |
| m1->reset(empty); |
| REGEX_ASSERT(m1->lookingAt(status) == FALSE); |
| REGEX_ASSERT(m1->input() == empty); |
| REGEX_ASSERT(&m1->pattern() == pat2); |
| |
| // |
| // reset(pos, status) |
| // |
| m1->reset(inStr1); |
| m1->reset(4, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(m1->input() == inStr1); |
| REGEX_ASSERT(m1->lookingAt(status) == TRUE); |
| |
| m1->reset(-1, status); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| status = U_ZERO_ERROR; |
| |
| m1->reset(0, status); |
| REGEX_CHECK_STATUS; |
| status = U_ZERO_ERROR; |
| |
| int32_t len = m1->input().length(); |
| m1->reset(len-1, status); |
| REGEX_CHECK_STATUS; |
| status = U_ZERO_ERROR; |
| |
| m1->reset(len, status); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| status = U_ZERO_ERROR; |
| |
| // |
| // match(pos, status) |
| // |
| m1->reset(instr2); |
| REGEX_ASSERT(m1->matches(4, status) == TRUE); |
| m1->reset(); |
| REGEX_ASSERT(m1->matches(3, status) == FALSE); |
| m1->reset(); |
| REGEX_ASSERT(m1->matches(5, status) == FALSE); |
| REGEX_ASSERT(m1->matches(4, status) == TRUE); |
| REGEX_ASSERT(m1->matches(-1, status) == FALSE); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| // Match() at end of string should fail, but should not |
| // be an error. |
| status = U_ZERO_ERROR; |
| len = m1->input().length(); |
| REGEX_ASSERT(m1->matches(len, status) == FALSE); |
| REGEX_CHECK_STATUS; |
| |
| // Match beyond end of string should fail with an error. |
| status = U_ZERO_ERROR; |
| REGEX_ASSERT(m1->matches(len+1, status) == FALSE); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| // Successful match at end of string. |
| { |
| status = U_ZERO_ERROR; |
| RegexMatcher m("A?", 0, status); // will match zero length string. |
| REGEX_CHECK_STATUS; |
| m.reset(inStr1); |
| len = inStr1.length(); |
| REGEX_ASSERT(m.matches(len, status) == TRUE); |
| REGEX_CHECK_STATUS; |
| m.reset(empty); |
| REGEX_ASSERT(m.matches(0, status) == TRUE); |
| REGEX_CHECK_STATUS; |
| } |
| |
| |
| // |
| // lookingAt(pos, status) |
| // |
| status = U_ZERO_ERROR; |
| m1->reset(instr2); // "not abc" |
| REGEX_ASSERT(m1->lookingAt(4, status) == TRUE); |
| REGEX_ASSERT(m1->lookingAt(5, status) == FALSE); |
| REGEX_ASSERT(m1->lookingAt(3, status) == FALSE); |
| REGEX_ASSERT(m1->lookingAt(4, status) == TRUE); |
| REGEX_ASSERT(m1->lookingAt(-1, status) == FALSE); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| status = U_ZERO_ERROR; |
| len = m1->input().length(); |
| REGEX_ASSERT(m1->lookingAt(len, status) == FALSE); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(m1->lookingAt(len+1, status) == FALSE); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| delete m1; |
| delete pat2; |
| } |
| |
| |
| // |
| // Capture Group. |
| // RegexMatcher::start(); |
| // RegexMatcher::end(); |
| // RegexMatcher::groupCount(); |
| // |
| { |
| int32_t flags=0; |
| UParseError pe; |
| UErrorCode status=U_ZERO_ERROR; |
| |
| UnicodeString re("01(23(45)67)(.*)"); |
| RegexPattern *pat = RegexPattern::compile(re, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString data = "0123456789"; |
| |
| RegexMatcher *matcher = pat->matcher(data, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(matcher->lookingAt(status) == TRUE); |
| int matchStarts[] = {0, 2, 4, 8}; |
| int matchEnds[] = {10, 8, 6, 10}; |
| int i; |
| for (i=0; i<4; i++) { |
| int32_t actualStart = matcher->start(i, status); |
| REGEX_CHECK_STATUS; |
| if (actualStart != matchStarts[i]) { |
| errln("RegexTest failure at line %d, index %d. Expected %d, got %d\n", |
| __LINE__, i, matchStarts[i], actualStart); |
| } |
| int32_t actualEnd = matcher->end(i, status); |
| REGEX_CHECK_STATUS; |
| if (actualEnd != matchEnds[i]) { |
| errln("RegexTest failure at line %d index %d. Expected %d, got %d\n", |
| __LINE__, i, matchEnds[i], actualEnd); |
| } |
| } |
| |
| REGEX_ASSERT(matcher->start(0, status) == matcher->start(status)); |
| REGEX_ASSERT(matcher->end(0, status) == matcher->end(status)); |
| |
| REGEX_ASSERT_FAIL(matcher->start(-1, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| REGEX_ASSERT_FAIL(matcher->start( 4, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| matcher->reset(); |
| REGEX_ASSERT_FAIL(matcher->start( 0, status), U_REGEX_INVALID_STATE); |
| |
| matcher->lookingAt(status); |
| REGEX_ASSERT(matcher->group(status) == "0123456789"); |
| REGEX_ASSERT(matcher->group(0, status) == "0123456789"); |
| REGEX_ASSERT(matcher->group(1, status) == "234567" ); |
| REGEX_ASSERT(matcher->group(2, status) == "45" ); |
| REGEX_ASSERT(matcher->group(3, status) == "89" ); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT_FAIL(matcher->group(-1, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| REGEX_ASSERT_FAIL(matcher->group( 4, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| matcher->reset(); |
| REGEX_ASSERT_FAIL(matcher->group( 0, status), U_REGEX_INVALID_STATE); |
| |
| delete matcher; |
| delete pat; |
| |
| } |
| |
| // |
| // find |
| // |
| { |
| int32_t flags=0; |
| UParseError pe; |
| UErrorCode status=U_ZERO_ERROR; |
| |
| UnicodeString re("abc"); |
| RegexPattern *pat = RegexPattern::compile(re, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString data = ".abc..abc...abc.."; |
| // 012345678901234567 |
| |
| RegexMatcher *matcher = pat->matcher(data, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 1); |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 6); |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 12); |
| REGEX_ASSERT(matcher->find() == FALSE); |
| REGEX_ASSERT(matcher->find() == FALSE); |
| |
| matcher->reset(); |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 1); |
| |
| REGEX_ASSERT(matcher->find(0, status)); |
| REGEX_ASSERT(matcher->start(status) == 1); |
| REGEX_ASSERT(matcher->find(1, status)); |
| REGEX_ASSERT(matcher->start(status) == 1); |
| REGEX_ASSERT(matcher->find(2, status)); |
| REGEX_ASSERT(matcher->start(status) == 6); |
| REGEX_ASSERT(matcher->find(12, status)); |
| REGEX_ASSERT(matcher->start(status) == 12); |
| REGEX_ASSERT(matcher->find(13, status) == FALSE); |
| REGEX_ASSERT(matcher->find(16, status) == FALSE); |
| REGEX_ASSERT(matcher->find(17, status) == FALSE); |
| REGEX_ASSERT_FAIL(matcher->start(status), U_REGEX_INVALID_STATE); |
| |
| status = U_ZERO_ERROR; |
| REGEX_ASSERT_FAIL(matcher->find(-1, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| status = U_ZERO_ERROR; |
| REGEX_ASSERT_FAIL(matcher->find(18, status), U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| REGEX_ASSERT(matcher->groupCount() == 0); |
| |
| delete matcher; |
| delete pat; |
| } |
| |
| |
| // |
| // find, with \G in pattern (true if at the end of a previous match). |
| // |
| { |
| int32_t flags=0; |
| UParseError pe; |
| UErrorCode status=U_ZERO_ERROR; |
| |
| UnicodeString re(".*?(?:(\\Gabc)|(abc))"); |
| RegexPattern *pat = RegexPattern::compile(re, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString data = ".abcabc.abc.."; |
| // 012345678901234567 |
| |
| RegexMatcher *matcher = pat->matcher(data, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 0); |
| REGEX_ASSERT(matcher->start(1, status) == -1); |
| REGEX_ASSERT(matcher->start(2, status) == 1); |
| |
| REGEX_ASSERT(matcher->find()); |
| REGEX_ASSERT(matcher->start(status) == 4); |
| REGEX_ASSERT(matcher->start(1, status) == 4); |
| REGEX_ASSERT(matcher->start(2, status) == -1); |
| REGEX_CHECK_STATUS; |
| |
| delete matcher; |
| delete pat; |
| } |
| |
| // |
| // find with zero length matches, match position should bump ahead |
| // to prevent loops. |
| // |
| { |
| int i; |
| UErrorCode status=U_ZERO_ERROR; |
| RegexMatcher m("(?= ?)", 0, status); // This pattern will zero-length matches anywhere, |
| // using an always-true look-ahead. |
| REGEX_CHECK_STATUS; |
| UnicodeString s(" "); |
| m.reset(s); |
| for (i=0; ; i++) { |
| if (m.find() == FALSE) { |
| break; |
| } |
| REGEX_ASSERT(m.start(status) == i); |
| REGEX_ASSERT(m.end(status) == i); |
| } |
| REGEX_ASSERT(i==5); |
| |
| // Check that the bump goes over surrogate pairs OK |
| s = "\\U00010001\\U00010002\\U00010003\\U00010004"; |
| s = s.unescape(); |
| m.reset(s); |
| for (i=0; ; i+=2) { |
| if (m.find() == FALSE) { |
| break; |
| } |
| REGEX_ASSERT(m.start(status) == i); |
| REGEX_ASSERT(m.end(status) == i); |
| } |
| REGEX_ASSERT(i==10); |
| } |
| { |
| // find() loop breaking test. |
| // with pattern of /.?/, should see a series of one char matches, then a single |
| // match of zero length at the end of the input string. |
| int i; |
| UErrorCode status=U_ZERO_ERROR; |
| RegexMatcher m(".?", 0, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString s(" "); |
| m.reset(s); |
| for (i=0; ; i++) { |
| if (m.find() == FALSE) { |
| break; |
| } |
| REGEX_ASSERT(m.start(status) == i); |
| REGEX_ASSERT(m.end(status) == (i<4 ? i+1 : i)); |
| } |
| REGEX_ASSERT(i==5); |
| } |
| |
| |
| // |
| // Matchers with no input string behave as if they had an empty input string. |
| // |
| |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| RegexMatcher m(".?", 0, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(m.find()); |
| REGEX_ASSERT(m.start(status) == 0); |
| REGEX_ASSERT(m.input() == ""); |
| } |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| RegexPattern *p = RegexPattern::compile(".", 0, status); |
| RegexMatcher *m = p->matcher(status); |
| REGEX_CHECK_STATUS; |
| |
| REGEX_ASSERT(m->find() == FALSE); |
| REGEX_ASSERT(m->input() == ""); |
| delete m; |
| delete p; |
| } |
| |
| // |
| // Compilation error on reset with UChar * |
| // These were a hazard that people were stumbling over with runtime errors. |
| // Changed them to compiler errors by adding private methods that more closely |
| // matched the incorrect use of the functions. |
| // |
| #if 0 |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| UChar ucharString[20]; |
| RegexMatcher m(".", 0, status); |
| m.reset(ucharString); // should not compile. |
| |
| RegexPattern *p = RegexPattern::compile(".", 0, status); |
| RegexMatcher *m2 = p->matcher(ucharString, status); // should not compile. |
| |
| RegexMatcher m3(".", ucharString, 0, status); // Should not compile |
| } |
| #endif |
| |
| } |
| |
| |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // API_Replace API test for class RegexMatcher, testing the |
| // Replace family of functions. |
| // |
| //--------------------------------------------------------------------------- |
| void RegexTest::API_Replace() { |
| // |
| // Replace |
| // |
| int32_t flags=0; |
| UParseError pe; |
| UErrorCode status=U_ZERO_ERROR; |
| |
| UnicodeString re("abc"); |
| RegexPattern *pat = RegexPattern::compile(re, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString data = ".abc..abc...abc.."; |
| // 012345678901234567 |
| RegexMatcher *matcher = pat->matcher(data, status); |
| |
| // |
| // Plain vanilla matches. |
| // |
| UnicodeString dest; |
| dest = matcher->replaceFirst("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ".yz..abc...abc.."); |
| |
| dest = matcher->replaceAll("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ".yz..yz...yz.."); |
| |
| // |
| // Plain vanilla non-matches. |
| // |
| UnicodeString d2 = ".abx..abx...abx.."; |
| matcher->reset(d2); |
| dest = matcher->replaceFirst("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ".abx..abx...abx.."); |
| |
| dest = matcher->replaceAll("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ".abx..abx...abx.."); |
| |
| // |
| // Empty source string |
| // |
| UnicodeString d3 = ""; |
| matcher->reset(d3); |
| dest = matcher->replaceFirst("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ""); |
| |
| dest = matcher->replaceAll("yz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == ""); |
| |
| // |
| // Empty substitution string |
| // |
| matcher->reset(data); // ".abc..abc...abc.." |
| dest = matcher->replaceFirst("", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "...abc...abc.."); |
| |
| dest = matcher->replaceAll("", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "........"); |
| |
| // |
| // match whole string |
| // |
| UnicodeString d4 = "abc"; |
| matcher->reset(d4); |
| dest = matcher->replaceFirst("xyz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "xyz"); |
| |
| dest = matcher->replaceAll("xyz", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "xyz"); |
| |
| // |
| // Capture Group, simple case |
| // |
| UnicodeString re2("a(..)"); |
| RegexPattern *pat2 = RegexPattern::compile(re2, flags, pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString d5 = "abcdefg"; |
| RegexMatcher *matcher2 = pat2->matcher(d5, status); |
| REGEX_CHECK_STATUS; |
| dest = matcher2->replaceFirst("$1$1", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "bcbcdefg"); |
| |
| dest = matcher2->replaceFirst("The value of \\$1 is $1.", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "The value of $1 is bc.defg"); |
| |
| dest = matcher2->replaceFirst("$ by itself, no group number $$$", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "$ by itself, no group number $$$defg"); |
| |
| UnicodeString replacement = "Supplemental Digit 1 $\\U0001D7CF."; |
| replacement = replacement.unescape(); |
| dest = matcher2->replaceFirst(replacement, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(dest == "Supplemental Digit 1 bc.defg"); |
| |
| REGEX_ASSERT_FAIL(matcher2->replaceFirst("bad capture group number $5...",status), U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| |
| // |
| // Replacement String with \u hex escapes |
| // |
| { |
| UnicodeString src = "abc 1 abc 2 abc 3"; |
| UnicodeString substitute = "--\\u0043--"; |
| matcher->reset(src); |
| UnicodeString result = matcher->replaceAll(substitute, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(result == "--C-- 1 --C-- 2 --C-- 3"); |
| } |
| { |
| UnicodeString src = "abc !"; |
| UnicodeString substitute = "--\\U00010000--"; |
| matcher->reset(src); |
| UnicodeString result = matcher->replaceAll(substitute, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString expected = UnicodeString("--"); |
| expected.append((UChar32)0x10000); |
| expected.append("-- !"); |
| REGEX_ASSERT(result == expected); |
| } |
| // TODO: need more through testing of capture substitutions. |
| |
| // Bug 4057 |
| // |
| { |
| status = U_ZERO_ERROR; |
| UnicodeString s = "The matches start with ss and end with ee ss stuff ee fin"; |
| RegexMatcher m("ss(.*?)ee", 0, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString result; |
| |
| // Multiple finds do NOT bump up the previous appendReplacement postion. |
| m.reset(s); |
| m.find(); |
| m.find(); |
| m.appendReplacement(result, "ooh", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(result == "The matches start with ss and end with ee ooh"); |
| |
| // After a reset into the interior of a string, appendReplacemnt still starts at beginning. |
| status = U_ZERO_ERROR; |
| result.truncate(0); |
| m.reset(10, status); |
| m.find(); |
| m.find(); |
| m.appendReplacement(result, "ooh", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(result == "The matches start with ss and end with ee ooh"); |
| |
| // find() at interior of string, appendReplacemnt still starts at beginning. |
| status = U_ZERO_ERROR; |
| result.truncate(0); |
| m.reset(); |
| m.find(10, status); |
| m.find(); |
| m.appendReplacement(result, "ooh", status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(result == "The matches start with ss and end with ee ooh"); |
| |
| m.appendTail(result); |
| REGEX_ASSERT(result == "The matches start with ss and end with ee ooh fin"); |
| |
| } |
| |
| delete matcher2; |
| delete pat2; |
| delete matcher; |
| delete pat; |
| } |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // API_Pattern Test that the API for class RegexPattern is |
| // present and nominally working. |
| // |
| //--------------------------------------------------------------------------- |
| void RegexTest::API_Pattern() { |
| RegexPattern pata; // Test default constructor to not crash. |
| RegexPattern patb; |
| |
| REGEX_ASSERT(pata == patb); |
| REGEX_ASSERT(pata == pata); |
| |
| UnicodeString re1("abc[a-l][m-z]"); |
| UnicodeString re2("def"); |
| UErrorCode status = U_ZERO_ERROR; |
| UParseError pe; |
| |
| RegexPattern *pat1 = RegexPattern::compile(re1, 0, pe, status); |
| RegexPattern *pat2 = RegexPattern::compile(re2, 0, pe, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(*pat1 == *pat1); |
| REGEX_ASSERT(*pat1 != pata); |
| |
| // Assign |
| patb = *pat1; |
| REGEX_ASSERT(patb == *pat1); |
| |
| // Copy Construct |
| RegexPattern patc(*pat1); |
| REGEX_ASSERT(patc == *pat1); |
| REGEX_ASSERT(patb == patc); |
| REGEX_ASSERT(pat1 != pat2); |
| patb = *pat2; |
| REGEX_ASSERT(patb != patc); |
| REGEX_ASSERT(patb == *pat2); |
| |
| // Compile with no flags. |
| RegexPattern *pat1a = RegexPattern::compile(re1, pe, status); |
| REGEX_ASSERT(*pat1a == *pat1); |
| |
| REGEX_ASSERT(pat1a->flags() == 0); |
| |
| // Compile with different flags should be not equal |
| RegexPattern *pat1b = RegexPattern::compile(re1, UREGEX_CASE_INSENSITIVE, pe, status); |
| REGEX_CHECK_STATUS; |
| |
| REGEX_ASSERT(*pat1b != *pat1a); |
| REGEX_ASSERT(pat1b->flags() == UREGEX_CASE_INSENSITIVE); |
| REGEX_ASSERT(pat1a->flags() == 0); |
| delete pat1b; |
| |
| // clone |
| RegexPattern *pat1c = pat1->clone(); |
| REGEX_ASSERT(*pat1c == *pat1); |
| REGEX_ASSERT(*pat1c != *pat2); |
| |
| delete pat1c; |
| delete pat1a; |
| delete pat1; |
| delete pat2; |
| |
| |
| // |
| // Verify that a matcher created from a cloned pattern works. |
| // (Jitterbug 3423) |
| // |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| RegexPattern *pSource = RegexPattern::compile("\\p{L}+", 0, status); |
| RegexPattern *pClone = pSource->clone(); |
| delete pSource; |
| RegexMatcher *mFromClone = pClone->matcher(status); |
| REGEX_CHECK_STATUS; |
| UnicodeString s = "Hello World"; |
| mFromClone->reset(s); |
| REGEX_ASSERT(mFromClone->find() == TRUE); |
| REGEX_ASSERT(mFromClone->group(status) == "Hello"); |
| REGEX_ASSERT(mFromClone->find() == TRUE); |
| REGEX_ASSERT(mFromClone->group(status) == "World"); |
| REGEX_ASSERT(mFromClone->find() == FALSE); |
| delete mFromClone; |
| delete pClone; |
| } |
| |
| // |
| // matches convenience API |
| // |
| REGEX_ASSERT(RegexPattern::matches(".*", "random input", pe, status) == TRUE); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(RegexPattern::matches("abc", "random input", pe, status) == FALSE); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(RegexPattern::matches(".*nput", "random input", pe, status) == TRUE); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(RegexPattern::matches("random input", "random input", pe, status) == TRUE); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(RegexPattern::matches(".*u", "random input", pe, status) == FALSE); |
| REGEX_CHECK_STATUS; |
| status = U_INDEX_OUTOFBOUNDS_ERROR; |
| REGEX_ASSERT(RegexPattern::matches("abc", "abc", pe, status) == FALSE); |
| REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR); |
| |
| |
| // |
| // Split() |
| // |
| status = U_ZERO_ERROR; |
| pat1 = RegexPattern::compile(" +", pe, status); |
| REGEX_CHECK_STATUS; |
| UnicodeString fields[10]; |
| |
| int32_t n; |
| n = pat1->split("Now is the time", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==4); |
| REGEX_ASSERT(fields[0]=="Now"); |
| REGEX_ASSERT(fields[1]=="is"); |
| REGEX_ASSERT(fields[2]=="the"); |
| REGEX_ASSERT(fields[3]=="time"); |
| REGEX_ASSERT(fields[4]==""); |
| |
| n = pat1->split("Now is the time", fields, 2, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==2); |
| REGEX_ASSERT(fields[0]=="Now"); |
| REGEX_ASSERT(fields[1]=="is the time"); |
| REGEX_ASSERT(fields[2]=="the"); // left over from previous test |
| |
| fields[1] = "*"; |
| status = U_ZERO_ERROR; |
| n = pat1->split("Now is the time", fields, 1, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==1); |
| REGEX_ASSERT(fields[0]=="Now is the time"); |
| REGEX_ASSERT(fields[1]=="*"); |
| status = U_ZERO_ERROR; |
| |
| n = pat1->split(" Now is the time ", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==5); |
| REGEX_ASSERT(fields[0]==""); |
| REGEX_ASSERT(fields[1]=="Now"); |
| REGEX_ASSERT(fields[2]=="is"); |
| REGEX_ASSERT(fields[3]=="the"); |
| REGEX_ASSERT(fields[4]=="time"); |
| REGEX_ASSERT(fields[5]==""); |
| |
| n = pat1->split(" ", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==1); |
| REGEX_ASSERT(fields[0]==""); |
| |
| fields[0] = "foo"; |
| n = pat1->split("", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==0); |
| REGEX_ASSERT(fields[0]=="foo"); |
| |
| delete pat1; |
| |
| // split, with a pattern with (capture) |
| pat1 = RegexPattern::compile("<(\\w*)>", pe, status); |
| REGEX_CHECK_STATUS; |
| |
| status = U_ZERO_ERROR; |
| n = pat1->split("<a>Now is <b>the time<c>", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==6); |
| REGEX_ASSERT(fields[0]==""); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="b"); |
| REGEX_ASSERT(fields[4]=="the time"); |
| REGEX_ASSERT(fields[5]=="c"); |
| REGEX_ASSERT(fields[6]==""); |
| REGEX_ASSERT(status==U_ZERO_ERROR); |
| |
| n = pat1->split(" <a>Now is <b>the time<c>", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==6); |
| REGEX_ASSERT(fields[0]==" "); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="b"); |
| REGEX_ASSERT(fields[4]=="the time"); |
| REGEX_ASSERT(fields[5]=="c"); |
| REGEX_ASSERT(fields[6]==""); |
| |
| status = U_ZERO_ERROR; |
| fields[6] = "foo"; |
| n = pat1->split(" <a>Now is <b>the time<c>", fields, 6, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==6); |
| REGEX_ASSERT(fields[0]==" "); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="b"); |
| REGEX_ASSERT(fields[4]=="the time"); |
| REGEX_ASSERT(fields[5]=="c"); |
| REGEX_ASSERT(fields[6]=="foo"); |
| |
| status = U_ZERO_ERROR; |
| fields[5] = "foo"; |
| n = pat1->split(" <a>Now is <b>the time<c>", fields, 5, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==5); |
| REGEX_ASSERT(fields[0]==" "); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="b"); |
| REGEX_ASSERT(fields[4]=="the time<c>"); |
| REGEX_ASSERT(fields[5]=="foo"); |
| |
| status = U_ZERO_ERROR; |
| fields[5] = "foo"; |
| n = pat1->split(" <a>Now is <b>the time", fields, 5, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==5); |
| REGEX_ASSERT(fields[0]==" "); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="b"); |
| REGEX_ASSERT(fields[4]=="the time"); |
| REGEX_ASSERT(fields[5]=="foo"); |
| |
| status = U_ZERO_ERROR; |
| n = pat1->split(" <a>Now is <b>the time<c>", fields, 4, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==4); |
| REGEX_ASSERT(fields[0]==" "); |
| REGEX_ASSERT(fields[1]=="a"); |
| REGEX_ASSERT(fields[2]=="Now is "); |
| REGEX_ASSERT(fields[3]=="the time<c>"); |
| status = U_ZERO_ERROR; |
| delete pat1; |
| |
| pat1 = RegexPattern::compile("([-,])", pe, status); |
| REGEX_CHECK_STATUS; |
| n = pat1->split("1-10,20", fields, 10, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(n==5); |
| REGEX_ASSERT(fields[0]=="1"); |
| REGEX_ASSERT(fields[1]=="-"); |
| REGEX_ASSERT(fields[2]=="10"); |
| REGEX_ASSERT(fields[3]==","); |
| REGEX_ASSERT(fields[4]=="20"); |
| delete pat1; |
| |
| |
| // |
| // RegexPattern::pattern() |
| // |
| pat1 = new RegexPattern(); |
| REGEX_ASSERT(pat1->pattern() == ""); |
| delete pat1; |
| |
| pat1 = RegexPattern::compile("(Hello, world)*", pe, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(pat1->pattern() == "(Hello, world)*"); |
| delete pat1; |
| |
| |
| // |
| // classID functions |
| // |
| pat1 = RegexPattern::compile("(Hello, world)*", pe, status); |
| REGEX_CHECK_STATUS; |
| REGEX_ASSERT(pat1->getDynamicClassID() == RegexPattern::getStaticClassID()); |
| REGEX_ASSERT(pat1->getDynamicClassID() != NULL); |
| UnicodeString Hello("Hello, world."); |
| RegexMatcher *m = pat1->matcher(Hello, status); |
| REGEX_ASSERT(pat1->getDynamicClassID() != m->getDynamicClassID()); |
| REGEX_ASSERT(m->getDynamicClassID() == RegexMatcher::getStaticClassID()); |
| REGEX_ASSERT(m->getDynamicClassID() != NULL); |
| delete m; |
| delete pat1; |
| |
| } |
| |
| //--------------------------------------------------------------------------- |
| // |
| // Extended A more thorough check for features of regex patterns |
| // The test cases are in a separate data file, |
| // source/tests/testdata/regextst.txt |
| // A description of the test data format is included in that file. |
| // |
| //--------------------------------------------------------------------------- |
| |
| const char * |
| RegexTest::getPath(char buffer[2048], const char *filename) { |
| UErrorCode status=U_ZERO_ERROR; |
| const char *testDataDirectory = IntlTest::getSourceTestData(status); |
| if (U_FAILURE(status)) { |
| errln("ERROR: loadTestData() failed - %s", u_errorName(status)); |
| return NULL; |
| } |
| |
| strcpy(buffer, testDataDirectory); |
| strcat(buffer, filename); |
| return buffer; |
| } |
| |
| void RegexTest::Extended() { |
| char tdd[2048]; |
| const char *srcPath; |
| UErrorCode status = U_ZERO_ERROR; |
| int32_t lineNum = 0; |
| |
| // |
| // Open and read the test data file. |
| // |
| srcPath=getPath(tdd, "regextst.txt"); |
| if(srcPath==NULL) { |
| return; /* something went wrong, error already output */ |
| } |
| |
| int len; |
| UChar *testData = ReadAndConvertFile(srcPath, len, status); |
| if (U_FAILURE(status)) { |
| return; /* something went wrong, error already output */ |
| } |
| |
| // |
| // Put the test data into a UnicodeString |
| // |
| UnicodeString testString(FALSE, testData, len); |
| |
| RegexMatcher quotedStuffMat("\\s*([\\'\\\"/])(.*?)\\1", 0, status); |
| RegexMatcher commentMat ("\\s*(#.*)?$", 0, status); |
| RegexMatcher flagsMat ("\\s*([ixsmdtGv2-9]*)([:letter:]*)", 0, status); |
| |
| RegexMatcher lineMat("(.*?)\\r?\\n", testString, 0, status); |
| UnicodeString testPattern; // The pattern for test from the test file. |
| UnicodeString testFlags; // the flags for a test. |
| UnicodeString matchString; // The marked up string to be used as input |
| |
| |
| |
| // |
| // Loop over the test data file, once per line. |
| // |
| while (lineMat.find()) { |
| lineNum++; |
| if (U_FAILURE(status)) { |
| errln("line %d: ICU Error \"%s\"", lineNum, u_errorName(status)); |
| } |
| |
| status = U_ZERO_ERROR; |
| UnicodeString testLine = lineMat.group(1, status); |
| if (testLine.length() == 0) { |
| continue; |
| } |
| |
| // |
| // Parse the test line. Skip blank and comment only lines. |
| // Separate out the three main fields - pattern, flags, target. |
| // |
| |
| commentMat.reset(testLine); |
| if (commentMat.lookingAt(status)) { |
| // This line is a comment, or blank. |
| continue; |
| } |
| |
| // |
| // Pull out the pattern field, remove it from the test file line. |
| // |
| quotedStuffMat.reset(testLine); |
| if (quotedStuffMat.lookingAt(status)) { |
| testPattern = quotedStuffMat.group(2, status); |
| testLine.remove(0, quotedStuffMat.end(0, status)); |
| } else { |
| errln("Bad pattern (missing quotes?) at test file line %d", lineNum); |
| continue; |
| } |
| |
| |
| // |
| // Pull out the flags from the test file line. |
| // |
| flagsMat.reset(testLine); |
| flagsMat.lookingAt(status); // Will always match, possibly an empty string. |
| testFlags = flagsMat.group(1, status); |
| if (flagsMat.group(2, status).length() > 0) { |
| errln("Bad Match flag at line %d. Scanning %c\n", |
| lineNum, flagsMat.group(2, status).charAt(0)); |
| continue; |
| } |
| testLine.remove(0, flagsMat.end(0, status)); |
| |
| // |
| // Pull out the match string, as a whole. |
| // We'll process the <tags> later. |
| // |
| quotedStuffMat.reset(testLine); |
| if (quotedStuffMat.lookingAt(status)) { |
| matchString = quotedStuffMat.group(2, status); |
| testLine.remove(0, quotedStuffMat.end(0, status)); |
| } else { |
| errln("Bad match string at test file line %d", lineNum); |
| continue; |
| } |
| |
| // |
| // The only thing left from the input line should be an optional trailing comment. |
| // |
| commentMat.reset(testLine); |
| if (commentMat.lookingAt(status) == FALSE) { |
| errln("Line %d: unexpected characters at end of test line.", lineNum); |
| continue; |
| } |
| |
| // |
| // Run the test |
| // |
| regex_find(testPattern, testFlags, matchString, lineNum); |
| } |
| |
| delete [] testData; |
| |
| } |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // |
| // Errors Check for error handling in patterns. |
| // |
| //--------------------------------------------------------------------------- |
| void RegexTest::Errors() { |
| // \escape sequences that aren't implemented yet. |
| //REGEX_ERR("hex format \\x{abcd} not implemented", 1, 13, U_REGEX_UNIMPLEMENTED); |
| |
| // Missing close parentheses |
| REGEX_ERR("Comment (?# with no close", 1, 25, U_REGEX_MISMATCHED_PAREN); |
| REGEX_ERR("Capturing Parenthesis(...", 1, 25, U_REGEX_MISMATCHED_PAREN); |
| REGEX_ERR("Grouping only parens (?: blah blah", 1, 34, U_REGEX_MISMATCHED_PAREN); |
| |
| // Extra close paren |
| REGEX_ERR("Grouping only parens (?: blah)) blah", 1, 31, U_REGEX_MISMATCHED_PAREN); |
| REGEX_ERR(")))))))", 1, 1, U_REGEX_MISMATCHED_PAREN); |
| REGEX_ERR("(((((((", 1, 7, U_REGEX_MISMATCHED_PAREN); |
| |
| // Look-ahead, Look-behind |
| // TODO: add tests for unbounded length look-behinds. |
| REGEX_ERR("abc(?<@xyz).*", 1, 7, U_REGEX_RULE_SYNTAX); // illegal construct |
| |
| // Attempt to use non-default flags |
| { |
| UParseError pe; |
| UErrorCode status = U_ZERO_ERROR; |
| int32_t flags = UREGEX_CANON_EQ | |
| UREGEX_COMMENTS | UREGEX_DOTALL | |
| UREGEX_MULTILINE; |
| RegexPattern *pat1= RegexPattern::compile(".*", flags, pe, status); |
| REGEX_ASSERT(status == U_REGEX_UNIMPLEMENTED); |
| delete pat1; |
| } |
| |
| |
| // Quantifiers are allowed only after something that can be quantified. |
| REGEX_ERR("+", 1, 1, U_REGEX_RULE_SYNTAX); |
| REGEX_ERR("abc\ndef(*2)", 2, 5, U_REGEX_RULE_SYNTAX); |
| REGEX_ERR("abc**", 1, 5, U_REGEX_RULE_SYNTAX); |
| |
| // Mal-formed {min,max} quantifiers |
| REGEX_ERR("abc{a,2}",1,5, U_REGEX_BAD_INTERVAL); |
| REGEX_ERR("abc{4,2}",1,8, U_REGEX_MAX_LT_MIN); |
| REGEX_ERR("abc{1,b}",1,7, U_REGEX_BAD_INTERVAL); |
| REGEX_ERR("abc{1,,2}",1,7, U_REGEX_BAD_INTERVAL); |
| REGEX_ERR("abc{1,2a}",1,8, U_REGEX_BAD_INTERVAL); |
| REGEX_ERR("abc{222222222222222222222}",1,14, U_REGEX_NUMBER_TOO_BIG); |
| REGEX_ERR("abc{5,50000000000}", 1, 17, U_REGEX_NUMBER_TOO_BIG); // Overflows int during scan |
| REGEX_ERR("abc{5,687865858}", 1, 16, U_REGEX_NUMBER_TOO_BIG); // Overflows regex binary format |
| REGEX_ERR("abc{687865858,687865859}", 1, 24, U_REGEX_NUMBER_TOO_BIG); |
| |
| |
| // UnicodeSet containing a string |
| REGEX_ERR("abc[{def}]xyz", 1, 10, U_REGEX_SET_CONTAINS_STRING); |
| |
| } |
| |
| |
| //------------------------------------------------------------------------------- |
| // |
| // Read a text data file, convert it to UChars, and return the data |
| // in one big UChar * buffer, which the caller must delete. |
| // |
| //-------------------------------------------------------------------------------- |
| UChar *RegexTest::ReadAndConvertFile(const char *fileName, int &ulen, UErrorCode &status) { |
| UChar *retPtr = NULL; |
| char *fileBuf = NULL; |
| UConverter* conv = NULL; |
| FILE *f = NULL; |
| |
| ulen = 0; |
| if (U_FAILURE(status)) { |
| return retPtr; |
| } |
| |
| // |
| // Open the file. |
| // |
| f = fopen(fileName, "rb"); |
| if (f == 0) { |
| errln("Error opening test data file %s\n", fileName); |
| status = U_FILE_ACCESS_ERROR; |
| return NULL; |
| } |
| // |
| // Read it in |
| // |
| int fileSize; |
| int amt_read; |
| |
| fseek( f, 0, SEEK_END); |
| fileSize = ftell(f); |
| fileBuf = new char[fileSize]; |
| fseek(f, 0, SEEK_SET); |
| amt_read = fread(fileBuf, 1, fileSize, f); |
| if (amt_read != fileSize || fileSize <= 0) { |
| errln("Error reading test data file."); |
| goto cleanUpAndReturn; |
| } |
| |
| // |
| // Look for a Unicode Signature (BOM) on the data just read |
| // |
| int32_t signatureLength; |
| const char * fileBufC; |
| const char* encoding; |
| |
| fileBufC = fileBuf; |
| encoding = ucnv_detectUnicodeSignature( |
| fileBuf, fileSize, &signatureLength, &status); |
| if(encoding!=NULL ){ |
| fileBufC += signatureLength; |
| fileSize -= signatureLength; |
| } |
| |
| // |
| // Open a converter to take the rule file to UTF-16 |
| // |
| conv = ucnv_open(encoding, &status); |
| if (U_FAILURE(status)) { |
| goto cleanUpAndReturn; |
| } |
| |
| // |
| // Convert the rules to UChar. |
| // Preflight first to determine required buffer size. |
| // |
| ulen = ucnv_toUChars(conv, |
| NULL, // dest, |
| 0, // destCapacity, |
| fileBufC, |
| fileSize, |
| &status); |
| if (status == U_BUFFER_OVERFLOW_ERROR) { |
| // Buffer Overflow is expected from the preflight operation. |
| status = U_ZERO_ERROR; |
| |
| retPtr = new UChar[ulen+1]; |
| ucnv_toUChars(conv, |
| retPtr, // dest, |
| ulen+1, |
| fileBufC, |
| fileSize, |
| &status); |
| } |
| |
| cleanUpAndReturn: |
| fclose(f); |
| delete[] fileBuf; |
| ucnv_close(conv); |
| if (U_FAILURE(status)) { |
| errln("ucnv_toUChars: ICU Error \"%s\"\n", u_errorName(status)); |
| delete retPtr; |
| retPtr = 0; |
| ulen = 0; |
| }; |
| return retPtr; |
| } |
| |
| |
| //------------------------------------------------------------------------------- |
| // |
| // PerlTests - Run Perl's regular expression tests |
| // The input file for this test is re_tests, the standard regular |
| // expression test data distributed with the Perl source code. |
| // |
| // Here is Perl's description of the test data file: |
| // |
| // # The tests are in a separate file 't/op/re_tests'. |
| // # Each line in that file is a separate test. |
| // # There are five columns, separated by tabs. |
| // # |
| // # Column 1 contains the pattern, optionally enclosed in C<''>. |
| // # Modifiers can be put after the closing C<'>. |
| // # |
| // # Column 2 contains the string to be matched. |
| // # |
| // # Column 3 contains the expected result: |
| // # y expect a match |
| // # n expect no match |
| // # c expect an error |
| // # B test exposes a known bug in Perl, should be skipped |
| // # b test exposes a known bug in Perl, should be skipped if noamp |
| // # |
| // # Columns 4 and 5 are used only if column 3 contains C<y> or C<c>. |
| // # |
| // # Column 4 contains a string, usually C<$&>. |
| // # |
| // # Column 5 contains the expected result of double-quote |
| // # interpolating that string after the match, or start of error message. |
| // # |
| // # Column 6, if present, contains a reason why the test is skipped. |
| // # This is printed with "skipped", for harness to pick up. |
| // # |
| // # \n in the tests are interpolated, as are variables of the form ${\w+}. |
| // # |
| // # If you want to add a regular expression test that can't be expressed |
| // # in this format, don't add it here: put it in op/pat.t instead. |
| // |
| // For ICU, if field 3 contains an 'i', the test will be skipped. |
| // The test exposes is some known incompatibility between ICU and Perl regexps. |
| // (The i is in addition to whatever was there before.) |
| // |
| //------------------------------------------------------------------------------- |
| void RegexTest::PerlTests() { |
| char tdd[2048]; |
| const char *srcPath; |
| UErrorCode status = U_ZERO_ERROR; |
| UParseError pe; |
| |
| // |
| // Open and read the test data file. |
| // |
| srcPath=getPath(tdd, "re_tests.txt"); |
| if(srcPath==NULL) { |
| return; /* something went wrong, error already output */ |
| } |
| |
| int len; |
| UChar *testData = ReadAndConvertFile(srcPath, len, status); |
| if (U_FAILURE(status)) { |
| return; /* something went wrong, error already output */ |
| } |
| |
| // |
| // Put the test data into a UnicodeString |
| // |
| UnicodeString testDataString(FALSE, testData, len); |
| |
| // |
| // Regex to break the input file into lines, and strip the new lines. |
| // One line per match, capture group one is the desired data. |
| // |
| RegexPattern* linePat = RegexPattern::compile("(.+?)[\\r\\n]+", 0, pe, status); |
| RegexMatcher* lineMat = linePat->matcher(testDataString, status); |
| |
| // |
| // Regex to split a test file line into fields. |
| // There are six fields, separated by tabs. |
| // |
| RegexPattern* fieldPat = RegexPattern::compile("\\t", 0, pe, status); |
| |
| // |
| // Regex to identify test patterns with flag settings, and to separate them. |
| // Test patterns with flags look like 'pattern'i |
| // Test patterns without flags are not quoted: pattern |
| // Coming out, capture group 2 is the pattern, capture group 3 is the flags. |
| // |
| RegexPattern *flagPat = RegexPattern::compile("('?)(.*)\\1(.*)", 0, pe, status); |
| RegexMatcher* flagMat = flagPat->matcher(status); |
| |
| // |
| // The Perl tests reference several perl-isms, which are evaluated/substituted |
| // in the test data. Not being perl, this must be done explicitly. Here |
| // are string constants and REs for these constructs. |
| // |
| UnicodeString nulnulSrc("${nulnul}"); |
| UnicodeString nulnul("\\u0000\\u0000"); |
| nulnul = nulnul.unescape(); |
| |
| UnicodeString ffffSrc("${ffff}"); |
| UnicodeString ffff("\\uffff"); |
| ffff = ffff.unescape(); |
| |
| // regexp for $-[0], $+[2], etc. |
| RegexPattern *groupsPat = RegexPattern::compile("\\$([+\\-])\\[(\\d+)\\]", 0, pe, status); |
| RegexMatcher *groupsMat = groupsPat->matcher(status); |
| |
| // regexp for $0, $1, $2, etc. |
| RegexPattern *cgPat = RegexPattern::compile("\\$(\\d+)", 0, pe, status); |
| RegexMatcher *cgMat = cgPat->matcher(status); |
| |
| |
| // |
| // Main Loop for the Perl Tests, runs once per line from the |
| // test data file. |
| // |
| int32_t lineNum = 0; |
| int32_t skippedUnimplementedCount = 0; |
| while (lineMat->find()) { |
| lineNum++; |
| |
| // |
| // Get a line, break it into its fields, do the Perl |
| // variable substitutions. |
| // |
| UnicodeString line = lineMat->group(1, status); |
| UnicodeString fields[7]; |
| fieldPat->split(line, fields, 7, status); |
| |
| flagMat->reset(fields[0]); |
| flagMat->matches(status); |
| UnicodeString pattern = flagMat->group(2, status); |
| pattern.findAndReplace("${bang}", "!"); |
| pattern.findAndReplace(nulnulSrc, "\\u0000\\u0000"); |
| pattern.findAndReplace(ffffSrc, ffff); |
| |
| // |
| // Identify patterns that include match flag settings, |
| // split off the flags, remove the extra quotes. |
| // |
| UnicodeString flagStr = flagMat->group(3, status); |
| if (U_FAILURE(status)) { |
| errln("ucnv_toUChars: ICU Error \"%s\"\n", u_errorName(status)); |
| return; |
| } |
| int32_t flags = 0; |
| const UChar UChar_c = 0x63; // Char constants for the flag letters. |
| const UChar UChar_i = 0x69; // (Damn the lack of Unicode support in C) |
| const UChar UChar_m = 0x6d; |
| const UChar UChar_x = 0x78; |
| const UChar UChar_y = 0x79; |
| if (flagStr.indexOf(UChar_i) != -1) { |
| flags |= UREGEX_CASE_INSENSITIVE; |
| } |
| if (flagStr.indexOf(UChar_m) != -1) { |
| flags |= UREGEX_MULTILINE; |
| } |
| if (flagStr.indexOf(UChar_x) != -1) { |
| flags |= UREGEX_COMMENTS; |
| } |
| |
| // |
| // Compile the test pattern. |
| // |
| status = U_ZERO_ERROR; |
| RegexPattern *testPat = RegexPattern::compile(pattern, flags, pe, status); |
| if (status == U_REGEX_UNIMPLEMENTED) { |
| // |
| // Test of a feature that is planned for ICU, but not yet implemented. |
| // skip the test. |
| skippedUnimplementedCount++; |
| delete testPat; |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| if (U_FAILURE(status)) { |
| // Some tests are supposed to generate errors. |
| // Only report an error for tests that are supposed to succeed. |
| if (fields[2].indexOf(UChar_c) == -1 && // Compilation is not supposed to fail AND |
| fields[2].indexOf(UChar_i) == -1) // it's not an accepted ICU incompatibility |
| { |
| errln("line %d: ICU Error \"%s\"\n", lineNum, u_errorName(status)); |
| } |
| status = U_ZERO_ERROR; |
| delete testPat; |
| continue; |
| } |
| |
| if (fields[2].indexOf(UChar_i) >= 0) { |
| // ICU should skip this test. |
| delete testPat; |
| continue; |
| } |
| |
| if (fields[2].indexOf(UChar_c) >= 0) { |
| // This pattern should have caused a compilation error, but didn't/ |
| errln("line %d: Expected a pattern compile error, got success.", lineNum); |
| delete testPat; |
| continue; |
| } |
| |
| // |
| // replace the Perl variables that appear in some of the |
| // match data strings. |
| // |
| UnicodeString matchString = fields[1]; |
| matchString.findAndReplace(nulnulSrc, nulnul); |
| matchString.findAndReplace(ffffSrc, ffff); |
| |
| // Replace any \n in the match string with an actual new-line char. |
| // Don't do full unescape, as this unescapes more than Perl does, which |
| // causes other spurious failures in the tests. |
| matchString.findAndReplace("\\n", "\n"); |
| |
| |
| |
| // |
| // Run the test, check for expected match/don't match result. |
| // |
| RegexMatcher *testMat = testPat->matcher(matchString, status); |
| UBool found = testMat->find(); |
| UBool expected = FALSE; |
| if (fields[2].indexOf(UChar_y) >=0) { |
| expected = TRUE; |
| } |
| if (expected != found) { |
| errln("line %d: Expected %smatch, got %smatch", |
| lineNum, expected?"":"no ", found?"":"no " ); |
| continue; |
| } |
| |
| // |
| // Interpret the Perl expression from the fourth field of the data file, |
| // building up an ICU string from the results of the ICU match. |
| // The Perl expression will contain references to the results of |
| // a regex match, including the matched string, capture group strings, |
| // group starting and ending indicies, etc. |
| // |
| UnicodeString resultString; |
| UnicodeString perlExpr = fields[3]; |
| groupsMat->reset(perlExpr); |
| cgMat->reset(perlExpr); |
| |
| while (perlExpr.length() > 0) { |
| if (perlExpr.startsWith("$&")) { |
| resultString.append(testMat->group(status)); |
| perlExpr.remove(0, 2); |
| } |
| |
| else if (groupsMat->lookingAt(status)) { |
| // $-[0] $+[2] etc. |
| UnicodeString digitString = groupsMat->group(2, status); |
| int32_t t = 0; |
| int32_t groupNum = ICU_Utility::parseNumber(digitString, t, 10); |
| UnicodeString plusOrMinus = groupsMat->group(1, status); |
| int32_t matchPosition; |
| if (plusOrMinus.compare("+") == 0) { |
| matchPosition = testMat->end(groupNum, status); |
| } else { |
| matchPosition = testMat->start(groupNum, status); |
| } |
| if (matchPosition != -1) { |
| ICU_Utility::appendNumber(resultString, matchPosition); |
| } |
| perlExpr.remove(0, groupsMat->end(status)); |
| } |
| |
| else if (cgMat->lookingAt(status)) { |
| // $1, $2, $3, etc. |
| UnicodeString digitString = cgMat->group(1, status); |
| int32_t t = 0; |
| int32_t groupNum = ICU_Utility::parseNumber(digitString, t, 10); |
| if (U_SUCCESS(status)) { |
| resultString.append(testMat->group(groupNum, status)); |
| status = U_ZERO_ERROR; |
| } |
| perlExpr.remove(0, cgMat->end(status)); |
| } |
| |
| else if (perlExpr.startsWith("@-")) { |
| int i; |
| for (i=0; i<=testMat->groupCount(); i++) { |
| if (i>0) { |
| resultString.append(" "); |
| } |
| ICU_Utility::appendNumber(resultString, testMat->start(i, status)); |
| } |
| perlExpr.remove(0, 2); |
| } |
| |
| else if (perlExpr.startsWith("@+")) { |
| int i; |
| for (i=0; i<=testMat->groupCount(); i++) { |
| if (i>0) { |
| resultString.append(" "); |
| } |
| ICU_Utility::appendNumber(resultString, testMat->end(i, status)); |
| } |
| perlExpr.remove(0, 2); |
| } |
| |
| else if (perlExpr.startsWith("\\")) { // \Escape. Take following char as a literal. |
| // or as an escaped sequence (e.g. \n) |
| if (perlExpr.length() > 1) { |
| perlExpr.remove(0, 1); // Remove the '\', but only if not last char. |
| } |
| UChar c = perlExpr.charAt(0); |
| switch (c) { |
| case 'n': c = '\n'; break; |
| // add any other escape sequences that show up in the test expected results. |
| } |
| resultString.append(c); |
| perlExpr.remove(0, 1); |
| } |
| |
| else { |
| // Any characters from the perl expression that we don't explicitly |
| // recognize before here are assumed to be literals and copied |
| // as-is to the expected results. |
| resultString.append(perlExpr.charAt(0)); |
| perlExpr.remove(0, 1); |
| } |
| |
| if (U_FAILURE(status)) { |
| errln("Line %d: ICU Error \"%s\"", lineNum, u_errorName(status)); |
| break; |
| } |
| } |
| |
| // |
| // Expected Results Compare |
| // |
| UnicodeString expectedS(fields[4]); |
| expectedS.findAndReplace(nulnulSrc, nulnul); |
| expectedS.findAndReplace(ffffSrc, ffff); |
| expectedS.findAndReplace("\\n", "\n"); |
| |
| |
| if (expectedS.compare(resultString) != 0) { |
| errln("Line %d: Incorrect perl expression results. Expected \"%s\"; got \"%s\"", |
| lineNum, (const char *)CharString(expectedS, 0), |
| (const char *)CharString(resultString, 0)); |
| } |
| |
| delete testMat; |
| delete testPat; |
| } |
| |
| // |
| // All done. Clean up allocated stuff. |
| // |
| delete cgMat; |
| delete cgPat; |
| |
| delete groupsMat; |
| delete groupsPat; |
| |
| delete flagMat; |
| delete flagPat; |
| |
| delete lineMat; |
| delete linePat; |
| |
| delete fieldPat; |
| delete [] testData; |
| |
| |
| logln("%d tests skipped because of unimplemented regexp features.", skippedUnimplementedCount); |
| |
| } |
| |
| |
| |
| #endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */ |
| |