alg (15407B)
1 #if 0 2 3 bin="$(mktemp)" 4 cc -o "$bin" -x c "$(realpath $0)" 5 "$bin" 6 7 exit 0 8 #endif 9 10 /* 11 TODO: 12 - fix: move_and... should be able to read tokens in quotes (with spaces) 13 - readline capabilities 14 */ 15 16 #include <stdint.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <unistd.h> 21 #include <sys/wait.h> 22 23 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 24 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 25 26 #define LINEMAXLEN 10000 27 28 #define ALGFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.txt" 29 #if 0 30 #define INFILE "/home/sebastiano/box/speedcubing/bld/algs/algs.backup" 31 #else 32 #define INFILE ALGFILE 33 #endif 34 #define EDITOR "vi" 35 36 char *alias[256][10] = { 37 ['a'] = { "FU", "FUL", "FLU", "Fu", "Ful", "Flu", "FUl", NULL }, 38 ['b'] = { "UF", "UBL", "ULB", "Ur", "Ufr", "Ulb", "URb", NULL }, 39 ['c'] = { "UL", "UFL", "ULF", "Ul", "Ufl", "Ulf", "ULf", 40 "corners", NULL }, 41 ['d'] = { "UB", "UBR", "URB", "Ub", "Ubr", "Urb", "UBl", NULL }, 42 ['e'] = { "FD", "FDR", "FRD", "Fd", "Frd", "Fdr", "FDr", 43 "edges", NULL }, 44 ['f'] = { "RD", "RBD", "RDB", "Rd", "Rdb", "Rbd", "RDb", 45 "2flips", NULL }, 46 ['g'] = { "LD", "LDF", "LFD", "Ld", "Ldf", "Lfd", "LDf", NULL }, 47 ['h'] = { NULL }, 48 ['i'] = { "FR", "FUR", "FRU", "Fr", "Fur", "Fru", "FRu", NULL }, 49 ['j'] = { "LU", "LUF", "LFU", "Lu", "Luf", "Lfu", "LUb", NULL }, 50 ['k'] = { "RB", "RUB", "RBU", "Rb", "Rub", "Rbu", "RBu", NULL }, 51 ['l'] = { "DF", "DFR", "DRF", "Df", "Dfr", "Drf", "DFl", NULL }, 52 ['m'] = { "DB", "DBR", "DRB", "Db", "Dbr", "Drb", "DBr", 53 "midges", NULL }, 54 ['n'] = { "DR", "DFL", "DLF", "Dr", "Dfl", "Dlf", "DRf", NULL }, 55 ['o'] = { "FL", "FLD", "FDL", "Fl", "Fdl", "Fld", "FLd", NULL }, 56 ['p'] = { "DL", "DLB", "DBL", "Dl", "Dbl", "Dlb", "DLb", 57 "parity", NULL }, 58 ['q'] = { NULL }, 59 ['r'] = { "BU", "BUR", "BRU", "Bu", "Bur", "Bru", "BUr", NULL }, 60 ['s'] = { "BD", "BUL", "BLU", "Bd", "Bul", "Blu", "BDl", NULL }, 61 ['t'] = { "BL", "BLD", "BDL", "Bl", "Bdl", "Bld", "BLu", 62 "tcenters", NULL }, 63 ['u'] = { "BR", "BRD", "BDR", "Br", "Brd", "Bdr", "BRd", NULL }, 64 ['v'] = { "LF", "LUB", "LBU", "Lf", "Lub", "Lbu", "LFu", NULL }, 65 ['w'] = { "LB", "LBD", "LDB", "Lb", "Ldb", "Lbd", "LBd", 66 "wings", NULL }, 67 ['x'] = { "RU", "RFU", "RUF", "Ru", "Rfu", "Ruf", "RUf", 68 "xcenters", NULL }, 69 ['y'] = { "UR", "UFR", "URF", "Uf", "Ubl", "Ulb", "UFr", NULL }, 70 ['z'] = { "RF", "RDF", "RFD", "Rf", "Rdf", "Rfd", "RFd", NULL }, 71 ['3'] = { "3twists", NULL }, 72 }; 73 74 struct node { 75 char *key; 76 size_t mchildren; 77 size_t nchildren; 78 struct node *child; 79 }; 80 81 enum command { 82 BACK, 83 EDIT, 84 EXPORT, 85 HELP, 86 PRINT, 87 QUIT, 88 RENAME, 89 NONE, 90 UNKNOWN 91 }; 92 93 char command_aliases[] = { 94 [BACK] = 'b', 95 [EDIT] = 'e', 96 [EXPORT] = 'x', 97 [HELP] = 'h', 98 [PRINT] = 'p', 99 [QUIT] = 'q', 100 [RENAME] = 'r', 101 }; 102 103 const char *HELPSTR = 104 "This program can be used interactively to navigate and modify a collection\n" 105 "of data organized as a tree. This data is saved to a file in a custom\n" 106 "text-based format. At any given time, the program points to one node in the\n" 107 "tree (the current node), and the user can give instructions.\n" 108 "\n" 109 "An instruction has the following syntax: [movement] [command]\n" 110 "\n" 111 "A movement can be specified either by a space-separated list of keys,\n" 112 "where keys containing spaces must be wrapped in double quotes, or by a\n" 113 "single word, each letter of which is translated to a key using the\n" 114 "alias table. Only if a word does not match the key of any child of the\n" 115 "current node is it interpreted as a list of aliases.\n" 116 "\n" 117 "Commands are a single letter preceded by a colon. For example ':q' quits\n" 118 "the program. See below for a list of commands.\n" 119 "\n" 120 "If only a command is specified, it is exectued on the current node.\n" 121 "\n" 122 "If only a movement is specified, the current node is changed to the one\n" 123 "specified by the movement. If the new current node is terminal, i.e. all\n" 124 "its children are childless, a ':p' command is implied and is exectued as\n" 125 "if 'movement :p' was given.\n" 126 "\n" 127 "If both a movement and a command are specified, the movement is applied,\n" 128 "then the command is exectued, and finally the current node is changed\n" 129 "back to the previous node.\n" 130 "\n" 131 "List of commands:\n" 132 " - :b Move back to the parent node (usually used without movement).\n" 133 " - :e Edit current node. Can be used to add or remove children.\n" 134 " - :x Export to csv.\n" 135 " - :h Print this help message.\n" 136 " - :p Print the key of the node.\n" 137 " - :q Quit (exit the program).\n" 138 " - :r Rename (edit the key of the current node).\n" 139 ; 140 141 int is_space(char c) { 142 return c == ' ' || c == '\t' || c == '\n'; 143 } 144 145 int has_spaces(const char *k) { 146 for (const char *c = k; *c; c++) 147 if (is_space(*c)) 148 return 1; 149 return 0; 150 } 151 152 int is_whitespace_only(const char *k) { 153 for (const char *c = k; *c; c++) 154 if (!is_space(*c)) 155 return 0; 156 return 1; 157 } 158 159 void write_key(FILE *f, const char *k) { 160 if (has_spaces(k)) 161 fprintf(f, "\"%s\"", k); 162 else 163 fprintf(f, "%s", k); 164 } 165 166 void write_indent(FILE *f, size_t n) { 167 for (size_t i = 0; i < n; i++) 168 fprintf(f, " "); 169 } 170 171 int is_terminal(const struct node *n) { 172 for (size_t i = 0; i < n->nchildren; i++) 173 if (n->child[i].nchildren > 0) 174 return 0; 175 return 1; 176 } 177 178 void write_node(FILE *f, const struct node *n, size_t ntabs, int s) { 179 write_indent(f, ntabs); 180 fprintf(f, "{ "); 181 write_key(f, n->key); 182 if (is_terminal(n)) { 183 for (size_t i = 0; i < n->nchildren; i++) { 184 fprintf(f, " "); 185 write_key(f, n->child[i].key); 186 } 187 fprintf(f, " }\n"); 188 } else { 189 fprintf(f, "\n"); 190 for (size_t i = 0; i < n->nchildren; i++) { 191 if (s && !is_terminal(&n->child[i])) { 192 write_indent(f, ntabs+1); 193 fprintf(f, "{ "); 194 write_key(f, n->child[i].key); 195 fprintf(f, " { ... } }\n"); 196 } else { 197 write_node(f, &n->child[i], ntabs+1, s); 198 } 199 } 200 write_indent(f, ntabs); 201 fprintf(f, "}\n"); 202 } 203 } 204 205 void delete_node(struct node *n) { 206 if (n->child) { 207 for (size_t i = 0; i < n->nchildren; i++) 208 delete_node(&n->child[i]); 209 free(n->child); 210 } 211 n->nchildren = n->mchildren = 0; 212 free(n->key); 213 } 214 215 void grow_children_if_needed(struct node *n) { 216 if (n->nchildren == n->mchildren) { 217 size_t m = MAX(1, 2*n->mchildren); 218 n->child = realloc(n->child, m * sizeof(struct node)); 219 memset(n->child + n->nchildren, 0, 220 (m - n->nchildren) * sizeof(struct node)); 221 n->mchildren = m; 222 } 223 } 224 225 void add_child(struct node *n, struct node c) { 226 grow_children_if_needed(n); 227 n->child[n->nchildren++] = c; 228 } 229 230 void parse_error(struct node *n, const char *a, size_t *i, size_t len) { 231 if (*i == len) 232 printf("Error: end of file reached unexpectedly.\n"); 233 else 234 printf("Error: unexpected '%c' at position %zu.\n", 235 a[*i], *i); 236 237 if (n != NULL) { 238 printf("Error occurred when reading node with key: "); 239 write_key(stdout, n->key); 240 printf("\n"); 241 } 242 } 243 244 int read_key(struct node *n, const char *a, size_t *i, size_t len) { 245 while (is_space(a[*i])) (*i)++; 246 if (a[*i] == '{') { 247 printf("Error: missing key at %zu\n", *i); 248 return 0; 249 } 250 bool q = a[*i] == '"'; 251 if (q) (*i)++; 252 size_t start = *i; 253 while (*i < len && ((q && a[*i] != '"') || (!q && !is_space(a[*i])))) 254 (*i)++; 255 size_t klen = *i - start; 256 n->key = malloc(klen+1); 257 memcpy(n->key, a+start, klen); 258 n->key[klen] = '\0'; 259 if (*i == len) { 260 printf("Error: EOF in key starting at %zu " 261 "(begins with '%.5s').\n", start, n->key); 262 return 0; 263 } 264 if (q) 265 (*i)++; // Skip quote 266 return 1; 267 } 268 269 int read_node(struct node *n, const char *a, size_t *i, size_t len) { 270 while (is_space(a[*i])) (*i)++; 271 if (a[*i] != '{') { 272 printf("Error: expected '{' at %zu, got '%c'.\n", *i, a[*i]); 273 return 0; 274 } 275 (*i)++; 276 if (!read_key(n, a, i, len)) 277 return 0; 278 for (; *i < len; (*i)++) { 279 switch (a[*i]) { 280 case ' ': 281 case '\t': 282 case '\n': 283 continue; 284 case '}': 285 (*i)++; 286 return 1; 287 case '{': 288 grow_children_if_needed(n); 289 read_node(&n->child[n->nchildren++], a, i, len); 290 break; 291 default: 292 grow_children_if_needed(n); 293 read_key(&n->child[n->nchildren++], a, i, len); 294 } 295 } 296 return 1; 297 } 298 299 int read_file(struct node *n) { 300 FILE *f = fopen(INFILE, "r"); 301 fseek(f, 0, SEEK_END); 302 size_t fsize = ftell(f); 303 fseek(f, 0, SEEK_SET); 304 305 char *a = malloc(fsize); 306 fread(a, 1, fsize, f); 307 308 size_t i = 0; 309 int result = read_node(n, a, &i, fsize); 310 311 free(a); 312 return result; 313 } 314 315 void write_tree(const struct node *root) { 316 FILE *f = fopen(ALGFILE, "w"); 317 write_node(f, root, 0, 0); 318 /* printf("Data written to %s.\n", ALGFILE); */ 319 fclose(f); 320 } 321 322 struct node *match_children(const char *w, size_t wlen, const struct node *n) { 323 for (size_t i = 0; i < n->nchildren; i++) { 324 if (strlen(n->child[i].key) != wlen) 325 continue; 326 if (!strncmp(w, n->child[i].key, wlen)) 327 return &n->child[i]; 328 } 329 return NULL; 330 } 331 332 int move_stack(const char *w, size_t wlen, struct node *stack[], size_t *top) { 333 struct node *c = match_children(w, wlen, stack[*top]); 334 if (c) { 335 stack[++(*top)] = c; 336 return 1; 337 } 338 339 int matched = 1; 340 for (size_t i = 0; matched && i < wlen; i++) { 341 matched = 0; 342 if ((w[i] < 'a' || w[i] > 'z') && (w[i] < '0' && w[i] > '9')) 343 return 1; 344 for (size_t j = 0; alias[(unsigned)w[i]][j]; j++) { 345 c = match_children(alias[(unsigned)w[i]][j], 346 strlen(alias[(unsigned)w[i]][j]), stack[*top]); 347 if (c) { 348 stack[++(*top)] = c; 349 matched = 1; 350 break; 351 } 352 } 353 } 354 return matched; 355 } 356 357 int move_and_parse_command(const char *line, 358 struct node *stack[], size_t *stack_top) { 359 size_t wstart, wend = 0; 360 while (line[wend] && line[wend] != ':') { 361 while (is_space(line[wend])) wend++; 362 wstart = wend; 363 while (!is_space(line[wend])) wend++; 364 if (!move_stack(line+wstart, wend-wstart, stack, stack_top)) { 365 printf("Invalid key '"); 366 for (size_t i = wstart; i < wend; i++) 367 printf("%c", line[i]); 368 printf("'.\n"); 369 return NONE; 370 } 371 while (is_space(line[wend])) wend++; 372 } 373 374 if (!line[wend]) 375 return is_terminal(stack[*stack_top]) ? PRINT : NONE; 376 for (size_t i = 0; i < NONE; i++) 377 if (command_aliases[i] == line[wend+1]) 378 return i; 379 printf("Unknown command.\n"); 380 return UNKNOWN; 381 } 382 383 void print_node(struct node *n) { 384 if (is_terminal(n)) { 385 for (size_t i = 0; i < n->nchildren; i++) 386 printf("%s\n", n->child[i].key); 387 } else { 388 write_node(stdout, n, 0, 1); 389 } 390 } 391 392 void delete_child(struct node *n, size_t i) { 393 delete_node(&n->child[i]); 394 for (size_t j = i+1; j < n->nchildren; j++) 395 n->child[j-1] = n->child[j]; 396 n->nchildren--; 397 } 398 399 void replace_child(struct node *n, size_t i, size_t j) { 400 if (i >= j) { 401 printf("Cannot replace child in wrong order.\n"); 402 return; 403 } 404 delete_node(&n->child[j]); 405 n->child[j] = n->child[i]; 406 n->nchildren--; 407 for (size_t k = i; k < n->nchildren; k++) 408 n->child[k] = n->child[k+1]; 409 } 410 411 void remove_duplicate_children(struct node *n) { 412 for (size_t i = 0; i < n->nchildren; i++) { 413 for (size_t j = i + 1; j < n->nchildren; j++) { 414 if (!strcmp(n->child[i].key, n->child[j].key)) { 415 replace_child(n, i, j); 416 i--; 417 j--; 418 break; 419 } 420 } 421 } 422 } 423 424 int edit(struct node *n) { 425 /* create tmp file with algs, one by line */ 426 char path[50] = "/tmp/alg.XXXXXXX"; 427 int fd = mkstemp(path); 428 if (fd == -1) { 429 printf("Could not create tmp file for editing.\n"); 430 return 0; 431 } 432 FILE *tmp = fdopen(fd, "w+"); 433 for (size_t i = 0; i < n->nchildren; i++) 434 fprintf(tmp, "%s\n", n->child[i].key); 435 fclose(tmp); 436 close(fd); 437 438 /* fork and open editor */ 439 int cpid = fork(); 440 if (cpid == 0) { 441 execlp(EDITOR, EDITOR, path, (char *)NULL); 442 } else { 443 wait(NULL); 444 tmp = fopen(path, "r"); 445 if (!tmp) { 446 printf("Could not read edited algs, aborting.\n"); 447 return 0; 448 } 449 450 size_t oldn = n->nchildren; 451 452 /* Append the new children */ 453 char line[LINEMAXLEN]; 454 for (size_t i = 0; fgets(line, LINEMAXLEN, tmp); i++) { 455 size_t len = strlen(line); 456 struct node c = {0}; 457 c.key = malloc(len); 458 memcpy(c.key, line, len); 459 c.key[len-1] = '\0'; 460 add_child(n, c); 461 } 462 fclose(tmp); 463 464 /* Delete removed node, asking for confirmation */ 465 for (size_t i = oldn-1; i != SIZE_MAX; i--) { 466 int found = 0; 467 for (size_t j = oldn; j < n->nchildren; j++) 468 found = found || 469 !strcmp(n->child[i].key, n->child[j].key); 470 if (!found) { 471 int d = 1; 472 if (!is_terminal(n)) { 473 printf("Confirm delete node "); 474 fflush(stdout); 475 write_key(stdout, n->child[i].key); 476 printf("? [y/N] "); 477 fgets(line, LINEMAXLEN, stdin); 478 d = line[0] == 'y' || line[0] == 'Y'; 479 } 480 if (d) { 481 delete_child(n, i); 482 oldn--; 483 } 484 } 485 } 486 487 remove_duplicate_children(n); 488 } 489 490 return 1; 491 } 492 493 int is_table(const struct node *n) { 494 return n->nchildren != 0 && 495 n->child[0].nchildren != 0 && 496 n->child[0].child[0].nchildren != 0 && 497 is_terminal(&(n->child[0].child[0])); 498 } 499 500 struct node *find_child(const struct node *n, const char *k) { 501 for (size_t i = 0; i < n->nchildren; i++) { 502 if (!strcmp(n->child[i].key, k)) 503 return &n->child[i]; 504 } 505 return NULL; 506 } 507 508 void export(const struct node *n) { 509 if (!is_table(n)) { 510 printf("Cannot export: not a table.\n"); 511 return; 512 } 513 514 char fname[50]; 515 sprintf(fname, "%.45s.csv", n->key); 516 FILE *f = fopen(fname, "w"); 517 518 /* Print first row (header) */ 519 fprintf(f, "\"\","); 520 for (size_t i = 0; i < n->nchildren; i++) 521 fprintf(f, "\"%s\",", n->child[i].key); 522 fprintf(f, "\n"); 523 524 /* Fill the rest of the table */ 525 for (size_t i = 0; i < n->nchildren; i++) { 526 fprintf(f, "\"%s\",", n->child[i].key); 527 for (size_t j = 0; j < n->nchildren; j++) { 528 const struct node *c = find_child( 529 &n->child[i], n->child[j].key); 530 if (c == NULL || c->nchildren == 0) 531 fprintf(f, "\"\","); 532 else 533 fprintf(f, "\"%s\",", c->child[0].key); 534 } 535 fprintf(f, "\n"); 536 } 537 538 fclose(f); 539 } 540 541 int rename_node(struct node *n) { 542 printf("Old key: "); 543 write_key(stdout, n->key); 544 printf("\nNew key (empty line to cancel): "); 545 546 char k[LINEMAXLEN]; 547 size_t len = fgets(k, LINEMAXLEN, stdin) == NULL ? 0 : strlen(k)-1; 548 k[len] = '\0'; /* Replace \n with \0 */ 549 if (len == 0) { 550 printf("\nRenaming cancelled, old key kept.\n"); 551 return 0; 552 } else { 553 if (strlen(n->key) < len) 554 n->key = realloc(n->key, len+1); 555 memcpy(n->key, k, len+1); 556 return 1; 557 } 558 } 559 560 void main_loop(struct node *root) { 561 char line[LINEMAXLEN]; 562 struct node *stack[20] = { root }; 563 size_t stack_top = 0; 564 while (1) { 565 for (size_t i = 0; i <= stack_top; i++) { 566 write_key(stdout, stack[i]->key); 567 printf(" "); 568 } 569 printf("> "); 570 fflush(stdout); 571 if (fgets(line, LINEMAXLEN, stdin) == NULL) { 572 printf("\n"); 573 return; 574 } 575 if (is_whitespace_only(line)) continue; 576 int oldtop = stack_top; 577 int command = move_and_parse_command(line, stack, &stack_top); 578 579 switch (command) { 580 case BACK: 581 if (stack_top > 0) 582 oldtop = --stack_top; 583 else 584 printf("Already at top of stack.\n"); 585 break; 586 case EDIT: 587 if(edit(stack[stack_top])) 588 write_tree(root); 589 break; 590 case EXPORT: 591 export(stack[stack_top]); 592 break; 593 case HELP: 594 printf("%s", HELPSTR); 595 break; 596 case PRINT: 597 print_node(stack[stack_top]); 598 break; 599 case QUIT: 600 return; 601 case RENAME: 602 if (rename_node(stack[stack_top])) 603 write_tree(root); 604 break; 605 case NONE: 606 oldtop = stack_top; 607 case UNKNOWN: 608 default: 609 break; 610 } 611 stack_top = oldtop; 612 } 613 } 614 615 int main() { 616 struct node root = {0}; 617 if (!read_file(&root)) { 618 printf("Error reading data from file.\n"); 619 return 1; 620 } 621 printf("Data read from %s\n", INFILE); 622 623 main_loop(&root); 624 625 delete_node(&root); 626 return 0; 627 }