/********************************************************************
* COPYRIGHT:
- * Copyright (c) 2002-2008, International Business Machines Corporation and
+ * Copyright (c) 2002-2015, International Business Machines Corporation and
* others. All Rights Reserved.
********************************************************************/
// ICU Regular Expressions test, part of intltest.
//
+/*
+ NOTE!!
+
+ PLEASE be careful about ASCII assumptions in this test.
+ This test is one of the worst repeat offenders.
+ If you have questions, contact someone on the ICU PMC
+ who has access to an EBCDIC system.
+
+ */
+
#include "intltest.h"
#if !UCONFIG_NO_REGULAR_EXPRESSIONS
+#include "unicode/localpointer.h"
#include "unicode/regex.h"
#include "unicode/uchar.h"
#include "unicode/ucnv.h"
+#include "unicode/uniset.h"
+#include "unicode/uregex.h"
+#include "unicode/usetiter.h"
+#include "unicode/ustring.h"
#include "regextst.h"
+#include "regexcmp.h"
#include "uvector.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
+#include "cmemory.h"
+#include "cstring.h"
+#include "uinvchar.h"
+#define SUPPORT_MUTATING_INPUT_STRING 0
//---------------------------------------------------------------------------
//
case 3: name = "API_Pattern";
if (exec) API_Pattern();
break;
- case 4: name = "Extended";
+ case 4:
+#if !UCONFIG_NO_FILE_IO
+ name = "Extended";
if (exec) Extended();
+#else
+ name = "skip";
+#endif
break;
case 5: name = "Errors";
if (exec) Errors();
if (exec) PerlTests();
break;
case 7: name = "Callbacks";
- if (exec) Callbacks();
+ if (exec) Callbacks();
+ break;
+ case 8: name = "FindProgressCallbacks";
+ if (exec) FindProgressCallbacks();
+ break;
+ case 9: name = "Bug 6149";
+ if (exec) Bug6149();
+ break;
+ case 10: name = "UTextBasic";
+ if (exec) UTextBasic();
break;
-
+ case 11: name = "API_Match_UTF8";
+ if (exec) API_Match_UTF8();
+ break;
+ case 12: name = "API_Replace_UTF8";
+ if (exec) API_Replace_UTF8();
+ break;
+ case 13: name = "API_Pattern_UTF8";
+ if (exec) API_Pattern_UTF8();
+ break;
+ case 14: name = "PerlTestsUTF8";
+ if (exec) PerlTestsUTF8();
+ break;
+ case 15: name = "PreAllocatedUTextCAPI";
+ if (exec) PreAllocatedUTextCAPI();
+ break;
+ case 16: name = "Bug 7651";
+ if (exec) Bug7651();
+ break;
+ case 17: name = "Bug 7740";
+ if (exec) Bug7740();
+ break;
+ case 18: name = "Bug 8479";
+ if (exec) Bug8479();
+ break;
+ case 19: name = "Bug 7029";
+ if (exec) Bug7029();
+ break;
+ case 20: name = "CheckInvBufSize";
+ if (exec) CheckInvBufSize();
+ break;
+ case 21: name = "Bug 9283";
+ if (exec) Bug9283();
+ break;
+ case 22: name = "Bug10459";
+ if (exec) Bug10459();
+ break;
+ case 23: name = "TestCaseInsensitiveStarters";
+ if (exec) TestCaseInsensitiveStarters();
+ break;
+ case 24: name = "TestBug11049";
+ if (exec) TestBug11049();
+ break;
+ case 25: name = "TestBug11371";
+ if (exec) TestBug11371();
+ break;
+ case 26: name = "TestBug11480";
+ if (exec) TestBug11480();
+ break;
+ case 27: name = "NamedCapture";
+ if (exec) NamedCapture();
+ break;
+ case 28: name = "NamedCaptureLimits";
+ if (exec) NamedCaptureLimits();
+ break;
default: name = "";
break; //needed to end loop
}
}
+
+/**
+ * Calls utext_openUTF8 after, potentially, converting invariant text from the compilation codepage
+ * into ASCII.
+ * @see utext_openUTF8
+ */
+static UText* regextst_openUTF8FromInvariant(UText* ut, const char *inv, int64_t length, UErrorCode *status);
+
//---------------------------------------------------------------------------
//
// 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__);};}
+static void utextToPrintable(char *buf, int32_t bufLen, UText *text) {
+ int64_t oldIndex = utext_getNativeIndex(text);
+ utext_setNativeIndex(text, 0);
+ char *bufPtr = buf;
+ UChar32 c = utext_next32From(text, 0);
+ while ((c != U_SENTINEL) && (bufPtr < buf+bufLen)) {
+ if (0x000020<=c && c<0x00007e) {
+ *bufPtr = c;
+ } else {
+#if 0
+ sprintf(bufPtr,"U+%04X", c);
+ bufPtr+= strlen(bufPtr)-1;
+#else
+ *bufPtr = '%';
+#endif
+ }
+ bufPtr++;
+ c = UTEXT_NEXT32(text);
+ }
+ *bufPtr = 0;
+#if (U_CHARSET_FAMILY==U_EBCDIC_FAMILY)
+ char *ebuf = (char*)malloc(bufLen);
+ uprv_eastrncpy((unsigned char*)ebuf, (const unsigned char*)buf, bufLen);
+ uprv_strncpy(buf, ebuf, bufLen);
+ free((void*)ebuf);
+#endif
+ utext_setNativeIndex(text, oldIndex);
+}
+
+
+static char ASSERT_BUF[1024];
+
+const char* RegexTest::extractToAssertBuf(const UnicodeString& message) {
+ if(message.length()==0) {
+ strcpy(ASSERT_BUF, "[[empty UnicodeString]]");
+ } else {
+ UnicodeString buf;
+ IntlTest::prettify(message,buf);
+ if(buf.length()==0) {
+ strcpy(ASSERT_BUF, "[[escape() returned 0 chars]]");
+ } else {
+ buf.extract(0, 0x7FFFFFFF, ASSERT_BUF, sizeof(ASSERT_BUF)-1);
+ if(ASSERT_BUF[0]==0) {
+ ASSERT_BUF[0]=0;
+ for(int32_t i=0;i<buf.length();i++) {
+ UChar ch = buf[i];
+ sprintf(ASSERT_BUF+strlen(ASSERT_BUF),"\\u%02x",ch);
+ }
+ }
+ }
+ }
+ ASSERT_BUF[sizeof(ASSERT_BUF)-1] = 0;
+ return ASSERT_BUF;
+}
+
+#define REGEX_VERBOSE_TEXT(text) {char buf[200];utextToPrintable(buf,sizeof(buf)/sizeof(buf[0]),text);logln("%s:%d: UText %s=\"%s\"", __FILE__, __LINE__, #text, buf);}
+
+#define REGEX_CHECK_STATUS {if (U_FAILURE(status)) {dataerrln("%s:%d: RegexTest failure. status=%s", \
+ __FILE__, __LINE__, u_errorName(status)); return;}}
+
+#define REGEX_ASSERT(expr) {if ((expr)==FALSE) {errln("%s:%d: RegexTest failure: REGEX_ASSERT(%s) failed \n", __FILE__, __LINE__, #expr);};}
#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", \
+if (status!=errcode) {dataerrln("RegexTest failure at line %d. Expected status=%s, got %s", \
__LINE__, u_errorName(errcode), u_errorName(status));};}
#define REGEX_CHECK_STATUS_L(line) {if (U_FAILURE(status)) {errln( \
#define REGEX_ASSERT_L(expr, line) {if ((expr)==FALSE) { \
errln("RegexTest failure at line %d, from %d.", __LINE__, (line)); return;}}
+// expected: const char * , restricted to invariant characters.
+// actual: const UnicodeString &
+#define REGEX_ASSERT_UNISTR(expected, actual) { \
+ if (UnicodeString(expected, -1, US_INV) != (actual)) { \
+ errln("%s:%d: RegexTest failure: REGEX_ASSERT_UNISTR(%s, %s) failed \n", \
+ __FILE__, __LINE__, expected, extractToAssertBuf(actual));};}
+
+
+static UBool testUTextEqual(UText *uta, UText *utb) {
+ UChar32 ca = 0;
+ UChar32 cb = 0;
+ utext_setNativeIndex(uta, 0);
+ utext_setNativeIndex(utb, 0);
+ do {
+ ca = utext_next32(uta);
+ cb = utext_next32(utb);
+ if (ca != cb) {
+ break;
+ }
+ } while (ca != U_SENTINEL);
+ return ca == cb;
+}
+
+
+/**
+ * @param expected expected text in UTF-8 (not platform) codepage
+ */
+void RegexTest::assertUText(const char *expected, UText *actual, const char *file, int line) {
+ UErrorCode status = U_ZERO_ERROR;
+ UText expectedText = UTEXT_INITIALIZER;
+ utext_openUTF8(&expectedText, expected, -1, &status);
+ if(U_FAILURE(status)) {
+ errln("%s:%d: assertUText: error %s calling utext_openUTF8(expected: %d chars)\n", file, line, u_errorName(status), strlen(expected));
+ return;
+ }
+ if(utext_nativeLength(&expectedText)==0 && (strlen(expected)!=0)) {
+ errln("%s:%d: assertUText: expected is %d utf-8 bytes, but utext_nativeLength(expectedText) returned 0.", file, line, strlen(expected));
+ return;
+ }
+ utext_setNativeIndex(actual, 0);
+ if (!testUTextEqual(&expectedText, actual)) {
+ char buf[201 /*21*/];
+ char expectedBuf[201];
+ utextToPrintable(buf, sizeof(buf)/sizeof(buf[0]), actual);
+ utextToPrintable(expectedBuf, sizeof(expectedBuf)/sizeof(expectedBuf[0]), &expectedText);
+ errln("%s:%d: assertUText: Failure: expected \"%s\" (%d chars), got \"%s\" (%d chars)", file, line, expectedBuf, (int)utext_nativeLength(&expectedText), buf, (int)utext_nativeLength(actual));
+ }
+ utext_close(&expectedText);
+}
+/**
+ * @param expected invariant (platform local text) input
+ */
+
+void RegexTest::assertUTextInvariant(const char *expected, UText *actual, const char *file, int line) {
+ UErrorCode status = U_ZERO_ERROR;
+ UText expectedText = UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&expectedText, expected, -1, &status);
+ if(U_FAILURE(status)) {
+ errln("%s:%d: assertUTextInvariant: error %s calling regextst_openUTF8FromInvariant(expected: %d chars)\n", file, line, u_errorName(status), strlen(expected));
+ return;
+ }
+ utext_setNativeIndex(actual, 0);
+ if (!testUTextEqual(&expectedText, actual)) {
+ char buf[201 /*21*/];
+ char expectedBuf[201];
+ utextToPrintable(buf, sizeof(buf)/sizeof(buf[0]), actual);
+ utextToPrintable(expectedBuf, sizeof(expectedBuf)/sizeof(expectedBuf[0]), &expectedText);
+ errln("%s:%d: assertUTextInvariant: Failure: expected \"%s\" (%d uchars), got \"%s\" (%d chars)", file, line, expectedBuf, (int)utext_nativeLength(&expectedText), buf, (int)utext_nativeLength(actual));
+ }
+ utext_close(&expectedText);
+}
+
+/**
+ * Assumes utf-8 input
+ */
+#define REGEX_ASSERT_UTEXT_UTF8(expected, actual) assertUText((expected), (actual), __FILE__, __LINE__)
+/**
+ * Assumes Invariant input
+ */
+#define REGEX_ASSERT_UTEXT_INVARIANT(expected, actual) assertUTextInvariant((expected), (actual), __FILE__, __LINE__)
+
+/**
+ * This buffer ( inv_buf ) is used to hold the UTF-8 strings
+ * passed into utext_openUTF8. An error will be given if
+ * INV_BUFSIZ is too small. It's only used on EBCDIC systems.
+ */
+
+#define INV_BUFSIZ 2048 /* increase this if too small */
+
+static int64_t inv_next=0;
+
+#if U_CHARSET_FAMILY!=U_ASCII_FAMILY
+static char inv_buf[INV_BUFSIZ];
+#endif
+
+static UText* regextst_openUTF8FromInvariant(UText *ut, const char *inv, int64_t length, UErrorCode *status) {
+ if(length==-1) length=strlen(inv);
+#if U_CHARSET_FAMILY==U_ASCII_FAMILY
+ inv_next+=length;
+ return utext_openUTF8(ut, inv, length, status);
+#else
+ if(inv_next+length+1>INV_BUFSIZ) {
+ fprintf(stderr, "%s:%d Error: INV_BUFSIZ #defined to be %d but needs to be at least %d.\n",
+ __FILE__, __LINE__, INV_BUFSIZ, (inv_next+length+1));
+ *status = U_MEMORY_ALLOCATION_ERROR;
+ return NULL;
+ }
+
+ unsigned char *buf = (unsigned char*)inv_buf+inv_next;
+ uprv_aestrncpy(buf, (const uint8_t*)inv, length);
+ inv_next+=length;
+
+#if 0
+ fprintf(stderr, " Note: INV_BUFSIZ at %d, used=%d\n", INV_BUFSIZ, inv_next);
+#endif
+
+ return utext_openUTF8(ut, (const char*)buf, length, status);
+#endif
+}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
-#define REGEX_TESTLM(pat, text, looking, match) doRegexLMTest(pat, text, looking, match, __LINE__);
+#define REGEX_TESTLM(pat, text, looking, match) {doRegexLMTest(pat, text, looking, match, __LINE__);doRegexLMTestUTF8(pat, text, looking, match, __LINE__);}
UBool RegexTest::doRegexLMTest(const char *pat, const char *text, UBool looking, UBool match, int32_t line) {
const UnicodeString pattern(pat, -1, US_INV);
UnicodeString patString(pat, -1, US_INV);
REPattern = RegexPattern::compile(patString, 0, pe, status);
if (U_FAILURE(status)) {
- errln("RegexTest failure in RegexPattern::compile() at line %d. Status = %s\n",
+ dataerrln("RegexTest failure in RegexPattern::compile() at line %d. Status = %s",
line, u_errorName(status));
return FALSE;
}
- if (line==376) { RegexPatternDump(REPattern);}
+ if (line==376) { REPattern->dumpPattern();}
UnicodeString inputString(inputText);
UnicodeString unEscapedInput = inputString.unescape();
}
if (retVal == FALSE) {
- RegexPatternDump(REPattern);
+ REPattern->dumpPattern();
}
delete REPattern;
}
+UBool RegexTest::doRegexLMTestUTF8(const char *pat, const char *text, UBool looking, UBool match, int32_t line) {
+ UText pattern = UTEXT_INITIALIZER;
+ int32_t inputUTF8Length;
+ char *textChars = NULL;
+ UText inputText = UTEXT_INITIALIZER;
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError pe;
+ RegexPattern *REPattern = NULL;
+ RegexMatcher *REMatcher = NULL;
+ UBool retVal = TRUE;
+
+ regextst_openUTF8FromInvariant(&pattern, pat, -1, &status);
+ REPattern = RegexPattern::compile(&pattern, 0, pe, status);
+ if (U_FAILURE(status)) {
+ dataerrln("RegexTest failure in RegexPattern::compile() at line %d (UTF8). Status = %s\n",
+ line, u_errorName(status));
+ return FALSE;
+ }
+
+ UnicodeString inputString(text, -1, US_INV);
+ UnicodeString unEscapedInput = inputString.unescape();
+ LocalUConverterPointer UTF8Converter(ucnv_open("UTF8", &status));
+ ucnv_setFromUCallBack(UTF8Converter.getAlias(), UCNV_FROM_U_CALLBACK_STOP, NULL, NULL, NULL, &status);
+
+ inputUTF8Length = unEscapedInput.extract(NULL, 0, UTF8Converter.getAlias(), status);
+ if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
+ // UTF-8 does not allow unpaired surrogates, so this could actually happen
+ logln("RegexTest unable to convert input to UTF8 at line %d. Status = %s\n", line, u_errorName(status));
+ return TRUE; // not a failure of the Regex engine
+ }
+ status = U_ZERO_ERROR; // buffer overflow
+ textChars = new char[inputUTF8Length+1];
+ unEscapedInput.extract(textChars, inputUTF8Length+1, UTF8Converter.getAlias(), status);
+ utext_openUTF8(&inputText, textChars, inputUTF8Length, &status);
+
+ REMatcher = &REPattern->matcher(status)->reset(&inputText);
+ if (U_FAILURE(status)) {
+ errln("RegexTest failure in REPattern::matcher() at line %d (UTF8). 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 (UTF8). Status = %s\n",
+ line, u_errorName(status));
+ retVal = FALSE;
+ }
+ if (actualmatch != looking) {
+ errln("RegexTest: wrong return from lookingAt() at line %d (UTF8).\n", line);
+ retVal = FALSE;
+ }
+
+ status = U_ZERO_ERROR;
+ actualmatch = REMatcher->matches(status);
+ if (U_FAILURE(status)) {
+ errln("RegexTest failure in matches() at line %d (UTF8). Status = %s\n",
+ line, u_errorName(status));
+ retVal = FALSE;
+ }
+ if (actualmatch != match) {
+ errln("RegexTest: wrong return from matches() at line %d (UTF8).\n", line);
+ retVal = FALSE;
+ }
+
+ if (retVal == FALSE) {
+ REPattern->dumpPattern();
+ }
+
+ delete REPattern;
+ delete REMatcher;
+ utext_close(&inputText);
+ utext_close(&pattern);
+ delete[] textChars;
+ return retVal;
+}
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));
+ dataerrln("Line %d: unexpected error %s compiling pattern.", line, u_errorName(status));
} else {
if (status != U_ZERO_ERROR) {
if (pe.line != errLine || pe.offset != errCol) {
}
delete callerPattern;
+
+ //
+ // Compile again, using a UTF-8-based UText
+ //
+ UText patternText = UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&patternText, pat, -1, &status);
+ callerPattern = RegexPattern::compile(&patternText, 0, pe, status);
+ if (status != expectedStatus) {
+ dataerrln("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;
+ utext_close(&patternText);
}
// 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");
+ RegexPattern *pattern;
+ pattern = RegexPattern::compile(UNICODE_STRING_SIMPLE("a\\u00dfx").unescape(), UREGEX_CASE_INSENSITIVE, pe, status);
+ pattern->dumpPattern();
+ RegexMatcher *m = pattern->matcher(UNICODE_STRING_SIMPLE("a\\u00dfxzzz").unescape(), status);
+ UBool result = m->find();
+ printf("result = %d\n", result);
+ // REGEX_FIND("", "<0>ab<1>cc</1><2>ccc</2></0>ddd");
// REGEX_FIND("(X([abc=X]+)+X)|(y[abc=]+)", "=XX====================");
}
exit(1);
// Escape of special chars in patterns
REGEX_TESTLM("\\\\\\|\\(\\)\\[\\{\\~\\$\\*\\+\\?\\.", "\\\\|()[{~$*+?.", TRUE, TRUE);
+}
+
+//---------------------------------------------------------------------------
+//
+// UTextBasic Check for quirks that are specific to the UText
+// implementation.
+//
+//---------------------------------------------------------------------------
+void RegexTest::UTextBasic() {
+ const char str_abc[] = { 0x61, 0x62, 0x63, 0x00 }; /* abc */
+ UErrorCode status = U_ZERO_ERROR;
+ UText pattern = UTEXT_INITIALIZER;
+ utext_openUTF8(&pattern, str_abc, -1, &status);
+ RegexMatcher matcher(&pattern, 0, status);
+ REGEX_CHECK_STATUS;
+
+ UText input = UTEXT_INITIALIZER;
+ utext_openUTF8(&input, str_abc, -1, &status);
+ REGEX_CHECK_STATUS;
+ matcher.reset(&input);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_abc, matcher.inputText());
+ matcher.reset(matcher.inputText());
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_abc, matcher.inputText());
+
+ utext_close(&pattern);
+ utext_close(&input);
}
status = U_ZERO_ERROR;
m1->reset(len, status);
+ REGEX_CHECK_STATUS;
+ status = U_ZERO_ERROR;
+
+ m1->reset(len+1, status);
REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
status = U_ZERO_ERROR;
delete m;
delete p;
}
-
+
//
// Regions
//
REGEX_ASSERT(m.regionEnd() == testString.length());
REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
-
+
m.region(2,4, status);
REGEX_CHECK_STATUS;
REGEX_ASSERT(m.matches(status));
REGEX_ASSERT(m.start(status)==2);
REGEX_ASSERT(m.end(status)==4);
REGEX_CHECK_STATUS;
-
+
m.reset();
REGEX_ASSERT(m.regionStart() == 0);
REGEX_ASSERT(m.regionEnd() == testString.length());
-
+
UnicodeString shorterString("short");
m.reset(shorterString);
REGEX_ASSERT(m.regionStart() == 0);
REGEX_ASSERT(m.regionEnd() == shorterString.length());
-
+
REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
REGEX_ASSERT(&m == &m.useAnchoringBounds(FALSE));
REGEX_ASSERT(m.hasAnchoringBounds() == FALSE);
REGEX_ASSERT(&m == &m.reset());
REGEX_ASSERT(m.hasAnchoringBounds() == FALSE);
-
+
REGEX_ASSERT(&m == &m.useAnchoringBounds(TRUE));
REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
REGEX_ASSERT(&m == &m.reset());
REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
-
+
REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
REGEX_ASSERT(&m == &m.useTransparentBounds(TRUE));
REGEX_ASSERT(m.hasTransparentBounds() == TRUE);
REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
REGEX_ASSERT(&m == &m.reset());
REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
-
+
}
-
+
//
// hitEnd() and requireEnd()
//
REGEX_ASSERT(m1.hitEnd() == TRUE);
REGEX_ASSERT(m1.requireEnd() == FALSE);
REGEX_CHECK_STATUS;
-
+
status = U_ZERO_ERROR;
RegexMatcher m2("a*", testString, 0, status);
REGEX_ASSERT(m2.lookingAt(status) == TRUE);
#endif
//
- // Time Outs.
+ // Time Outs.
// Note: These tests will need to be changed when the regexp engine is
// able to detect and cut short the exponential time behavior on
// this type of match.
REGEX_ASSERT(matcher.lookingAt(status) == FALSE);
REGEX_CHECK_STATUS;
}
-
+
//
// Stack Limits
//
{
UErrorCode status = U_ZERO_ERROR;
- UnicodeString testString(600000, 0x41, 600000); // Length 600,000, filled with 'A'
-
+ UnicodeString testString(1000000, 0x41, 1000000); // Length 1,000,000, filled with 'A'
+
// Adding the capturing parentheses to the pattern "(A)+A$" inhibits optimizations
// of the '+', and makes the stack frames larger.
RegexMatcher matcher("(A)+A$", testString, 0, status);
-
+
// With the default stack, this match should fail to run
REGEX_ASSERT(matcher.lookingAt(status) == FALSE);
REGEX_ASSERT(status == U_REGEX_STACK_OVERFLOW);
-
+
// With unlimited stack, it should run
status = U_ZERO_ERROR;
matcher.setStackLimit(0, status);
REGEX_ASSERT(status == U_REGEX_STACK_OVERFLOW);
REGEX_ASSERT(matcher.getStackLimit() == 10000);
}
-
+
// A pattern that doesn't save state should work with
// a minimal sized stack
{
REGEX_ASSERT(matcher.matches(status) == TRUE);
REGEX_CHECK_STATUS;
REGEX_ASSERT(matcher.getStackLimit() == 30);
-
+
// Negative stack sizes should fail
status = U_ZERO_ERROR;
matcher.setStackLimit(1000, status);
REGEX_ASSERT(status == U_ILLEGAL_ARGUMENT_ERROR);
REGEX_ASSERT(matcher.getStackLimit() == 1000);
}
-
+
}
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");
+ REGEX_ASSERT(U_FAILURE(status));
+ status = U_ZERO_ERROR;
UnicodeString replacement = UNICODE_STRING_SIMPLE("Supplemental Digit 1 $\\U0001D7CF.");
replacement = replacement.unescape();
n = pat1->split(" Now is the time ", fields, 10, status);
REGEX_CHECK_STATUS;
- REGEX_ASSERT(n==5);
+ REGEX_ASSERT(n==6);
REGEX_ASSERT(fields[0]=="");
REGEX_ASSERT(fields[1]=="Now");
REGEX_ASSERT(fields[2]=="is");
n = pat1->split(" ", fields, 10, status);
REGEX_CHECK_STATUS;
- REGEX_ASSERT(n==1);
+ REGEX_ASSERT(n==2);
REGEX_ASSERT(fields[0]=="");
+ REGEX_ASSERT(fields[1]=="");
fields[0] = "foo";
n = pat1->split("", fields, 10, 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(n==7);
REGEX_ASSERT(fields[0]=="");
REGEX_ASSERT(fields[1]=="a");
REGEX_ASSERT(fields[2]=="Now is ");
n = pat1->split(" <a>Now is <b>the time<c>", fields, 10, status);
REGEX_CHECK_STATUS;
- REGEX_ASSERT(n==6);
+ REGEX_ASSERT(n==7);
REGEX_ASSERT(fields[0]==" ");
REGEX_ASSERT(fields[1]=="a");
REGEX_ASSERT(fields[2]=="Now is ");
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[5]==""); // All text following "<c>" field delimiter.
REGEX_ASSERT(fields[6]=="foo");
status = U_ZERO_ERROR;
REGEX_ASSERT(fields[4]=="20");
delete pat1;
+ // Test split of string with empty trailing fields
+ pat1 = RegexPattern::compile(",", pe, status);
+ REGEX_CHECK_STATUS;
+ n = pat1->split("a,b,c,", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==4);
+ REGEX_ASSERT(fields[0]=="a");
+ REGEX_ASSERT(fields[1]=="b");
+ REGEX_ASSERT(fields[2]=="c");
+ REGEX_ASSERT(fields[3]=="");
+
+ n = pat1->split("a,,,", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==4);
+ REGEX_ASSERT(fields[0]=="a");
+ REGEX_ASSERT(fields[1]=="");
+ REGEX_ASSERT(fields[2]=="");
+ REGEX_ASSERT(fields[3]=="");
+ delete pat1;
+
+ // Split Separator with zero length match.
+ pat1 = RegexPattern::compile(":?", pe, status);
+ REGEX_CHECK_STATUS;
+ n = pat1->split("abc", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==5);
+ REGEX_ASSERT(fields[0]=="");
+ REGEX_ASSERT(fields[1]=="a");
+ REGEX_ASSERT(fields[2]=="b");
+ REGEX_ASSERT(fields[3]=="c");
+ REGEX_ASSERT(fields[4]=="");
+
+ delete pat1;
//
// RegexPattern::pattern()
//---------------------------------------------------------------------------
//
-// 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.
+// API_Match_UTF8 Test that the alternate engine for class RegexMatcher
+// is present and working, but excluding functions
+// implementing replace operations.
//
//---------------------------------------------------------------------------
-
-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;
+void RegexTest::API_Match_UTF8() {
+ UParseError pe;
+ UErrorCode status=U_ZERO_ERROR;
+ int32_t flags = 0;
//
- // Open and read the test data file.
+ // Debug - slide failing test cases early
//
- srcPath=getPath(tdd, "regextst.txt");
- if(srcPath==NULL) {
- return; /* something went wrong, error already output */
- }
-
- int32_t len;
- UChar *testData = ReadAndConvertFile(srcPath, len, "utf-8", status);
- if (U_FAILURE(status)) {
- return; /* something went wrong, error already output */
+#if 0
+ {
}
+ return;
+#endif
//
- // Put the test data into a UnicodeString
+ // Simple pattern compilation
//
- UnicodeString testString(FALSE, testData, len);
-
- RegexMatcher quotedStuffMat(UNICODE_STRING_SIMPLE("\\s*([\\'\\\"/])(.*?)\\1"), 0, status);
- RegexMatcher commentMat (UNICODE_STRING_SIMPLE("\\s*(#.*)?$"), 0, status);
- RegexMatcher flagsMat (UNICODE_STRING_SIMPLE("\\s*([ixsmdteDEGLMvabtyYzZ2-9]*)([:letter:]*)"), 0, status);
-
- RegexMatcher lineMat(UNICODE_STRING_SIMPLE("(.*?)\\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
+ {
+ UText re = UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&re, "abc", -1, &status);
+ REGEX_VERBOSE_TEXT(&re);
+ RegexPattern *pat2;
+ pat2 = RegexPattern::compile(&re, flags, pe, status);
+ REGEX_CHECK_STATUS;
- if (U_FAILURE(status)){
- dataerrln("Construct RegexMatcher() error.");
- delete [] testData;
- return;
- }
+ UText input1 = UTEXT_INITIALIZER;
+ UText input2 = UTEXT_INITIALIZER;
+ UText empty = UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&input1, "abcdef this is a test", -1, &status);
+ REGEX_VERBOSE_TEXT(&input1);
+ regextst_openUTF8FromInvariant(&input2, "not abc", -1, &status);
+ REGEX_VERBOSE_TEXT(&input2);
+ utext_openUChars(&empty, NULL, 0, &status);
- //
- // 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));
- }
+ int32_t input1Len = strlen("abcdef this is a test"); /* TODO: why not nativelen (input1) ? */
+ int32_t input2Len = strlen("not abc");
- 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.
+ // Matcher creation and reset.
//
-
- commentMat.reset(testLine);
- if (commentMat.lookingAt(status)) {
- // This line is a comment, or blank.
- continue;
- }
+ RegexMatcher *m1 = &pat2->matcher(status)->reset(&input1);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(m1->lookingAt(status) == TRUE);
+ const char str_abcdefthisisatest[] = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x00 }; /* abcdef this is a test */
+ REGEX_ASSERT_UTEXT_UTF8(str_abcdefthisisatest, m1->inputText());
+ m1->reset(&input2);
+ REGEX_ASSERT(m1->lookingAt(status) == FALSE);
+ const char str_notabc[] = { 0x6e, 0x6f, 0x74, 0x20, 0x61, 0x62, 0x63, 0x00 }; /* not abc */
+ REGEX_ASSERT_UTEXT_UTF8(str_notabc, m1->inputText());
+ m1->reset(&input1);
+ REGEX_ASSERT_UTEXT_UTF8(str_abcdefthisisatest, m1->inputText());
+ REGEX_ASSERT(m1->lookingAt(status) == TRUE);
+ m1->reset(&empty);
+ REGEX_ASSERT(m1->lookingAt(status) == FALSE);
+ REGEX_ASSERT(utext_nativeLength(&empty) == 0);
//
- // Pull out the pattern field, remove it from the test file line.
+ // reset(pos, status)
//
- 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;
- }
+ m1->reset(&input1);
+ m1->reset(4, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_abcdefthisisatest, m1->inputText());
+ REGEX_ASSERT(m1->lookingAt(status) == TRUE);
+ m1->reset(-1, status);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+ status = U_ZERO_ERROR;
- //
- // 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));
+ m1->reset(0, status);
+ REGEX_CHECK_STATUS;
+ status = U_ZERO_ERROR;
- //
- // 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;
- }
+ m1->reset(input1Len-1, status);
+ REGEX_CHECK_STATUS;
+ status = U_ZERO_ERROR;
- //
- // 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;
- }
+ m1->reset(input1Len, status);
+ REGEX_CHECK_STATUS;
+ status = U_ZERO_ERROR;
+
+ m1->reset(input1Len+1, status);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+ status = U_ZERO_ERROR;
//
- // Run the test
+ // match(pos, status)
//
- regex_find(testPattern, testFlags, matchString, lineNum);
- }
+ m1->reset(&input2);
+ 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);
- delete [] testData;
+ // Match() at end of string should fail, but should not
+ // be an error.
+ status = U_ZERO_ERROR;
+ REGEX_ASSERT(m1->matches(input2Len, status) == FALSE);
+ REGEX_CHECK_STATUS;
-}
+ // Match beyond end of string should fail with an error.
+ status = U_ZERO_ERROR;
+ REGEX_ASSERT(m1->matches(input2Len+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(&input1);
+ REGEX_ASSERT(m.matches(input1Len, status) == TRUE);
+ REGEX_CHECK_STATUS;
+ m.reset(&empty);
+ REGEX_ASSERT(m.matches(0, status) == TRUE);
+ REGEX_CHECK_STATUS;
+ }
-//---------------------------------------------------------------------------
-//
-// regex_find(pattern, flags, inputString, lineNumber)
-//
-// Function to run a single test from the Extended (data driven) tests.
-// See file test/testdata/regextst.txt for a description of the
-// pattern and inputString fields, and the allowed flags.
-// lineNumber is the source line in regextst.txt of the test.
-//
-//---------------------------------------------------------------------------
+ //
+ // lookingAt(pos, status)
+ //
+ status = U_ZERO_ERROR;
+ m1->reset(&input2); // "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;
+ REGEX_ASSERT(m1->lookingAt(input2Len, status) == FALSE);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(m1->lookingAt(input2Len+1, status) == FALSE);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+ delete m1;
+ delete pat2;
-// 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, int32_t val, UnicodeString index) {
- UErrorCode status=U_ZERO_ERROR;
- int32_t idx = 0;
- for (int32_t i=0; i<index.length(); i++) {
- int32_t d=u_charDigitValue(index.charAt(i));
- if (d<0) {return;}
- idx = idx*10 + d;
+ utext_close(&re);
+ utext_close(&input1);
+ utext_close(&input2);
+ utext_close(&empty);
}
- 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,
- int32_t 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;
- int32_t numFinds;
- int32_t i;
- UBool useMatchesFunc = FALSE;
- UBool useLookingAtFunc = FALSE;
- int32_t regionStart = -1;
- int32_t regionEnd = -1;
//
- // Compile the caller's pattern
+ // Capture Group.
+ // RegexMatcher::start();
+ // RegexMatcher::end();
+ // RegexMatcher::groupCount();
//
- 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;
- }
-
- if (flags.indexOf((UChar)0x65) >= 0) { // 'e' flag
- bflags |= UREGEX_ERROR_ON_UNKNOWN_ESCAPES;
- }
- if (flags.indexOf((UChar)0x44) >= 0) { // 'D' flag
- bflags |= UREGEX_UNIX_LINES;
- }
+ {
+ int32_t flags=0;
+ UParseError pe;
+ UErrorCode status=U_ZERO_ERROR;
+ UText re=UTEXT_INITIALIZER;
+ const char str_01234567_pat[] = { 0x30, 0x31, 0x28, 0x32, 0x33, 0x28, 0x34, 0x35, 0x29, 0x36, 0x37, 0x29, 0x28, 0x2e, 0x2a, 0x29, 0x00 }; /* 01(23(45)67)(.*) */
+ utext_openUTF8(&re, str_01234567_pat, -1, &status);
+ RegexPattern *pat = RegexPattern::compile(&re, flags, pe, status);
+ REGEX_CHECK_STATUS;
- 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
- if (flags.indexOf((UChar)0x45) >= 0) { // flags contain 'E'
- // Expected pattern compilation error.
- if (flags.indexOf((UChar)0x64) >= 0) { // flags contain 'd'
- logln("Pattern Compile returns \"%s\"", u_errorName(status));
+ UText input = UTEXT_INITIALIZER;
+ const char str_0123456789[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00 }; /* 0123456789 */
+ utext_openUTF8(&input, str_0123456789, -1, &status);
+
+ RegexMatcher *matcher = &pat->matcher(status)->reset(&input);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(matcher->lookingAt(status) == TRUE);
+ static const int32_t matchStarts[] = {0, 2, 4, 8};
+ static const int32_t matchEnds[] = {10, 8, 6, 10};
+ int32_t 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 %s:%d, index %d. Expected %d, got %d\n",
+ __FILE__, __LINE__, i, matchStarts[i], actualStart);
+ }
+ int32_t actualEnd = matcher->end(i, status);
+ REGEX_CHECK_STATUS;
+ if (actualEnd != matchEnds[i]) {
+ errln("RegexTest failure at %s:%d index %d. Expected %d, got %d\n",
+ __FILE__, __LINE__, i, matchEnds[i], actualEnd);
}
- goto cleanupAndReturn;
- } else {
- // Unexpected pattern compilation error.
- errln("Line %d: error %s compiling pattern.", line, u_errorName(status));
- goto cleanupAndReturn;
}
- }
- if (flags.indexOf((UChar)0x64) >= 0) { // 'd' flag
- RegexPatternDump(callerPattern);
- }
+ REGEX_ASSERT(matcher->start(0, status) == matcher->start(status));
+ REGEX_ASSERT(matcher->end(0, status) == matcher->end(status));
- if (flags.indexOf((UChar)0x45) >= 0) { // 'E' flag
- errln("Expected, but did not get, a pattern compilation error.");
- goto cleanupAndReturn;
- }
+ 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);
- //
- // 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;
- }
- }
-
- // 'M' flag. Use matches() instead of find()
- if (flags.indexOf((UChar)0x4d) >= 0) {
- useMatchesFunc = TRUE;
- }
- if (flags.indexOf((UChar)0x4c) >= 0) {
- useLookingAtFunc = TRUE;
- }
+ UnicodeString dest;
+ UText destText = UTEXT_INITIALIZER;
+ utext_openUnicodeString(&destText, &dest, &status);
+ UText *result;
+ //const char str_0123456789[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00 }; /* 0123456789 */
+ // Test shallow-clone API
+ int64_t group_len;
+ result = matcher->group((UText *)NULL, group_len, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_0123456789, result);
+ utext_close(result);
+ result = matcher->group(0, &destText, group_len, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_0123456789, result);
+ // destText is now immutable, reopen it
+ utext_close(&destText);
+ utext_openUnicodeString(&destText, &dest, &status);
+
+ int64_t length;
+ result = matcher->group(0, NULL, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_0123456789, result);
+ utext_close(result);
+ result = matcher->group(0, &destText, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT(utext_getNativeIndex(result) == 0);
+ REGEX_ASSERT(length == 10);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
- //
- // Find the tags in the input data, remove them, and record the group boundary
- // positions.
- //
- parsePat = RegexPattern::compile("<(/?)(r|[0-9]+)>", 0, pe, status);
- REGEX_CHECK_STATUS_L(line);
+ // Capture Group 1 == "234567"
+ result = matcher->group(1, NULL, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(utext_getNativeIndex(result) == 2);
+ REGEX_ASSERT(length == 6);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
- unEscapedInput = inputString.unescape();
- parseMatcher = parsePat->matcher(unEscapedInput, status);
- REGEX_CHECK_STATUS_L(line);
- while(parseMatcher->find()) {
- parseMatcher->appendReplacement(deTaggedInput, "", status);
+ result = matcher->group(1, &destText, length, status);
REGEX_CHECK_STATUS;
- UnicodeString groupNum = parseMatcher->group(2, status);
- if (groupNum == "r") {
- // <r> or </r>, a region specification within the string
- if (parseMatcher->group(1, status) == "/") {
- regionEnd = deTaggedInput.length();
- } else {
- regionStart = deTaggedInput.length();
- }
- } else {
- // <digits> or </digits>, a group match boundary tag.
- if (parseMatcher->group(1, status) == "/") {
- set(groupEnds, deTaggedInput.length(), groupNum);
- } else {
- set(groupStarts, deTaggedInput.length(), groupNum);
- }
- }
- }
- parseMatcher->appendTail(deTaggedInput);
- REGEX_ASSERT_L(groupStarts.size() == groupEnds.size(), line);
- if ((regionStart>=0 || regionEnd>=0) && (regionStart<0 || regionStart>regionEnd)) {
- errln("mismatched <r> tags");
- failed = TRUE;
- goto cleanupAndReturn;
- }
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT(utext_getNativeIndex(result) == 2);
+ REGEX_ASSERT(length == 6);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
+
+ // Capture Group 2 == "45"
+ result = matcher->group(2, NULL, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(utext_getNativeIndex(result) == 4);
+ REGEX_ASSERT(length == 2);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
+ result = matcher->group(2, &destText, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT(utext_getNativeIndex(result) == 4);
+ REGEX_ASSERT(length == 2);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
+
+ // Capture Group 3 == "89"
+ result = matcher->group(3, NULL, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(utext_getNativeIndex(result) == 8);
+ REGEX_ASSERT(length == 2);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
- //
- // Configure the matcher according to the flags specified with this test.
- //
- matcher = callerPattern->matcher(deTaggedInput, status);
- REGEX_CHECK_STATUS_L(line);
- if (flags.indexOf((UChar)0x74) >= 0) { // 't' trace flag
- matcher->setTrace(TRUE);
- }
- if (regionStart>=0) {
- matcher->region(regionStart, regionEnd, status);
- REGEX_CHECK_STATUS_L(line);
- }
- if (flags.indexOf((UChar)0x61) >= 0) { // 'a' anchoring bounds flag
- matcher->useAnchoringBounds(FALSE);
- }
- if (flags.indexOf((UChar)0x62) >= 0) { // 'b' transparent bounds flag
- matcher->useTransparentBounds(TRUE);
- }
-
-
+ result = matcher->group(3, &destText, length, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT(utext_getNativeIndex(result) == 8);
+ REGEX_ASSERT(length == 2);
+ REGEX_ASSERT_UTEXT_INVARIANT("0123456789", result);
+ utext_close(result);
- //
- // Do a find on the de-tagged input using the caller's pattern
- // TODO: error on count>1 and not find().
- // error on both matches() and lookingAt().
- //
- for (i=0; i<numFinds; i++) {
- if (useMatchesFunc) {
- isMatch = matcher->matches(status);
- } else if (useLookingAtFunc) {
- isMatch = matcher->lookingAt(status);
- } else {
- isMatch = matcher->find();
- }
+ // Capture Group number out of range.
+ status = U_ZERO_ERROR;
+ REGEX_ASSERT_FAIL(matcher->group(-1, status), U_INDEX_OUTOFBOUNDS_ERROR);
+ status = U_ZERO_ERROR;
+ REGEX_ASSERT_FAIL(matcher->group( 4, status), U_INDEX_OUTOFBOUNDS_ERROR);
+ status = U_ZERO_ERROR;
+ matcher->reset();
+ REGEX_ASSERT_FAIL(matcher->group( 0, status), U_REGEX_INVALID_STATE);
+
+ delete matcher;
+ delete pat;
+
+ utext_close(&destText);
+ utext_close(&input);
+ utext_close(&re);
}
- matcher->setTrace(FALSE);
//
- // Match up the groups from the find() with the groups from the tags
+ // find
//
+ {
+ int32_t flags=0;
+ UParseError pe;
+ UErrorCode status=U_ZERO_ERROR;
+ UText re=UTEXT_INITIALIZER;
+ const char str_abc[] = { 0x61, 0x62, 0x63, 0x00 }; /* abc */
+ utext_openUTF8(&re, str_abc, -1, &status);
- // 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;
- }
+ RegexPattern *pat = RegexPattern::compile(&re, flags, pe, status);
+ REGEX_CHECK_STATUS;
+ UText input = UTEXT_INITIALIZER;
+ const char str_abcabcabc[] = { 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x00 }; /* .abc..abc...abc.. */
+ utext_openUTF8(&input, str_abcabcabc, -1, &status);
+ // 012345678901234567
- 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;
- }
+ RegexMatcher *matcher = &pat->matcher(status)->reset(&input);
+ 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);
- 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;
- }
+ matcher->reset();
+ REGEX_ASSERT(matcher->find());
+ REGEX_ASSERT(matcher->start(status) == 1);
- if ((flags.indexOf((UChar)0x59) >= 0) && // 'Y' flag: RequireEnd() == false
- matcher->requireEnd() == TRUE) {
- errln("Error at line %d: requireEnd() returned TRUE. Expected FALSE", line);
- failed = TRUE;
- }
- if ((flags.indexOf((UChar)0x79) >= 0) && // 'y' flag: RequireEnd() == true
- matcher->requireEnd() == FALSE) {
- errln("Error at line %d: requireEnd() returned FALSE. Expected TRUE", line);
- failed = TRUE;
- }
- if ((flags.indexOf((UChar)0x5A) >= 0) && // 'Z' flag: hitEnd() == false
- matcher->hitEnd() == TRUE) {
- errln("Error at line %d: hitEnd() returned TRUE. Expected FALSE", line);
- failed = TRUE;
- }
- if ((flags.indexOf((UChar)0x7A) >= 0) && // 'z' flag: hitEnd() == true
- matcher->hitEnd() == FALSE) {
- errln("Error at line %d: hitEnd() returned FALSE. Expected TRUE", line);
- failed = TRUE;
- }
+ 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);
-cleanupAndReturn:
- if (failed) {
- errln((UnicodeString)"\""+pattern+(UnicodeString)"\" "
- +flags+(UnicodeString)" \""+inputString+(UnicodeString)"\"");
- // callerPattern->dump();
+ REGEX_ASSERT(matcher->groupCount() == 0);
+
+ delete matcher;
+ delete pat;
+
+ utext_close(&input);
+ utext_close(&re);
}
- delete parseMatcher;
- delete parsePat;
- delete matcher;
- delete callerPattern;
-}
+ //
+ // 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;
+ UText re=UTEXT_INITIALIZER;
+ const char str_Gabcabc[] = { 0x2e, 0x2a, 0x3f, 0x28, 0x3f, 0x3a, 0x28, 0x5c, 0x47, 0x61, 0x62, 0x63, 0x29, 0x7c, 0x28, 0x61, 0x62, 0x63, 0x29, 0x29, 0x00 }; /* .*?(?:(\\Gabc)|(abc)) */
+ utext_openUTF8(&re, str_Gabcabc, -1, &status);
+ RegexPattern *pat = RegexPattern::compile(&re, flags, pe, status);
-//---------------------------------------------------------------------------
-//
-// 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);
+ REGEX_CHECK_STATUS;
+ UText input = UTEXT_INITIALIZER;
+ const char str_abcabcabc[] = { 0x2e, 0x61, 0x62, 0x63, 0x61, 0x62, 0x63, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x00 }; /* .abcabc.abc.. */
+ utext_openUTF8(&input, str_abcabcabc, -1, &status);
+ // 012345678901234567
- // 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);
+ RegexMatcher *matcher = &pat->matcher(status)->reset(&input);
+ 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);
- // 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);
+ 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;
- // Look-ahead, Look-behind
- // TODO: add tests for unbounded length look-behinds.
- REGEX_ERR("abc(?<@xyz).*", 1, 7, U_REGEX_RULE_SYNTAX); // illegal construct
+ delete matcher;
+ delete pat;
- // 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;
+ utext_close(&input);
+ utext_close(&re);
}
+ //
+ // find with zero length matches, match position should bump ahead
+ // to prevent loops.
+ //
+ {
+ int32_t 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;
+ UText s = UTEXT_INITIALIZER;
+ utext_openUTF8(&s, " ", -1, &status);
+ 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);
- // 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);
+ // Check that the bump goes over characters outside the BMP OK
+ // "\\U00010001\\U00010002\\U00010003\\U00010004".unescape()...in UTF-8
+ unsigned char aboveBMP[] = {0xF0, 0x90, 0x80, 0x81, 0xF0, 0x90, 0x80, 0x82, 0xF0, 0x90, 0x80, 0x83, 0xF0, 0x90, 0x80, 0x84, 0x00};
+ utext_openUTF8(&s, (char *)aboveBMP, -1, &status);
+ m.reset(&s);
+ for (i=0; ; i+=4) {
+ if (m.find() == FALSE) {
+ break;
+ }
+ REGEX_ASSERT(m.start(status) == i);
+ REGEX_ASSERT(m.end(status) == i);
+ }
+ REGEX_ASSERT(i==20);
- // 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);
+ utext_close(&s);
+ }
+ {
+ // 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.
+ int32_t i;
+ UErrorCode status=U_ZERO_ERROR;
+ RegexMatcher m(".?", 0, status);
+ REGEX_CHECK_STATUS;
+ UText s = UTEXT_INITIALIZER;
+ utext_openUTF8(&s, " ", -1, &status);
+ 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);
- // Ticket 5389
- REGEX_ERR("*c", 1, 1, U_REGEX_RULE_SYNTAX);
+ utext_close(&s);
+ }
-}
+ //
+ // Matchers with no input string behave as if they had an empty input 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, int32_t &ulen,
- const char *defEncoding, UErrorCode &status) {
- UChar *retPtr = NULL;
- char *fileBuf = NULL;
- UConverter* conv = NULL;
- FILE *f = NULL;
+ {
+ 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;
- ulen = 0;
- if (U_FAILURE(status)) {
- return retPtr;
+ REGEX_ASSERT(m->find() == FALSE);
+ REGEX_ASSERT(utext_nativeLength(m->inputText()) == 0);
+ delete m;
+ delete p;
}
//
- // Open the file.
+ // Regions
//
- f = fopen(fileName, "rb");
- if (f == 0) {
- dataerrln("[DATA] Error opening test data file %s\n", fileName);
- status = U_FILE_ACCESS_ERROR;
- return NULL;
+ {
+ UErrorCode status = U_ZERO_ERROR;
+ UText testPattern = UTEXT_INITIALIZER;
+ UText testText = UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&testPattern, ".*", -1, &status);
+ REGEX_VERBOSE_TEXT(&testPattern);
+ regextst_openUTF8FromInvariant(&testText, "This is test data", -1, &status);
+ REGEX_VERBOSE_TEXT(&testText);
+
+ RegexMatcher m(&testPattern, &testText, 0, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(m.regionStart() == 0);
+ REGEX_ASSERT(m.regionEnd() == (int32_t)strlen("This is test data"));
+ REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
+ REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
+
+ m.region(2,4, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(m.matches(status));
+ REGEX_ASSERT(m.start(status)==2);
+ REGEX_ASSERT(m.end(status)==4);
+ REGEX_CHECK_STATUS;
+
+ m.reset();
+ REGEX_ASSERT(m.regionStart() == 0);
+ REGEX_ASSERT(m.regionEnd() == (int32_t)strlen("This is test data"));
+
+ regextst_openUTF8FromInvariant(&testText, "short", -1, &status);
+ REGEX_VERBOSE_TEXT(&testText);
+ m.reset(&testText);
+ REGEX_ASSERT(m.regionStart() == 0);
+ REGEX_ASSERT(m.regionEnd() == (int32_t)strlen("short"));
+
+ REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
+ REGEX_ASSERT(&m == &m.useAnchoringBounds(FALSE));
+ REGEX_ASSERT(m.hasAnchoringBounds() == FALSE);
+ REGEX_ASSERT(&m == &m.reset());
+ REGEX_ASSERT(m.hasAnchoringBounds() == FALSE);
+
+ REGEX_ASSERT(&m == &m.useAnchoringBounds(TRUE));
+ REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
+ REGEX_ASSERT(&m == &m.reset());
+ REGEX_ASSERT(m.hasAnchoringBounds() == TRUE);
+
+ REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
+ REGEX_ASSERT(&m == &m.useTransparentBounds(TRUE));
+ REGEX_ASSERT(m.hasTransparentBounds() == TRUE);
+ REGEX_ASSERT(&m == &m.reset());
+ REGEX_ASSERT(m.hasTransparentBounds() == TRUE);
+
+ REGEX_ASSERT(&m == &m.useTransparentBounds(FALSE));
+ REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
+ REGEX_ASSERT(&m == &m.reset());
+ REGEX_ASSERT(m.hasTransparentBounds() == FALSE);
+
+ utext_close(&testText);
+ utext_close(&testPattern);
}
+
//
- // Read it in
+ // hitEnd() and requireEnd()
//
- int32_t fileSize;
- int32_t amt_read;
+ {
+ UErrorCode status = U_ZERO_ERROR;
+ UText testPattern = UTEXT_INITIALIZER;
+ UText testText = UTEXT_INITIALIZER;
+ const char str_[] = { 0x2e, 0x2a, 0x00 }; /* .* */
+ const char str_aabb[] = { 0x61, 0x61, 0x62, 0x62, 0x00 }; /* aabb */
+ utext_openUTF8(&testPattern, str_, -1, &status);
+ utext_openUTF8(&testText, str_aabb, -1, &status);
+
+ RegexMatcher m1(&testPattern, &testText, 0, status);
+ REGEX_ASSERT(m1.lookingAt(status) == TRUE);
+ REGEX_ASSERT(m1.hitEnd() == TRUE);
+ REGEX_ASSERT(m1.requireEnd() == FALSE);
+ REGEX_CHECK_STATUS;
- 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;
+ status = U_ZERO_ERROR;
+ const char str_a[] = { 0x61, 0x2a, 0x00 }; /* a* */
+ utext_openUTF8(&testPattern, str_a, -1, &status);
+ RegexMatcher m2(&testPattern, &testText, 0, status);
+ REGEX_ASSERT(m2.lookingAt(status) == TRUE);
+ REGEX_ASSERT(m2.hitEnd() == FALSE);
+ REGEX_ASSERT(m2.requireEnd() == FALSE);
+ REGEX_CHECK_STATUS;
+
+ status = U_ZERO_ERROR;
+ const char str_dotstardollar[] = { 0x2e, 0x2a, 0x24, 0x00 }; /* .*$ */
+ utext_openUTF8(&testPattern, str_dotstardollar, -1, &status);
+ RegexMatcher m3(&testPattern, &testText, 0, status);
+ REGEX_ASSERT(m3.lookingAt(status) == TRUE);
+ REGEX_ASSERT(m3.hitEnd() == TRUE);
+ REGEX_ASSERT(m3.requireEnd() == TRUE);
+ REGEX_CHECK_STATUS;
+
+ utext_close(&testText);
+ utext_close(&testPattern);
}
+}
+
+//---------------------------------------------------------------------------
+//
+// API_Replace_UTF8 API test for class RegexMatcher, testing the
+// Replace family of functions.
+//
+//---------------------------------------------------------------------------
+void RegexTest::API_Replace_UTF8() {
//
- // Look for a Unicode Signature (BOM) on the data just read
+ // Replace
//
- int32_t signatureLength;
- const char * fileBufC;
- const char* encoding;
+ int32_t flags=0;
+ UParseError pe;
+ UErrorCode status=U_ZERO_ERROR;
- fileBufC = fileBuf;
- encoding = ucnv_detectUnicodeSignature(
- fileBuf, fileSize, &signatureLength, &status);
- if(encoding!=NULL ){
- fileBufC += signatureLength;
- fileSize -= signatureLength;
- } else {
- encoding = defEncoding;
- if (strcmp(encoding, "utf-8") == 0) {
- errln("file %s is missing its BOM", fileName);
- }
- }
+ UText re=UTEXT_INITIALIZER;
+ regextst_openUTF8FromInvariant(&re, "abc", -1, &status);
+ REGEX_VERBOSE_TEXT(&re);
+ RegexPattern *pat = RegexPattern::compile(&re, flags, pe, status);
+ REGEX_CHECK_STATUS;
+
+ char data[] = { 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x00 }; /* .abc..abc...abc.. */
+ // 012345678901234567
+ UText dataText = UTEXT_INITIALIZER;
+ utext_openUTF8(&dataText, data, -1, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_VERBOSE_TEXT(&dataText);
+ RegexMatcher *matcher = &pat->matcher(status)->reset(&dataText);
//
- // Open a converter to take the rule file to UTF-16
+ // Plain vanilla matches.
//
- conv = ucnv_open(encoding, &status);
- if (U_FAILURE(status)) {
- goto cleanUpAndReturn;
- }
+ UnicodeString dest;
+ UText destText = UTEXT_INITIALIZER;
+ utext_openUnicodeString(&destText, &dest, &status);
+ UText *result;
+
+ UText replText = UTEXT_INITIALIZER;
+
+ const char str_yz[] = { 0x79, 0x7a, 0x00 }; /* yz */
+ utext_openUTF8(&replText, str_yz, -1, &status);
+ REGEX_VERBOSE_TEXT(&replText);
+ result = matcher->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_yzabcabc[] = { 0x2e, 0x79, 0x7a, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x00 }; /* .yz..abc...abc.. */
+ REGEX_ASSERT_UTEXT_UTF8(str_yzabcabc, result);
+ utext_close(result);
+ result = matcher->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_yzabcabc, result);
+
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_yzyzyz[] = { 0x2e, 0x79, 0x7a, 0x2e, 0x2e, 0x79, 0x7a, 0x2e, 0x2e, 0x2e, 0x79, 0x7a, 0x2e, 0x2e, 0x00 }; /* .yz..yz...yz.. */
+ REGEX_ASSERT_UTEXT_UTF8(str_yzyzyz, result);
+ utext_close(result);
+
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_yzyzyz, result);
//
- // Convert the rules to UChar.
- // Preflight first to determine required buffer size.
+ // Plain vanilla non-matches.
//
- 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;
+ const char str_abxabxabx[] = { 0x2e, 0x61, 0x62, 0x78, 0x2e, 0x2e, 0x61, 0x62, 0x78, 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x78, 0x2e, 0x2e, 0x00 }; /* .abx..abx...abx.. */
+ utext_openUTF8(&dataText, str_abxabxabx, -1, &status);
+ matcher->reset(&dataText);
- retPtr = new UChar[ulen+1];
- ucnv_toUChars(conv,
- retPtr, // dest,
- ulen+1,
- fileBufC,
- fileSize,
- &status);
- }
+ result = matcher->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_abxabxabx, result);
+ utext_close(result);
+ result = matcher->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_abxabxabx, result);
-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;
-}
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_abxabxabx, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_abxabxabx, result);
+ //
+ // Empty source string
+ //
+ utext_openUTF8(&dataText, NULL, 0, &status);
+ matcher->reset(&dataText);
-//-------------------------------------------------------------------------------
-//
-// 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;
+ result = matcher->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8("", result);
+ utext_close(result);
+ result = matcher->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8("", result);
+
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8("", result);
+ utext_close(result);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8("", result);
//
- // Open and read the test data file.
+ // Empty substitution string
//
- srcPath=getPath(tdd, "re_tests.txt");
- if(srcPath==NULL) {
- return; /* something went wrong, error already output */
- }
+ utext_openUTF8(&dataText, data, -1, &status); // ".abc..abc...abc.."
+ matcher->reset(&dataText);
- int32_t len;
- UChar *testData = ReadAndConvertFile(srcPath, len, "iso-8859-1", status);
- if (U_FAILURE(status)) {
- return; /* something went wrong, error already output */
- }
+ utext_openUTF8(&replText, NULL, 0, &status);
+ result = matcher->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_abcabc[] = { 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x2e, 0x61, 0x62, 0x63, 0x2e, 0x2e, 0x00 }; /* ...abc...abc.. */
+ REGEX_ASSERT_UTEXT_UTF8(str_abcabc, result);
+ utext_close(result);
+ result = matcher->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_abcabc, result);
- //
- // Put the test data into a UnicodeString
- //
- UnicodeString testDataString(FALSE, testData, len);
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_dots[] = { 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x00 }; /* ........ */
+ REGEX_ASSERT_UTEXT_UTF8(str_dots, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_dots, result);
//
- // Regex to break the input file into lines, and strip the new lines.
- // One line per match, capture group one is the desired data.
+ // match whole string
//
- RegexPattern* linePat = RegexPattern::compile(UNICODE_STRING_SIMPLE("(.+?)[\\r\\n]+"), 0, pe, status);
- if (U_FAILURE(status)) {
- dataerrln("RegexPattern::compile() error");
- return;
- }
- RegexMatcher* lineMat = linePat->matcher(testDataString, status);
+ const char str_abc[] = { 0x61, 0x62, 0x63, 0x00 }; /* abc */
+ utext_openUTF8(&dataText, str_abc, -1, &status);
+ matcher->reset(&dataText);
+
+ const char str_xyz[] = { 0x78, 0x79, 0x7a, 0x00 }; /* xyz */
+ utext_openUTF8(&replText, str_xyz, -1, &status);
+ result = matcher->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_xyz, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_xyz, result);
+
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8(str_xyz, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_xyz, result);
//
- // Regex to split a test file line into fields.
- // There are six fields, separated by tabs.
+ // Capture Group, simple case
//
- RegexPattern* fieldPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\t"), 0, pe, status);
+ const char str_add[] = { 0x61, 0x28, 0x2e, 0x2e, 0x29, 0x00 }; /* a(..) */
+ utext_openUTF8(&re, str_add, -1, &status);
+ RegexPattern *pat2 = RegexPattern::compile(&re, flags, pe, status);
+ REGEX_CHECK_STATUS;
+
+ const char str_abcdefg[] = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x00 }; /* abcdefg */
+ utext_openUTF8(&dataText, str_abcdefg, -1, &status);
+ RegexMatcher *matcher2 = &pat2->matcher(status)->reset(&dataText);
+ REGEX_CHECK_STATUS;
+
+ const char str_11[] = { 0x24, 0x31, 0x24, 0x31, 0x00 }; /* $1$1 */
+ utext_openUTF8(&replText, str_11, -1, &status);
+ result = matcher2->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_bcbcdefg[] = { 0x62, 0x63, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x00 }; /* bcbcdefg */
+ REGEX_ASSERT_UTEXT_UTF8(str_bcbcdefg, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher2->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_bcbcdefg, result);
+
+ const char str_v[24] = { 0x54, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x5c, 0x24, 0x31, 0x20, 0x69, 0x73, 0x20, 0x24, 0x31, 0x2e, 0x00 }; /* The value of \$1 is $1. */
+ utext_openUTF8(&replText, str_v, -1, &status);
+ REGEX_VERBOSE_TEXT(&replText);
+ result = matcher2->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_Thevalueof1isbcdefg[] = { 0x54, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x24, 0x31, 0x20, 0x69, 0x73, 0x20, 0x62, 0x63, 0x2e, 0x64, 0x65, 0x66, 0x67, 0x00 }; /* The value of $1 is bc.defg */
+ REGEX_ASSERT_UTEXT_UTF8(str_Thevalueof1isbcdefg, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher2->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_Thevalueof1isbcdefg, result);
+
+ const char str_byitselfnogroupnumber[] = { 0x5c, 0x24, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, 0x65, 0x6c,
+ 0x66, 0x2c, 0x20, 0x6e, 0x6f, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6e, 0x75, 0x6d, 0x62,
+ 0x65, 0x72, 0x20, 0x5c, 0x24, 0x5c, 0x24, 0x5c, 0x24, 0x00 }; /* \$ by itself, no group number \$\$\$ */
+ utext_openUTF8(&replText, str_byitselfnogroupnumber, -1, &status);
+ result = matcher2->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_byitselfnogroupnumberdefg[] = { 0x24, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, 0x65, 0x6c, 0x66, 0x2c, 0x20, 0x6e, 0x6f, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x24, 0x24, 0x24, 0x64, 0x65, 0x66, 0x67, 0x00 }; /* $ by itself, no group number $$$defg */
+ REGEX_ASSERT_UTEXT_UTF8(str_byitselfnogroupnumberdefg, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher2->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_byitselfnogroupnumberdefg, result);
+
+ unsigned char supplDigitChars[] = { 0x53, 0x75, 0x70, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x20, 0x31, 0x20, 0x24, 0x78, 0x78, 0x78, 0x78, 0x2e, 0x00 }; /* Supplemental Digit 1 $xxxx. */
+ //unsigned char supplDigitChars[] = "Supplemental Digit 1 $xxxx."; // \U0001D7CF, MATHEMATICAL BOLD DIGIT ONE
+ // 012345678901234567890123456
+ supplDigitChars[22] = 0xF0;
+ supplDigitChars[23] = 0x9D;
+ supplDigitChars[24] = 0x9F;
+ supplDigitChars[25] = 0x8F;
+ utext_openUTF8(&replText, (char *)supplDigitChars, -1, &status);
+
+ result = matcher2->replaceFirst(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_SupplementalDigit1bcdefg[] = { 0x53, 0x75, 0x70, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x20, 0x31, 0x20, 0x62, 0x63, 0x2e, 0x64, 0x65, 0x66, 0x67, 0x00 }; /* Supplemental Digit 1 bc.defg */
+ REGEX_ASSERT_UTEXT_UTF8(str_SupplementalDigit1bcdefg, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher2->replaceFirst(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_SupplementalDigit1bcdefg, result);
+ const char str_badcapturegroupnumber5[] = { 0x62, 0x61, 0x64, 0x20, 0x63, 0x61, 0x70, 0x74, 0x75, 0x72, 0x65, 0x20, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x24, 0x35, 0x2e, 0x2e, 0x2e, 0x00 }; /* bad capture group number $5..." */
+ utext_openUTF8(&replText, str_badcapturegroupnumber5, -1, &status);
+ REGEX_ASSERT_FAIL((result = matcher2->replaceFirst(&replText, NULL, status)), U_INDEX_OUTOFBOUNDS_ERROR);
+// REGEX_ASSERT_UTEXT_UTF8("abcdefg", result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ REGEX_ASSERT_FAIL((result = matcher2->replaceFirst(&replText, &destText, status)), U_INDEX_OUTOFBOUNDS_ERROR);
+ REGEX_ASSERT(result == &destText);
+// REGEX_ASSERT_UTEXT_UTF8("abcdefg", result);
//
- // 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.
+ // Replacement String with \u hex escapes
//
- RegexPattern *flagPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("('?)(.*)\\1(.*)"), 0, pe, status);
- RegexMatcher* flagMat = flagPat->matcher(status);
+ {
+ const char str_abc1abc2abc3[] = { 0x61, 0x62, 0x63, 0x20, 0x31, 0x20, 0x61, 0x62, 0x63, 0x20, 0x32, 0x20, 0x61, 0x62, 0x63, 0x20, 0x33, 0x00 }; /* abc 1 abc 2 abc 3 */
+ const char str_u0043[] = { 0x2d, 0x2d, 0x5c, 0x75, 0x30, 0x30, 0x34, 0x33, 0x2d, 0x2d, 0x00 }; /* --\u0043-- */
+ utext_openUTF8(&dataText, str_abc1abc2abc3, -1, &status);
+ utext_openUTF8(&replText, str_u0043, -1, &status);
+ matcher->reset(&dataText);
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ const char str_C1C2C3[] = { 0x2d, 0x2d, 0x43, 0x2d, 0x2d, 0x20, 0x31, 0x20, 0x2d, 0x2d, 0x43, 0x2d, 0x2d, 0x20, 0x32, 0x20, 0x2d, 0x2d, 0x43, 0x2d, 0x2d, 0x20, 0x33, 0x00 }; /* --C-- 1 --C-- 2 --C-- 3 */
+ REGEX_ASSERT_UTEXT_UTF8(str_C1C2C3, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8(str_C1C2C3, result);
+ }
+ {
+ const char str_abc[] = { 0x61, 0x62, 0x63, 0x20, 0x21, 0x00 }; /* abc ! */
+ utext_openUTF8(&dataText, str_abc, -1, &status);
+ const char str_U00010000[] = { 0x2d, 0x2d, 0x5c, 0x55, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x2d, 0x2d, 0x00 }; /* --\U00010000-- */
+ utext_openUTF8(&replText, str_U00010000, -1, &status);
+ matcher->reset(&dataText);
+
+ unsigned char expected[] = { 0x2d, 0x2d, 0x78, 0x78, 0x78, 0x78, 0x2d, 0x2d, 0x20, 0x21, 0x00 }; /* --xxxx-- ! */ // \U00010000, "LINEAR B SYLLABLE B008 A"
+ // 0123456789
+ expected[2] = 0xF0;
+ expected[3] = 0x90;
+ expected[4] = 0x80;
+ expected[5] = 0x80;
+
+ result = matcher->replaceAll(&replText, NULL, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UTEXT_UTF8((char *)expected, result);
+ utext_close(result);
+ utext_replace(&destText, 0, utext_nativeLength(&destText), NULL, 0, &status);
+ result = matcher->replaceAll(&replText, &destText, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &destText);
+ REGEX_ASSERT_UTEXT_UTF8((char *)expected, result);
+ }
+ // TODO: need more through testing of capture substitutions.
+
+ // Bug 4057
//
- // 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", -1, US_INV);
- nulnul = nulnul.unescape();
+ {
+ status = U_ZERO_ERROR;
+const char str_ssee[] = { 0x73, 0x73, 0x28, 0x2e, 0x2a, 0x3f, 0x29, 0x65, 0x65, 0x00 }; /* ss(.*?)ee */
+const char str_blah[] = { 0x54, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x65, 0x20, 0x73, 0x73, 0x20, 0x73, 0x74, 0x75, 0x66, 0x66, 0x20, 0x65, 0x65, 0x20, 0x66, 0x69, 0x6e, 0x00 }; /* The matches start with ss and end with ee ss stuff ee fin */
+const char str_ooh[] = { 0x6f, 0x6f, 0x68, 0x00 }; /* ooh */
+ utext_openUTF8(&re, str_ssee, -1, &status);
+ utext_openUTF8(&dataText, str_blah, -1, &status);
+ utext_openUTF8(&replText, str_ooh, -1, &status);
+
+ RegexMatcher m(&re, 0, status);
+ REGEX_CHECK_STATUS;
- UnicodeString ffffSrc("${ffff}");
- UnicodeString ffff("\\uffff", -1, US_INV);
- ffff = ffff.unescape();
+ UnicodeString result;
+ UText resultText = UTEXT_INITIALIZER;
+ utext_openUnicodeString(&resultText, &result, &status);
- // regexp for $-[0], $+[2], etc.
- RegexPattern *groupsPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$([+\\-])\\[(\\d+)\\]"), 0, pe, status);
- RegexMatcher *groupsMat = groupsPat->matcher(status);
+ // Multiple finds do NOT bump up the previous appendReplacement postion.
+ m.reset(&dataText);
+ m.find();
+ m.find();
+ m.appendReplacement(&resultText, &replText, status);
+ REGEX_CHECK_STATUS;
+ const char str_blah2[] = { 0x54, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x65, 0x20, 0x6f, 0x6f, 0x68, 0x00 }; /* The matches start with ss and end with ee ooh */
+ REGEX_ASSERT_UTEXT_UTF8(str_blah2, &resultText);
- // regexp for $0, $1, $2, etc.
- RegexPattern *cgPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$(\\d+)"), 0, pe, status);
- RegexMatcher *cgMat = cgPat->matcher(status);
+ // After a reset into the interior of a string, appendReplacement still starts at beginning.
+ status = U_ZERO_ERROR;
+ result.truncate(0);
+ utext_openUnicodeString(&resultText, &result, &status);
+ m.reset(10, status);
+ m.find();
+ m.find();
+ m.appendReplacement(&resultText, &replText, status);
+ REGEX_CHECK_STATUS;
+ const char str_blah3[] = { 0x54, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x65, 0x20, 0x6f, 0x6f, 0x68, 0x00 }; /* The matches start with ss and end with ee ooh */
+ REGEX_ASSERT_UTEXT_UTF8(str_blah3, &resultText);
+ // find() at interior of string, appendReplacement still starts at beginning.
+ status = U_ZERO_ERROR;
+ result.truncate(0);
+ utext_openUnicodeString(&resultText, &result, &status);
+ m.reset();
+ m.find(10, status);
+ m.find();
+ m.appendReplacement(&resultText, &replText, status);
+ REGEX_CHECK_STATUS;
+ const char str_blah8[] = { 0x54, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x65, 0x20, 0x6f, 0x6f, 0x68, 0x00 }; /* The matches start with ss and end with ee ooh */
+ REGEX_ASSERT_UTEXT_UTF8(str_blah8, &resultText);
- //
- // 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++;
+ m.appendTail(&resultText, status);
+ const char str_blah9[] = { 0x54, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x65, 0x20, 0x6f, 0x6f, 0x68, 0x20, 0x66, 0x69, 0x6e, 0x00 }; /* The matches start with ss and end with ee ooh fin */
+ REGEX_ASSERT_UTEXT_UTF8(str_blah9, &resultText);
- //
- // 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);
+ utext_close(&resultText);
+ }
- flagMat->reset(fields[0]);
- flagMat->matches(status);
- UnicodeString pattern = flagMat->group(2, status);
- pattern.findAndReplace("${bang}", "!");
- pattern.findAndReplace(nulnulSrc, UNICODE_STRING_SIMPLE("\\u0000\\u0000"));
- pattern.findAndReplace(ffffSrc, ffff);
+ delete matcher2;
+ delete pat2;
+ delete matcher;
+ delete pat;
- //
- // 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;
- }
+ utext_close(&dataText);
+ utext_close(&replText);
+ utext_close(&destText);
+ utext_close(&re);
+}
- //
- // 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;
- }
+//---------------------------------------------------------------------------
+//
+// API_Pattern_UTF8 Test that the API for class RegexPattern is
+// present and nominally working.
+//
+//---------------------------------------------------------------------------
+void RegexTest::API_Pattern_UTF8() {
+ RegexPattern pata; // Test default constructor to not crash.
+ RegexPattern patb;
- if (fields[2].indexOf(UChar_i) >= 0) {
- // ICU should skip this test.
- delete testPat;
- continue;
- }
+ REGEX_ASSERT(pata == patb);
+ REGEX_ASSERT(pata == pata);
- 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;
- }
+ UText re1 = UTEXT_INITIALIZER;
+ UText re2 = UTEXT_INITIALIZER;
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError pe;
- //
- // 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);
+ const char str_abcalmz[] = { 0x61, 0x62, 0x63, 0x5b, 0x61, 0x2d, 0x6c, 0x5d, 0x5b, 0x6d, 0x2d, 0x7a, 0x5d, 0x00 }; /* abc[a-l][m-z] */
+ const char str_def[] = { 0x64, 0x65, 0x66, 0x00 }; /* def */
+ utext_openUTF8(&re1, str_abcalmz, -1, &status);
+ utext_openUTF8(&re2, str_def, -1, &status);
- // 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(UNICODE_STRING_SIMPLE("\\n"), "\n");
+ 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;
+
+ utext_close(&re1);
+ utext_close(&re2);
+
+
+ //
+ // Verify that a matcher created from a cloned pattern works.
+ // (Jitterbug 3423)
+ //
+ {
+ UErrorCode status = U_ZERO_ERROR;
+ UText pattern = UTEXT_INITIALIZER;
+ const char str_pL[] = { 0x5c, 0x70, 0x7b, 0x4c, 0x7d, 0x2b, 0x00 }; /* \p{L}+ */
+ utext_openUTF8(&pattern, str_pL, -1, &status);
+
+ RegexPattern *pSource = RegexPattern::compile(&pattern, 0, status);
+ RegexPattern *pClone = pSource->clone();
+ delete pSource;
+ RegexMatcher *mFromClone = pClone->matcher(status);
+ REGEX_CHECK_STATUS;
+
+ UText input = UTEXT_INITIALIZER;
+ const char str_HelloWorld[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x00 }; /* Hello World */
+ utext_openUTF8(&input, str_HelloWorld, -1, &status);
+ mFromClone->reset(&input);
+ 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;
+
+ utext_close(&input);
+ utext_close(&pattern);
+ }
+
+ //
+ // matches convenience API
+ //
+ {
+ UErrorCode status = U_ZERO_ERROR;
+ UText pattern = UTEXT_INITIALIZER;
+ UText input = UTEXT_INITIALIZER;
+
+ const char str_randominput[] = { 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x00 }; /* random input */
+ utext_openUTF8(&input, str_randominput, -1, &status);
+
+ const char str_dotstar[] = { 0x2e, 0x2a, 0x00 }; /* .* */
+ utext_openUTF8(&pattern, str_dotstar, -1, &status);
+ REGEX_ASSERT(RegexPattern::matches(&pattern, &input, pe, status) == TRUE);
+ REGEX_CHECK_STATUS;
+
+ const char str_abc[] = { 0x61, 0x62, 0x63, 0x00 }; /* abc */
+ utext_openUTF8(&pattern, str_abc, -1, &status);
+ REGEX_ASSERT(RegexPattern::matches("abc", "random input", pe, status) == FALSE);
+ REGEX_CHECK_STATUS;
+
+ const char str_nput[] = { 0x2e, 0x2a, 0x6e, 0x70, 0x75, 0x74, 0x00 }; /* .*nput */
+ utext_openUTF8(&pattern, str_nput, -1, &status);
+ REGEX_ASSERT(RegexPattern::matches(".*nput", "random input", pe, status) == TRUE);
+ REGEX_CHECK_STATUS;
+
+ utext_openUTF8(&pattern, str_randominput, -1, &status);
+ REGEX_ASSERT(RegexPattern::matches("random input", "random input", pe, status) == TRUE);
+ REGEX_CHECK_STATUS;
+
+ const char str_u[] = { 0x2e, 0x2a, 0x75, 0x00 }; /* .*u */
+ utext_openUTF8(&pattern, str_u, -1, &status);
+ REGEX_ASSERT(RegexPattern::matches(".*u", "random input", pe, status) == FALSE);
+ REGEX_CHECK_STATUS;
+
+ utext_openUTF8(&input, str_abc, -1, &status);
+ utext_openUTF8(&pattern, str_abc, -1, &status);
+ status = U_INDEX_OUTOFBOUNDS_ERROR;
+ REGEX_ASSERT(RegexPattern::matches("abc", "abc", pe, status) == FALSE);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+
+ utext_close(&input);
+ utext_close(&pattern);
+ }
+
+
+ //
+ // Split()
+ //
+ status = U_ZERO_ERROR;
+ const char str_spaceplus[] = { 0x20, 0x2b, 0x00 }; /* + */
+ utext_openUTF8(&re1, str_spaceplus, -1, &status);
+ pat1 = RegexPattern::compile(&re1, 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==6);
+ 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]=="");
+ REGEX_ASSERT(fields[6]=="");
+
+ fields[2] = "*";
+ n = pat1->split(" ", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==2);
+ REGEX_ASSERT(fields[0]=="");
+ REGEX_ASSERT(fields[1]=="");
+ REGEX_ASSERT(fields[2]=="*");
+
+ 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)
+ regextst_openUTF8FromInvariant(&re1, "<(\\w*)>", -1, &status);
+ pat1 = RegexPattern::compile(&re1, pe, status);
+ REGEX_CHECK_STATUS;
+
+ status = U_ZERO_ERROR;
+ fields[6] = fields[7] = "*";
+ n = pat1->split("<a>Now is <b>the time<c>", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==7);
+ 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(fields[7]=="*");
+ REGEX_ASSERT(status==U_ZERO_ERROR);
+
+ fields[6] = fields[7] = "*";
+ n = pat1->split(" <a>Now is <b>the time<c>", fields, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(n==7);
+ 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(fields[7]=="*");
+
+ 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]==" ");
+ 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;
+
+ regextst_openUTF8FromInvariant(&re1, "([-,])", -1, &status);
+ pat1 = RegexPattern::compile(&re1, 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;
+
+
+ //
+ // split of a UText based string, with library allocating output UTexts.
+ //
+ {
+ status = U_ZERO_ERROR;
+ RegexMatcher matcher(UnicodeString("(:)"), 0, status);
+ UnicodeString stringToSplit("first:second:third");
+ UText *textToSplit = utext_openUnicodeString(NULL, &stringToSplit, &status);
+ REGEX_CHECK_STATUS;
+
+ UText *splits[10] = {NULL};
+ int32_t numFields = matcher.split(textToSplit, splits, UPRV_LENGTHOF(splits), status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(numFields == 5);
+ REGEX_ASSERT_UTEXT_INVARIANT("first", splits[0]);
+ REGEX_ASSERT_UTEXT_INVARIANT(":", splits[1]);
+ REGEX_ASSERT_UTEXT_INVARIANT("second", splits[2]);
+ REGEX_ASSERT_UTEXT_INVARIANT(":", splits[3]);
+ REGEX_ASSERT_UTEXT_INVARIANT("third", splits[4]);
+ REGEX_ASSERT(splits[5] == NULL);
+
+ for (int i=0; i<UPRV_LENGTHOF(splits); i++) {
+ if (splits[i]) {
+ utext_close(splits[i]);
+ splits[i] = NULL;
+ }
+ }
+ utext_close(textToSplit);
+ }
+
+
+ //
+ // RegexPattern::pattern() and patternText()
+ //
+ pat1 = new RegexPattern();
+ REGEX_ASSERT(pat1->pattern() == "");
+ REGEX_ASSERT_UTEXT_UTF8("", pat1->patternText(status));
+ delete pat1;
+ const char *helloWorldInvariant = "(Hello, world)*";
+ regextst_openUTF8FromInvariant(&re1, helloWorldInvariant, -1, &status);
+ pat1 = RegexPattern::compile(&re1, pe, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT_UNISTR("(Hello, world)*", pat1->pattern());
+ REGEX_ASSERT_UTEXT_INVARIANT("(Hello, world)*", pat1->patternText(status));
+ delete pat1;
+
+ utext_close(&re1);
+}
+
+
+//---------------------------------------------------------------------------
+//
+// 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 */
+ }
+
+ int32_t len;
+ UChar *testData = ReadAndConvertFile(srcPath, len, "utf-8", 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(UNICODE_STRING_SIMPLE("\\s*([\\'\\\"/])(.*?)\\1"), 0, status);
+ RegexMatcher commentMat (UNICODE_STRING_SIMPLE("\\s*(#.*)?$"), 0, status);
+ RegexMatcher flagsMat (UNICODE_STRING_SIMPLE("\\s*([ixsmdteDEGLMQvabtyYzZ2-9]*)([:letter:]*)"), 0, status);
+
+ RegexMatcher lineMat(UNICODE_STRING_SIMPLE("(.*?)\\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
+
+ if (U_FAILURE(status)){
+ dataerrln("Construct RegexMatcher() error - %s", u_errorName(status));
+ delete [] testData;
+ return;
+ }
+
+ //
+ // Loop over the test data file, once per line.
+ //
+ while (lineMat.find()) {
+ lineNum++;
+ if (U_FAILURE(status)) {
+ errln("%s:%d: ICU Error \"%s\"", srcPath, 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 %s:%d", srcPath, 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, srcPath, lineNum);
+ }
+
+ delete [] testData;
+
+}
+
+
+
+//---------------------------------------------------------------------------
+//
+// regex_find(pattern, flags, inputString, lineNumber)
+//
+// Function to run a single test from the Extended (data driven) tests.
+// See file test/testdata/regextst.txt for a description of the
+// pattern and inputString fields, and the allowed flags.
+// lineNumber is the source line in regextst.txt of the test.
+//
+//---------------------------------------------------------------------------
+
+
+// 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, int32_t val, UnicodeString index) {
+ UErrorCode status=U_ZERO_ERROR;
+ int32_t idx = 0;
+ for (int32_t i=0; i<index.length(); i++) {
+ int32_t 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);
+}
+
+static void setInt(UVector &vec, int32_t val, int32_t idx) {
+ UErrorCode status=U_ZERO_ERROR;
+ while (vec.size()<idx+1) {vec.addElement(-1, status);}
+ vec.setElementAt(val, idx);
+}
+
+static UBool utextOffsetToNative(UText *utext, int32_t unistrOffset, int32_t& nativeIndex)
+{
+ UBool couldFind = TRUE;
+ UTEXT_SETNATIVEINDEX(utext, 0);
+ int32_t i = 0;
+ while (i < unistrOffset) {
+ UChar32 c = UTEXT_NEXT32(utext);
+ if (c != U_SENTINEL) {
+ i += U16_LENGTH(c);
+ } else {
+ couldFind = FALSE;
+ break;
+ }
+ }
+ nativeIndex = (int32_t)UTEXT_GETNATIVEINDEX(utext);
+ return couldFind;
+}
+
+
+void RegexTest::regex_find(const UnicodeString &pattern,
+ const UnicodeString &flags,
+ const UnicodeString &inputString,
+ const char *srcPath,
+ int32_t line) {
+ UnicodeString unEscapedInput;
+ UnicodeString deTaggedInput;
+
+ int32_t patternUTF8Length, inputUTF8Length;
+ char *patternChars = NULL, *inputChars = NULL;
+ UText patternText = UTEXT_INITIALIZER;
+ UText inputText = UTEXT_INITIALIZER;
+ UConverter *UTF8Converter = NULL;
+
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError pe;
+ RegexPattern *parsePat = NULL;
+ RegexMatcher *parseMatcher = NULL;
+ RegexPattern *callerPattern = NULL, *UTF8Pattern = NULL;
+ RegexMatcher *matcher = NULL, *UTF8Matcher = NULL;
+ UVector groupStarts(status);
+ UVector groupEnds(status);
+ UVector groupStartsUTF8(status);
+ UVector groupEndsUTF8(status);
+ UBool isMatch = FALSE, isUTF8Match = FALSE;
+ UBool failed = FALSE;
+ int32_t numFinds;
+ int32_t i;
+ UBool useMatchesFunc = FALSE;
+ UBool useLookingAtFunc = FALSE;
+ int32_t regionStart = -1;
+ int32_t regionEnd = -1;
+ int32_t regionStartUTF8 = -1;
+ int32_t regionEndUTF8 = -1;
+
+
+ //
+ // 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;
+ }
+
+ if (flags.indexOf((UChar)0x65) >= 0) { // 'e' flag
+ bflags |= UREGEX_ERROR_ON_UNKNOWN_ESCAPES;
+ }
+ if (flags.indexOf((UChar)0x44) >= 0) { // 'D' flag
+ bflags |= UREGEX_UNIX_LINES;
+ }
+ if (flags.indexOf((UChar)0x51) >= 0) { // 'Q' flag
+ bflags |= UREGEX_LITERAL;
+ }
+
+
+ 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
+ if (flags.indexOf((UChar)0x45) >= 0) { // flags contain 'E'
+ // Expected pattern compilation error.
+ if (flags.indexOf((UChar)0x64) >= 0) { // flags contain 'd'
+ logln("Pattern Compile returns \"%s\"", u_errorName(status));
+ }
+ goto cleanupAndReturn;
+ } else {
+ // Unexpected pattern compilation error.
+ dataerrln("Line %d: error %s compiling pattern.", line, u_errorName(status));
+ goto cleanupAndReturn;
+ }
+ }
+
+ UTF8Converter = ucnv_open("UTF8", &status);
+ ucnv_setFromUCallBack(UTF8Converter, UCNV_FROM_U_CALLBACK_STOP, NULL, NULL, NULL, &status);
+
+ patternUTF8Length = pattern.extract(NULL, 0, UTF8Converter, status);
+ status = U_ZERO_ERROR; // buffer overflow
+ patternChars = new char[patternUTF8Length+1];
+ pattern.extract(patternChars, patternUTF8Length+1, UTF8Converter, status);
+ utext_openUTF8(&patternText, patternChars, patternUTF8Length, &status);
+
+ if (status == U_ZERO_ERROR) {
+ UTF8Pattern = RegexPattern::compile(&patternText, 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
+ if (flags.indexOf((UChar)0x45) >= 0) { // flags contain 'E'
+ // Expected pattern compilation error.
+ if (flags.indexOf((UChar)0x64) >= 0) { // flags contain 'd'
+ logln("Pattern Compile returns \"%s\" (UTF8)", u_errorName(status));
+ }
+ goto cleanupAndReturn;
+ } else {
+ // Unexpected pattern compilation error.
+ errln("Line %d: error %s compiling pattern. (UTF8)", line, u_errorName(status));
+ goto cleanupAndReturn;
+ }
+ }
+ }
+
+ if (UTF8Pattern == NULL) {
+ // UTF-8 does not allow unpaired surrogates, so this could actually happen without being a failure of the engine
+ logln("Unable to create UTF-8 pattern, skipping UTF-8 tests for %s:%d", srcPath, line);
+ status = U_ZERO_ERROR;
+ }
+
+ if (flags.indexOf((UChar)0x64) >= 0) { // 'd' flag
+ callerPattern->dumpPattern();
+ }
+
+ if (flags.indexOf((UChar)0x45) >= 0) { // 'E' flag
+ errln("%s, Line %d: Expected, but did not get, a pattern compilation error.", srcPath, line);
+ goto cleanupAndReturn;
+ }
+
+
+ //
+ // 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;
+ }
+ }
+
+ // 'M' flag. Use matches() instead of find()
+ if (flags.indexOf((UChar)0x4d) >= 0) {
+ useMatchesFunc = TRUE;
+ }
+ if (flags.indexOf((UChar)0x4c) >= 0) {
+ useLookingAtFunc = TRUE;
+ }
+
+ //
+ // Find the tags in the input data, remove them, and record the group boundary
+ // positions.
+ //
+ parsePat = RegexPattern::compile("<(/?)(r|[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 (groupNum == "r") {
+ // <r> or </r>, a region specification within the string
+ if (parseMatcher->group(1, status) == "/") {
+ regionEnd = deTaggedInput.length();
+ } else {
+ regionStart = deTaggedInput.length();
+ }
+ } else {
+ // <digits> or </digits>, a group match boundary tag.
+ if (parseMatcher->group(1, status) == "/") {
+ set(groupEnds, deTaggedInput.length(), groupNum);
+ } else {
+ set(groupStarts, deTaggedInput.length(), groupNum);
+ }
+ }
+ }
+ parseMatcher->appendTail(deTaggedInput);
+ REGEX_ASSERT_L(groupStarts.size() == groupEnds.size(), line);
+ if ((regionStart>=0 || regionEnd>=0) && (regionStart<0 || regionStart>regionEnd)) {
+ errln("mismatched <r> tags");
+ failed = TRUE;
+ goto cleanupAndReturn;
+ }
+
+ //
+ // Configure the matcher according to the flags specified with this test.
+ //
+ matcher = callerPattern->matcher(deTaggedInput, status);
+ REGEX_CHECK_STATUS_L(line);
+ if (flags.indexOf((UChar)0x74) >= 0) { // 't' trace flag
+ matcher->setTrace(TRUE);
+ }
+
+ if (UTF8Pattern != NULL) {
+ inputUTF8Length = deTaggedInput.extract(NULL, 0, UTF8Converter, status);
+ status = U_ZERO_ERROR; // buffer overflow
+ inputChars = new char[inputUTF8Length+1];
+ deTaggedInput.extract(inputChars, inputUTF8Length+1, UTF8Converter, status);
+ utext_openUTF8(&inputText, inputChars, inputUTF8Length, &status);
+
+ if (status == U_ZERO_ERROR) {
+ UTF8Matcher = &UTF8Pattern->matcher(status)->reset(&inputText);
+ REGEX_CHECK_STATUS_L(line);
+ }
+
+ if (UTF8Matcher == NULL) {
+ // UTF-8 does not allow unpaired surrogates, so this could actually happen without being a failure of the engine
+ logln("Unable to create UTF-8 matcher, skipping UTF-8 tests for %s:%d", srcPath, line);
+ status = U_ZERO_ERROR;
+ }
+ }
+
+ //
+ // Generate native indices for UTF8 versions of region and capture group info
+ //
+ if (UTF8Matcher != NULL) {
+ if (regionStart>=0) (void) utextOffsetToNative(&inputText, regionStart, regionStartUTF8);
+ if (regionEnd>=0) (void) utextOffsetToNative(&inputText, regionEnd, regionEndUTF8);
+
+ // Fill out the native index UVector info.
+ // Only need 1 loop, from above we know groupStarts.size() = groupEnds.size()
+ for (i=0; i<groupStarts.size(); i++) {
+ int32_t start = groupStarts.elementAti(i);
+ // -1 means there was no UVector slot and we won't be requesting that capture group for this test, don't bother inserting
+ if (start >= 0) {
+ int32_t startUTF8;
+ if (!utextOffsetToNative(&inputText, start, startUTF8)) {
+ errln("Error at line %d: could not find native index for group start %d. UTF16 index %d", line, i, start);
+ failed = TRUE;
+ goto cleanupAndReturn; // Good chance of subsequent bogus errors. Stop now.
+ }
+ setInt(groupStartsUTF8, startUTF8, i);
+ }
+
+ int32_t end = groupEnds.elementAti(i);
+ // -1 means there was no UVector slot and we won't be requesting that capture group for this test, don't bother inserting
+ if (end >= 0) {
+ int32_t endUTF8;
+ if (!utextOffsetToNative(&inputText, end, endUTF8)) {
+ errln("Error at line %d: could not find native index for group end %d. UTF16 index %d", line, i, end);
+ failed = TRUE;
+ goto cleanupAndReturn; // Good chance of subsequent bogus errors. Stop now.
+ }
+ setInt(groupEndsUTF8, endUTF8, i);
+ }
+ }
+ }
+
+ if (regionStart>=0) {
+ matcher->region(regionStart, regionEnd, status);
+ REGEX_CHECK_STATUS_L(line);
+ if (UTF8Matcher != NULL) {
+ UTF8Matcher->region(regionStartUTF8, regionEndUTF8, status);
+ REGEX_CHECK_STATUS_L(line);
+ }
+ }
+ if (flags.indexOf((UChar)0x61) >= 0) { // 'a' anchoring bounds flag
+ matcher->useAnchoringBounds(FALSE);
+ if (UTF8Matcher != NULL) {
+ UTF8Matcher->useAnchoringBounds(FALSE);
+ }
+ }
+ if (flags.indexOf((UChar)0x62) >= 0) { // 'b' transparent bounds flag
+ matcher->useTransparentBounds(TRUE);
+ if (UTF8Matcher != NULL) {
+ UTF8Matcher->useTransparentBounds(TRUE);
+ }
+ }
+
+
+
+ //
+ // Do a find on the de-tagged input using the caller's pattern
+ // TODO: error on count>1 and not find().
+ // error on both matches() and lookingAt().
+ //
+ for (i=0; i<numFinds; i++) {
+ if (useMatchesFunc) {
+ isMatch = matcher->matches(status);
+ if (UTF8Matcher != NULL) {
+ isUTF8Match = UTF8Matcher->matches(status);
+ }
+ } else if (useLookingAtFunc) {
+ isMatch = matcher->lookingAt(status);
+ if (UTF8Matcher != NULL) {
+ isUTF8Match = UTF8Matcher->lookingAt(status);
+ }
+ } else {
+ isMatch = matcher->find();
+ if (UTF8Matcher != NULL) {
+ isUTF8Match = UTF8Matcher->find();
+ }
+ }
+ }
+ matcher->setTrace(FALSE);
+ if (U_FAILURE(status)) {
+ errln("Error at line %d. ICU ErrorCode is %s", u_errorName(status));
+ }
+
+ //
+ // 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) {
+ dataerrln("Error at line %d: Match expected, but none found.", line);
+ failed = TRUE;
+ goto cleanupAndReturn;
+ } else if (UTF8Matcher != NULL && isUTF8Match == FALSE && groupStarts.size() != 0) {
+ errln("Error at line %d: Match expected, but none found. (UTF8)", 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.", line);
+ failed = TRUE;
+ } else if (UTF8Matcher != NULL && isUTF8Match && groupStarts.size() == 0) {
+ errln("Error at line %d: No match expected, but one found. (UTF8)", line);
+ failed = TRUE;
+ }
+ goto cleanupAndReturn;
+ }
+
+ REGEX_CHECK_STATUS_L(line);
+ for (i=0; i<=matcher->groupCount(); i++) {
+ int32_t expectedStart = (i >= groupStarts.size()? -1 : groupStarts.elementAti(i));
+ int32_t expectedStartUTF8 = (i >= groupStartsUTF8.size()? -1 : groupStartsUTF8.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.
+ } else if (UTF8Matcher != NULL && UTF8Matcher->start(i, status) != expectedStartUTF8) {
+ errln("Error at line %d: incorrect start position for group %d. Expected %d, got %d (UTF8)",
+ line, i, expectedStartUTF8, UTF8Matcher->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));
+ int32_t expectedEndUTF8 = (i >= groupEndsUTF8.size()? -1 : groupEndsUTF8.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.
+ } else if (UTF8Matcher != NULL && UTF8Matcher->end(i, status) != expectedEndUTF8) {
+ errln("Error at line %d: incorrect end position for group %d. Expected %d, got %d (UTF8)",
+ line, i, expectedEndUTF8, UTF8Matcher->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;
+ }
+ else if (UTF8Matcher != NULL && UTF8Matcher->groupCount()+1 < groupStarts.size()) {
+ errln("Error at line %d: Expected %d capture groups, found %d. (UTF8)",
+ line, groupStarts.size()-1, UTF8Matcher->groupCount());
+ failed = TRUE;
+ }
+
+ if ((flags.indexOf((UChar)0x59) >= 0) && // 'Y' flag: RequireEnd() == false
+ matcher->requireEnd() == TRUE) {
+ errln("Error at line %d: requireEnd() returned TRUE. Expected FALSE", line);
+ failed = TRUE;
+ } else if (UTF8Matcher != NULL && (flags.indexOf((UChar)0x59) >= 0) && // 'Y' flag: RequireEnd() == false
+ UTF8Matcher->requireEnd() == TRUE) {
+ errln("Error at line %d: requireEnd() returned TRUE. Expected FALSE (UTF8)", line);
+ failed = TRUE;
+ }
+
+ if ((flags.indexOf((UChar)0x79) >= 0) && // 'y' flag: RequireEnd() == true
+ matcher->requireEnd() == FALSE) {
+ errln("Error at line %d: requireEnd() returned FALSE. Expected TRUE", line);
+ failed = TRUE;
+ } else if (UTF8Matcher != NULL && (flags.indexOf((UChar)0x79) >= 0) && // 'Y' flag: RequireEnd() == false
+ UTF8Matcher->requireEnd() == FALSE) {
+ errln("Error at line %d: requireEnd() returned FALSE. Expected TRUE (UTF8)", line);
+ failed = TRUE;
+ }
+
+ if ((flags.indexOf((UChar)0x5A) >= 0) && // 'Z' flag: hitEnd() == false
+ matcher->hitEnd() == TRUE) {
+ errln("Error at line %d: hitEnd() returned TRUE. Expected FALSE", line);
+ failed = TRUE;
+ } else if (UTF8Matcher != NULL && (flags.indexOf((UChar)0x5A) >= 0) && // 'Z' flag: hitEnd() == false
+ UTF8Matcher->hitEnd() == TRUE) {
+ errln("Error at line %d: hitEnd() returned TRUE. Expected FALSE (UTF8)", line);
+ failed = TRUE;
+ }
+
+ if ((flags.indexOf((UChar)0x7A) >= 0) && // 'z' flag: hitEnd() == true
+ matcher->hitEnd() == FALSE) {
+ errln("Error at line %d: hitEnd() returned FALSE. Expected TRUE", line);
+ failed = TRUE;
+ } else if (UTF8Matcher != NULL && (flags.indexOf((UChar)0x7A) >= 0) && // 'z' flag: hitEnd() == true
+ UTF8Matcher->hitEnd() == FALSE) {
+ errln("Error at line %d: hitEnd() returned FALSE. Expected TRUE (UTF8)", line);
+ failed = TRUE;
+ }
+
+
+cleanupAndReturn:
+ if (failed) {
+ infoln((UnicodeString)"\""+pattern+(UnicodeString)"\" "
+ +flags+(UnicodeString)" \""+inputString+(UnicodeString)"\"");
+ // callerPattern->dump();
+ }
+ delete parseMatcher;
+ delete parsePat;
+ delete UTF8Matcher;
+ delete UTF8Pattern;
+ delete matcher;
+ delete callerPattern;
+
+ utext_close(&inputText);
+ delete[] inputChars;
+ utext_close(&patternText);
+ delete[] patternChars;
+ ucnv_close(UTF8Converter);
+}
+
+
+
+
+//---------------------------------------------------------------------------
+//
+// 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, 16, 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);
+
+ // Ticket 5389
+ REGEX_ERR("*c", 1, 1, U_REGEX_RULE_SYNTAX);
+
+ // Invalid Back Reference \0
+ // For ICU 3.8 and earlier
+ // For ICU versions newer than 3.8, \0 introduces an octal escape.
+ //
+ REGEX_ERR("(ab)\\0", 1, 6, U_REGEX_BAD_ESCAPE_SEQUENCE);
+
+}
+
+
+//-------------------------------------------------------------------------------
+//
+// 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, int32_t &ulen,
+ const char *defEncoding, 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) {
+ dataerrln("Error opening test data file %s\n", fileName);
+ status = U_FILE_ACCESS_ERROR;
+ return NULL;
+ }
+ //
+ // Read it in
+ //
+ int32_t fileSize;
+ int32_t 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;
+ } else {
+ encoding = defEncoding;
+ if (strcmp(encoding, "utf-8") == 0) {
+ errln("file %s is missing its BOM", fileName);
+ }
+ }
+
+ //
+ // 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 */
+ }
+
+ int32_t len;
+ UChar *testData = ReadAndConvertFile(srcPath, len, "iso-8859-1", 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(UNICODE_STRING_SIMPLE("(.+?)[\\r\\n]+"), 0, pe, status);
+ if (U_FAILURE(status)) {
+ dataerrln("RegexPattern::compile() error");
+ return;
+ }
+ 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(UNICODE_STRING_SIMPLE("\\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(UNICODE_STRING_SIMPLE("('?)(.*)\\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", -1, US_INV);
+ nulnul = nulnul.unescape();
+
+ UnicodeString ffffSrc("${ffff}");
+ UnicodeString ffff("\\uffff", -1, US_INV);
+ ffff = ffff.unescape();
+
+ // regexp for $-[0], $+[2], etc.
+ RegexPattern *groupsPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$([+\\-])\\[(\\d+)\\]"), 0, pe, status);
+ RegexMatcher *groupsMat = groupsPat->matcher(status);
+
+ // regexp for $0, $1, $2, etc.
+ RegexPattern *cgPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$(\\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, UNICODE_STRING_SIMPLE("\\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(UNICODE_STRING_SIMPLE("\\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;
+ }
+
+ // Don't try to check expected results if there is no match.
+ // (Some have stuff in the expected fields)
+ if (!found) {
+ delete testMat;
+ delete testPat;
+ 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];
+#if SUPPORT_MUTATING_INPUT_STRING
+ groupsMat->reset(perlExpr);
+ cgMat->reset(perlExpr);
+#endif
+
+ while (perlExpr.length() > 0) {
+#if !SUPPORT_MUTATING_INPUT_STRING
+ // Perferred usage. Reset after any modification to input string.
+ groupsMat->reset(perlExpr);
+ cgMat->reset(perlExpr);
+#endif
+
+ 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("@-")) {
+ int32_t 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("@+")) {
+ int32_t 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(UNICODE_STRING_SIMPLE("\\"))) { // \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(UNICODE_STRING_SIMPLE("\\n"), "\n");
+
+
+ if (expectedS.compare(resultString) != 0) {
+ err("Line %d: Incorrect perl expression results.", lineNum);
+ infoln((UnicodeString)"Expected \""+expectedS+(UnicodeString)"\"; got \""+resultString+(UnicodeString)"\"");
+ }
+
+ 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);
+
+}
+
+
+//-------------------------------------------------------------------------------
+//
+// PerlTestsUTF8 Run Perl's regular expression tests on UTF-8-based UTexts
+// (instead of using UnicodeStrings) to test the alternate engine.
+// The input file for this test is re_tests, the standard regular
+// expression test data distributed with the Perl source code.
+// See PerlTests() for more information.
+//
+//-------------------------------------------------------------------------------
+void RegexTest::PerlTestsUTF8() {
+ char tdd[2048];
+ const char *srcPath;
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError pe;
+ LocalUConverterPointer UTF8Converter(ucnv_open("UTF-8", &status));
+ UText patternText = UTEXT_INITIALIZER;
+ char *patternChars = NULL;
+ int32_t patternLength;
+ int32_t patternCapacity = 0;
+ UText inputText = UTEXT_INITIALIZER;
+ char *inputChars = NULL;
+ int32_t inputLength;
+ int32_t inputCapacity = 0;
+
+ ucnv_setFromUCallBack(UTF8Converter.getAlias(), UCNV_FROM_U_CALLBACK_STOP, NULL, NULL, NULL, &status);
+
+ //
+ // Open and read the test data file.
+ //
+ srcPath=getPath(tdd, "re_tests.txt");
+ if(srcPath==NULL) {
+ return; /* something went wrong, error already output */
+ }
+
+ int32_t len;
+ UChar *testData = ReadAndConvertFile(srcPath, len, "iso-8859-1", 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(UNICODE_STRING_SIMPLE("(.+?)[\\r\\n]+"), 0, pe, status);
+ if (U_FAILURE(status)) {
+ dataerrln("RegexPattern::compile() error");
+ return;
+ }
+ 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(UNICODE_STRING_SIMPLE("\\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(UNICODE_STRING_SIMPLE("('?)(.*)\\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", -1, US_INV);
+ nulnul = nulnul.unescape();
+
+ UnicodeString ffffSrc("${ffff}");
+ UnicodeString ffff("\\uffff", -1, US_INV);
+ ffff = ffff.unescape();
+
+ // regexp for $-[0], $+[2], etc.
+ RegexPattern *groupsPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$([+\\-])\\[(\\d+)\\]"), 0, pe, status);
+ RegexMatcher *groupsMat = groupsPat->matcher(status);
+
+ // regexp for $0, $1, $2, etc.
+ RegexPattern *cgPat = RegexPattern::compile(UNICODE_STRING_SIMPLE("\\$(\\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, UNICODE_STRING_SIMPLE("\\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;
+ }
+
+ //
+ // Put the pattern in a UTF-8 UText
+ //
+ status = U_ZERO_ERROR;
+ patternLength = pattern.extract(patternChars, patternCapacity, UTF8Converter.getAlias(), status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ status = U_ZERO_ERROR;
+ delete[] patternChars;
+ patternCapacity = patternLength + 1;
+ patternChars = new char[patternCapacity];
+ pattern.extract(patternChars, patternCapacity, UTF8Converter.getAlias(), status);
+ }
+ utext_openUTF8(&patternText, patternChars, patternLength, &status);
+
+ //
+ // Compile the test pattern.
+ //
+ RegexPattern *testPat = RegexPattern::compile(&patternText, 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(UNICODE_STRING_SIMPLE("\\n"), "\n");
+
+ //
+ // Put the input in a UTF-8 UText
+ //
+ status = U_ZERO_ERROR;
+ inputLength = matchString.extract(inputChars, inputCapacity, UTF8Converter.getAlias(), status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ status = U_ZERO_ERROR;
+ delete[] inputChars;
+ inputCapacity = inputLength + 1;
+ inputChars = new char[inputCapacity];
+ matchString.extract(inputChars, inputCapacity, UTF8Converter.getAlias(), status);
+ }
+ utext_openUTF8(&inputText, inputChars, inputLength, &status);
+
+ //
+ // Run the test, check for expected match/don't match result.
+ //
+ RegexMatcher *testMat = &testPat->matcher(status)->reset(&inputText);
+ 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;
+ }
+
+ // Don't try to check expected results if there is no match.
+ // (Some have stuff in the expected fields)
+ if (!found) {
+ delete testMat;
+ delete testPat;
+ 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];
+
+ while (perlExpr.length() > 0) {
+ groupsMat->reset(perlExpr);
+ cgMat->reset(perlExpr);
+
+ 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("@-")) {
+ int32_t 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("@+")) {
+ int32_t 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(UNICODE_STRING_SIMPLE("\\"))) { // \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(UNICODE_STRING_SIMPLE("\\n"), "\n");
+
+
+ if (expectedS.compare(resultString) != 0) {
+ err("Line %d: Incorrect perl expression results.", lineNum);
+ infoln((UnicodeString)"Expected \""+expectedS+(UnicodeString)"\"; got \""+resultString+(UnicodeString)"\"");
+ }
+
+ 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;
+
+ utext_close(&patternText);
+ utext_close(&inputText);
+
+ delete [] patternChars;
+ delete [] inputChars;
+
+
+ logln("%d tests skipped because of unimplemented regexp features.", skippedUnimplementedCount);
+
+}
+
+
+//--------------------------------------------------------------
+//
+// Bug6149 Verify limits to heap expansion for backtrack stack.
+// Use this pattern,
+// "(a?){1,8000000}"
+// Note: was an unbounded upperbounds, but that now has loop-breaking enabled.
+// This test is likely to be fragile, as further optimizations stop
+// more cases of pointless looping in the match engine.
+//
+//---------------------------------------------------------------
+void RegexTest::Bug6149() {
+ UnicodeString pattern("(a?){1,8000000}");
+ UnicodeString s("xyz");
+ uint32_t flags = 0;
+ UErrorCode status = U_ZERO_ERROR;
+
+ RegexMatcher matcher(pattern, s, flags, status);
+ UBool result = false;
+ REGEX_ASSERT_FAIL(result=matcher.matches(status), U_REGEX_STACK_OVERFLOW);
+ REGEX_ASSERT(result == FALSE);
+ }
+
+
+//
+// Callbacks() Test the callback function.
+// When set, callbacks occur periodically during matching operations,
+// giving the application code the ability to abort the operation
+// before it's normal completion.
+//
+
+struct callBackContext {
+ RegexTest *test;
+ int32_t maxCalls;
+ int32_t numCalls;
+ int32_t lastSteps;
+ void reset(int32_t max) {maxCalls=max; numCalls=0; lastSteps=0;};
+};
+
+U_CDECL_BEGIN
+static UBool U_CALLCONV
+testCallBackFn(const void *context, int32_t steps) {
+ callBackContext *info = (callBackContext *)context;
+ if (info->lastSteps+1 != steps) {
+ info->test->errln("incorrect steps in callback. Expected %d, got %d\n", info->lastSteps+1, steps);
+ }
+ info->lastSteps = steps;
+ info->numCalls++;
+ return (info->numCalls < info->maxCalls);
+}
+U_CDECL_END
+
+void RegexTest::Callbacks() {
+ {
+ // Getter returns NULLs if no callback has been set
+
+ // The variables that the getter will fill in.
+ // Init to non-null values so that the action of the getter can be seen.
+ const void *returnedContext = &returnedContext;
+ URegexMatchCallback *returnedFn = &testCallBackFn;
+
+ UErrorCode status = U_ZERO_ERROR;
+ RegexMatcher matcher("x", 0, status);
+ REGEX_CHECK_STATUS;
+ matcher.getMatchCallback(returnedFn, returnedContext, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(returnedFn == NULL);
+ REGEX_ASSERT(returnedContext == NULL);
+ }
+
+ {
+ // Set and Get work
+ callBackContext cbInfo = {this, 0, 0, 0};
+ const void *returnedContext;
+ URegexMatchCallback *returnedFn;
+ UErrorCode status = U_ZERO_ERROR;
+ RegexMatcher matcher(UNICODE_STRING_SIMPLE("((.)+\\2)+x"), 0, status); // A pattern that can run long.
+ REGEX_CHECK_STATUS;
+ matcher.setMatchCallback(testCallBackFn, &cbInfo, status);
+ REGEX_CHECK_STATUS;
+ matcher.getMatchCallback(returnedFn, returnedContext, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(returnedFn == testCallBackFn);
+ REGEX_ASSERT(returnedContext == &cbInfo);
+
+ // A short-running match shouldn't invoke the callback
+ status = U_ZERO_ERROR;
+ cbInfo.reset(1);
+ UnicodeString s = "xxx";
+ matcher.reset(s);
+ REGEX_ASSERT(matcher.matches(status));
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(cbInfo.numCalls == 0);
+
+ // A medium-length match that runs long enough to invoke the
+ // callback, but not so long that the callback aborts it.
+ status = U_ZERO_ERROR;
+ cbInfo.reset(4);
+ s = "aaaaaaaaaaaaaaaaaaab";
+ matcher.reset(s);
+ REGEX_ASSERT(matcher.matches(status)==FALSE);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(cbInfo.numCalls > 0);
+
+ // A longer running match that the callback function will abort.
+ status = U_ZERO_ERROR;
+ cbInfo.reset(4);
+ s = "aaaaaaaaaaaaaaaaaaaaaaab";
+ matcher.reset(s);
+ REGEX_ASSERT(matcher.matches(status)==FALSE);
+ REGEX_ASSERT(status == U_REGEX_STOPPED_BY_CALLER);
+ REGEX_ASSERT(cbInfo.numCalls == 4);
+
+ // A longer running find that the callback function will abort.
+ status = U_ZERO_ERROR;
+ cbInfo.reset(4);
+ s = "aaaaaaaaaaaaaaaaaaaaaaab";
+ matcher.reset(s);
+ REGEX_ASSERT(matcher.find(status)==FALSE);
+ REGEX_ASSERT(status == U_REGEX_STOPPED_BY_CALLER);
+ REGEX_ASSERT(cbInfo.numCalls == 4);
+ }
+
+
+}
+
+
+//
+// FindProgressCallbacks() Test the find "progress" callback function.
+// When set, the find progress callback will be invoked during a find operations
+// after each return from a match attempt, giving the application the opportunity
+// to terminate a long-running find operation before it's normal completion.
+//
+
+struct progressCallBackContext {
+ RegexTest *test;
+ int64_t lastIndex;
+ int32_t maxCalls;
+ int32_t numCalls;
+ void reset(int32_t max) {maxCalls=max; numCalls=0;lastIndex=0;};
+};
+
+// call-back function for find().
+// Return TRUE to continue the find().
+// Return FALSE to stop the find().
+U_CDECL_BEGIN
+static UBool U_CALLCONV
+testProgressCallBackFn(const void *context, int64_t matchIndex) {
+ progressCallBackContext *info = (progressCallBackContext *)context;
+ info->numCalls++;
+ info->lastIndex = matchIndex;
+// info->test->infoln("ProgressCallback - matchIndex = %d, numCalls = %d\n", matchIndex, info->numCalls);
+ return (info->numCalls < info->maxCalls);
+}
+U_CDECL_END
+
+void RegexTest::FindProgressCallbacks() {
+ {
+ // Getter returns NULLs if no callback has been set
+
+ // The variables that the getter will fill in.
+ // Init to non-null values so that the action of the getter can be seen.
+ const void *returnedContext = &returnedContext;
+ URegexFindProgressCallback *returnedFn = &testProgressCallBackFn;
+
+ UErrorCode status = U_ZERO_ERROR;
+ RegexMatcher matcher("x", 0, status);
+ REGEX_CHECK_STATUS;
+ matcher.getFindProgressCallback(returnedFn, returnedContext, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(returnedFn == NULL);
+ REGEX_ASSERT(returnedContext == NULL);
+ }
+
+ {
+ // Set and Get work
+ progressCallBackContext cbInfo = {this, 0, 0, 0};
+ const void *returnedContext;
+ URegexFindProgressCallback *returnedFn;
+ UErrorCode status = U_ZERO_ERROR;
+ RegexMatcher matcher(UNICODE_STRING_SIMPLE("((.)\\2)x"), 0, status);
+ REGEX_CHECK_STATUS;
+ matcher.setFindProgressCallback(testProgressCallBackFn, &cbInfo, status);
+ REGEX_CHECK_STATUS;
+ matcher.getFindProgressCallback(returnedFn, returnedContext, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(returnedFn == testProgressCallBackFn);
+ REGEX_ASSERT(returnedContext == &cbInfo);
+
+ // A find that matches on the initial position does NOT invoke the callback.
+ status = U_ZERO_ERROR;
+ cbInfo.reset(100);
+ UnicodeString s = "aaxxx";
+ matcher.reset(s);
+#if 0
+ matcher.setTrace(TRUE);
+#endif
+ REGEX_ASSERT(matcher.find(0, status));
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(cbInfo.numCalls == 0);
+
+ // A medium running find() that causes matcher.find() to invoke our callback for each index,
+ // but not so many times that we interrupt the operation.
+ status = U_ZERO_ERROR;
+ s = "aaaaaaaaaaaaaaaaaaab";
+ cbInfo.reset(s.length()); // Some upper limit for number of calls that is greater than size of our input string
+ matcher.reset(s);
+ REGEX_ASSERT(matcher.find(0, status)==FALSE);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(cbInfo.numCalls > 0 && cbInfo.numCalls < 25);
+
+ // A longer running match that causes matcher.find() to invoke our callback which we cancel/interrupt at some point.
+ status = U_ZERO_ERROR;
+ UnicodeString s1 = "aaaaaaaaaaaaaaaaaaaaaaab";
+ cbInfo.reset(s1.length() - 5); // Bail early somewhere near the end of input string
+ matcher.reset(s1);
+ REGEX_ASSERT(matcher.find(0, status)==FALSE);
+ REGEX_ASSERT(status == U_REGEX_STOPPED_BY_CALLER);
+ REGEX_ASSERT(cbInfo.numCalls == s1.length() - 5);
+
+ // Now a match that will succeed, but after an interruption
+ status = U_ZERO_ERROR;
+ UnicodeString s2 = "aaaaaaaaaaaaaa aaaaaaaaab xxx";
+ cbInfo.reset(s2.length() - 10); // Bail early somewhere near the end of input string
+ matcher.reset(s2);
+ REGEX_ASSERT(matcher.find(0, status)==FALSE);
+ REGEX_ASSERT(status == U_REGEX_STOPPED_BY_CALLER);
+ // Now retry the match from where left off
+ cbInfo.maxCalls = 100; // No callback limit
+ status = U_ZERO_ERROR;
+ REGEX_ASSERT(matcher.find(cbInfo.lastIndex, status));
+ REGEX_CHECK_STATUS;
+ }
+
+
+}
+
+
+//---------------------------------------------------------------------------
+//
+// PreAllocatedUTextCAPI Check the C API with pre-allocated mutable
+// UTexts. The pure-C implementation of UText
+// has no mutable backing stores, but we can
+// use UnicodeString here to test the functionality.
+//
+//---------------------------------------------------------------------------
+void RegexTest::PreAllocatedUTextCAPI () {
+ UErrorCode status = U_ZERO_ERROR;
+ URegularExpression *re;
+ UText patternText = UTEXT_INITIALIZER;
+ UnicodeString buffer;
+ UText bufferText = UTEXT_INITIALIZER;
+
+ utext_openUnicodeString(&bufferText, &buffer, &status);
+
+ /*
+ * getText() and getUText()
+ */
+ {
+ UText text1 = UTEXT_INITIALIZER;
+ UText text2 = UTEXT_INITIALIZER;
+ UChar text2Chars[20];
+ UText *resultText;
+
+ status = U_ZERO_ERROR;
+ regextst_openUTF8FromInvariant(&text1, "abcccd", -1, &status);
+ regextst_openUTF8FromInvariant(&text2, "abcccxd", -1, &status);
+ u_uastrncpy(text2Chars, "abcccxd", sizeof(text2)/2);
+ utext_openUChars(&text2, text2Chars, -1, &status);
+
+ regextst_openUTF8FromInvariant(&patternText, "abc*d", -1, &status);
+ re = uregex_openUText(&patternText, 0, NULL, &status);
+
+ /* First set a UText */
+ uregex_setUText(re, &text1, &status);
+ resultText = uregex_getUText(re, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(resultText == &bufferText);
+ utext_setNativeIndex(resultText, 0);
+ utext_setNativeIndex(&text1, 0);
+ REGEX_ASSERT(testUTextEqual(resultText, &text1));
+
+ resultText = uregex_getUText(re, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(resultText == &bufferText);
+ utext_setNativeIndex(resultText, 0);
+ utext_setNativeIndex(&text1, 0);
+ REGEX_ASSERT(testUTextEqual(resultText, &text1));
+
+ /* Then set a UChar * */
+ uregex_setText(re, text2Chars, 7, &status);
+ resultText = uregex_getUText(re, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(resultText == &bufferText);
+ utext_setNativeIndex(resultText, 0);
+ utext_setNativeIndex(&text2, 0);
+ REGEX_ASSERT(testUTextEqual(resultText, &text2));
+
+ uregex_close(re);
+ utext_close(&text1);
+ utext_close(&text2);
+ }
+
+ /*
+ * group()
+ */
+ {
+ UChar text1[80];
+ UText *actual;
+ UBool result;
+ int64_t length = 0;
+
+ u_uastrncpy(text1, "noise abc interior def, and this is off the end", UPRV_LENGTHOF(text1));
+ // 012345678901234567890123456789012345678901234567
+ // 0 1 2 3 4
+
+ status = U_ZERO_ERROR;
+ re = uregex_openC("abc(.*?)def", 0, NULL, &status);
+ REGEX_CHECK_STATUS;
+
+ uregex_setText(re, text1, -1, &status);
+ result = uregex_find(re, 0, &status);
+ REGEX_ASSERT(result==TRUE);
+
+ /* Capture Group 0, the full match. Should succeed. "abc interior def" */
+ status = U_ZERO_ERROR;
+ actual = uregex_groupUText(re, 0, &bufferText, &length, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(actual == &bufferText);
+ REGEX_ASSERT(utext_getNativeIndex(actual) == 6);
+ REGEX_ASSERT(length == 16);
+ REGEX_ASSERT(utext_nativeLength(actual) == 47);
+
+ /* Capture group #1. Should succeed, matching " interior ". */
+ status = U_ZERO_ERROR;
+ actual = uregex_groupUText(re, 1, &bufferText, &length, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(actual == &bufferText);
+ REGEX_ASSERT(utext_getNativeIndex(actual) == 9); // position of " interior "
+ REGEX_ASSERT(length == 10);
+ REGEX_ASSERT(utext_nativeLength(actual) == 47);
+
+ /* Capture group out of range. Error. */
+ status = U_ZERO_ERROR;
+ actual = uregex_groupUText(re, 2, &bufferText, &length, &status);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+ REGEX_ASSERT(actual == &bufferText);
+ uregex_close(re);
+
+ }
+
+ /*
+ * replaceFirst()
+ */
+ {
+ UChar text1[80];
+ UChar text2[80];
+ UText replText = UTEXT_INITIALIZER;
+ UText *result;
+ status = U_ZERO_ERROR;
+ utext_openUnicodeString(&bufferText, &buffer, &status);
+
+ status = U_ZERO_ERROR;
+ u_uastrncpy(text1, "Replace xaax x1x x...x.", UPRV_LENGTHOF(text1));
+ u_uastrncpy(text2, "No match here.", UPRV_LENGTHOF(text2)/2);
+ regextst_openUTF8FromInvariant(&replText, "<$1>", -1, &status);
+
+ re = uregex_openC("x(.*?)x", 0, NULL, &status);
+ REGEX_CHECK_STATUS;
+
+ /* Normal case, with match */
+ uregex_setText(re, text1, -1, &status);
+ REGEX_CHECK_STATUS;
+ utext_replace(&bufferText, 0, utext_nativeLength(&bufferText), NULL, 0, &status);
+ REGEX_CHECK_STATUS;
+ result = uregex_replaceFirstUText(re, &replText, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &bufferText);
+ REGEX_ASSERT_UTEXT_INVARIANT("Replace <aa> x1x x...x.", result);
+
+ /* No match. Text should copy to output with no changes. */
+ uregex_setText(re, text2, -1, &status);
+ utext_replace(&bufferText, 0, utext_nativeLength(&bufferText), NULL, 0, &status);
+ result = uregex_replaceFirstUText(re, &replText, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &bufferText);
+ REGEX_ASSERT_UTEXT_INVARIANT("No match here.", result);
+
+ /* Unicode escapes */
+ uregex_setText(re, text1, -1, &status);
+ regextst_openUTF8FromInvariant(&replText, "\\\\\\u0041$1\\U00000042\\$\\a", -1, &status);
+ utext_replace(&bufferText, 0, utext_nativeLength(&bufferText), NULL, 0, &status);
+ result = uregex_replaceFirstUText(re, &replText, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &bufferText);
+ REGEX_ASSERT_UTEXT_INVARIANT("Replace \\AaaB$a x1x x...x.", result);
+
+ uregex_close(re);
+ utext_close(&replText);
+ }
+
+
+ /*
+ * replaceAll()
+ */
+ {
+ UChar text1[80];
+ UChar text2[80];
+ UText replText = UTEXT_INITIALIZER;
+ UText *result;
+
+ status = U_ZERO_ERROR;
+ u_uastrncpy(text1, "Replace xaax x1x x...x.", sizeof(text1)/2);
+ u_uastrncpy(text2, "No match here.", sizeof(text2)/2);
+ regextst_openUTF8FromInvariant(&replText, "<$1>", -1, &status);
+
+ re = uregex_openC("x(.*?)x", 0, NULL, &status);
+ REGEX_CHECK_STATUS;
+
+ /* Normal case, with match */
+ uregex_setText(re, text1, -1, &status);
+ utext_replace(&bufferText, 0, utext_nativeLength(&bufferText), NULL, 0, &status);
+ result = uregex_replaceAllUText(re, &replText, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &bufferText);
+ REGEX_ASSERT_UTEXT_INVARIANT("Replace <aa> <1> <...>.", result);
+
+ /* No match. Text should copy to output with no changes. */
+ uregex_setText(re, text2, -1, &status);
+ utext_replace(&bufferText, 0, utext_nativeLength(&bufferText), NULL, 0, &status);
+ result = uregex_replaceAllUText(re, &replText, &bufferText, &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(result == &bufferText);
+ REGEX_ASSERT_UTEXT_INVARIANT("No match here.", result);
+
+ uregex_close(re);
+ utext_close(&replText);
+ }
+
+
+ /*
+ * splitUText() uses the C++ API directly, and the UnicodeString version uses mutable UTexts,
+ * so we don't need to test it here.
+ */
+
+ utext_close(&bufferText);
+ utext_close(&patternText);
+}
+
+
+//--------------------------------------------------------------
+//
+// NamedCapture Check basic named capture group functionality
+//
+//--------------------------------------------------------------
+void RegexTest::NamedCapture() {
+ UErrorCode status = U_ZERO_ERROR;
+ RegexPattern *pat = RegexPattern::compile(UnicodeString(
+ "abc()()(?<three>xyz)(de)(?<five>hmm)(?<six>oh)f\\k<five>"), 0, status);
+ REGEX_CHECK_STATUS;
+ int32_t group = pat->groupNumberFromName("five", -1, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(5 == group);
+ group = pat->groupNumberFromName("three", -1, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(3 == group);
+
+ status = U_ZERO_ERROR;
+ group = pat->groupNumberFromName(UnicodeString("six"), status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(6 == group);
+
+ status = U_ZERO_ERROR;
+ group = pat->groupNumberFromName(UnicodeString("nosuch"), status);
+ U_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+
+ // After copying a pattern, named capture should still work in the copy.
+ RegexPattern *copiedPat = new RegexPattern(*pat);
+ REGEX_ASSERT(*copiedPat == *pat);
+ delete pat; pat = NULL; // Delete original, copy should have no references back to it.
+
+ group = copiedPat->groupNumberFromName("five", -1, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(5 == group);
+ group = copiedPat->groupNumberFromName("three", -1, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(3 == group);
+ delete copiedPat;
+
+ // ReplaceAll with named capture group.
+ status = U_ZERO_ERROR;
+ UnicodeString text("Substitution of <<quotes>> for <<double brackets>>");
+ RegexMatcher *m = new RegexMatcher(UnicodeString("<<(?<mid>.+?)>>"), text, 0, status);
+ REGEX_CHECK_STATUS;
+ // m.pattern().dumpPattern();
+ UnicodeString replacedText = m->replaceAll("'${mid}'", status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("Substitution of 'quotes' for 'double brackets'") == replacedText);
+ delete m;
+
+ // ReplaceAll, allowed capture group numbers.
+ text = UnicodeString("abcmxyz");
+ m = new RegexMatcher(UnicodeString("..(?<one>m)(.)(.)"), text, 0, status);
+ REGEX_CHECK_STATUS;
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$0>"), status); // group 0, full match, is allowed.
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<bcmxy>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$1>"), status); // group 1 by number.
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<m>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<${one}>"), status); // group 1 by name.
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<m>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$2>"), status); // group 2.
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<x>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$3>"), status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<y>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$4>"), status);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$04>"), status); // group 0, leading 0,
+ REGEX_CHECK_STATUS; // trailing out-of-range 4 passes through.
+ REGEX_ASSERT(UnicodeString("a<bcmxy4>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$000016>"), status); // Consume leading zeroes. Don't consume digits
+ REGEX_CHECK_STATUS; // that push group num out of range.
+ REGEX_ASSERT(UnicodeString("a<m6>z") == replacedText); // This is group 1.
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<$3$2$1${one}>"), status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<yxmm>z") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("$3$2$1${one}"), status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("ayxmmz") == replacedText);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<${noSuchName}>"), status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<${invalid-name}>"), status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("<${one"), status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ replacedText = m->replaceAll(UnicodeString("$not a capture group"), status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ delete m;
+
+ // Repeat the above replaceAll() tests using the plain C API, which
+ // has a separate implementation internally.
+ // TODO: factor out the test data.
+
+ status = U_ZERO_ERROR;
+ URegularExpression *re = uregex_openC("..(?<one>m)(.)(.)", 0, NULL, &status);
+ REGEX_CHECK_STATUS;
+ text = UnicodeString("abcmxyz");
+ uregex_setText(re, text.getBuffer(), text.length(), &status);
+ REGEX_CHECK_STATUS;
+
+ UChar resultBuf[100];
+ int32_t resultLength;
+ UnicodeString repl;
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$0>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<bcmxy>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$1>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<m>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<${one}>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<m>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$2>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<x>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$3>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<y>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$4>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_ASSERT(status == U_INDEX_OUTOFBOUNDS_ERROR);
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$04>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<bcmxy4>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$000016>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<m6>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<$3$2$1${one}>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("a<yxmm>z") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("$3$2$1${one}");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(UnicodeString("ayxmmz") == UnicodeString(resultBuf, resultLength));
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<${noSuchName}>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<${invalid-name}>");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("<${one");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ status = U_ZERO_ERROR;
+ repl = UnicodeString("$not a capture group");
+ resultLength = uregex_replaceAll(re, repl.getBuffer(), repl.length(), resultBuf, UPRV_LENGTHOF(resultBuf), &status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_CAPTURE_GROUP_NAME);
+
+ uregex_close(re);
+}
+
+//--------------------------------------------------------------
+//
+// NamedCaptureLimits Patterns with huge numbers of named capture groups.
+// The point is not so much what the exact limit is,
+// but that a largish number doesn't hit bad non-linear performance,
+// and that exceeding the limit fails cleanly.
+//
+//--------------------------------------------------------------
+void RegexTest::NamedCaptureLimits() {
+ if (quick) {
+ logln("Skipping test. Runs in exhuastive mode only.");
+ return;
+ }
+ const int32_t goodLimit = 1000000; // Pattern w this many groups builds successfully.
+ const int32_t failLimit = 10000000; // Pattern exceeds internal limits, fails to compile.
+ char nnbuf[100];
+ UnicodeString pattern;
+ int32_t nn;
+
+ for (nn=1; nn<goodLimit; nn++) {
+ sprintf(nnbuf, "(?<nn%d>)", nn);
+ pattern.append(UnicodeString(nnbuf, -1, US_INV));
+ }
+ UErrorCode status = U_ZERO_ERROR;
+ RegexPattern *pat = RegexPattern::compile(pattern, 0, status);
+ REGEX_CHECK_STATUS;
+ for (nn=1; nn<goodLimit; nn++) {
+ sprintf(nnbuf, "nn%d", nn);
+ int32_t groupNum = pat->groupNumberFromName(nnbuf, -1, status);
+ REGEX_ASSERT(nn == groupNum);
+ if (nn != groupNum) {
+ break;
+ }
+ }
+ delete pat;
+
+ pattern.remove();
+ for (nn=1; nn<failLimit; nn++) {
+ sprintf(nnbuf, "(?<nn%d>)", nn);
+ pattern.append(UnicodeString(nnbuf, -1, US_INV));
+ }
+ status = U_ZERO_ERROR;
+ pat = RegexPattern::compile(pattern, 0, status);
+ REGEX_ASSERT(status == U_REGEX_PATTERN_TOO_BIG);
+ delete pat;
+}
+
+
+//--------------------------------------------------------------
+//
+// Bug7651 Regex pattern that exceeds default operator stack depth in matcher.
+//
+//---------------------------------------------------------------
+void RegexTest::Bug7651() {
+ UnicodeString pattern1("((?<![A-Za-z0-9])[#\\uff03][A-Za-z0-9_][A-Za-z0-9_\\u00c0-\\u00d6\\u00c8-\\u00f6\\u00f8-\\u00ff]*|(?<![A-Za-z0-9_])[@\\uff20][A-Za-z0-9_]+(?:\\/[\\w-]+)?|(https?\\:\\/\\/|www\\.)\\S+(?<![\\!\\),\\.:;\\]\\u0080-\\uFFFF])|\\$[A-Za-z]+)");
+ // The following should exceed the default operator stack depth in the matcher, i.e. force the matcher to malloc instead of using fSmallData.
+ // It will cause a segfault if RegexMatcher tries to use fSmallData instead of malloc'ing the memory needed (see init2) for the pattern operator stack allocation.
+ UnicodeString pattern2("((https?\\:\\/\\/|www\\.)\\S+(?<![\\!\\),\\.:;\\]\\u0080-\\uFFFF])|(?<![A-Za-z0-9_])[\\@\\uff20][A-Za-z0-9_]+(?:\\/[\\w\\-]+)?|(?<![A-Za-z0-9])[\\#\\uff03][A-Za-z0-9_][A-Za-z0-9_\\u00c0-\\u00d6\\u00c8-\\u00f6\\u00f8-\\u00ff]*|\\$[A-Za-z]+)");
+ UnicodeString s("#ff @abcd This is test");
+ RegexPattern *REPattern = NULL;
+ RegexMatcher *REMatcher = NULL;
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError pe;
+
+ REPattern = RegexPattern::compile(pattern1, 0, pe, status);
+ REGEX_CHECK_STATUS;
+ REMatcher = REPattern->matcher(s, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(REMatcher->find());
+ REGEX_ASSERT(REMatcher->start(status) == 0);
+ delete REPattern;
+ delete REMatcher;
+ status = U_ZERO_ERROR;
+
+ REPattern = RegexPattern::compile(pattern2, 0, pe, status);
+ REGEX_CHECK_STATUS;
+ REMatcher = REPattern->matcher(s, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(REMatcher->find());
+ REGEX_ASSERT(REMatcher->start(status) == 0);
+ delete REPattern;
+ delete REMatcher;
+ status = U_ZERO_ERROR;
+ }
+
+void RegexTest::Bug7740() {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString pattern = "(a)";
+ UnicodeString text = "abcdef";
+ RegexMatcher *m = new RegexMatcher(pattern, text, 0, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(m->lookingAt(status));
+ REGEX_CHECK_STATUS;
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ UnicodeString s = m->group(1, status); // Bug 7740: segfault here.
+ REGEX_ASSERT(status == U_ILLEGAL_ARGUMENT_ERROR);
+ REGEX_ASSERT(s == "");
+ delete m;
+}
+
+// Bug 8479: was crashing whith a Bogus UnicodeString as input.
+
+void RegexTest::Bug8479() {
+ UErrorCode status = U_ZERO_ERROR;
+
+ RegexMatcher* const pMatcher = new RegexMatcher("\\Aboo\\z", UREGEX_DOTALL|UREGEX_CASE_INSENSITIVE, status);
+ REGEX_CHECK_STATUS;
+ if (U_SUCCESS(status))
+ {
+ UnicodeString str;
+ str.setToBogus();
+ pMatcher->reset(str);
+ status = U_ZERO_ERROR;
+ pMatcher->matches(status);
+ REGEX_ASSERT(status == U_ILLEGAL_ARGUMENT_ERROR);
+ delete pMatcher;
+ }
+}
+// Bug 7029
+void RegexTest::Bug7029() {
+ UErrorCode status = U_ZERO_ERROR;
- //
- // 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;
- }
-
- // Don't try to check expected results if there is no match.
- // (Some have stuff in the expected fields)
- if (!found) {
- delete testMat;
- delete testPat;
- continue;
+ RegexMatcher* const pMatcher = new RegexMatcher(".", 0, status);
+ UnicodeString text = "abc.def";
+ UnicodeString splits[10];
+ REGEX_CHECK_STATUS;
+ int32_t numFields = pMatcher->split(text, splits, 10, status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(numFields == 8);
+ delete pMatcher;
+}
+
+// Bug 9283
+// This test is checking for the existance of any supplemental characters that case-fold
+// to a bmp character.
+//
+// At the time of this writing there are none. If any should appear in a subsequent release
+// of Unicode, the code in regular expressions compilation that determines the longest
+// posssible match for a literal string will need to be enhanced.
+//
+// See file regexcmp.cpp, case URX_STRING_I in RegexCompile::maxMatchLength()
+// for details on what to do in case of a failure of this test.
+//
+void RegexTest::Bug9283() {
+#if !UCONFIG_NO_NORMALIZATION
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeSet supplementalsWithCaseFolding("[[:CWCF:]&[\\U00010000-\\U0010FFFF]]", status);
+ REGEX_CHECK_STATUS;
+ int32_t index;
+ UChar32 c;
+ for (index=0; ; index++) {
+ c = supplementalsWithCaseFolding.charAt(index);
+ if (c == -1) {
+ break;
}
+ UnicodeString cf = UnicodeString(c).foldCase();
+ REGEX_ASSERT(cf.length() >= 2);
+ }
+#endif /* #if !UCONFIG_NO_NORMALIZATION */
+}
- //
- // 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);
- }
+void RegexTest::CheckInvBufSize() {
+ if(inv_next>=INV_BUFSIZ) {
+ errln("%s: increase #define of INV_BUFSIZ ( is %d but needs to be at least %d )\n",
+ __FILE__, INV_BUFSIZ, inv_next);
+ } else {
+ logln("%s: INV_BUFSIZ is %d, usage %d\n", __FILE__, INV_BUFSIZ, inv_next);
+ }
+}
- 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));
- }
+void RegexTest::Bug10459() {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString patternString("(txt)");
+ UnicodeString txtString("txt");
- else if (perlExpr.startsWith("@-")) {
- int32_t 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);
- }
+ UText *utext_pat = utext_openUnicodeString(NULL, &patternString, &status);
+ REGEX_CHECK_STATUS;
+ UText *utext_txt = utext_openUnicodeString(NULL, &txtString, &status);
+ REGEX_CHECK_STATUS;
- else if (perlExpr.startsWith("@+")) {
- int32_t 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);
- }
+ URegularExpression *icu_re = uregex_openUText(utext_pat, 0, NULL, &status);
+ REGEX_CHECK_STATUS;
- else if (perlExpr.startsWith(UNICODE_STRING_SIMPLE("\\"))) { // \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);
- }
+ uregex_setUText(icu_re, utext_txt, &status);
+ REGEX_CHECK_STATUS;
- 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);
- }
+ // The bug was that calling uregex_group() before doing a matching operation
+ // was causing a segfault. Only for Regular Expressions created from UText.
+ // It should set an U_REGEX_INVALID_STATE.
- if (U_FAILURE(status)) {
- errln("Line %d: ICU Error \"%s\"", lineNum, u_errorName(status));
- break;
- }
- }
+ UChar buf[100];
+ int32_t len = uregex_group(icu_re, 0, buf, UPRV_LENGTHOF(buf), &status);
+ REGEX_ASSERT(status == U_REGEX_INVALID_STATE);
+ REGEX_ASSERT(len == 0);
- //
- // Expected Results Compare
- //
- UnicodeString expectedS(fields[4]);
- expectedS.findAndReplace(nulnulSrc, nulnul);
- expectedS.findAndReplace(ffffSrc, ffff);
- expectedS.findAndReplace(UNICODE_STRING_SIMPLE("\\n"), "\n");
+ uregex_close(icu_re);
+ utext_close(utext_pat);
+ utext_close(utext_txt);
+}
+void RegexTest::TestCaseInsensitiveStarters() {
+ // Test that the data used by RegexCompile::findCaseInsensitiveStarters() hasn't
+ // become stale because of new Unicode characters.
+ // If it is stale, rerun the generation tool
+ // svn+ssh://source.icu-project.org/repos/icu/tools/trunk/unicode/c/genregexcasing
+ // and replace the embedded data in i18n/regexcmp.cpp
- if (expectedS.compare(resultString) != 0) {
- err("Line %d: Incorrect perl expression results.", lineNum);
- errln((UnicodeString)"Expected \""+expectedS+(UnicodeString)"\"; got \""+resultString+(UnicodeString)"\"");
+ for (UChar32 cp=0; cp<=0x10ffff; cp++) {
+ if (!u_hasBinaryProperty(cp, UCHAR_CASE_SENSITIVE)) {
+ continue;
+ }
+ UnicodeSet s(cp, cp);
+ s.closeOver(USET_CASE_INSENSITIVE);
+ UnicodeSetIterator setIter(s);
+ while (setIter.next()) {
+ if (!setIter.isString()) {
+ continue;
+ }
+ const UnicodeString &str = setIter.getString();
+ UChar32 firstChar = str.char32At(0);
+ UnicodeSet starters;
+ RegexCompile::findCaseInsensitiveStarters(firstChar, &starters);
+ if (!starters.contains(cp)) {
+ errln("CaseInsensitiveStarters for \\u%x is missing character \\u%x.", cp, firstChar);
+ return;
+ }
}
-
- delete testMat;
- delete testPat;
}
+}
- //
- // All done. Clean up allocated stuff.
- //
- delete cgMat;
- delete cgPat;
- delete groupsMat;
- delete groupsPat;
+void RegexTest::TestBug11049() {
+ // Original bug report: pattern with match start consisting of one of several individual characters,
+ // and the text being matched ending with a supplementary character. find() would read past the
+ // end of the input text when searching for potential match starting points.
- delete flagMat;
- delete flagPat;
+ // To see the problem, the text must exactly fill an allocated buffer, so that valgrind will
+ // detect the bad read.
- delete lineMat;
- delete linePat;
+ TestCase11049("A|B|C", "a string \\ud800\\udc00", FALSE, __LINE__);
+ TestCase11049("A|B|C", "string matches at end C", TRUE, __LINE__);
- delete fieldPat;
- delete [] testData;
+ // Test again with a pattern starting with a single character,
+ // which takes a different code path than starting with an OR expression,
+ // but with similar logic.
+ TestCase11049("C", "a string \\ud800\\udc00", FALSE, __LINE__);
+ TestCase11049("C", "string matches at end C", TRUE, __LINE__);
+}
+// Run a single test case from TestBug11049(). Internal function.
+void RegexTest::TestCase11049(const char *pattern, const char *data, UBool expectMatch, int32_t lineNumber) {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString patternString = UnicodeString(pattern).unescape();
+ LocalPointer<RegexPattern> compiledPat(RegexPattern::compile(patternString, 0, status));
- logln("%d tests skipped because of unimplemented regexp features.", skippedUnimplementedCount);
+ UnicodeString dataString = UnicodeString(data).unescape();
+ UChar *exactBuffer = new UChar[dataString.length()];
+ dataString.extract(exactBuffer, dataString.length(), status);
+ UText *ut = utext_openUChars(NULL, exactBuffer, dataString.length(), &status);
+
+ LocalPointer<RegexMatcher> matcher(compiledPat->matcher(status));
+ REGEX_CHECK_STATUS;
+ matcher->reset(ut);
+ UBool result = matcher->find();
+ if (result != expectMatch) {
+ errln("File %s, line %d: expected %d, got %d. Pattern = \"%s\", text = \"%s\"",
+ __FILE__, lineNumber, expectMatch, result, pattern, data);
+ }
+
+ // Rerun test with UTF-8 input text. Won't see buffer overreads, but could see
+ // off-by-one on find() with match at the last code point.
+ // Size of the original char * data (invariant charset) will be <= than the equivalent UTF-8
+ // because string.unescape() will only shrink it.
+ char * utf8Buffer = new char[uprv_strlen(data)+1];
+ u_strToUTF8(utf8Buffer, uprv_strlen(data)+1, NULL, dataString.getBuffer(), dataString.length(), &status);
+ REGEX_CHECK_STATUS;
+ ut = utext_openUTF8(ut, utf8Buffer, -1, &status);
+ REGEX_CHECK_STATUS;
+ matcher->reset(ut);
+ result = matcher->find();
+ if (result != expectMatch) {
+ errln("File %s, line %d (UTF-8 check): expected %d, got %d. Pattern = \"%s\", text = \"%s\"",
+ __FILE__, lineNumber, expectMatch, result, pattern, data);
+ }
+ delete [] utf8Buffer;
+ utext_close(ut);
+ delete [] exactBuffer;
}
-//
-// Callbacks() Test the callback function.
-// When set, callbacks occur periodically during matching operations,
-// giving the application code the ability to abort the operation
-// before it's normal completion.
-//
+void RegexTest::TestBug11371() {
+ if (quick) {
+ logln("Skipping test. Runs in exhuastive mode only.");
+ return;
+ }
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString patternString;
-struct callBackContext {
- RegexTest *test;
- int32_t maxCalls;
- int32_t numCalls;
- int32_t lastSteps;
- void reset(int32_t max) {maxCalls=max; numCalls=0; lastSteps=0;};
-};
+ for (int i=0; i<8000000; i++) {
+ patternString.append(UnicodeString("()"));
+ }
+ LocalPointer<RegexPattern> compiledPat(RegexPattern::compile(patternString, 0, status));
+ if (status != U_REGEX_PATTERN_TOO_BIG) {
+ errln("File %s, line %d expected status=U_REGEX_PATTERN_TOO_BIG; got %s.",
+ __FILE__, __LINE__, u_errorName(status));
+ }
-U_CDECL_BEGIN
-static UBool U_CALLCONV
-testCallBackFn(const void *context, int32_t steps) {
- callBackContext *info = (callBackContext *)context;
- if (info->lastSteps+1 != steps) {
- info->test->errln("incorrect steps in callback. Expected %d, got %d\n", info->lastSteps+1, steps);
+ status = U_ZERO_ERROR;
+ patternString = "(";
+ for (int i=0; i<20000000; i++) {
+ patternString.append(UnicodeString("A++"));
+ }
+ patternString.append(UnicodeString("){0}B++"));
+ LocalPointer<RegexPattern> compiledPat2(RegexPattern::compile(patternString, 0, status));
+ if (status != U_REGEX_PATTERN_TOO_BIG) {
+ errln("File %s, line %d expected status=U_REGEX_PATTERN_TOO_BIG; got %s.",
+ __FILE__, __LINE__, u_errorName(status));
}
- info->lastSteps = steps;
- info->numCalls++;
- return (info->numCalls < info->maxCalls);
-}
-U_CDECL_END
-void RegexTest::Callbacks() {
- {
- // Getter returns NULLs if no callback has been set
-
- // The variables that the getter will fill in.
- // Init to non-null values so that the action of the getter can be seen.
- const void *returnedContext = &returnedContext;
- URegexMatchCallback *returnedFn = &testCallBackFn;
-
- UErrorCode status = U_ZERO_ERROR;
- RegexMatcher matcher("x", 0, status);
- REGEX_CHECK_STATUS;
- matcher.getMatchCallback(returnedFn, returnedContext, status);
- REGEX_CHECK_STATUS;
- REGEX_ASSERT(returnedFn == NULL);
- REGEX_ASSERT(returnedContext == NULL);
+ // Pattern with too much string data, such that string indexes overflow operand data field size
+ // in compiled instruction.
+ status = U_ZERO_ERROR;
+ patternString = "";
+ while (patternString.length() < 0x00ffffff) {
+ patternString.append(UnicodeString("stuff and things dont you know, these are a few of my favorite strings\n"));
}
-
- {
- // Set and Get work
- callBackContext cbInfo = {this, 0, 0, 0};
- const void *returnedContext;
- URegexMatchCallback *returnedFn;
- UErrorCode status = U_ZERO_ERROR;
- RegexMatcher matcher(UNICODE_STRING_SIMPLE("((.)+\\2)+x"), 0, status); // A pattern that can run long.
- REGEX_CHECK_STATUS;
- matcher.setMatchCallback(testCallBackFn, &cbInfo, status);
- REGEX_CHECK_STATUS;
- matcher.getMatchCallback(returnedFn, returnedContext, status);
- REGEX_CHECK_STATUS;
- REGEX_ASSERT(returnedFn == testCallBackFn);
- REGEX_ASSERT(returnedContext == &cbInfo);
-
- // A short-running match shouldn't invoke the callback
- status = U_ZERO_ERROR;
- cbInfo.reset(1);
- UnicodeString s = "xxx";
- matcher.reset(s);
- REGEX_ASSERT(matcher.matches(status));
- REGEX_CHECK_STATUS;
- REGEX_ASSERT(cbInfo.numCalls == 0);
-
- // A medium-length match that runs long enough to invoke the
- // callback, but not so long that the callback aborts it.
- status = U_ZERO_ERROR;
- cbInfo.reset(4);
- s = "aaaaaaaaaaaaaaaaaaab";
- matcher.reset(s);
- REGEX_ASSERT(matcher.matches(status)==FALSE);
- REGEX_CHECK_STATUS;
- REGEX_ASSERT(cbInfo.numCalls > 0);
-
- // A longer running match that the callback function will abort.
- status = U_ZERO_ERROR;
- cbInfo.reset(4);
- s = "aaaaaaaaaaaaaaaaaaaaaaab";
- matcher.reset(s);
- REGEX_ASSERT(matcher.matches(status)==FALSE);
- REGEX_ASSERT(status == U_REGEX_STOPPED_BY_CALLER);
- REGEX_ASSERT(cbInfo.numCalls == 4);
+ patternString.append(UnicodeString("X? trailing string"));
+ LocalPointer<RegexPattern> compiledPat3(RegexPattern::compile(patternString, 0, status));
+ if (status != U_REGEX_PATTERN_TOO_BIG) {
+ errln("File %s, line %d expected status=U_REGEX_PATTERN_TOO_BIG; got %s.",
+ __FILE__, __LINE__, u_errorName(status));
}
-
+}
+
+void RegexTest::TestBug11480() {
+ // C API, get capture group of a group that does not participate in the match.
+ // (Returns a zero length string, with nul termination,
+ // indistinguishable from a group with a zero lenght match.)
+ UErrorCode status = U_ZERO_ERROR;
+ URegularExpression *re = uregex_openC("(A)|(B)", 0, NULL, &status);
+ REGEX_CHECK_STATUS;
+ UnicodeString text = UNICODE_STRING_SIMPLE("A");
+ uregex_setText(re, text.getBuffer(), text.length(), &status);
+ REGEX_CHECK_STATUS;
+ REGEX_ASSERT(uregex_lookingAt(re, 0, &status));
+ UChar buf[10] = {(UChar)13, (UChar)13, (UChar)13, (UChar)13};
+ int32_t length = uregex_group(re, 2, buf+1, UPRV_LENGTHOF(buf)-1, &status);
+ REGEX_ASSERT(length == 0);
+ REGEX_ASSERT(buf[0] == 13);
+ REGEX_ASSERT(buf[1] == 0);
+ REGEX_ASSERT(buf[2] == 13);
+ uregex_close(re);
}
-#endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */
+#endif /* !UCONFIG_NO_REGULAR_EXPRESSIONS */