shell.c (17899B)
1 #include <inttypes.h> 2 #include <errno.h> 3 #include <stdarg.h> 4 #include <stdbool.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <time.h> 9 10 #include "../src/nissy.h" 11 12 #define PRINTCUBE_BUFFER_SIZE UINT64_C(1024) 13 #define SOLUTIONS_BUFFER_SIZE UINT64_C(500000) 14 #define MAX_PATH_LENGTH UINT64_C(10000) 15 16 #define FLAG_CUBE "-cube" 17 #define FLAG_PERM "-perm" 18 #define FLAG_COMMAND "-command" 19 #define FLAG_STR_CUBE "-cubestr" 20 #define FLAG_FORMAT "-format" 21 #define FLAG_FORMAT_IN "-fin" 22 #define FLAG_FORMAT_OUT "-fout" 23 #define FLAG_MOVES "-moves" 24 #define FLAG_TRANS "-trans" 25 #define FLAG_SOLVER "-solver" 26 #define FLAG_NISSTYPE "-nisstype" 27 #define FLAG_MINMOVES "-m" 28 #define FLAG_MAXMOVES "-M" 29 #define FLAG_OPTIMAL "-O" 30 #define FLAG_MAXSOLUTIONS "-n" 31 #define FLAG_THREADS "-t" 32 33 #define INFO_CUBEFORMAT(cube) cube " must be given in B32 format." 34 #define INFO_MOVESFORMAT "The accepted moves are U, D, R, L, F and B, " \ 35 "optionally followed by a 2, a ' or a 3." 36 #define INFO_TRANSFORMAT "The transformation must be given in the format " \ 37 "(rotation|mirrored) (2 letters), for exmple " \ 38 "'rotation UF' or 'mirrored BL'." 39 #define INFO_FORMATS "The available formats are H48, B32 and SRC." 40 41 typedef struct { 42 int command_index; 43 char cube[22]; 44 char cube_perm[22]; 45 char *str_command; 46 char *str_cube; 47 char *str_format; 48 char *str_format_in; 49 char *str_format_out; 50 char *str_moves; 51 char *str_trans; 52 char *str_solver; 53 char *str_nisstype; 54 unsigned minmoves; 55 unsigned maxmoves; 56 unsigned optimal; 57 unsigned maxsolutions; 58 unsigned threads; 59 } args_t; 60 61 static int64_t compose_exec(args_t *); 62 static int64_t inverse_exec(args_t *); 63 static int64_t applymoves_exec(args_t *); 64 static int64_t applytrans_exec(args_t *); 65 static int64_t frommoves_exec(args_t *); 66 static int64_t convert_exec(args_t *); 67 static int64_t randomcube_exec(args_t *); 68 static int64_t solverinfo_exec(args_t *); 69 static int64_t gendata_exec(args_t *); 70 static int64_t solve_exec(args_t *); 71 static int64_t solve_scramble_exec(args_t *); 72 static int64_t countmoves_exec(args_t *); 73 static int64_t help_exec(args_t *); 74 75 static int parse_args(int, char **, args_t *); 76 static bool parse_uint(const char *, unsigned *); 77 static uint8_t parse_nisstype(const char *); 78 79 static bool set_cube(int, char **, args_t *); 80 static bool set_cube_perm(int, char **, args_t *); 81 static bool set_str_command(int, char **, args_t *); 82 static bool set_str_cube(int, char **, args_t *); 83 static bool set_str_format(int, char **, args_t *); 84 static bool set_str_format_in(int, char **, args_t *); 85 static bool set_str_format_out(int, char **, args_t *); 86 static bool set_str_moves(int, char **, args_t *); 87 static bool set_str_trans(int, char **, args_t *); 88 static bool set_str_solver(int, char **, args_t *); 89 static bool set_str_nisstype(int, char **, args_t *); 90 static bool set_minmoves(int, char **, args_t *); 91 static bool set_maxmoves(int, char **, args_t *); 92 static bool set_optimal(int, char **, args_t *); 93 static bool set_maxsolutions(int, char **, args_t *); 94 static bool set_threads(int, char **, args_t *); 95 static bool set_id(int, char **, args_t *); 96 97 static uint64_t rand64(void); 98 99 #define OPTION(N, A, S) { .name = N, .nargs = A, .set = S } 100 struct { 101 char *name; 102 int nargs; 103 bool (*set)(int, char **, args_t *); 104 } options[] = { 105 OPTION(FLAG_CUBE, 1, set_cube), 106 OPTION(FLAG_PERM, 1, set_cube_perm), 107 OPTION(FLAG_COMMAND, 1, set_str_command), 108 OPTION(FLAG_STR_CUBE, 1, set_str_cube), 109 OPTION(FLAG_FORMAT, 1, set_str_format), 110 OPTION(FLAG_FORMAT_IN, 1, set_str_format_in), 111 OPTION(FLAG_FORMAT_OUT, 1, set_str_format_out), 112 OPTION(FLAG_MOVES, 1, set_str_moves), 113 OPTION(FLAG_TRANS, 1, set_str_trans), 114 OPTION(FLAG_SOLVER, 1, set_str_solver), 115 OPTION(FLAG_NISSTYPE, 1, set_str_nisstype), 116 OPTION(FLAG_MINMOVES, 1, set_minmoves), 117 OPTION(FLAG_MAXMOVES, 1, set_maxmoves), 118 OPTION(FLAG_OPTIMAL, 1, set_optimal), 119 OPTION(FLAG_MAXSOLUTIONS, 1, set_maxsolutions), 120 OPTION(FLAG_THREADS, 1, set_threads), 121 OPTION(NULL, 0, NULL) 122 }; 123 124 #define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E } 125 struct { 126 char *name; 127 char *syn; 128 char *desc; 129 int64_t (*exec)(args_t *); 130 } commands[] = { 131 /* TODO: add synopsis and description here */ 132 COMMAND( 133 "compose", 134 "compose " FLAG_CUBE " CUBE " FLAG_PERM " PERM", 135 "Apply on CUBE the permutation defined by PERM. " 136 INFO_CUBEFORMAT("CUBE and PERM"), 137 compose_exec 138 ), 139 COMMAND( 140 "inverse", 141 "inverse " FLAG_CUBE " CUBE ", 142 "Compute the inverse of the given CUBE. " 143 INFO_CUBEFORMAT("CUBE"), 144 inverse_exec 145 ), 146 COMMAND( 147 "applymoves", 148 "applymoves " FLAG_CUBE " CUBE " FLAG_MOVES " MOVES", 149 "Apply the given MOVES to the given CUBE. " 150 INFO_CUBEFORMAT("CUBE") " " INFO_MOVESFORMAT, 151 applymoves_exec 152 ), 153 COMMAND( 154 "applytrans", 155 "applytrans " FLAG_CUBE " CUBE " FLAG_TRANS " TRANS", 156 "Apply the single transformation TRANS to the given CUBE. " 157 INFO_CUBEFORMAT("CUBE") " " INFO_TRANSFORMAT, 158 applytrans_exec 159 ), 160 COMMAND( 161 "frommoves", 162 "frommoves " FLAG_MOVES " MOVES", 163 "Return the cube obtained by applying the given MOVES " 164 "to a solved cube. " INFO_MOVESFORMAT, 165 frommoves_exec 166 ), 167 COMMAND( 168 "convert", 169 "convert " FLAG_STR_CUBE " CUBESTR " 170 FLAG_FORMAT_IN " FORMAT_IN " FLAG_FORMAT_OUT " FORMAT_OUT", 171 "Convert the cube described by CUBESTR from FORMAT_IN to " 172 "FORMAT_OUT." 173 INFO_FORMATS " " 174 "CUBESTR must be a valid cube in the FORMAT_IN format.", 175 convert_exec 176 ), 177 COMMAND( 178 "randomcube", 179 "randomcube", 180 "Returns a random cube in B32 format.", 181 randomcube_exec 182 ), 183 COMMAND( 184 "solverinfo", 185 "solverinfo " FLAG_SOLVER " SOLVER", 186 "Return the size in bytes and the id of the data table " 187 "used by SOLVER when called with the given OPTIONS.", 188 solverinfo_exec 189 ), 190 COMMAND( 191 "gendata", 192 "gendata " FLAG_SOLVER " SOLVER", 193 "Generate the data table used by " 194 "SOLVER when called with the given OPTIONS.", 195 gendata_exec 196 ), 197 COMMAND( 198 "solve", 199 "solve " FLAG_SOLVER " SOLVER" 200 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 201 FLAG_CUBE " CUBE" 202 FLAG_THREADS " T", 203 "Solve the given CUBE using SOLVER, " 204 "using at least n and at most N moves, and T threads. " 205 INFO_CUBEFORMAT("CUBE"), 206 solve_exec 207 ), 208 COMMAND( 209 "solve_scramble", 210 "solve_scramble " FLAG_SOLVER " SOLVER" 211 "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] " 212 FLAG_MOVES " MOVES", 213 "Solve the given SCRAMBLE using SOLVER, " 214 "using at least n and at most N moves. " 215 INFO_MOVESFORMAT, 216 solve_scramble_exec 217 ), 218 COMMAND( 219 "countmoves", 220 "countmoves " FLAG_MOVES " MOVES", 221 "Count the given MOVES in HTM. " 222 INFO_MOVESFORMAT, 223 countmoves_exec 224 ), 225 COMMAND( 226 "help", 227 "help [" FLAG_COMMAND " COMMAND]", 228 "If no COMMAND is specified, prints some generic information " 229 "and the list of commands. Otherwise it prints detailed " 230 "information about the specified COMMAND.", 231 help_exec 232 ), 233 COMMAND(NULL, NULL, NULL, NULL) 234 }; 235 236 char *tablepaths[] = { 237 "tables/", 238 "", 239 NULL 240 }; 241 242 static uint64_t 243 rand64(void) 244 { 245 uint64_t i, ret; 246 247 for (i = 0, ret = 0; i < 64; i++) 248 ret |= (uint64_t)(rand() % 2) << i; 249 250 return ret; 251 } 252 253 static int64_t 254 compose_exec(args_t *args) 255 { 256 char result[22]; 257 int64_t ret; 258 259 ret = nissy_compose(args->cube, args->cube_perm, result); 260 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 261 printf("%s\n", result); 262 263 return ret; 264 } 265 266 static int64_t 267 inverse_exec(args_t *args) 268 { 269 char result[22]; 270 int64_t ret; 271 272 ret = nissy_inverse(args->cube, result); 273 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 274 printf("%s\n", result); 275 276 return ret; 277 } 278 279 static int64_t 280 applymoves_exec(args_t *args) 281 { 282 char result[22]; 283 int64_t ret; 284 285 ret = nissy_applymoves(args->cube, args->str_moves, result); 286 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 287 printf("%s\n", result); 288 289 return ret; 290 } 291 292 static int64_t 293 applytrans_exec(args_t *args) 294 { 295 char result[22]; 296 int64_t ret; 297 298 ret = nissy_applytrans(args->cube, args->str_trans, result); 299 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 300 printf("%s\n", result); 301 302 return ret; 303 } 304 305 static int64_t 306 frommoves_exec(args_t *args) 307 { 308 sprintf(args->cube, NISSY_SOLVED_CUBE); 309 return applymoves_exec(args); 310 } 311 312 static int64_t 313 convert_exec(args_t *args) 314 { 315 char result[PRINTCUBE_BUFFER_SIZE]; 316 int64_t ret; 317 318 ret = nissy_convert(args->str_format_in, args->str_format_out, 319 args->str_cube, PRINTCUBE_BUFFER_SIZE, result); 320 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 321 printf("%s\n", result); 322 323 return ret; 324 } 325 326 static int64_t 327 randomcube_exec(args_t *args) 328 { 329 char result[PRINTCUBE_BUFFER_SIZE]; 330 int64_t ret, ep, eo, cp, co; 331 332 ep = rand64(); 333 eo = rand64(); 334 cp = rand64(); 335 co = rand64(); 336 ret = nissy_getcube(ep, eo, cp, co, "fix", result); 337 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 338 printf("%s\n", result); 339 340 return ret; 341 } 342 343 static int64_t 344 solverinfo_exec(args_t *args) 345 { 346 int64_t ret; 347 char buf[NISSY_DATAID_SIZE]; 348 349 ret = nissy_solverinfo(args->str_solver, buf); 350 if (ret < 0) 351 fprintf(stderr, "Unknown error (make sure solver is valid)\n"); 352 printf("%" PRId64 "\n%s\n", ret, buf); 353 354 return ret; 355 } 356 357 static int64_t 358 gendata_exec(args_t *args) 359 { 360 int i; 361 FILE *file; 362 char *buf, path[MAX_PATH_LENGTH], dataid[NISSY_DATAID_SIZE]; 363 int64_t ret, size; 364 size_t written; 365 366 size = nissy_solverinfo(args->str_solver, dataid); 367 368 if (size < 0) { 369 fprintf(stderr, "gendata: unknown solver %s\n", 370 args->str_solver); 371 return -3; 372 } 373 374 /* TODO: should give warning if overwriting existing file */ 375 for (i = 0; tablepaths[i] != NULL; i++) { 376 strcpy(path, tablepaths[i]); 377 strcat(path, dataid); 378 file = fopen(path, "wb"); 379 if (file != NULL) 380 break; 381 } 382 383 if (tablepaths[i] == NULL) { 384 fprintf(stderr, "Cannot write data to file\n"); 385 fclose(file); 386 return -2; 387 } 388 389 buf = malloc(size); 390 391 ret = nissy_gendata(args->str_solver, size, buf); 392 if (ret < 0) { 393 fprintf(stderr, "Unknown error in generating data\n"); 394 fclose(file); 395 free(buf); 396 return -4; 397 } 398 if (ret != size) { 399 fprintf(stderr, "Unknown error: unexpected data size " 400 "got %" PRId64 ", expected %" PRId64 ")\n", ret, size); 401 fclose(file); 402 free(buf); 403 return -5; 404 } 405 406 written = fwrite(buf, size, 1, file); 407 fclose(file); 408 free(buf); 409 410 if (written != 1) { 411 fprintf(stderr, 412 "Error: data was generated correctly, but could not be " 413 "written to file (generated %" PRId64 " bytes, written " 414 "%zu)\n", size, written); 415 return -6; 416 } 417 418 fprintf(stderr, "Data written to %s\n", path); 419 420 return 0; 421 } 422 423 static int64_t 424 solve_exec(args_t *args) 425 { 426 int i; 427 uint8_t nissflag; 428 FILE *file; 429 char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH]; 430 char dataid[NISSY_DATAID_SIZE]; 431 long long stats[NISSY_SIZE_SOLVE_STATS]; 432 int64_t ret, gendata_ret, size; 433 size_t read; 434 435 nissflag = parse_nisstype(args->str_nisstype); 436 if (nissflag == UINT8_MAX) { 437 fprintf(stderr, "solve: unknown niss type '%s', use one " 438 "of the following:\nnormal\ninverse\nlinear\nmixed\nall", 439 args->str_nisstype); 440 return -1; 441 } 442 443 size = nissy_solverinfo(args->str_solver, dataid); 444 445 if (size < 0) { 446 fprintf(stderr, "solve: unknown solver '%s'\n", 447 args->str_solver); 448 return size; 449 } 450 451 for (i = 0; tablepaths[i] != NULL; i++) { 452 strcpy(path, tablepaths[i]); 453 strcat(path, dataid); 454 file = fopen(path, "rb"); 455 if (file != NULL) 456 break; 457 } 458 459 if (tablepaths[i] == NULL) { 460 fprintf(stderr, 461 "Cannot read data file, " 462 "generating it (this can take a while)\n"); 463 gendata_ret = gendata_exec(args); 464 if (gendata_ret) 465 return gendata_ret; 466 } 467 468 /* Ugh, this is not elegant TODO */ 469 if (file == NULL) { 470 for (i = 0; tablepaths[i] != NULL; i++) { 471 strcpy(path, tablepaths[i]); 472 strcat(path, dataid); 473 file = fopen(path, "rb"); 474 if (file != NULL) 475 break; 476 } 477 } 478 479 if (tablepaths[i] == NULL) { 480 fprintf(stderr, "Error: data file not found\n"); 481 fclose(file); 482 return -1; 483 } 484 485 buf = malloc(size); 486 read = fread(buf, size, 1, file); 487 fclose(file); 488 if (read != 1) { 489 fprintf(stderr, "Error reading data from file: " 490 "fread() returned %zu instead of 1 when attempting to " 491 "read %" PRId64 " bytes from file %s\n", read, size, path); 492 goto solve_exec_error; 493 } 494 495 ret = nissy_solve( 496 args->cube, args->str_solver, nissflag, args->minmoves, 497 args->maxmoves, args->maxsolutions, args->optimal, args->threads, 498 size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats); 499 500 free(buf); 501 502 if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE) 503 fprintf(stderr, "No solutions found\n"); 504 else 505 printf("%s", solutions); 506 507 return 0; 508 509 solve_exec_error: 510 free(buf); 511 return -2; 512 } 513 514 static int64_t 515 solve_scramble_exec(args_t *args) 516 { 517 nissy_applymoves(NISSY_SOLVED_CUBE, args->str_moves, args->cube); 518 519 return solve_exec(args); 520 } 521 522 static int64_t 523 countmoves_exec(args_t *args) 524 { 525 long long count; 526 527 count = nissy_countmoves(args->str_moves); 528 529 if (count >= 0) 530 printf("%lld\n", count); 531 532 return count >= 0 ? 0 : count; 533 } 534 535 static int64_t 536 help_exec(args_t *args) 537 { 538 int i; 539 540 if (args->str_command == NULL || args->str_command[0] == '\0') { 541 printf("This is a rudimentary shell for the H48 library.\n"); 542 printf("Available commands and usage:\n\n"); 543 for (i = 0; commands[i].name != NULL; i++) 544 printf("%-15s%s\n", commands[i].name, commands[i].syn); 545 printf("\nUse 'help -command COMMAND' for more information.\n"); 546 } else { 547 for (i = 0; commands[i].name != NULL; i++) 548 if (!strcmp(args->str_command, commands[i].name)) 549 break; 550 if (commands[i].name == NULL) { 551 printf("Unknown command %s\n", args->str_command); 552 return 1; 553 } 554 printf("Command %s\n\n", commands[i].name); 555 printf("Synopsis: %s\n\n", commands[i].syn); 556 printf("Description: %s\n", commands[i].desc); 557 } 558 559 return 0; 560 } 561 562 static int 563 parse_args(int argc, char **argv, args_t *args) 564 { 565 int i, j, n; 566 567 *args = (args_t) { 568 .command_index = -1, 569 .cube = "", 570 .cube_perm = "", 571 .str_cube = "", 572 .str_format = "", 573 .str_format_in = "", 574 .str_format_out = "", 575 .str_moves = "", 576 .str_trans = "", 577 .str_solver = "", 578 .str_nisstype = "", 579 .minmoves = 0, 580 .maxmoves = 20, 581 .optimal = -1, 582 .maxsolutions = 1, 583 .threads = 0, 584 }; 585 586 if (argc == 0) { 587 printf("No command given\n"); 588 return 1; 589 } 590 591 for (i = 0; commands[i].name != NULL; i++) { 592 if (!strcmp(argv[0], commands[i].name)) { 593 args->command_index = i; 594 break; 595 } 596 } 597 598 if (commands[i].name == NULL) { 599 fprintf(stderr, "Unknown command %s\n", argv[0]); 600 return 1; 601 } 602 603 for (i = 1; i < argc; i++) { 604 for (j = 0; options[j].name != NULL; j++) { 605 n = argc - i - 1; 606 if (strcmp(argv[i], options[j].name)) 607 continue; 608 if (n < options[j].nargs) { 609 fprintf(stderr, 610 "Too few arguments for option %s\n", 611 options[j].name); 612 return 1; 613 } 614 if (!options[j].set(n, argv+i+1, args)) { 615 fprintf(stderr, 616 "Error parsing arguments for option %s\n", 617 options[j].name); 618 return 1; 619 } 620 i += options[j].nargs; 621 break; 622 } 623 if (options[j].name == NULL) { 624 fprintf(stderr, "Unknown option %s\n", argv[i]); 625 return 1; 626 } 627 } 628 629 return 0; 630 } 631 632 static bool 633 parse_uint(const char *argv, unsigned *result) 634 { 635 *result = strtol(argv, NULL, 10); 636 637 /* TODO: figure out how errno works and use it */ 638 return true; 639 } 640 641 static uint8_t 642 parse_nisstype(const char *arg) 643 { 644 if (!strcmp("normal", arg) || !strcmp("", arg)) 645 return NISSY_NISSFLAG_NORMAL; 646 647 if (!strcmp("inverse", arg)) 648 return NISSY_NISSFLAG_INVERSE; 649 650 if (!strcmp("linear", arg)) 651 return NISSY_NISSFLAG_LINEAR; 652 653 if (!strcmp("mixed", arg)) 654 return NISSY_NISSFLAG_MIXED; 655 656 if (!strcmp("all", arg)) 657 return NISSY_NISSFLAG_ALL; 658 659 return UINT8_MAX; 660 } 661 662 static bool 663 set_cube(int argc, char **argv, args_t *args) 664 { 665 memcpy(args->cube, argv[0], 22); 666 args->cube[21] = 0; 667 668 return true; 669 } 670 671 static bool 672 set_cube_perm(int argc, char **argv, args_t *args) 673 { 674 memcpy(args->cube_perm, argv[0], 22); 675 args->cube_perm[21] = 0; 676 677 return true; 678 } 679 680 static bool 681 set_str_command(int argc, char **argv, args_t *args) 682 { 683 args->str_command = argv[0]; 684 685 return true; 686 } 687 688 static bool 689 set_str_cube(int argc, char **argv, args_t *args) 690 { 691 args->str_cube = argv[0]; 692 693 return true; 694 } 695 696 static bool 697 set_str_format(int argc, char **argv, args_t *args) 698 { 699 args->str_format = argv[0]; 700 701 return true; 702 } 703 704 static bool 705 set_str_format_in(int argc, char **argv, args_t *args) 706 { 707 args->str_format_in = argv[0]; 708 709 return true; 710 } 711 712 static bool 713 set_str_format_out(int argc, char **argv, args_t *args) 714 { 715 args->str_format_out = argv[0]; 716 717 return true; 718 } 719 720 static bool 721 set_str_moves(int argc, char **argv, args_t *args) 722 { 723 args->str_moves = argv[0]; 724 725 return true; 726 } 727 728 static bool 729 set_str_trans(int argc, char **argv, args_t *args) 730 { 731 args->str_trans = argv[0]; 732 733 return true; 734 } 735 736 static bool 737 set_str_solver(int argc, char **argv, args_t *args) 738 { 739 args->str_solver = argv[0]; 740 741 return true; 742 } 743 744 static bool 745 set_str_nisstype(int argc, char **argv, args_t *args) 746 { 747 args->str_nisstype = argv[0]; 748 749 return true; 750 } 751 752 static bool 753 set_minmoves(int argc, char **argv, args_t *args) 754 { 755 return parse_uint(argv[0], &args->minmoves); 756 } 757 758 static bool 759 set_maxmoves(int argc, char **argv, args_t *args) 760 { 761 return parse_uint(argv[0], &args->maxmoves); 762 } 763 764 static bool 765 set_optimal(int argc, char **argv, args_t *args) 766 { 767 return parse_uint(argv[0], &args->optimal); 768 } 769 770 static bool 771 set_maxsolutions(int argc, char **argv, args_t *args) 772 { 773 return parse_uint(argv[0], &args->maxsolutions); 774 } 775 776 static bool 777 set_threads(int argc, char **argv, args_t *args) 778 { 779 return parse_uint(argv[0], &args->threads); 780 } 781 782 void 783 log_stderr(const char *str, ...) 784 { 785 va_list args; 786 787 va_start(args, str); 788 vfprintf(stderr, str, args); 789 va_end(args); 790 } 791 792 int 793 main(int argc, char **argv) 794 { 795 int parse_error; 796 args_t args; 797 798 srand(time(NULL)); 799 nissy_setlogger(log_stderr); 800 801 parse_error = parse_args(argc-1, argv+1, &args); 802 if (parse_error) 803 return parse_error; 804 805 return (int)commands[args.command_index].exec(&args); 806 }