version 1.2, 2015/04/20 18:10:35
|
version 1.3, 2015/04/20 18:20:35
|
|
|
using namespace std; | using namespace std; |
const char* arg0; | const char* arg0; |
| |
typedef pair<string, string> Pair; |
struct Tuple |
typedef vector<Pair> Vector; |
{ |
|
Tuple(char ch_, const string& str_, const string& tag_) : |
|
ch(ch_), str(str_), tag(tag_) |
|
{ |
|
} |
|
|
|
char ch; |
|
string str; |
|
string tag; |
|
}; |
|
|
|
typedef vector<Tuple> Vector; |
|
|
|
string BaseName(const string& name) |
|
{ |
|
string base = name; |
|
|
|
size_t pos = base.rfind('/'); |
|
|
|
if (pos != string::npos) |
|
base = base.substr(pos + 1); |
|
|
|
pos = base.rfind('.'); |
|
|
|
if (pos != string::npos) |
|
base = base.substr(0, pos); |
| |
void LoadSpecFile(const char* path, Vector& pairs) |
return base; |
|
} |
|
|
|
void LoadSpecFile(const char* path, Vector& tuples) |
{ | { |
// Open input file: | // Open input file: |
FILE* is = fopen(path, "r"); | FILE* is = fopen(path, "r"); |
|
|
vector<string> toks; | vector<string> toks; |
char* str; | char* str; |
char* tag; | char* tag; |
|
char ch; |
| |
// Skip leading spaces: | // Skip leading spaces: |
for (p = buf; isspace(*p); p++) | for (p = buf; isspace(*p); p++) |
|
|
*--end = '\0'; | *--end = '\0'; |
} | } |
| |
// Expect string |
// Ignore empty lines: |
|
|
|
if (*p == '\0') |
|
continue; |
|
|
|
// Get tag: |
{ | { |
str = p; |
ch = *p++; |
|
|
|
if (!isalpha(ch) && ch != '0') |
|
{ |
|
fprintf(stderr, "%s: expected character identifier: %u\n", arg0, line); |
|
exit(1); |
|
} |
| |
while (*p && *p != ',') | while (*p && *p != ',') |
p++; | p++; |
} |
|
| |
// Skip spaces: | // Skip spaces: |
while (isspace(*p)) | while (isspace(*p)) |
p++; | p++; |
| |
if (!*p) |
// Expect ',' |
|
if (*p != ',') |
{ | { |
char buf[32]; |
fprintf(stderr, "%s: expected comma and then string: %u\n", arg0, line); |
sprintf(buf, "TAG%u", line); |
exit(1); |
pairs.push_back(Pair(str, buf)); |
|
continue; |
|
} | } |
| |
// Expect ':' |
p++; |
|
} |
|
|
|
// Skip spaces: |
|
while (isspace(*p)) |
|
p++; |
|
|
|
// Get string: |
|
{ |
|
str = p; |
|
|
|
while (*p && *p != ',') |
|
p++; |
|
|
|
// Skip spaces: |
|
while (isspace(*p)) |
|
p++; |
|
|
|
// Expect ',' |
if (*p != ',') | if (*p != ',') |
{ | { |
fprintf(stderr, "%s: syntax error on line %u\n", arg0, line); |
fprintf(stderr, "%s: expected comman and then tag name: %u\n", arg0, line); |
exit(1); | exit(1); |
} | } |
| |
*p++ = '\0'; | *p++ = '\0'; |
|
} |
| |
// Skip spaces: | // Skip spaces: |
while (isspace(*p)) | while (isspace(*p)) |
|
|
exit(1); | exit(1); |
} | } |
| |
pairs.push_back(Pair(str, tag)); |
tuples.push_back(Tuple(ch, str, tag)); |
} | } |
} | } |
| |
static void _GenEnum(FILE* f, const Vector& pairs) |
static void _GenEnum(FILE* f, const Vector& tuples) |
{ | { |
// Generate enumeration: | // Generate enumeration: |
{ | { |
fprintf(f, "enum\n"); | fprintf(f, "enum\n"); |
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
| |
for (size_t i = 0; i < pairs.size(); i++) |
for (size_t i = 0; i < tuples.size(); i++) |
{ | { |
fprintf(f, " %s = %u%s\n", pairs[i].second.c_str(), (int)(i + 1), ((i+1) == pairs.size() ? "" : ",")); |
fprintf(f, " %s = %u%s\n", |
|
tuples[i].tag.c_str(), |
|
(int)(i + 1), |
|
((i+1) == tuples.size() ? "" : ",")); |
} | } |
| |
fprintf(f, "};\n"); | fprintf(f, "};\n"); |
|
|
} | } |
} | } |
| |
#if 0 |
typedef multimap<unsigned int, Tuple> Map; |
static void GenSimpleFunction(const char* fileName, const Vector& pairs) |
typedef pair<unsigned int, Tuple> MTuple; |
{ |
static void _GenStringCmp(FILE* f, const Tuple& p) |
FILE* f = fopen(fileName, "w"); |
{ |
|
char ch = p.ch; |
if (!f) |
const string& str = p.str.c_str(); |
{ |
const string& tok = p.tag.c_str(); |
fprintf(stderr, "%s: failed to open: %s\n", arg0, fileName); |
|
exit(1); |
|
} |
|
|
|
|
|
//_GenEnum(f, pairs); |
|
|
|
/* header */ |
|
fprintf(f, "static int SimpleStr(const char* s, size_t n)\n"); |
|
fprintf(f, "{\n"); |
|
|
|
/* generate function body */ |
|
fprintf(f, "n=n;\n "); |
|
for (size_t i = 0; i < pairs.size(); i++) |
|
{ |
|
const string& str = pairs[i].first.c_str(); |
|
const string& tok = pairs[i].second.c_str(); |
|
|
|
fprintf(f, " if (strcmp(s, \"%s\") == 0)\n", |
|
str.c_str()); |
|
| |
fprintf(f, " "); | fprintf(f, " "); |
fprintf(f, "return %s;\n", tok.c_str()); |
|
fprintf(f, " else"); |
|
} |
|
|
|
fprintf(f, "\n return 0; /* not found */\n"); |
|
|
|
/* close the function */ |
|
//fprintf(f, "\n"); |
|
fprintf(f, "}\n\n"); |
|
|
|
|
|
fclose(f); |
|
} |
|
#endif |
|
|
|
#if 0 |
|
static void GenHashFunction(const char* fileName, const Vector& pairs) |
|
{ |
|
FILE* f = fopen(fileName, "w"); |
|
|
|
if (!f) |
|
{ |
|
fprintf(stderr, "%s: failed to open: %s\n", arg0, fileName); |
|
exit(1); |
|
} |
|
|
|
|
|
//_GenEnum(f, pairs); |
|
|
|
// Generate function |
|
{ |
|
typedef pair<unsigned int, Vector> MPair; |
|
typedef map<unsigned int, Vector> Map; |
|
Map m; |
|
|
|
fprintf(f, "int HashStr(const char* s, size_t n)\n"); |
|
fprintf(f, "{\n"); |
|
fprintf(f, " unsigned int h = (unsigned int)(n ^ s[0] ^ s[n-1]);\n"); |
|
fprintf(f, "\n"); |
|
fprintf(f, " switch (h)\n"); |
|
fprintf(f, " {\n"); |
|
| |
// Conslidate pairs with like hash codes |
if (ch == '0') |
for (size_t i = 0; i < pairs.size(); i++) |
|
{ | { |
const char* s = pairs[i].first.c_str(); |
fprintf(f, "if (HASHSTR_STRCMP(s, HASHSTR_T(\"%s\")) == 0)\n", |
size_t n = pairs[i].first.size(); |
str.c_str()); |
unsigned int h = n ^ s[0] ^ s[n-1]; |
|
|
|
Map::iterator pos = m.find(h); |
|
|
|
if (pos == m.end()) |
|
{ |
|
Vector v; |
|
v.push_back(pairs[i]); |
|
m.insert(MPair(h,v)); |
|
} | } |
else | else |
{ | { |
(*pos).second.push_back(pairs[i]); |
fprintf(f, "if (c == '%c' && HASHSTR_STRCMP(s, HASHSTR_T(\"%s\")) == 0)\n", |
|
ch, str.c_str()); |
} | } |
} |
|
|
|
// Print cases: |
|
for (Map::iterator p = m.begin(); p != m.end(); p++) |
|
{ |
|
const Vector& v = (*p).second; |
|
|
|
fprintf(f, " case %u:\n", (*p).first); |
|
|
|
for (size_t i = 0; i < v.size(); i++) |
|
{ |
|
const string& str = v[i].first.c_str(); |
|
const string& tok = v[i].second.c_str(); |
|
|
|
fprintf(f, " "); |
|
fprintf(f, "if (n == %u && memcmp(s, \"%s\", n) == 0)\n", |
|
(int)str.size(), str.c_str()); |
|
|
|
fprintf(f, " "); |
|
fprintf(f, "return %s;\n", tok.c_str()); |
|
} |
|
|
|
fprintf(f, " break;\n"); |
|
} |
|
|
|
fprintf(f, " }\n"); |
|
fprintf(f, " /* Not found */\n"); |
|
fprintf(f, " return 0;\n"); |
|
fprintf(f, "}\n"); |
|
} |
|
fclose(f); |
|
} |
|
#endif |
|
|
|
typedef multimap<unsigned int, Pair> Map; |
|
typedef pair<unsigned int, Pair> MPair; |
|
static void _GenStringCmp(FILE* f, const Pair& p) |
|
{ |
|
const string& str = p.first.c_str(); |
|
const string& tok = p.second.c_str(); |
|
|
|
fprintf(f, " "); |
|
fprintf(f, "if (strcmp(s, \"%s\") == 0)\n", |
|
str.c_str()); |
|
| |
fprintf(f, " "); | fprintf(f, " "); |
fprintf(f, "return %s;\n", tok.c_str()); | fprintf(f, "return %s;\n", tok.c_str()); |
|
|
| |
for ( Map::const_iterator it = pos; it != range_end; it++ ) | for ( Map::const_iterator it = pos; it != range_end; it++ ) |
{ | { |
unsigned char c = it->second.first[index]; |
unsigned char c = it->second.str[index]; |
collisions[c]++; | collisions[c]++; |
if (collisions[c] > currentMax) | if (collisions[c] > currentMax) |
currentMax = collisions[c]; | currentMax = collisions[c]; |
|
|
| |
for ( Map::const_iterator it = pos; it != range_end; it++ ) | for ( Map::const_iterator it = pos; it != range_end; it++ ) |
{ | { |
unsigned char c = it->second.first[bestIndex]; |
unsigned char c = it->second.str[bestIndex]; |
| |
subMap.insert(MPair(c, it->second)); |
subMap.insert(MTuple(c, it->second)); |
} | } |
| |
fprintf(f, " switch (s[%u])\n", bestIndex); | fprintf(f, " switch (s[%u])\n", bestIndex); |
|
|
| |
} | } |
| |
|
static void GenDoNotEditMessage( |
|
FILE* f) |
|
{ |
|
fprintf(f, "/*\n"); |
|
fprintf(f, "**==============================================================================\n"); |
|
fprintf(f, "**\n"); |
|
fprintf(f, "** DO NOT EDIT THIS FILE!!! IT WAS AUTOMATICALLY GENERATED\n"); |
|
fprintf(f, "**\n"); |
|
fprintf(f, "**==============================================================================\n"); |
|
fprintf(f, "*/\n"); |
|
fprintf(f, "\n"); |
|
} |
|
|
static void GenHashStrHeader( | static void GenHashStrHeader( |
const string& fileName, | const string& fileName, |
const Vector& pairs) |
const Vector& tuples) |
{ | { |
FILE* f = fopen(fileName.c_str(), "w"); | FILE* f = fopen(fileName.c_str(), "w"); |
| |
|
|
exit(1); | exit(1); |
} | } |
| |
_GenEnum(f, pairs); |
GenDoNotEditMessage(f); |
|
|
|
string base = BaseName(fileName); |
|
|
|
fprintf(f, "#ifndef _%s_h\n", base.c_str()); |
|
fprintf(f, "#define _%s_h\n", base.c_str()); |
|
fprintf(f, "\n"); |
|
|
|
_GenEnum(f, tuples); |
|
|
|
fprintf(f, "#if !defined(HASHSTR_CHAR)\n"); |
|
fprintf(f, "# define HASHSTR_CHAR char\n"); |
|
fprintf(f, "#endif\n\n"); |
|
|
|
fprintf(f, "#if !defined(HASHSTR_T)\n"); |
|
fprintf(f, "# define HASHSTR_T(STR) STR\n"); |
|
fprintf(f, "#endif\n\n"); |
|
|
|
fprintf(f, "#if !defined(HASHSTR_STRCMP)\n"); |
|
fprintf(f, "# define HASHSTR_STRCMP strcmp\n"); |
|
fprintf(f, "#endif\n\n"); |
| |
/* file header */ | /* file header */ |
fprintf(f, "int HashStr(const char* s, size_t n);\n"); |
fprintf(f, "int HashStr(HASHSTR_CHAR c, const HASHSTR_CHAR* s, size_t n);\n"); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
| |
|
fprintf(f, "#endif /* %s_h */\n", base.c_str()); |
|
|
printf("Created %s\n", fileName.c_str()); | printf("Created %s\n", fileName.c_str()); |
} | } |
| |
static void GenHashStrFunction( |
static void GenFastHashStrFunction( |
const string& fileName, | const string& fileName, |
const Vector& pairs) |
const string& base, |
|
const Vector& tuples) |
{ | { |
FILE* f = fopen(fileName.c_str(), "w"); | FILE* f = fopen(fileName.c_str(), "w"); |
| |
|
|
exit(1); | exit(1); |
} | } |
| |
//_GenEnum(f, pairs); |
GenDoNotEditMessage(f); |
|
|
|
fprintf(f, "#include \"%s.h\"\n", base.c_str()); |
|
fprintf(f, "\n"); |
| |
/* file header */ | /* file header */ |
fprintf(f, "int HashStr(const char* s, size_t n)\n"); |
fprintf(f, "int HashStr(HASHSTR_CHAR c, const HASHSTR_CHAR* s, size_t n)\n"); |
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
fprintf(f, " switch (n)\n"); | fprintf(f, " switch (n)\n"); |
|
|
| |
Map m; | Map m; |
| |
// Conslidate pairs with like str length |
// Conslidate tuples with like str length |
for (size_t i = 0; i < pairs.size(); i++) |
for (size_t i = 0; i < tuples.size(); i++) |
{ | { |
size_t n = pairs[i].first.size(); |
size_t n = tuples[i].str.size(); |
| |
m.insert(MPair(n, pairs[i])); |
m.insert(MTuple(n, tuples[i])); |
} | } |
| |
|
|
// generate cases for each length | // generate cases for each length |
Map::const_iterator pos = m.begin(); | Map::const_iterator pos = m.begin(); |
| |
|
|
printf("Created %s\n", fileName.c_str()); | printf("Created %s\n", fileName.c_str()); |
} | } |
| |
#if 0 |
static void GenSmallHashStrFunction( |
static void GenUnittest(const char* fileName, const Vector& pairs) |
const string& fileName, |
|
const string& base, |
|
const Vector& tuples) |
{ | { |
FILE* f = fopen(fileName, "w"); |
FILE* f = fopen(fileName.c_str(), "w"); |
| |
if (!f) | if (!f) |
{ | { |
fprintf(stderr, "%s: failed to open: %s\n", arg0, fileName); |
fprintf(stderr, "%s: failed to open: %s\n", arg0, fileName.c_str()); |
exit(1); | exit(1); |
} | } |
| |
/* file header */ |
GenDoNotEditMessage(f); |
fprintf(f, "#include <ut/ut.h>\n"); |
|
fprintf(f, "#include <string.h>\n"); |
|
fprintf(f, "#include \"../quickstr.h\"\n"); |
|
fprintf(f, "#include \"../quickstr.inc\"\n"); |
|
fprintf(f, "#include \"hash.inc\"\n"); |
|
fprintf(f, "#include \"simple.inc\"\n"); |
|
| |
|
fprintf(f, "#include \"%s.h\"\n", base.c_str()); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
fprintf(f, "static void setUp()\n{\n}\n"); |
|
fprintf(f, "static void cleanup()\n{\n}\n"); |
|
|
|
fprintf(f, "typedef int (*FN)(const char* s, size_t n);\n"); |
|
| |
fprintf(f, "static void testAllStrings(FN f)\n{\n"); |
Map m; |
| |
/* generate all tests */ |
// Conslidate tuples with like hash code: |
for (size_t i = 0; i < pairs.size(); i++) |
for (size_t i = 0; i < tuples.size(); i++) |
{ | { |
const string& str = pairs[i].first.c_str(); |
const string& s = tuples[i].str; |
const string& tok = pairs[i].second.c_str(); |
|
| |
fprintf(f, " UT_ASSERT((*f)(\"%s\", %u) == %s);\n", |
size_t n = tuples[i].str.size(); |
str.c_str(), (int)str.size(), tok.c_str() ); |
|
| |
} |
unsigned char h = (unsigned char)s[0] ^ (unsigned char)s[n-1] ^ (unsigned char)n; |
| |
/* add comparison with not-exisitng strings */ |
m.insert(MTuple(h, tuples[i])); |
fprintf(f, " UT_ASSERT((*f)(\"*\", 1) == 0);\n") ; |
} |
fprintf(f, " UT_ASSERT((*f)(\"**\", 2) == 0);\n") ; |
|
fprintf(f, " UT_ASSERT((*f)(\"***\", 3) == 0);\n") ; |
|
fprintf(f, " UT_ASSERT((*f)(\"****\", 4) == 0);\n") ; |
|
fprintf(f, " UT_ASSERT((*f)(\"*****\", 5) == 0);\n") ; |
|
fprintf(f, " UT_ASSERT((*f)(\"******\", 6) == 0);\n") ; |
|
| |
|
// generate cases for each length |
|
Map::const_iterator pos = m.begin(); |
| |
/* Generate end of unittest file */ |
fprintf(f, "typedef struct _HashStrTuple\n"); |
fprintf(f, "}\n\n"); |
|
fprintf(f, "static void TestSimple()\n"); |
|
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
fprintf(f, " for(int i=0; i< 10000; i++) testAllStrings(SimpleStr);\n"); |
fprintf(f, " unsigned char code;\n"); |
|
fprintf(f, " char ch;\n"); |
|
fprintf(f, " unsigned short tag;\n"); |
|
fprintf(f, " const HASHSTR_CHAR* str;\n"); |
fprintf(f, "}\n"); | fprintf(f, "}\n"); |
|
fprintf(f, "HashStrTuple;\n"); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
| |
fprintf(f, "static void TestHash()\n"); |
fprintf(f, "static const HashStrTuple _tuples[] =\n"); |
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
fprintf(f, " for(int i=0; i< 10000; i++) testAllStrings(HashStr);\n"); |
|
|
while (pos != m.end()) |
|
{ |
|
unsigned int h = pos->first; |
|
const Tuple& t = pos->second; |
|
|
|
// Lookup tag id: |
|
|
|
size_t tag = size_t(-1); |
|
|
|
for (size_t i = 0; i < tuples.size(); i++) |
|
{ |
|
if (tuples[i].tag == t.tag) |
|
{ |
|
tag = i + 1; |
|
} |
|
} |
|
|
|
if (tag == size_t(-1)) |
|
{ |
|
fprintf(stderr, "%s: tag not found: %s\n", arg0, t.tag.c_str()); |
|
exit(1); |
|
} |
|
|
|
fprintf(f, " {\n"); |
|
fprintf(f, " 0x%02X, /* code */\n", h); |
|
|
|
if (t.ch == '0') |
|
fprintf(f, " 0, /* ch */\n"); |
|
else |
|
fprintf(f, " '%c', /* ch */\n", t.ch); |
|
|
|
fprintf(f, " %s,\n", t.tag.c_str()); |
|
fprintf(f, " HASHSTR_T(\"%s\")\n", t.str.c_str()); |
|
fprintf(f, " },\n"); |
|
|
|
pos++; |
|
} |
|
|
|
fprintf(f, " {\n"); |
|
fprintf(f, " 0xFF, /* code */\n"); |
fprintf(f, "}\n"); | fprintf(f, "}\n"); |
|
|
|
fprintf(f, "};\n"); |
|
fprintf(f, "\n"); |
|
fprintf(f, "static const size_t _ntuples = sizeof(_tuples) / sizeof(_tuples[0]) - 1;\n"); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
| |
fprintf(f, "static void TestHashStr()\n"); |
/* file header */ |
|
fprintf(f, "int HashStr(HASHSTR_CHAR c, const HASHSTR_CHAR* s, size_t n)\n"); |
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
fprintf(f, " for(int i=0; i< 10000; i++) testAllStrings(HashStr);\n"); |
fprintf(f, "\n"); |
fprintf(f, "}\n"); |
fprintf(f, " size_t i;\n"); |
|
fprintf(f, " size_t j;\n"); |
|
fprintf(f, " unsigned char h = (unsigned char)s[0] ^ (unsigned char)s[n-1] ^ (unsigned char)n;\n"); |
fprintf(f, "\n"); | fprintf(f, "\n"); |
| |
fprintf(f, "static void RunTests()\n"); |
fprintf(f, " for (i = 0; h > _tuples[i].code; i++)\n"); |
|
fprintf(f, " ;\n"); |
|
fprintf(f, "\n"); |
|
fprintf(f, " for (j = i; j < _ntuples && h == _tuples[j].code; j++)\n"); |
fprintf(f, "{\n"); | fprintf(f, "{\n"); |
fprintf(f, " UT_TEST(TestSimple);\n"); |
fprintf(f, " if (c == _tuples[j].ch && HASHSTR_STRCMP(s, _tuples[j].str) == 0)\n"); |
fprintf(f, " UT_TEST(TestHash);\n"); |
fprintf(f, " return _tuples[j].tag;\n"); |
fprintf(f, " UT_TEST(TestHashStr);\n"); |
|
fprintf(f, "}\n"); | fprintf(f, "}\n"); |
|
|
fprintf(f, "\n"); | fprintf(f, "\n"); |
fprintf(f, "UT_ENTRY_POINT(RunTests);\n"); |
|
| |
|
// ATTN: |
|
|
|
fprintf(f, " /* Not found */\n"); |
|
fprintf(f, " return 0;\n"); |
|
fprintf(f, "}\n"); |
| |
fclose(f); | fclose(f); |
|
|
|
printf("Created %s\n", fileName.c_str()); |
} | } |
#endif |
|
| |
int main(int argc, char** argv) | int main(int argc, char** argv) |
{ | { |
|
|
exit(1); | exit(1); |
} | } |
| |
Vector pairs; |
Vector tuples; |
LoadSpecFile(argv[1], pairs); |
LoadSpecFile(argv[1], tuples); |
| |
if (pairs.empty()) |
#if 0 |
|
for (size_t i = 0; i < tuples.size(); i++) |
{ | { |
fprintf(stderr, "no data for building hash function\n"); |
const Tuple& t = tuples[i]; |
exit(1); |
printf("{%c}{%s}{%s}\n", t.ch, t.str.c_str(), t.tag.c_str()); |
} | } |
|
|
#if 0 |
|
GenSimpleFunction("simple.inc", pairs); |
|
GenHashFunction("hash.inc", pairs); |
|
#endif | #endif |
| |
// Determine the base filename: |
if (tuples.empty()) |
string base = argv[1]; |
|
{ | { |
size_t pos = base.rfind('/'); |
fprintf(stderr, "no data for building hash function\n"); |
|
exit(1); |
if (pos != string::npos) |
|
base = base.substr(pos + 1); |
|
|
|
pos = base.rfind('.'); |
|
|
|
if (pos != string::npos) |
|
base = base.substr(0, pos); |
|
} | } |
| |
GenHashStrHeader(base + ".h", pairs); |
// Determine the base filename: |
GenHashStrFunction(base + ".inc", pairs); |
string base = BaseName(argv[1]); |
| |
#if 0 |
GenHashStrHeader(base + ".h", tuples); |
GenUnittest("tests/test_strhash.cpp", pairs); |
GenFastHashStrFunction(base + "_quick.inc", base, tuples); |
#endif |
GenSmallHashStrFunction(base + "_small.inc", base, tuples); |
| |
return 0; | return 0; |
} | } |