commit 9b680fadec01789c1fb1317b939c15d186a572c9
parent c4f64cf2556c0f8597e1fbc19d7c664b8da94612
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Sat, 12 Oct 2024 10:21:52 +0200
Added shelltest
Will add more tests when I feel like it
Diffstat:
11 files changed, 795 insertions(+), 758 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -8,6 +8,8 @@ utils/.DS_Store
perf.data
perf.data.old
run
+shell/lasttest.out
+shell/lasttest.err
tables/*
test/*/runtest
test/.DS_Store
diff --git a/Makefile b/Makefile
@@ -30,10 +30,13 @@ debugtool: debugnissy.o
shell: nissy.o
mkdir -p tables
- ${CC} ${MACROS} ${CFLAGS} -o run nissy.o shell.c
+ ${CC} ${MACROS} ${CFLAGS} -o run nissy.o shell/shell.c
debugshell: debugnissy.o
mkdir -p tables
- ${CC} ${MACROS} ${DBGFLAGS} -o run debugnissy.o shell.c
+ ${CC} ${MACROS} ${DBGFLAGS} -o run debugnissy.o shell/shell.c
-.PHONY: all clean test tool debugtool shell debugshell
+shelltest: debugshell
+ ./shell/test.sh
+
+.PHONY: all clean test tool debugtool shell debugshell shelltest
diff --git a/README.md b/README.md
@@ -108,8 +108,14 @@ commands manually. To build the shell use:
$ make shell
```
-This will create an executable called `run`. Then you can for example
-get a cube from a sequence of moves:
+This will create an executable called `run`.
+Optionally, you can run some tests:
+
+```
+$ make shelltest
+```
+
+Then you can for example get a cube from a sequence of moves:
```
$ ./run frommoves -moves "R' U' F"
diff --git a/shell.c b/shell.c
@@ -1,752 +0,0 @@
-#include <inttypes.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include "src/nissy.h"
-
-#define PRINTCUBE_BUFFER_SIZE UINT64_C(1024)
-#define SOLUTIONS_BUFFER_SIZE UINT64_C(500000)
-#define MAX_PATH_LENGTH UINT64_C(10000)
-
-#define FLAG_CUBE "-cube"
-#define FLAG_PERM "-perm"
-#define FLAG_COMMAND "-command"
-#define FLAG_STR_CUBE "-cubestr"
-#define FLAG_FORMAT "-format"
-#define FLAG_FORMAT_IN "-fin"
-#define FLAG_FORMAT_OUT "-fout"
-#define FLAG_MOVES "-moves"
-#define FLAG_TRANS "-trans"
-#define FLAG_SOLVER "-solver"
-#define FLAG_NISSTYPE "-nisstype"
-#define FLAG_MINMOVES "-m"
-#define FLAG_MAXMOVES "-M"
-#define FLAG_OPTIMAL "-O"
-#define FLAG_MAXSOLUTIONS "-n"
-
-#define INFO_CUBEFORMAT(cube) cube " must be given in B32 format."
-#define INFO_MOVESFORMAT "The accepted moves are U, D, R, L, F and B, " \
- "optionally followed by a 2, a ' or a 3."
-#define INFO_TRANSFORMAT "The transformation must be given in the format " \
- "(rotation|mirrored) (2 letters), for exmple " \
- "'rotation UF' or 'mirrored BL'."
-#define INFO_FORMATS "The available formats are H48, B32 and SRC."
-
-typedef struct {
- int command_index;
- char cube[22];
- char cube_perm[22];
- char *str_command;
- char *str_cube;
- char *str_format;
- char *str_format_in;
- char *str_format_out;
- char *str_moves;
- char *str_trans;
- char *str_solver;
- char *str_nisstype;
- int8_t minmoves;
- int8_t maxmoves;
- int8_t optimal;
- int64_t maxsolutions;
-} args_t;
-
-static int64_t compose_exec(args_t *);
-static int64_t inverse_exec(args_t *);
-static int64_t applymoves_exec(args_t *);
-static int64_t applytrans_exec(args_t *);
-static int64_t frommoves_exec(args_t *);
-static int64_t convert_exec(args_t *);
-static int64_t randomcube_exec(args_t *);
-static int64_t datasize_exec(args_t *);
-static int64_t gendata_exec(args_t *);
-static int64_t solve_exec(args_t *);
-static int64_t solve_scramble_exec(args_t *);
-static int64_t help_exec(args_t *);
-
-static int parse_args(int, char **, args_t *);
-static bool parse_int8(char *, int8_t *);
-static bool parse_int64(char *, int64_t *);
-
-static bool set_cube(int, char **, args_t *);
-static bool set_cube_perm(int, char **, args_t *);
-static bool set_str_command(int, char **, args_t *);
-static bool set_str_cube(int, char **, args_t *);
-static bool set_str_format(int, char **, args_t *);
-static bool set_str_format_in(int, char **, args_t *);
-static bool set_str_format_out(int, char **, args_t *);
-static bool set_str_moves(int, char **, args_t *);
-static bool set_str_trans(int, char **, args_t *);
-static bool set_str_solver(int, char **, args_t *);
-static bool set_str_nisstype(int, char **, args_t *);
-static bool set_minmoves(int, char **, args_t *);
-static bool set_maxmoves(int, char **, args_t *);
-static bool set_optimal(int, char **, args_t *);
-static bool set_maxsolutions(int, char **, args_t *);
-static bool set_id(int, char **, args_t *);
-
-static uint64_t rand64(void);
-
-#define OPTION(N, A, S) { .name = N, .nargs = A, .set = S }
-struct {
- char *name;
- int nargs;
- bool (*set)(int, char **, args_t *);
-} options[] = {
- OPTION(FLAG_CUBE, 1, set_cube),
- OPTION(FLAG_PERM, 1, set_cube_perm),
- OPTION(FLAG_COMMAND, 1, set_str_command),
- OPTION(FLAG_STR_CUBE, 1, set_str_cube),
- OPTION(FLAG_FORMAT, 1, set_str_format),
- OPTION(FLAG_FORMAT_IN, 1, set_str_format_in),
- OPTION(FLAG_FORMAT_OUT, 1, set_str_format_out),
- OPTION(FLAG_MOVES, 1, set_str_moves),
- OPTION(FLAG_TRANS, 1, set_str_trans),
- OPTION(FLAG_SOLVER, 1, set_str_solver),
- OPTION(FLAG_NISSTYPE, 1, set_str_nisstype), /* TODO: more args ? */
- OPTION(FLAG_MINMOVES, 1, set_minmoves),
- OPTION(FLAG_MAXMOVES, 1, set_maxmoves),
- OPTION(FLAG_OPTIMAL, 1, set_optimal),
- OPTION(FLAG_MAXSOLUTIONS, 1, set_maxsolutions),
- OPTION(NULL, 0, NULL)
-};
-
-#define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E }
-struct {
- char *name;
- char *syn;
- char *desc;
- int64_t (*exec)(args_t *);
-} commands[] = {
-/* TODO: add synopsis and description here */
- COMMAND(
- "compose",
- "compose " FLAG_CUBE " CUBE " FLAG_PERM " PERM",
- "Apply on CUBE the permutation defined by PERM. "
- INFO_CUBEFORMAT("CUBE and PERM"),
- compose_exec
- ),
- COMMAND(
- "inverse",
- "inverse " FLAG_CUBE " CUBE ",
- "Compute the inverse of the given CUBE. "
- INFO_CUBEFORMAT("CUBE"),
- inverse_exec
- ),
- COMMAND(
- "applymoves",
- "applymoves " FLAG_CUBE " CUBE " FLAG_MOVES " MOVES",
- "Apply the given MOVES to the given CUBE. "
- INFO_CUBEFORMAT("CUBE") " " INFO_MOVESFORMAT,
- applymoves_exec
- ),
- COMMAND(
- "applytrans",
- "applytrans " FLAG_CUBE " CUBE " FLAG_TRANS " TRANS",
- "Apply the single transformation TRANS to the given CUBE. "
- INFO_CUBEFORMAT("CUBE") " " INFO_TRANSFORMAT,
- applytrans_exec
- ),
- COMMAND(
- "frommoves",
- "frommoves " FLAG_MOVES " MOVES",
- "Return the cube obtained by applying the given MOVES "
- "to a solved cube. " INFO_MOVESFORMAT,
- frommoves_exec
- ),
- COMMAND(
- "convert",
- "convert " FLAG_STR_CUBE " CUBESTR "
- FLAG_FORMAT_IN " FORMAT_IN " FLAG_FORMAT_OUT " FORMAT_OUT",
- "Convert the cube described by CUBESTR from FORMAT_IN to "
- "FORMAT_OUT."
- INFO_FORMATS " "
- "CUBESTR must be a valid cube in the FORMAT_IN format.",
- convert_exec
- ),
- COMMAND(
- "randomcube",
- "randomcube",
- "Returns a random cube in B32 format.",
- randomcube_exec
- ),
- COMMAND(
- "datasize",
- "datasize " FLAG_SOLVER " SOLVER",
- "Return the size in bytes of the data table used by "
- "SOLVER when called with the given OPTIONS.",
- datasize_exec
- ),
- COMMAND(
- "gendata",
- "gendata " FLAG_SOLVER " SOLVER",
- "Generate the data table used by "
- "SOLVER when called with the given OPTIONS.",
- gendata_exec
- ),
- COMMAND(
- "solve",
- "solve " FLAG_SOLVER " SOLVER"
- "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] "
- FLAG_CUBE " CUBE",
- "Solve the given CUBE using SOLVER, "
- "using at least n and at most N moves. "
- INFO_CUBEFORMAT("CUBE"),
- solve_exec
- ),
- COMMAND(
- "solve_scramble",
- "solve_scramble " FLAG_SOLVER " SOLVER"
- "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] "
- FLAG_CUBE " CUBE",
- "Solve the given SCRAMBLE using SOLVER, "
- "using at least n and at most N moves. "
- INFO_MOVESFORMAT,
- solve_scramble_exec
- ),
- COMMAND(
- "help",
- "help [" FLAG_COMMAND " COMMAND]",
- "If no COMMAND is specified, prints some generic information "
- "and the list of commands. Otherwise it prints detailed "
- "information about the specified COMMAND.",
- help_exec
- ),
- COMMAND(NULL, NULL, NULL, NULL)
-};
-
-char *tablepaths[] = {
- "tables/",
- "",
- NULL
-};
-
-static uint64_t
-rand64(void)
-{
- uint64_t i, ret;
-
- for (i = 0, ret = 0; i < 64; i++)
- ret |= (uint64_t)(rand() % 2) << i;
-
- return ret;
-}
-
-static int64_t
-compose_exec(args_t *args)
-{
- char result[22];
- int64_t ret;
-
- ret = nissy_compose(args->cube, args->cube_perm, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-inverse_exec(args_t *args)
-{
- char result[22];
- int64_t ret;
-
- ret = nissy_inverse(args->cube, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-applymoves_exec(args_t *args)
-{
- char result[22];
- int64_t ret;
-
- ret = nissy_applymoves(args->cube, args->str_moves, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-applytrans_exec(args_t *args)
-{
- char result[22];
- int64_t ret;
-
- ret = nissy_applytrans(args->cube, args->str_trans, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-frommoves_exec(args_t *args)
-{
- char result[22];
- int64_t ret;
-
- ret = nissy_frommoves(args->str_moves, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-convert_exec(args_t *args)
-{
- char result[PRINTCUBE_BUFFER_SIZE];
- int64_t ret;
-
- ret = nissy_convert(args->str_format_in, args->str_format_out,
- args->str_cube, PRINTCUBE_BUFFER_SIZE, result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-randomcube_exec(args_t *args)
-{
- char result[PRINTCUBE_BUFFER_SIZE];
- int64_t ret, ep, eo, cp, co;
-
- ep = rand64();
- eo = rand64();
- cp = rand64();
- co = rand64();
- ret = nissy_getcube(ep, eo, cp, co, "fix", result);
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- printf("%s\n", result);
-
- return ret;
-}
-
-static int64_t
-datasize_exec(args_t *args)
-{
- int64_t ret;
-
- ret = nissy_datasize(args->str_solver);
- if (ret < 0)
- fprintf(stderr, "Unknown error (make sure solver is valid)\n");
- printf("%" PRId64 "\n", ret);
-
- return ret;
-}
-
-static int64_t
-gendata_exec(args_t *args)
-{
- int i;
- FILE *file;
- char *buf, path[MAX_PATH_LENGTH];
- int64_t ret, size;
- size_t written;
-
- /* TODO: should give warning if overwriting existing file */
- for (i = 0; tablepaths[i] != NULL; i++) {
- strcpy(path, tablepaths[i]);
- strcat(path, args->str_solver);
- file = fopen(path, "wb");
- if (file != NULL)
- break;
- }
-
- if (tablepaths[i] == NULL) {
- fprintf(stderr, "Cannot write data to file\n");
- fclose(file);
- return -2;
- }
-
- size = nissy_datasize(args->str_solver);
-
- if (size < 0) {
- fprintf(stderr,
- "Unknown error in retrieving data size"
- "(make sure solver is valid)\n");
- fclose(file);
- return -3;
- }
-
- buf = malloc(size);
-
- ret = nissy_gendata(args->str_solver, size, buf);
- if (ret < 0) {
- fprintf(stderr, "Unknown error in generating data\n");
- fclose(file);
- free(buf);
- return -4;
- }
- if (ret != size) {
- fprintf(stderr, "Unknown error: unexpected data size "
- "got %" PRId64 ", expected %" PRId64 ")\n", ret, size);
- fclose(file);
- free(buf);
- return -5;
- }
-
- written = fwrite(buf, size, 1, file);
- fclose(file);
- free(buf);
-
- if (written != 1) {
- fprintf(stderr,
- "Error: data was generated correctly, but could not be "
- "written to file (generated %" PRId64 " bytes, written "
- "%zu)\n", size, written);
- return -6;
- }
-
- fprintf(stderr, "Data written to %s\n", path);
-
- return 0;
-}
-
-static int64_t
-solve_exec(args_t *args)
-{
- int i;
- uint8_t nissflag;
- FILE *file;
- char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH];
- int64_t ret, gendata_ret, size;
- size_t read;
-
- nissflag = NISSY_NISSFLAG_NORMAL; /* TODO: parse str_nisstype */
-
- for (i = 0; tablepaths[i] != NULL; i++) {
- strcpy(path, tablepaths[i]);
- strcat(path, args->str_solver);
- file = fopen(path, "rb");
- if (file != NULL)
- break;
- }
-
- if (tablepaths[i] == NULL) {
- fprintf(stderr,
- "Cannot read data file, "
- "generating it (this can take a while)\n");
- gendata_ret = gendata_exec(args);
- if (gendata_ret)
- return gendata_ret;
- }
-
- /* Ugh, this is not elegant TODO */
- if (file == NULL) {
- for (i = 0; tablepaths[i] != NULL; i++) {
- strcpy(path, tablepaths[i]);
- strcat(path, args->str_solver);
- file = fopen(path, "rb");
- if (file != NULL)
- break;
- }
- }
-
- if (tablepaths[i] == NULL) {
- fprintf(stderr, "Error: data file not found\n");
- fclose(file);
- return -1;
- }
-
- size = nissy_datasize(args->str_solver);
- buf = malloc(size);
- read = fread(buf, size, 1, file);
- fclose(file);
- if (read != 1) {
- fprintf(stderr, "Error reading data from file: "
- "fread() returned %zu instead of 1 when attempting to"
- "read %" PRId64 " bytes from file %s\n", read, size, path);
- return -2;
- }
-
- ret = nissy_solve(
- args->cube, args->str_solver, nissflag, args->minmoves,
- args->maxmoves, args->maxsolutions, args->optimal,
- size, buf, SOLUTIONS_BUFFER_SIZE, solutions);
-
- free(buf);
-
- if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
- fprintf(stderr, "No solutions found\n");
- else
- printf("%s", solutions);
-
- return 0;
-}
-
-static int64_t
-solve_scramble_exec(args_t *args)
-{
- nissy_frommoves(args->str_moves, args->cube);
-
- return solve_exec(args);
-}
-
-static int64_t
-help_exec(args_t *args)
-{
- int i;
-
- if (args->str_command == NULL || args->str_command[0] == '\0') {
- printf("This is a rudimentary shell for the H48 library.\n");
- printf("Available commands and usage:\n\n");
- for (i = 0; commands[i].name != NULL; i++)
- printf("%-15s%s\n", commands[i].name, commands[i].syn);
- printf("\nUse 'help -command COMMAND' for more information.\n");
- } else {
- for (i = 0; commands[i].name != NULL; i++)
- if (!strcmp(args->str_command, commands[i].name))
- break;
- if (commands[i].name == NULL) {
- printf("Unknown command %s\n", args->str_command);
- return 1;
- }
- printf("Command %s\n\n", commands[i].name);
- printf("Synopsis: %s\n\n", commands[i].syn);
- printf("Description: %s\n", commands[i].desc);
- }
-
- return 0;
-}
-
-static int
-parse_args(int argc, char **argv, args_t *args)
-{
- int i, j, n;
-
- *args = (args_t) {
- .command_index = -1,
- .cube = "",
- .cube_perm = "",
- .str_cube = "",
- .str_format = "",
- .str_format_in = "",
- .str_format_out = "",
- .str_moves = "",
- .str_trans = "",
- .str_solver = "",
- .str_nisstype = "",
- .minmoves = 0,
- .maxmoves = 20,
- .optimal = -1,
- .maxsolutions = 1,
- };
-
- if (argc == 0) {
- printf("No command given\n");
- return 1;
- }
-
- for (i = 0; commands[i].name != NULL; i++) {
- if (!strcmp(argv[0], commands[i].name)) {
- args->command_index = i;
- break;
- }
- }
-
- if (commands[i].name == NULL) {
- fprintf(stderr, "Unknown command %s\n", argv[0]);
- return 1;
- }
-
- for (i = 1; i < argc; i++) {
- for (j = 0; options[j].name != NULL; j++) {
- n = argc - i - 1;
- if (strcmp(argv[i], options[j].name))
- continue;
- if (n < options[j].nargs) {
- fprintf(stderr,
- "Too few arguments for option %s\n",
- options[j].name);
- return 1;
- }
- if (!options[j].set(n, argv+i+1, args)) {
- fprintf(stderr,
- "Error parsing arguments for option %s\n",
- options[j].name);
- return 1;
- }
- i += options[j].nargs;
- break;
- }
- if (options[j].name == NULL) {
- fprintf(stderr, "Unknown option %s\n", argv[i]);
- return 1;
- }
- }
-
- return 0;
-}
-
-bool
-parse_int8(char *argv, int8_t *result)
-{
- bool noerror;
- int64_t n;
-
- noerror = parse_int64(argv, &n);
- *result = (int8_t)n;
-
- return noerror && n >= INT8_MIN && n <= INT8_MAX;
-}
-
-bool
-parse_int64(char *argv, int64_t *result)
-{
- *result = strtoll(argv, NULL, 10);
-
- /* TODO: figure out how errno works and use it */
- return true;
-}
-
-static bool
-set_cube(int argc, char **argv, args_t *args)
-{
- memcpy(args->cube, argv[0], 22);
- args->cube[21] = 0;
-
- return true;
-}
-
-static bool
-set_cube_perm(int argc, char **argv, args_t *args)
-{
- memcpy(args->cube_perm, argv[0], 22);
- args->cube_perm[21] = 0;
-
- return true;
-}
-
-static bool
-set_str_command(int argc, char **argv, args_t *args)
-{
- args->str_command = argv[0];
-
- return true;
-}
-
-static bool
-set_str_cube(int argc, char **argv, args_t *args)
-{
- args->str_cube = argv[0];
-
- return true;
-}
-
-static bool
-set_str_format(int argc, char **argv, args_t *args)
-{
- args->str_format = argv[0];
-
- return true;
-}
-
-static bool
-set_str_format_in(int argc, char **argv, args_t *args)
-{
- args->str_format_in = argv[0];
-
- return true;
-}
-
-static bool
-set_str_format_out(int argc, char **argv, args_t *args)
-{
- args->str_format_out = argv[0];
-
- return true;
-}
-
-static bool
-set_str_moves(int argc, char **argv, args_t *args)
-{
- args->str_moves = argv[0];
-
- return true;
-}
-
-static bool
-set_str_trans(int argc, char **argv, args_t *args)
-{
- args->str_trans = argv[0];
-
- return true;
-}
-
-static bool
-set_str_solver(int argc, char **argv, args_t *args)
-{
- args->str_solver = argv[0];
-
- return true;
-}
-
-static bool
-set_str_nisstype(int argc, char **argv, args_t *args)
-{
- args->str_nisstype = argv[0];
-
- return true;
-}
-
-static bool
-set_minmoves(int argc, char **argv, args_t *args)
-{
- return parse_int8(argv[0], &args->minmoves);
-}
-
-static bool
-set_maxmoves(int argc, char **argv, args_t *args)
-{
- return parse_int8(argv[0], &args->maxmoves);
-}
-
-static bool
-set_optimal(int argc, char **argv, args_t *args)
-{
- return parse_int8(argv[0], &args->optimal);
-}
-
-static bool
-set_maxsolutions(int argc, char **argv, args_t *args)
-{
- return parse_int64(argv[0], &args->maxsolutions);
-}
-
-void
-log_stderr(const char *str, ...)
-{
- va_list args;
-
- va_start(args, str);
- vfprintf(stderr, str, args);
- va_end(args);
-}
-
-int
-main(int argc, char **argv)
-{
- int parse_error;
- args_t args;
-
- srand(time(NULL));
- nissy_setlogger(log_stderr);
-
- parse_error = parse_args(argc-1, argv+1, &args);
- if (parse_error)
- return parse_error;
-
- return (int)commands[args.command_index].exec(&args);
-}
diff --git a/shell/shell.c b/shell/shell.c
@@ -0,0 +1,752 @@
+#include <inttypes.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "../src/nissy.h"
+
+#define PRINTCUBE_BUFFER_SIZE UINT64_C(1024)
+#define SOLUTIONS_BUFFER_SIZE UINT64_C(500000)
+#define MAX_PATH_LENGTH UINT64_C(10000)
+
+#define FLAG_CUBE "-cube"
+#define FLAG_PERM "-perm"
+#define FLAG_COMMAND "-command"
+#define FLAG_STR_CUBE "-cubestr"
+#define FLAG_FORMAT "-format"
+#define FLAG_FORMAT_IN "-fin"
+#define FLAG_FORMAT_OUT "-fout"
+#define FLAG_MOVES "-moves"
+#define FLAG_TRANS "-trans"
+#define FLAG_SOLVER "-solver"
+#define FLAG_NISSTYPE "-nisstype"
+#define FLAG_MINMOVES "-m"
+#define FLAG_MAXMOVES "-M"
+#define FLAG_OPTIMAL "-O"
+#define FLAG_MAXSOLUTIONS "-n"
+
+#define INFO_CUBEFORMAT(cube) cube " must be given in B32 format."
+#define INFO_MOVESFORMAT "The accepted moves are U, D, R, L, F and B, " \
+ "optionally followed by a 2, a ' or a 3."
+#define INFO_TRANSFORMAT "The transformation must be given in the format " \
+ "(rotation|mirrored) (2 letters), for exmple " \
+ "'rotation UF' or 'mirrored BL'."
+#define INFO_FORMATS "The available formats are H48, B32 and SRC."
+
+typedef struct {
+ int command_index;
+ char cube[22];
+ char cube_perm[22];
+ char *str_command;
+ char *str_cube;
+ char *str_format;
+ char *str_format_in;
+ char *str_format_out;
+ char *str_moves;
+ char *str_trans;
+ char *str_solver;
+ char *str_nisstype;
+ int8_t minmoves;
+ int8_t maxmoves;
+ int8_t optimal;
+ int64_t maxsolutions;
+} args_t;
+
+static int64_t compose_exec(args_t *);
+static int64_t inverse_exec(args_t *);
+static int64_t applymoves_exec(args_t *);
+static int64_t applytrans_exec(args_t *);
+static int64_t frommoves_exec(args_t *);
+static int64_t convert_exec(args_t *);
+static int64_t randomcube_exec(args_t *);
+static int64_t datasize_exec(args_t *);
+static int64_t gendata_exec(args_t *);
+static int64_t solve_exec(args_t *);
+static int64_t solve_scramble_exec(args_t *);
+static int64_t help_exec(args_t *);
+
+static int parse_args(int, char **, args_t *);
+static bool parse_int8(char *, int8_t *);
+static bool parse_int64(char *, int64_t *);
+
+static bool set_cube(int, char **, args_t *);
+static bool set_cube_perm(int, char **, args_t *);
+static bool set_str_command(int, char **, args_t *);
+static bool set_str_cube(int, char **, args_t *);
+static bool set_str_format(int, char **, args_t *);
+static bool set_str_format_in(int, char **, args_t *);
+static bool set_str_format_out(int, char **, args_t *);
+static bool set_str_moves(int, char **, args_t *);
+static bool set_str_trans(int, char **, args_t *);
+static bool set_str_solver(int, char **, args_t *);
+static bool set_str_nisstype(int, char **, args_t *);
+static bool set_minmoves(int, char **, args_t *);
+static bool set_maxmoves(int, char **, args_t *);
+static bool set_optimal(int, char **, args_t *);
+static bool set_maxsolutions(int, char **, args_t *);
+static bool set_id(int, char **, args_t *);
+
+static uint64_t rand64(void);
+
+#define OPTION(N, A, S) { .name = N, .nargs = A, .set = S }
+struct {
+ char *name;
+ int nargs;
+ bool (*set)(int, char **, args_t *);
+} options[] = {
+ OPTION(FLAG_CUBE, 1, set_cube),
+ OPTION(FLAG_PERM, 1, set_cube_perm),
+ OPTION(FLAG_COMMAND, 1, set_str_command),
+ OPTION(FLAG_STR_CUBE, 1, set_str_cube),
+ OPTION(FLAG_FORMAT, 1, set_str_format),
+ OPTION(FLAG_FORMAT_IN, 1, set_str_format_in),
+ OPTION(FLAG_FORMAT_OUT, 1, set_str_format_out),
+ OPTION(FLAG_MOVES, 1, set_str_moves),
+ OPTION(FLAG_TRANS, 1, set_str_trans),
+ OPTION(FLAG_SOLVER, 1, set_str_solver),
+ OPTION(FLAG_NISSTYPE, 1, set_str_nisstype), /* TODO: more args ? */
+ OPTION(FLAG_MINMOVES, 1, set_minmoves),
+ OPTION(FLAG_MAXMOVES, 1, set_maxmoves),
+ OPTION(FLAG_OPTIMAL, 1, set_optimal),
+ OPTION(FLAG_MAXSOLUTIONS, 1, set_maxsolutions),
+ OPTION(NULL, 0, NULL)
+};
+
+#define COMMAND(N, S, D, E) { .name = N, .syn = S, .desc = D, .exec = E }
+struct {
+ char *name;
+ char *syn;
+ char *desc;
+ int64_t (*exec)(args_t *);
+} commands[] = {
+/* TODO: add synopsis and description here */
+ COMMAND(
+ "compose",
+ "compose " FLAG_CUBE " CUBE " FLAG_PERM " PERM",
+ "Apply on CUBE the permutation defined by PERM. "
+ INFO_CUBEFORMAT("CUBE and PERM"),
+ compose_exec
+ ),
+ COMMAND(
+ "inverse",
+ "inverse " FLAG_CUBE " CUBE ",
+ "Compute the inverse of the given CUBE. "
+ INFO_CUBEFORMAT("CUBE"),
+ inverse_exec
+ ),
+ COMMAND(
+ "applymoves",
+ "applymoves " FLAG_CUBE " CUBE " FLAG_MOVES " MOVES",
+ "Apply the given MOVES to the given CUBE. "
+ INFO_CUBEFORMAT("CUBE") " " INFO_MOVESFORMAT,
+ applymoves_exec
+ ),
+ COMMAND(
+ "applytrans",
+ "applytrans " FLAG_CUBE " CUBE " FLAG_TRANS " TRANS",
+ "Apply the single transformation TRANS to the given CUBE. "
+ INFO_CUBEFORMAT("CUBE") " " INFO_TRANSFORMAT,
+ applytrans_exec
+ ),
+ COMMAND(
+ "frommoves",
+ "frommoves " FLAG_MOVES " MOVES",
+ "Return the cube obtained by applying the given MOVES "
+ "to a solved cube. " INFO_MOVESFORMAT,
+ frommoves_exec
+ ),
+ COMMAND(
+ "convert",
+ "convert " FLAG_STR_CUBE " CUBESTR "
+ FLAG_FORMAT_IN " FORMAT_IN " FLAG_FORMAT_OUT " FORMAT_OUT",
+ "Convert the cube described by CUBESTR from FORMAT_IN to "
+ "FORMAT_OUT."
+ INFO_FORMATS " "
+ "CUBESTR must be a valid cube in the FORMAT_IN format.",
+ convert_exec
+ ),
+ COMMAND(
+ "randomcube",
+ "randomcube",
+ "Returns a random cube in B32 format.",
+ randomcube_exec
+ ),
+ COMMAND(
+ "datasize",
+ "datasize " FLAG_SOLVER " SOLVER",
+ "Return the size in bytes of the data table used by "
+ "SOLVER when called with the given OPTIONS.",
+ datasize_exec
+ ),
+ COMMAND(
+ "gendata",
+ "gendata " FLAG_SOLVER " SOLVER",
+ "Generate the data table used by "
+ "SOLVER when called with the given OPTIONS.",
+ gendata_exec
+ ),
+ COMMAND(
+ "solve",
+ "solve " FLAG_SOLVER " SOLVER"
+ "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] "
+ FLAG_CUBE " CUBE",
+ "Solve the given CUBE using SOLVER, "
+ "using at least n and at most N moves. "
+ INFO_CUBEFORMAT("CUBE"),
+ solve_exec
+ ),
+ COMMAND(
+ "solve_scramble",
+ "solve_scramble " FLAG_SOLVER " SOLVER"
+ "[" FLAG_MINMOVES " n] [" FLAG_MAXMOVES " N] "
+ FLAG_CUBE " CUBE",
+ "Solve the given SCRAMBLE using SOLVER, "
+ "using at least n and at most N moves. "
+ INFO_MOVESFORMAT,
+ solve_scramble_exec
+ ),
+ COMMAND(
+ "help",
+ "help [" FLAG_COMMAND " COMMAND]",
+ "If no COMMAND is specified, prints some generic information "
+ "and the list of commands. Otherwise it prints detailed "
+ "information about the specified COMMAND.",
+ help_exec
+ ),
+ COMMAND(NULL, NULL, NULL, NULL)
+};
+
+char *tablepaths[] = {
+ "tables/",
+ "",
+ NULL
+};
+
+static uint64_t
+rand64(void)
+{
+ uint64_t i, ret;
+
+ for (i = 0, ret = 0; i < 64; i++)
+ ret |= (uint64_t)(rand() % 2) << i;
+
+ return ret;
+}
+
+static int64_t
+compose_exec(args_t *args)
+{
+ char result[22];
+ int64_t ret;
+
+ ret = nissy_compose(args->cube, args->cube_perm, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+inverse_exec(args_t *args)
+{
+ char result[22];
+ int64_t ret;
+
+ ret = nissy_inverse(args->cube, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+applymoves_exec(args_t *args)
+{
+ char result[22];
+ int64_t ret;
+
+ ret = nissy_applymoves(args->cube, args->str_moves, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+applytrans_exec(args_t *args)
+{
+ char result[22];
+ int64_t ret;
+
+ ret = nissy_applytrans(args->cube, args->str_trans, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+frommoves_exec(args_t *args)
+{
+ char result[22];
+ int64_t ret;
+
+ ret = nissy_frommoves(args->str_moves, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+convert_exec(args_t *args)
+{
+ char result[PRINTCUBE_BUFFER_SIZE];
+ int64_t ret;
+
+ ret = nissy_convert(args->str_format_in, args->str_format_out,
+ args->str_cube, PRINTCUBE_BUFFER_SIZE, result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+randomcube_exec(args_t *args)
+{
+ char result[PRINTCUBE_BUFFER_SIZE];
+ int64_t ret, ep, eo, cp, co;
+
+ ep = rand64();
+ eo = rand64();
+ cp = rand64();
+ co = rand64();
+ ret = nissy_getcube(ep, eo, cp, co, "fix", result);
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ printf("%s\n", result);
+
+ return ret;
+}
+
+static int64_t
+datasize_exec(args_t *args)
+{
+ int64_t ret;
+
+ ret = nissy_datasize(args->str_solver);
+ if (ret < 0)
+ fprintf(stderr, "Unknown error (make sure solver is valid)\n");
+ printf("%" PRId64 "\n", ret);
+
+ return ret;
+}
+
+static int64_t
+gendata_exec(args_t *args)
+{
+ int i;
+ FILE *file;
+ char *buf, path[MAX_PATH_LENGTH];
+ int64_t ret, size;
+ size_t written;
+
+ /* TODO: should give warning if overwriting existing file */
+ for (i = 0; tablepaths[i] != NULL; i++) {
+ strcpy(path, tablepaths[i]);
+ strcat(path, args->str_solver);
+ file = fopen(path, "wb");
+ if (file != NULL)
+ break;
+ }
+
+ if (tablepaths[i] == NULL) {
+ fprintf(stderr, "Cannot write data to file\n");
+ fclose(file);
+ return -2;
+ }
+
+ size = nissy_datasize(args->str_solver);
+
+ if (size < 0) {
+ fprintf(stderr,
+ "Unknown error in retrieving data size"
+ "(make sure solver is valid)\n");
+ fclose(file);
+ return -3;
+ }
+
+ buf = malloc(size);
+
+ ret = nissy_gendata(args->str_solver, size, buf);
+ if (ret < 0) {
+ fprintf(stderr, "Unknown error in generating data\n");
+ fclose(file);
+ free(buf);
+ return -4;
+ }
+ if (ret != size) {
+ fprintf(stderr, "Unknown error: unexpected data size "
+ "got %" PRId64 ", expected %" PRId64 ")\n", ret, size);
+ fclose(file);
+ free(buf);
+ return -5;
+ }
+
+ written = fwrite(buf, size, 1, file);
+ fclose(file);
+ free(buf);
+
+ if (written != 1) {
+ fprintf(stderr,
+ "Error: data was generated correctly, but could not be "
+ "written to file (generated %" PRId64 " bytes, written "
+ "%zu)\n", size, written);
+ return -6;
+ }
+
+ fprintf(stderr, "Data written to %s\n", path);
+
+ return 0;
+}
+
+static int64_t
+solve_exec(args_t *args)
+{
+ int i;
+ uint8_t nissflag;
+ FILE *file;
+ char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH];
+ int64_t ret, gendata_ret, size;
+ size_t read;
+
+ nissflag = NISSY_NISSFLAG_NORMAL; /* TODO: parse str_nisstype */
+
+ for (i = 0; tablepaths[i] != NULL; i++) {
+ strcpy(path, tablepaths[i]);
+ strcat(path, args->str_solver);
+ file = fopen(path, "rb");
+ if (file != NULL)
+ break;
+ }
+
+ if (tablepaths[i] == NULL) {
+ fprintf(stderr,
+ "Cannot read data file, "
+ "generating it (this can take a while)\n");
+ gendata_ret = gendata_exec(args);
+ if (gendata_ret)
+ return gendata_ret;
+ }
+
+ /* Ugh, this is not elegant TODO */
+ if (file == NULL) {
+ for (i = 0; tablepaths[i] != NULL; i++) {
+ strcpy(path, tablepaths[i]);
+ strcat(path, args->str_solver);
+ file = fopen(path, "rb");
+ if (file != NULL)
+ break;
+ }
+ }
+
+ if (tablepaths[i] == NULL) {
+ fprintf(stderr, "Error: data file not found\n");
+ fclose(file);
+ return -1;
+ }
+
+ size = nissy_datasize(args->str_solver);
+ buf = malloc(size);
+ read = fread(buf, size, 1, file);
+ fclose(file);
+ if (read != 1) {
+ fprintf(stderr, "Error reading data from file: "
+ "fread() returned %zu instead of 1 when attempting to"
+ "read %" PRId64 " bytes from file %s\n", read, size, path);
+ return -2;
+ }
+
+ ret = nissy_solve(
+ args->cube, args->str_solver, nissflag, args->minmoves,
+ args->maxmoves, args->maxsolutions, args->optimal,
+ size, buf, SOLUTIONS_BUFFER_SIZE, solutions);
+
+ free(buf);
+
+ if (ret == NISSY_OK || ret == NISSY_WARNING_UNSOLVABLE)
+ fprintf(stderr, "No solutions found\n");
+ else
+ printf("%s", solutions);
+
+ return 0;
+}
+
+static int64_t
+solve_scramble_exec(args_t *args)
+{
+ nissy_frommoves(args->str_moves, args->cube);
+
+ return solve_exec(args);
+}
+
+static int64_t
+help_exec(args_t *args)
+{
+ int i;
+
+ if (args->str_command == NULL || args->str_command[0] == '\0') {
+ printf("This is a rudimentary shell for the H48 library.\n");
+ printf("Available commands and usage:\n\n");
+ for (i = 0; commands[i].name != NULL; i++)
+ printf("%-15s%s\n", commands[i].name, commands[i].syn);
+ printf("\nUse 'help -command COMMAND' for more information.\n");
+ } else {
+ for (i = 0; commands[i].name != NULL; i++)
+ if (!strcmp(args->str_command, commands[i].name))
+ break;
+ if (commands[i].name == NULL) {
+ printf("Unknown command %s\n", args->str_command);
+ return 1;
+ }
+ printf("Command %s\n\n", commands[i].name);
+ printf("Synopsis: %s\n\n", commands[i].syn);
+ printf("Description: %s\n", commands[i].desc);
+ }
+
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv, args_t *args)
+{
+ int i, j, n;
+
+ *args = (args_t) {
+ .command_index = -1,
+ .cube = "",
+ .cube_perm = "",
+ .str_cube = "",
+ .str_format = "",
+ .str_format_in = "",
+ .str_format_out = "",
+ .str_moves = "",
+ .str_trans = "",
+ .str_solver = "",
+ .str_nisstype = "",
+ .minmoves = 0,
+ .maxmoves = 20,
+ .optimal = -1,
+ .maxsolutions = 1,
+ };
+
+ if (argc == 0) {
+ printf("No command given\n");
+ return 1;
+ }
+
+ for (i = 0; commands[i].name != NULL; i++) {
+ if (!strcmp(argv[0], commands[i].name)) {
+ args->command_index = i;
+ break;
+ }
+ }
+
+ if (commands[i].name == NULL) {
+ fprintf(stderr, "Unknown command %s\n", argv[0]);
+ return 1;
+ }
+
+ for (i = 1; i < argc; i++) {
+ for (j = 0; options[j].name != NULL; j++) {
+ n = argc - i - 1;
+ if (strcmp(argv[i], options[j].name))
+ continue;
+ if (n < options[j].nargs) {
+ fprintf(stderr,
+ "Too few arguments for option %s\n",
+ options[j].name);
+ return 1;
+ }
+ if (!options[j].set(n, argv+i+1, args)) {
+ fprintf(stderr,
+ "Error parsing arguments for option %s\n",
+ options[j].name);
+ return 1;
+ }
+ i += options[j].nargs;
+ break;
+ }
+ if (options[j].name == NULL) {
+ fprintf(stderr, "Unknown option %s\n", argv[i]);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+bool
+parse_int8(char *argv, int8_t *result)
+{
+ bool noerror;
+ int64_t n;
+
+ noerror = parse_int64(argv, &n);
+ *result = (int8_t)n;
+
+ return noerror && n >= INT8_MIN && n <= INT8_MAX;
+}
+
+bool
+parse_int64(char *argv, int64_t *result)
+{
+ *result = strtoll(argv, NULL, 10);
+
+ /* TODO: figure out how errno works and use it */
+ return true;
+}
+
+static bool
+set_cube(int argc, char **argv, args_t *args)
+{
+ memcpy(args->cube, argv[0], 22);
+ args->cube[21] = 0;
+
+ return true;
+}
+
+static bool
+set_cube_perm(int argc, char **argv, args_t *args)
+{
+ memcpy(args->cube_perm, argv[0], 22);
+ args->cube_perm[21] = 0;
+
+ return true;
+}
+
+static bool
+set_str_command(int argc, char **argv, args_t *args)
+{
+ args->str_command = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_cube(int argc, char **argv, args_t *args)
+{
+ args->str_cube = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_format(int argc, char **argv, args_t *args)
+{
+ args->str_format = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_format_in(int argc, char **argv, args_t *args)
+{
+ args->str_format_in = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_format_out(int argc, char **argv, args_t *args)
+{
+ args->str_format_out = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_moves(int argc, char **argv, args_t *args)
+{
+ args->str_moves = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_trans(int argc, char **argv, args_t *args)
+{
+ args->str_trans = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_solver(int argc, char **argv, args_t *args)
+{
+ args->str_solver = argv[0];
+
+ return true;
+}
+
+static bool
+set_str_nisstype(int argc, char **argv, args_t *args)
+{
+ args->str_nisstype = argv[0];
+
+ return true;
+}
+
+static bool
+set_minmoves(int argc, char **argv, args_t *args)
+{
+ return parse_int8(argv[0], &args->minmoves);
+}
+
+static bool
+set_maxmoves(int argc, char **argv, args_t *args)
+{
+ return parse_int8(argv[0], &args->maxmoves);
+}
+
+static bool
+set_optimal(int argc, char **argv, args_t *args)
+{
+ return parse_int8(argv[0], &args->optimal);
+}
+
+static bool
+set_maxsolutions(int argc, char **argv, args_t *args)
+{
+ return parse_int64(argv[0], &args->maxsolutions);
+}
+
+void
+log_stderr(const char *str, ...)
+{
+ va_list args;
+
+ va_start(args, str);
+ vfprintf(stderr, str, args);
+ va_end(args);
+}
+
+int
+main(int argc, char **argv)
+{
+ int parse_error;
+ args_t args;
+
+ srand(time(NULL));
+ nissy_setlogger(log_stderr);
+
+ parse_error = parse_args(argc-1, argv+1, &args);
+ if (parse_error)
+ return parse_error;
+
+ return (int)commands[args.command_index].exec(&args);
+}
diff --git a/shell/test.sh b/shell/test.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+SHELLBIN="./run"
+TESTDIR="./shell/testcases"
+TESTOUT="shell/lasttest.out"
+TESTERR="shell/lasttest.err"
+
+for cin in "$TESTDIR"/*.in; do
+ c=$(echo "$cin" | sed 's/\.in//')
+ cout="$c.out"
+ printf "%s: " "$c"
+ (cat "$cin" | xargs "$SHELLBIN") > $TESTOUT 2> $TESTERR
+ if diff "$cout" "$TESTOUT"; then
+ printf "OK\n"
+ else
+ printf "Test failed! stderr:\n"
+ cat $TESTERR
+ exit 1
+ fi
+done
+
+echo "All tests passed!"
diff --git a/shell/testcases/000_frommoves.in b/shell/testcases/000_frommoves.in
@@ -0,0 +1 @@
+frommoves -moves "UFRR"
diff --git a/shell/testcases/000_frommoves.out b/shell/testcases/000_frommoves.out
@@ -0,0 +1 @@
+DEOISVBH=ZFCYHAGBLTKU
diff --git a/shell/testcases/001_compose.in b/shell/testcases/001_compose.in
@@ -0,0 +1 @@
+compose -cube NEORSQLH=ZFCYUAGLHTKB -perm NEORSQLH=ZFCYUAGLHTKB
diff --git a/shell/testcases/001_compose.out b/shell/testcases/001_compose.out
@@ -0,0 +1 @@
+ASTUGFBH=DACXEZGBLIKF
diff --git a/test/test.sh b/test/test.sh
@@ -10,7 +10,7 @@ for t in test/*; do
if [ -n "$re" ] && !(echo "$t" | grep -q "$re"); then
continue
fi
-
+
# Verify if $t is a directory and if its name starts with three digits
if [ -d "$t" ] && basename "$t" | grep -Eq '^[0-9]{3}'; then
$CC -o $TESTBIN "$t"/*.c $OBJ || exit 1