nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

shell.c (15540B)


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