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