1 /* 2 ******************************************************************************* 3 * 4 * Copyright (C) 2004-2008, International Business Machines 5 * Corporation and others. All Rights Reserved. 6 * 7 ******************************************************************************* 8 * file name: genbidi.c 9 * encoding: US-ASCII 10 * tab size: 8 (not used) 11 * indentation:4 12 * 13 * created on: 2004dec30 14 * created by: Markus W. Scherer 15 * 16 * This program reads several of the Unicode character database text files, 17 * parses them, and extracts the bidi/shaping properties for each character. 18 * It then writes a binary file containing the properties 19 * that is designed to be used directly for random-access to 20 * the properties of each Unicode character. 21 */ 22 23 #include <stdio.h> 24 #include "unicode/utypes.h" 25 #include "unicode/uchar.h" 26 #include "unicode/putil.h" 27 #include "unicode/uclean.h" 28 #include "cmemory.h" 29 #include "cstring.h" 30 #include "uarrsort.h" 31 #include "unewdata.h" 32 #include "uoptions.h" 33 #include "uparse.h" 34 #include "propsvec.h" 35 #include "ubidi_props.h" 36 #include "genbidi.h" 37 38 #define LENGTHOF(array) (sizeof(array)/sizeof((array)[0])) 39 40 /* data --------------------------------------------------------------------- */ 41 42 UPropsVectors *pv; 43 44 UBool beVerbose=FALSE, haveCopyright=TRUE; 45 46 /* prototypes --------------------------------------------------------------- */ 47 48 static UBool 49 isToken(const char *token, const char *s); 50 51 static void 52 parseBidiMirroring(const char *filename, UErrorCode *pErrorCode); 53 54 static void 55 parseDB(const char *filename, UErrorCode *pErrorCode); 56 57 /* miscellaneous ------------------------------------------------------------ */ 58 59 /* TODO: more common code, move functions to uparse.h|c */ 60 61 static char * 62 trimTerminateField(char *s, char *limit) { 63 /* trim leading whitespace */ 64 s=(char *)u_skipWhitespace(s); 65 66 /* trim trailing whitespace */ 67 while(s<limit && (*(limit-1)==' ' || *(limit-1)=='\t')) { 68 --limit; 69 } 70 *limit=0; 71 72 return s; 73 } 74 75 static void 76 parseTwoFieldFile(char *filename, char *basename, 77 const char *ucdFile, const char *suffix, 78 UParseLineFn *lineFn, 79 UErrorCode *pErrorCode) { 80 char *fields[2][2]; 81 82 if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { 83 return; 84 } 85 86 writeUCDFilename(basename, ucdFile, suffix); 87 88 u_parseDelimitedFile(filename, ';', fields, 2, lineFn, NULL, pErrorCode); 89 if(U_FAILURE(*pErrorCode)) { 90 fprintf(stderr, "error parsing %s.txt: %s\n", ucdFile, u_errorName(*pErrorCode)); 91 } 92 } 93 94 static void U_CALLCONV 95 bidiClassLineFn(void *context, 96 char *fields[][2], int32_t fieldCount, 97 UErrorCode *pErrorCode); 98 99 /* parse files with single enumerated properties ---------------------------- */ 100 101 /* TODO: more common code, move functions to uparse.h|c */ 102 103 struct SingleEnum { 104 const char *ucdFile, *propName; 105 UProperty prop; 106 int32_t vecWord, vecShift; 107 uint32_t vecMask; 108 }; 109 typedef struct SingleEnum SingleEnum; 110 111 static void 112 parseSingleEnumFile(char *filename, char *basename, const char *suffix, 113 const SingleEnum *sen, 114 UErrorCode *pErrorCode); 115 116 static const SingleEnum jtSingleEnum={ 117 "DerivedJoiningType", "joining type", 118 UCHAR_JOINING_TYPE, 119 0, UBIDI_JT_SHIFT, UBIDI_JT_MASK 120 }; 121 122 static const SingleEnum jgSingleEnum={ 123 "DerivedJoiningGroup", "joining group", 124 UCHAR_JOINING_GROUP, 125 1, 0, 0xff /* column 1 bits 7..0 */ 126 }; 127 128 static void U_CALLCONV 129 singleEnumLineFn(void *context, 130 char *fields[][2], int32_t fieldCount, 131 UErrorCode *pErrorCode) { 132 const SingleEnum *sen; 133 char *s; 134 uint32_t start, end, uv; 135 int32_t value; 136 137 sen=(const SingleEnum *)context; 138 139 u_parseCodePointRange(fields[0][0], &start, &end, pErrorCode); 140 if(U_FAILURE(*pErrorCode)) { 141 fprintf(stderr, "genbidi: syntax error in %s.txt field 0 at %s\n", sen->ucdFile, fields[0][0]); 142 exit(*pErrorCode); 143 } 144 145 /* parse property alias */ 146 s=trimTerminateField(fields[1][0], fields[1][1]); 147 value=u_getPropertyValueEnum(sen->prop, s); 148 if(value<0) { 149 if(sen->prop==UCHAR_BLOCK) { 150 if(isToken("Greek", s)) { 151 value=UBLOCK_GREEK; /* Unicode 3.2 renames this to "Greek and Coptic" */ 152 } else if(isToken("Combining Marks for Symbols", s)) { 153 value=UBLOCK_COMBINING_MARKS_FOR_SYMBOLS; /* Unicode 3.2 renames this to "Combining Diacritical Marks for Symbols" */ 154 } else if(isToken("Private Use", s)) { 155 value=UBLOCK_PRIVATE_USE; /* Unicode 3.2 renames this to "Private Use Area" */ 156 } 157 } 158 } 159 if(value<0) { 160 fprintf(stderr, "genbidi error: unknown %s name in %s.txt field 1 at %s\n", 161 sen->propName, sen->ucdFile, s); 162 exit(U_PARSE_ERROR); 163 } 164 165 uv=(uint32_t)(value<<sen->vecShift); 166 if((uv&sen->vecMask)!=uv) { 167 fprintf(stderr, "genbidi error: %s value overflow (0x%x) at %s\n", 168 sen->propName, (int)uv, s); 169 exit(U_INTERNAL_PROGRAM_ERROR); 170 } 171 172 upvec_setValue(pv, start, end, sen->vecWord, uv, sen->vecMask, pErrorCode); 173 if(U_FAILURE(*pErrorCode)) { 174 fprintf(stderr, "genbidi error: unable to set %s code: %s\n", 175 sen->propName, u_errorName(*pErrorCode)); 176 exit(*pErrorCode); 177 } 178 } 179 180 static void 181 parseSingleEnumFile(char *filename, char *basename, const char *suffix, 182 const SingleEnum *sen, 183 UErrorCode *pErrorCode) { 184 char *fields[2][2]; 185 186 if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { 187 return; 188 } 189 190 writeUCDFilename(basename, sen->ucdFile, suffix); 191 192 u_parseDelimitedFile(filename, ';', fields, 2, singleEnumLineFn, (void *)sen, pErrorCode); 193 if(U_FAILURE(*pErrorCode)) { 194 fprintf(stderr, "error parsing %s.txt: %s\n", sen->ucdFile, u_errorName(*pErrorCode)); 195 } 196 } 197 198 /* parse files with multiple binary properties ------------------------------ */ 199 200 /* TODO: more common code, move functions to uparse.h|c */ 201 202 /* TODO: similar to genbidi/props2.c but not the same; same as in gencase/gencase.c */ 203 204 struct Binary { 205 const char *propName; 206 int32_t vecWord; 207 uint32_t vecValue, vecMask; 208 }; 209 typedef struct Binary Binary; 210 211 struct Binaries { 212 const char *ucdFile; 213 const Binary *binaries; 214 int32_t binariesCount; 215 }; 216 typedef struct Binaries Binaries; 217 218 static const Binary 219 propListNames[]={ 220 { "Bidi_Control", 0, U_MASK(UBIDI_BIDI_CONTROL_SHIFT), U_MASK(UBIDI_BIDI_CONTROL_SHIFT) }, 221 { "Join_Control", 0, U_MASK(UBIDI_JOIN_CONTROL_SHIFT), U_MASK(UBIDI_JOIN_CONTROL_SHIFT) } 222 }; 223 224 static const Binaries 225 propListBinaries={ 226 "PropList", propListNames, LENGTHOF(propListNames) 227 }; 228 229 static void U_CALLCONV 230 binariesLineFn(void *context, 231 char *fields[][2], int32_t fieldCount, 232 UErrorCode *pErrorCode) { 233 const Binaries *bin; 234 char *s; 235 uint32_t start, end; 236 int32_t i; 237 238 bin=(const Binaries *)context; 239 240 u_parseCodePointRange(fields[0][0], &start, &end, pErrorCode); 241 if(U_FAILURE(*pErrorCode)) { 242 fprintf(stderr, "genbidi: syntax error in %s.txt field 0 at %s\n", bin->ucdFile, fields[0][0]); 243 exit(*pErrorCode); 244 } 245 246 /* parse binary property name */ 247 s=(char *)u_skipWhitespace(fields[1][0]); 248 for(i=0;; ++i) { 249 if(i==bin->binariesCount) { 250 /* ignore unrecognized properties */ 251 return; 252 } 253 if(isToken(bin->binaries[i].propName, s)) { 254 break; 255 } 256 } 257 258 if(bin->binaries[i].vecMask==0) { 259 fprintf(stderr, "genbidi error: mask value %d==0 for %s %s\n", 260 (int)bin->binaries[i].vecMask, bin->ucdFile, bin->binaries[i].propName); 261 exit(U_INTERNAL_PROGRAM_ERROR); 262 } 263 264 upvec_setValue(pv, start, end, bin->binaries[i].vecWord, bin->binaries[i].vecValue, bin->binaries[i].vecMask, pErrorCode); 265 if(U_FAILURE(*pErrorCode)) { 266 fprintf(stderr, "genbidi error: unable to set %s, code: %s\n", 267 bin->binaries[i].propName, u_errorName(*pErrorCode)); 268 exit(*pErrorCode); 269 } 270 } 271 272 static void 273 parseBinariesFile(char *filename, char *basename, const char *suffix, 274 const Binaries *bin, 275 UErrorCode *pErrorCode) { 276 char *fields[2][2]; 277 278 if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { 279 return; 280 } 281 282 writeUCDFilename(basename, bin->ucdFile, suffix); 283 284 u_parseDelimitedFile(filename, ';', fields, 2, binariesLineFn, (void *)bin, pErrorCode); 285 if(U_FAILURE(*pErrorCode)) { 286 fprintf(stderr, "error parsing %s.txt: %s\n", bin->ucdFile, u_errorName(*pErrorCode)); 287 } 288 } 289 290 /* -------------------------------------------------------------------------- */ 291 292 enum { 293 HELP_H, 294 HELP_QUESTION_MARK, 295 VERBOSE, 296 COPYRIGHT, 297 DESTDIR, 298 SOURCEDIR, 299 UNICODE_VERSION, 300 ICUDATADIR, 301 CSOURCE 302 }; 303 304 /* Keep these values in sync with the above enums */ 305 static UOption options[]={ 306 UOPTION_HELP_H, 307 UOPTION_HELP_QUESTION_MARK, 308 UOPTION_VERBOSE, 309 UOPTION_COPYRIGHT, 310 UOPTION_DESTDIR, 311 UOPTION_SOURCEDIR, 312 UOPTION_DEF("unicode", 'u', UOPT_REQUIRES_ARG), 313 UOPTION_ICUDATADIR, 314 UOPTION_DEF("csource", 'C', UOPT_NO_ARG) 315 }; 316 317 extern int 318 main(int argc, char* argv[]) { 319 char filename[300]; 320 const char *srcDir=NULL, *destDir=NULL, *suffix=NULL; 321 char *basename=NULL; 322 UErrorCode errorCode=U_ZERO_ERROR; 323 324 U_MAIN_INIT_ARGS(argc, argv); 325 326 /* preset then read command line options */ 327 options[DESTDIR].value=u_getDataDirectory(); 328 options[SOURCEDIR].value=""; 329 options[UNICODE_VERSION].value=""; 330 options[ICUDATADIR].value=u_getDataDirectory(); 331 argc=u_parseArgs(argc, argv, sizeof(options)/sizeof(options[0]), options); 332 333 /* error handling, printing usage message */ 334 if(argc<0) { 335 fprintf(stderr, 336 "error in command line argument \"%s\"\n", 337 argv[-argc]); 338 } 339 if(argc<0 || options[HELP_H].doesOccur || options[HELP_QUESTION_MARK].doesOccur) { 340 /* 341 * Broken into chucks because the C89 standard says the minimum 342 * required supported string length is 509 bytes. 343 */ 344 fprintf(stderr, 345 "Usage: %s [-options] [suffix]\n" 346 "\n" 347 "read the UnicodeData.txt file and other Unicode properties files and\n" 348 "create a binary file " UBIDI_DATA_NAME "." UBIDI_DATA_TYPE " with the bidi/shaping properties\n" 349 "\n", 350 argv[0]); 351 fprintf(stderr, 352 "Options:\n" 353 "\t-h or -? or --help this usage text\n" 354 "\t-v or --verbose verbose output\n" 355 "\t-c or --copyright include a copyright notice\n" 356 "\t-u or --unicode Unicode version, followed by the version like 3.0.0\n" 357 "\t-C or --csource generate a .c source file rather than the .icu binary\n"); 358 fprintf(stderr, 359 "\t-d or --destdir destination directory, followed by the path\n" 360 "\t-s or --sourcedir source directory, followed by the path\n" 361 "\t-i or --icudatadir directory for locating any needed intermediate data files,\n" 362 "\t followed by path, defaults to %s\n" 363 "\tsuffix suffix that is to be appended with a '-'\n" 364 "\t to the source file basenames before opening;\n" 365 "\t 'genbidi new' will read UnicodeData-new.txt etc.\n", 366 u_getDataDirectory()); 367 return argc<0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR; 368 } 369 370 /* get the options values */ 371 beVerbose=options[VERBOSE].doesOccur; 372 haveCopyright=options[COPYRIGHT].doesOccur; 373 srcDir=options[SOURCEDIR].value; 374 destDir=options[DESTDIR].value; 375 376 if(argc>=2) { 377 suffix=argv[1]; 378 } else { 379 suffix=NULL; 380 } 381 382 if(options[UNICODE_VERSION].doesOccur) { 383 setUnicodeVersion(options[UNICODE_VERSION].value); 384 } 385 /* else use the default dataVersion in store.c */ 386 387 if (options[ICUDATADIR].doesOccur) { 388 u_setDataDirectory(options[ICUDATADIR].value); 389 } 390 391 /* prepare the filename beginning with the source dir */ 392 uprv_strcpy(filename, srcDir); 393 basename=filename+uprv_strlen(filename); 394 if(basename>filename && *(basename-1)!=U_FILE_SEP_CHAR) { 395 *basename++=U_FILE_SEP_CHAR; 396 } 397 398 /* initialize */ 399 pv=upvec_open(2, &errorCode); 400 401 /* process BidiMirroring.txt */ 402 writeUCDFilename(basename, "BidiMirroring", suffix); 403 parseBidiMirroring(filename, &errorCode); 404 405 /* process additional properties files */ 406 *basename=0; 407 408 parseBinariesFile(filename, basename, suffix, &propListBinaries, &errorCode); 409 410 parseSingleEnumFile(filename, basename, suffix, &jtSingleEnum, &errorCode); 411 412 parseSingleEnumFile(filename, basename, suffix, &jgSingleEnum, &errorCode); 413 414 /* process UnicodeData.txt */ 415 writeUCDFilename(basename, "UnicodeData", suffix); 416 parseDB(filename, &errorCode); 417 418 /* set proper bidi class for unassigned code points (Cn) */ 419 parseTwoFieldFile(filename, basename, "DerivedBidiClass", suffix, bidiClassLineFn, &errorCode); 420 421 /* process parsed data */ 422 if(U_SUCCESS(errorCode)) { 423 /* write the properties data file */ 424 generateData(destDir, options[CSOURCE].doesOccur); 425 } 426 427 u_cleanup(); 428 return errorCode; 429 } 430 431 U_CFUNC void 432 writeUCDFilename(char *basename, const char *filename, const char *suffix) { 433 int32_t length=(int32_t)uprv_strlen(filename); 434 uprv_strcpy(basename, filename); 435 if(suffix!=NULL) { 436 basename[length++]='-'; 437 uprv_strcpy(basename+length, suffix); 438 length+=(int32_t)uprv_strlen(suffix); 439 } 440 uprv_strcpy(basename+length, ".txt"); 441 } 442 443 /* TODO: move to toolutil */ 444 static UBool 445 isToken(const char *token, const char *s) { 446 const char *z; 447 int32_t j; 448 449 s=u_skipWhitespace(s); 450 for(j=0;; ++j) { 451 if(token[j]!=0) { 452 if(s[j]!=token[j]) { 453 break; 454 } 455 } else { 456 z=u_skipWhitespace(s+j); 457 if(*z==';' || *z==0) { 458 return TRUE; 459 } else { 460 break; 461 } 462 } 463 } 464 465 return FALSE; 466 } 467 468 /* parser for BidiMirroring.txt --------------------------------------------- */ 469 470 static void U_CALLCONV 471 mirrorLineFn(void *context, 472 char *fields[][2], int32_t fieldCount, 473 UErrorCode *pErrorCode) { 474 char *end; 475 UChar32 src, mirror; 476 477 src=(UChar32)uprv_strtoul(fields[0][0], &end, 16); 478 if(end<=fields[0][0] || end!=fields[0][1]) { 479 fprintf(stderr, "genbidi: syntax error in BidiMirroring.txt field 0 at %s\n", fields[0][0]); 480 *pErrorCode=U_PARSE_ERROR; 481 exit(U_PARSE_ERROR); 482 } 483 484 mirror=(UChar32)uprv_strtoul(fields[1][0], &end, 16); 485 if(end<=fields[1][0] || end!=fields[1][1]) { 486 fprintf(stderr, "genbidi: syntax error in BidiMirroring.txt field 1 at %s\n", fields[1][0]); 487 *pErrorCode=U_PARSE_ERROR; 488 exit(U_PARSE_ERROR); 489 } 490 491 addMirror(src, mirror); 492 } 493 494 static void 495 parseBidiMirroring(const char *filename, UErrorCode *pErrorCode) { 496 char *fields[2][2]; 497 498 if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { 499 return; 500 } 501 502 u_parseDelimitedFile(filename, ';', fields, 2, mirrorLineFn, NULL, pErrorCode); 503 } 504 505 /* parser for UnicodeData.txt ----------------------------------------------- */ 506 507 static void U_CALLCONV 508 unicodeDataLineFn(void *context, 509 char *fields[][2], int32_t fieldCount, 510 UErrorCode *pErrorCode) { 511 char *end; 512 UErrorCode errorCode; 513 UChar32 c; 514 515 errorCode=U_ZERO_ERROR; 516 517 /* get the character code, field 0 */ 518 c=(UChar32)uprv_strtoul(fields[0][0], &end, 16); 519 if(end<=fields[0][0] || end!=fields[0][1]) { 520 fprintf(stderr, "genbidi: syntax error in field 0 at %s\n", fields[0][0]); 521 *pErrorCode=U_PARSE_ERROR; 522 exit(U_PARSE_ERROR); 523 } 524 525 /* get Mirrored flag, field 9 */ 526 if(*fields[9][0]=='Y') { 527 upvec_setValue(pv, c, c, 0, U_MASK(UBIDI_IS_MIRRORED_SHIFT), U_MASK(UBIDI_IS_MIRRORED_SHIFT), &errorCode); 528 if(U_FAILURE(*pErrorCode)) { 529 fprintf(stderr, "genbidi error: unable to set 'is mirrored' for U+%04lx, code: %s\n", 530 (long)c, u_errorName(errorCode)); 531 exit(errorCode); 532 } 533 } else if(fields[9][1]-fields[9][0]!=1 || *fields[9][0]!='N') { 534 fprintf(stderr, "genbidi: syntax error in field 9 at U+%04lx\n", 535 (long)c); 536 *pErrorCode=U_PARSE_ERROR; 537 exit(U_PARSE_ERROR); 538 } 539 } 540 541 static void 542 parseDB(const char *filename, UErrorCode *pErrorCode) { 543 /* default Bidi classes for unassigned code points */ 544 static const UChar32 defaultBidi[][3]={ /* { start, end, class } */ 545 /* R: U+0590..U+05FF, U+07C0..U+08FF, U+FB1D..U+FB4F, U+10800..U+10FFF */ 546 { 0x0590, 0x05FF, U_RIGHT_TO_LEFT }, 547 { 0x07C0, 0x08FF, U_RIGHT_TO_LEFT }, 548 { 0xFB1D, 0xFB4F, U_RIGHT_TO_LEFT }, 549 { 0x10800, 0x10FFF, U_RIGHT_TO_LEFT }, 550 551 /* AL: U+0600..U+07BF, U+FB50..U+FDCF, U+FDF0..U+FDFF, U+FE70..U+FEFE */ 552 { 0x0600, 0x07BF, U_RIGHT_TO_LEFT_ARABIC }, 553 { 0xFB50, 0xFDCF, U_RIGHT_TO_LEFT_ARABIC }, 554 { 0xFDF0, 0xFDFF, U_RIGHT_TO_LEFT_ARABIC }, 555 { 0xFE70, 0xFEFE, U_RIGHT_TO_LEFT_ARABIC } 556 557 /* L otherwise */ 558 }; 559 560 char *fields[15][2]; 561 UChar32 start, end; 562 int32_t i; 563 564 if(pErrorCode==NULL || U_FAILURE(*pErrorCode)) { 565 return; 566 } 567 568 /* 569 * Set default Bidi classes for unassigned code points. 570 * See the documentation for Bidi_Class in UCD.html in the Unicode data. 571 * http://www.unicode.org/Public/ 572 * 573 * Starting with Unicode 5.0, DerivedBidiClass.txt should (re)set 574 * the Bidi_Class values for all code points including unassigned ones 575 * and including L values for these. 576 * This code becomes unnecesary but harmless. Leave it for now in case 577 * someone uses genbidi on pre-Unicode 5.0 data. 578 */ 579 for(i=0; i<LENGTHOF(defaultBidi); ++i) { 580 start=defaultBidi[i][0]; 581 end=defaultBidi[i][1]; 582 upvec_setValue(pv, start, end, 0, (uint32_t)defaultBidi[i][2], UBIDI_CLASS_MASK, pErrorCode); 583 if(U_FAILURE(*pErrorCode)) { 584 fprintf(stderr, "genbidi error: unable to set default bidi class for U+%04lx..U+%04lx, code: %s\n", 585 (long)start, (long)end, u_errorName(*pErrorCode)); 586 exit(*pErrorCode); 587 } 588 } 589 590 u_parseDelimitedFile(filename, ';', fields, 15, unicodeDataLineFn, NULL, pErrorCode); 591 592 if(U_FAILURE(*pErrorCode)) { 593 return; 594 } 595 } 596 597 /* DerivedBidiClass.txt ----------------------------------------------------- */ 598 599 static void U_CALLCONV 600 bidiClassLineFn(void *context, 601 char *fields[][2], int32_t fieldCount, 602 UErrorCode *pErrorCode) { 603 char *s; 604 uint32_t start, end, value; 605 606 /* get the code point range */ 607 u_parseCodePointRange(fields[0][0], &start, &end, pErrorCode); 608 if(U_FAILURE(*pErrorCode)) { 609 fprintf(stderr, "genbidi: syntax error in DerivedBidiClass.txt field 0 at %s\n", fields[0][0]); 610 exit(*pErrorCode); 611 } 612 613 /* parse bidi class */ 614 s=trimTerminateField(fields[1][0], fields[1][1]); 615 value=u_getPropertyValueEnum(UCHAR_BIDI_CLASS, s); 616 if((int32_t)value<0) { 617 fprintf(stderr, "genbidi error: unknown bidi class in DerivedBidiClass.txt field 1 at %s\n", s); 618 exit(U_PARSE_ERROR); 619 } 620 621 upvec_setValue(pv, start, end, 0, value, UBIDI_CLASS_MASK, pErrorCode); 622 if(U_FAILURE(*pErrorCode)) { 623 fprintf(stderr, "genbidi error: unable to set derived bidi class for U+%04x..U+%04x - %s\n", 624 (int)start, (int)end, u_errorName(*pErrorCode)); 625 exit(*pErrorCode); 626 } 627 } 628 629 /* 630 * Hey, Emacs, please set the following: 631 * 632 * Local Variables: 633 * indent-tabs-mode: nil 634 * End: 635 * 636 */ 637