diff --git a/ctl/u8string.h b/ctl/u8string.h index afa1136c..c62b02d2 100644 --- a/ctl/u8string.h +++ b/ctl/u8string.h @@ -1,22 +1,29 @@ +/* We don't even have yet in the libstdc++! + Do normalized NFD comparisons. Check validity. + SPDX-License-Identifier: MIT */ #ifndef __CTL_U8STRING_H__ #define __CTL_U8STRING_H__ #ifdef T -#error "Template type T defined for " +# error "Template type T defined for " #endif #ifndef __cpp_lib_char8_t typedef unsigned char char8_t; #endif -#define HOLD -#define T char8_t -#ifndef vec -# define u8str_char8_t u8str -# define vec u8str -# define A u8str +// check backends +#ifdef _LIBUNISTRING_VERSION +# include #endif +#define POD +#undef A +#define T char8_t +#define A u8str +#define u8str_char8_t u8str +#define vec u8str + enum u8str_norm { NORM_NONE = 0, NFD, /* fastest, not composed */ @@ -29,34 +36,221 @@ enum u8str_norm { typedef struct A { - str rawstr; - str *normstr; + T* vector; + size_t size; + size_t capacity; void (*free)(T*); T (*copy)(T*); -#ifdef COMPARE int (*compare)(T*, T*); -#endif + int (*equal)(T*, T*); + struct str *normstr; unsigned valid:1; /* is already validated */ unsigned repair:1; /* do repair */ unsigned normalized:3; /* 6 normalize enums. NONE,NFD,NFC,...*/ - unsigned same_norm:1; /* optimization norm_value == value */ + unsigned same_norm:1; /* optimization normstr.vector == vector */ } A; -#define compare __COMPARE -#include -#undef compare +#define HOLD +#define u8str_init u8str___INIT +#define u8str_equal u8str___EQUAL +#define u8str_find u8str___FIND +//#define at __AT +#undef A +#include +#undef A +#define A u8str +#ifdef u8id_char8_t +# define HOLD +#endif +#undef u8str_init +#undef u8str_equal +#undef u8str_find +//#undef at + +#include + +// for simplicity start with a signed char*? or demand char8_t and u8"" literals? +static inline A +JOIN(A, init)(const T* c_str) +{ + A self = u8str___INIT(); + size_t len = strlen((char*)c_str); + size_t min = 15; + JOIN(A, reserve)(&self, len < min ? min : len); + for(const T* s = c_str; *s; s++) + JOIN(A, push_back)(&self, (T)*s); + return self; +} + +// Compare with append, and push_back to add a single char8_t +static inline void +u8str_str_push_back(A* self, A s) +{ + if(self->size == self->capacity) + JOIN(A, reserve)(self, self->capacity == 0 ? s.size : (2 * self->capacity) + s.size); + for(size_t i = 0; i < s.size; i++) + self->vector[self->size + i] = s.vector[i]; + self->size += s.size; +} + +static inline void +JOIN(A, append)(A* self, const T* s) +{ + size_t start = self->size; + size_t len = strlen((char*)s); + JOIN(A, resize)(self, self->size + len, '\0'); + for(size_t i = 0; i < len; i++) + self->vector[start + i] = s[i]; +} + +static inline void +JOIN(A, insert_str)(A* self, size_t index, const T* s) +{ + size_t start = self->size; + size_t len = strlen((char*)s); + JOIN(A, resize)(self, self->size + len, '\0'); + self->size = start; + while(len != 0) + { + len--; + JOIN(A, insert)(self, index, s[len]); + } +} + +static inline void +JOIN(A, replace)(A* self, size_t index, size_t size, const T* s) +{ + size_t end = index + size; + if(end >= self->size) + end = self->size; + for(size_t i = index; i < end; i++) + JOIN(A, erase)(self, index); + JOIN(A, insert_str)(self, index, s); +} + +static inline T* +JOIN(A, c_str)(A* self) +{ + return JOIN(A, data)(self); +} + +static inline size_t +JOIN(A, find)(A* self, const T* s) +{ + T* c_str = self->vector; + char* found = strstr((char*)c_str, (char*)s); + if(found) + return found - (char*)c_str; + return SIZE_MAX; +} + +static inline int +JOIN(A, count)(A* self, T c) +{ + size_t count = 0; + for(size_t i = 0; i < self->size; i++) + if(self->vector[i] == c) + count++; + return count; +} + +static inline size_t +JOIN(A, rfind)(A* self, const T* s) +{ + T* c_str = self->vector; + for(size_t i = self->size; i != SIZE_MAX; i--) + { + char* found = strstr((char*)&c_str[i], (char*)s); + if(found) + return found - (char*)c_str; + } + return SIZE_MAX; +} + +static inline size_t +JOIN(A, find_first_of)(A* self, const T* s) +{ + for(size_t i = 0; i < self->size; i++) + for(const T* p = s; *p; p++) + if(self->vector[i] == *p) + return i; + return SIZE_MAX; +} + +static inline size_t +JOIN(A, find_last_of)(A* self, const T* s) +{ + for(size_t i = self->size; i != SIZE_MAX; i--) + for(const T* p = s; *p; p++) + if(self->vector[i] == *p) + return i; + return SIZE_MAX; +} + +static inline size_t +JOIN(A, find_first_not_of)(A* self, const T* s) +{ + for(size_t i = 0; i < self->size; i++) + { + size_t count = 0; + for(const T* p = s; *p; p++) + if(self->vector[i] == *p) + count++; + if(count == 0) + return i; + } + return SIZE_MAX; +} + +static inline size_t +JOIN(A, find_last_not_of)(A* self, const T* s) +{ + for(size_t i = self->size - 1; i != SIZE_MAX; i--) + { + size_t count = 0; + for(const T* p = s; *p; p++) + if(self->vector[i] == *p) + count++; + if(count == 0) + return i; + } + return SIZE_MAX; +} + +static inline A +JOIN(A, substr)(A* self, size_t index, size_t size) +{ + A s = JOIN(A, init)((T*)""); + JOIN(A, resize )(&s, size, '\0'); + for(size_t i = 0; i < size; i++) + // FIXME + s.vector[i] = self->vector[index + i]; + return s; +} /* decompose only */ static inline str* JOIN(A, NFD)(A* self) { if (self->normalized == NFD) - return self; + return self->same_norm ? (str*)self : self->normstr; if (!self->normstr) { - str norm = str_init(self->rawstr.vector); - self->normstr = &norm; + str _norm = str_init(""); + str_resize(&_norm, self->capacity * 2, '\0'); + self->normstr = &_norm; } + str *norm = self->normstr; +#ifdef _LIBUNISTRING_VERSION + norm->vector = (char*)u8_normalize(UNINORM_NFD, self->vector, self->size, + norm->vector, &norm->size); +#else + // TODO other backends + strcpy (norm->vector, (char*)self->vector); +#endif + if (strcmp(norm->vector, (char*)self->vector) == 0) + self->same_norm = 1; + // free norm? return self->normstr; } @@ -65,17 +259,21 @@ static inline str* JOIN(A, NFC)(A* self) { if (self->normalized == NFC) - return self; + return self->same_norm ? (str*)self : self->normstr; if (!self->normstr) { - str _norm = str_init(self->rawstr.vector); + str _norm = str_init(""); + str_resize(&_norm, self->capacity * 2, '\0'); self->normstr = &_norm; } str *norm = self->normstr; - norm->capacity = self->rawstr.size * 2; - norm->vector = (T*) malloc (norm->capacity); - // TODO - strcpy ((char*)norm->vector, (char*)self->rawstr.vector); +#ifdef _LIBUNISTRING_VERSION + norm->vector = (char*)u8_normalize(UNINORM_NFC, self->vector, self->size, + norm->vector, &norm->size); +#else + // TODO other backends + strcpy (norm->vector, (char*)self->vector); +#endif /* dest = self->norm_value; dmax = self->norm_capacity; @@ -96,6 +294,8 @@ JOIN(A, NFC)(A* self) } */ self->normalized = NFC; + if (strcmp(norm->vector, (char*)self->vector) == 0) + self->same_norm = 1; return self->normstr; } @@ -106,7 +306,7 @@ JOIN(A, normalize)(A* self) return self; } -/* Assuming s is normalized. +/* Assuming arg `s` is normalized. W3C recommends not to normalize. We think different. */ static inline int @@ -115,10 +315,22 @@ JOIN(A, compare)(A* self, const T* s) if (!self->normalized) { JOIN(A, normalize)(self); - return strcmp ((char*)self->normstr->vector, (char*)s); + return strcmp (self->normstr->vector, (char*)s); + } + else + return strcmp ((char*)self->vector, (char*)s); +} + +static inline int +JOIN(A, key_compare)(A* self, A* s) +{ + if (!self->normalized) + { + JOIN(A, normalize)(self); + return strcmp (self->normstr->vector, (char*)s->vector); } else - return strcmp ((char*)self->rawstr.vector, (char*)s); + return strcmp ((char*)self->vector, (char*)s->vector); } #ifdef HOLD /* for u8ident.h */ @@ -126,7 +338,10 @@ JOIN(A, compare)(A* self, const T* s) #else # undef T # undef A +# undef I +# undef vec # undef u8str +# undef u8str_char8_t #endif #endif diff --git a/ctl/vector.h b/ctl/vector.h index cc76523c..69c14ed2 100644 --- a/ctl/vector.h +++ b/ctl/vector.h @@ -27,13 +27,13 @@ #ifndef u8str_char8_t typedef struct A { - T *vector; - void (*free)(T *); - T (*copy)(T *); - int (*compare)(T *, T *); // 2-way operator< - int (*equal)(T *, T *); // optional + T* vector; size_t size; size_t capacity; + void (*free)(T*); + T (*copy)(T*); + int (*compare)(T*, T*); // 2-way operator< + int (*equal)(T*, T*); // optional } A; #endif diff --git a/docs/index.md b/docs/index.md index 560dd83c..918a828e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -364,7 +364,7 @@ And in its grandiosity (esp. not header-only): array.h: stack/heap allocated vector.h: realloc string.h: vector.h - u8string.h: vector.h ++ + u8string.h: vector.h ++ deque.h: realloc (paged) queue.h: deque.h stack.h: deque.h diff --git a/tests/func/test_c11.c b/tests/func/test_c11.c index 5e829a73..4f19985f 100644 --- a/tests/func/test_c11.c +++ b/tests/func/test_c11.c @@ -196,6 +196,8 @@ typedef char *charp; #endif #include +#define T u8str +#include #ifdef POD #error "POD leftover" @@ -309,6 +311,16 @@ int main(void) vec_str_resize(&b, 512, str_init("")); vec_str_free(&b); } + { + vec_double b = vec_double_init(); + vec_double_push_back(&b, 1.0); + vec_double_free(&b); + } + { + vec_unsigned b = vec_unsigned_init(); + vec_unsigned_push_back(&b, 1U); + vec_unsigned_free(&b); + } { vec_person c = vec_person_init(); vec_person_push_back(&c, person_init(128, "FIRST", "JONES")); @@ -403,12 +415,12 @@ int main(void) map_charint_free(&a); } { - u8str b = u8str_init(); - u8str_push_back(&b, str_init("This")); - u8str_push_back(&b, str_init("is")); - u8str_push_back(&b, str_init("a")); - u8str_push_back(&b, str_init("test")); - u8str_resize(&b, 512, str_init("")); + u8str b = u8str_init(""); + u8str_str_push_back(&b, u8str_init("This")); + u8str_str_push_back(&b, u8str_init("is")); + u8str_push_back(&u, 'a'); + u8str_str_push_back(&u, u8str_init("test")); + u8str_resize(&b, 512, (char8_t)'\0'); u8str_free(&b); } TEST_PASS(__FILE__); diff --git a/tests/func/test_u8string.cc b/tests/func/test_u8string.cc new file mode 100644 index 00000000..5c4f6887 --- /dev/null +++ b/tests/func/test_u8string.cc @@ -0,0 +1,463 @@ +#include "../test.h" + +// rely on GNU libunistring backend for now +#include +#include +#include +//#include +#include +#include + +static locale_t utf8_locale; + +// WHOA, not C++ support for u8string yet, even if defined a bit in C++20 +#include +#include + +#define MIN_STR_SIZE (30) // NO SUPPORT FOR SMALL STRINGS. +#define ALPHA_LETTERS (23) + +#define CHECK(_x, _y) { \ + assert(u8strcmp(u8str_c_str(&_x), _y.c_str()) == 0); \ + assert(_x.capacity == _y.capacity()); \ + assert(_x.size == _y.size()); \ + assert(u8str_empty(&_x) == _y.empty()); \ + if(_x.size > 0) { \ + assert(_y.front() == *u8str_front(&_x)); \ + assert(_y.back() == *u8str_back(&_x)); \ + } \ + std::string::iterator _iter = _y.begin(); \ + foreach(u8str, &_x, _it) { \ + assert(*_it.ref == *_iter); \ + _iter++; \ + } \ + u8str_it _it = u8str_it_each(&_x); \ + for(auto& _d : _y) { \ + assert(*_it.ref == _d); \ + _it.step(&_it); \ + } \ + for(size_t i = 0; i < _y.size(); i++) \ + assert(_y.at(i) == *u8str_at(&_x, i)); \ +} + +static int +is_proper_uni(wint_t wc) +{ + return (wc < 0x3400 || + (wc >= 0xa000 && wc < 0xAC00) || + (wc >= 0xd7b0 && wc < 0xd800) || + (wc >= 0xf900 && wc < 0x1BC6A)); +} + +// want u8"Привет, мир!" +static wint_t +rand_uni_alpha() +{ + wint_t a = 'A' + TEST_RAND(0x1BC6A); + + while (!is_proper_uni(a) || !iswalnum_l(a, utf8_locale)) + a = 'A' + TEST_RAND(0x1BC6A); + return a; +} + +static inline char* +rand_utf8_alpha() +{ + int i = 0; + char *buf = (char*) calloc(1, MB_CUR_MAX); + switch (TEST_RAND(4)) + { + case 0: case_0: + buf[0] = 'a' + TEST_RAND(ALPHA_LETTERS); + return buf; + case 1: + buf[0] = 'A' + TEST_RAND(ALPHA_LETTERS); + return buf; + case 2: + { + wchar_t a = rand_uni_alpha(); + while (wctomb(buf, a) < 0 && i < 100) + { + a = rand_uni_alpha(); + i++; + } + if (i >= 100) // broken wctomb, e.g. glibc + goto case_0; + return buf; + } + case 3: + { + wchar_t a = towupper_l(rand_uni_alpha(), utf8_locale); + while (wctomb(buf, a) < 0 && i < 100) + { + a = towupper_l(rand_uni_alpha(), utf8_locale); + i++; + } + if (i >= 100) // broken wctomb, e.g. glibc + goto case_0; + return buf; + } + } + return buf; +} + +static char8_t* +create_test_string(size_t size) +{ + char8_t* temp = (char8_t*) malloc(size + 1); + for(size_t i = 0; i < size; i++) + { + char *u8 = rand_utf8_alpha(); + int len = strlen(u8); + if (len > 1) + { + size += len; + temp = (char8_t*) realloc(temp, size); + } + strcpy ((char*)temp, u8); + free (u8); + } + temp[size] = '\0'; + return temp; +} + +// silly hacks for now +static int +u8strcmp(char8_t* a, const char* b) +{ + return strcmp((char*)a, b); +} + +static int +char8_compare(char8_t* a, char8_t* b) +{ + return *a > *b; +} + +int +main(void) +{ + INIT_SRAND; + utf8_locale = newlocale(LC_ALL_MASK, "en_US.UTF8", (locale_t)0); + setenv("LC_CTYPE", "en_US.UTF8", 1); + size_t loops = TEST_RAND(TEST_MAX_LOOPS); + int test = -1; + char *env = getenv ("TEST"); + if (env) + sscanf(env, "%d", &test); + if (test >= 0) + loops = 30; + for(size_t loop = 0; loop < loops; loop++) + { + size_t u8str_size = TEST_RAND(TEST_MAX_SIZE); + if(u8str_size < MIN_STR_SIZE) + u8str_size = MIN_STR_SIZE; +#if defined(DEBUG) && !defined(LONG) + u8str_size = MIN_STR_SIZE; +#endif + enum + { + MODE_DIRECT, + MODE_GROWTH, + MODE_TOTAL + }; + for(size_t mode = MODE_DIRECT; mode < MODE_TOTAL; mode++) + { + char8_t* base = create_test_string(u8str_size); + u8str a = u8str_init(u8""); + a.compare = char8_compare; + std::string b; + if(mode == MODE_DIRECT) + { + u8str_free(&a); + a = u8str_init(base); + b = (char*)base; + } + if(mode == MODE_GROWTH) + { + for(size_t i = 0; i < u8str_size; i++) + { + u8str_push_back(&a, base[i]); + b.push_back((char)base[i]); + } + } + enum + { + TEST_PUSH_BACK, + TEST_POP_BACK, + TEST_APPEND, + TEST_C_STR, + TEST_CLEAR, + TEST_ERASE, + TEST_RESIZE, + TEST_RESERVE, + TEST_SHRINK_TO_FIT, + TEST_SORT, + TEST_COPY, + TEST_SWAP, + TEST_INSERT, + TEST_ASSIGN, + TEST_REPLACE, + TEST_FIND, + TEST_RFIND, + TEST_FIND_FIRST_OF, + TEST_FIND_LAST_OF, + TEST_FIND_FIRST_NOT_OF, + TEST_FIND_LAST_NOT_OF, + TEST_SUBSTR, + TEST_COMPARE, + TEST_COUNT, + TEST_TOTAL + }; + int which = TEST_RAND(TEST_TOTAL); + if (test >= 0 && test < (int)TEST_TOTAL) + which = test; + LOG ("TEST %d\n", which); + switch(which) + { + case TEST_PUSH_BACK: + { + const char value = TEST_RAND(ALPHA_LETTERS); + b.push_back(value); + u8str_push_back(&a, value); + CHECK(a, b); + break; + } + case TEST_POP_BACK: + { + if(a.size > 0) + { + b.pop_back(); + u8str_pop_back(&a); + } + CHECK(a, b); + break; + } + case TEST_APPEND: + { + char8_t* temp = create_test_string(TEST_RAND(256)); + u8str_append(&a, temp); + b.append((char*)temp); + free(temp); + CHECK(a, b); + break; + } + case TEST_C_STR: + { + assert(strlen((char*)u8str_c_str(&a))); + assert(u8str_c_str(&a) == u8str_data(&a)); + CHECK(a, b); + break; + } + case TEST_REPLACE: + { + char8_t* temp = create_test_string(TEST_RAND(a.size)); + const size_t index = TEST_RAND(a.size); + const size_t size = TEST_RAND(a.size); + u8str_replace(&a, index, size, temp); + b.replace(index, size, (char*)temp); + free(temp); + CHECK(a, b); + break; + } + case TEST_FIND: + { + const size_t size = TEST_RAND(3); + char8_t* temp = create_test_string(size); + assert(u8str_find(&a, temp) == b.find((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_RFIND: + { + char8_t* temp = create_test_string(TEST_RAND(3)); + assert(u8str_rfind(&a, temp) == b.rfind((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_FIND_FIRST_OF: + { + const size_t size = TEST_RAND(3); + char8_t* temp = create_test_string(size); + assert(u8str_find_first_of(&a, temp) == b.find_first_of((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_FIND_LAST_OF: + { + const size_t size = TEST_RAND(3); + char8_t* temp = create_test_string(size); + assert(u8str_find_last_of(&a, temp) == b.find_last_of((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_FIND_FIRST_NOT_OF: + { + const size_t size = TEST_RAND(192); + char8_t* temp = create_test_string(size); + assert(u8str_find_first_not_of(&a, temp) == b.find_first_not_of((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_FIND_LAST_NOT_OF: + { + const size_t size = TEST_RAND(192); + char8_t* temp = create_test_string(size); + assert(u8str_find_last_not_of(&a, temp) == b.find_last_not_of((char*)temp)); + free(temp); + CHECK(a, b); + break; + } + case TEST_SUBSTR: + { + const size_t index = TEST_RAND(a.size); + const size_t size = TEST_RAND(a.size - index); + if(size > MIN_STR_SIZE) + { + u8str substr1 = u8str_substr(&a, index, size); + std::string substr2 = b.substr(index, size); + CHECK(substr1, substr2); + u8str_free(&substr1); + } + CHECK(a, b); + break; + } + case TEST_COMPARE: + { + size_t size = TEST_RAND(512); + char8_t* _ta = create_test_string(size); + char8_t* _tb = create_test_string(size); + u8str _a = u8str_init(_ta); + u8str _b = u8str_init(_tb); + std::string _aa = (char*)_ta; + std::string _bb = (char*)_tb; + assert(TEST_SIGN(u8str_compare(&_a, _tb)) == TEST_SIGN(_aa.compare((char*)_tb))); + assert(TEST_SIGN(u8str_compare(&_b, _ta)) == TEST_SIGN(_bb.compare((char*)_ta))); + assert(TEST_SIGN(u8str_compare(&_a, _ta)) == TEST_SIGN(_aa.compare((char*)_ta))); + assert(TEST_SIGN(u8str_compare(&_b, _tb)) == TEST_SIGN(_bb.compare((char*)_tb))); + u8str_free(&_a); + u8str_free(&_b); + free(_ta); + free(_tb); + CHECK(a, b); + break; + } + case TEST_CLEAR: + { + u8str_clear(&a); + b.clear(); + CHECK(a, b); + break; + } + case TEST_ERASE: + { + const size_t index = TEST_RAND(a.size); + b.erase(b.begin() + index); + u8str_erase(&a, index); + CHECK(a, b); + break; + } + case TEST_INSERT: + { + size_t letters = TEST_RAND(512); + for(size_t count = 0; count < letters; count++) + { + const char value = TEST_RAND(ALPHA_LETTERS); + const size_t index = TEST_RAND(a.size); + b.insert(b.begin() + index, value); + u8str_insert(&a, index, value); + } + CHECK(a, b); + break; + } + case TEST_RESIZE: + { + const size_t resize = 3 * TEST_RAND(a.size); + b.resize(resize); + u8str_resize(&a, resize, '\0'); + CHECK(a, b); + break; + } + case TEST_RESERVE: + { + const size_t capacity = 3 * TEST_RAND(a.capacity); + b.reserve(capacity); + u8str_reserve(&a, capacity); + LOG("CTL reserve %zu %zu\n", a.size, a.capacity); + LOG("STL reserve %zu %zu\n", b.size(), b.capacity()); + CHECK(a, b); + break; + } + case TEST_SHRINK_TO_FIT: + { + b.shrink_to_fit(); + u8str_shrink_to_fit(&a); + LOG("CTL shrink_to_fit %zu %zu\n", a.size, a.capacity); + LOG("STL shrink_to_fit %zu %zu\n", b.size(), b.capacity()); + CHECK(a, b); + break; + } + case TEST_SORT: + { + LOG("before sort \"%s\"\n", (char*)a.value); + u8str_sort(&a); + LOG("after sort \"%s\"\n", (char*)a.value); + std::sort(b.begin(), b.end()); + LOG("STL sort \"%s\"\n", b.c_str()); + CHECK(a, b); + break; + } + case TEST_COPY: + { + u8str ca = u8str_copy(&a); + std::string cb = b; + LOG("copy a: \"%s\": %zu\n", (char*)a.value, a.capacity); + LOG("copy ca: \"%s\": %zu\n", (char*)ca.value, ca.capacity); + CHECK(ca, cb); + u8str_free(&ca); + CHECK(a, b); + break; + } + case TEST_ASSIGN: + { + const char value = TEST_RAND(ALPHA_LETTERS); + size_t assign_size = TEST_RAND(a.size); + u8str_assign(&a, assign_size, value); + b.assign(assign_size, value); + CHECK(a, b); + break; + } + case TEST_SWAP: + { + u8str aa = u8str_copy(&a); + u8str aaa = u8str_init(u8""); + std::string cb = b; + std::string bbb; + u8str_swap(&aaa, &aa); + std::swap(cb, bbb); + CHECK(aaa, bbb); + u8str_free(&aaa); + u8str_free(&aa); + CHECK(a, b); + break; + } + case TEST_COUNT: + { + const char value = TEST_RAND(ALPHA_LETTERS); + assert(count(b.begin(), b.end(), value) == u8str_count(&a, value)); + CHECK(a, b); + break; + } + } + CHECK(a, b); + u8str_free(&a); + free(base); + } + } + TEST_PASS(__FILE__); +}