#include #include #include #include #include #include #include #include #include #include #include #include #define argv0 program_invocation_name #ifdef ZDEBUG #define zprintf(fmt, ...) \ printf(fmt, ## __VA_ARGS__); #else #define zprintf(...); #endif bool print_all_and_quit = false; const char* command = NULL; struct { bool not; bool or, orn, nor; bool and, andn, nand; bool xor, nxor; bool lt, lte, gt, gte; bool ternary; } use_operators; bool assume_yes = false; bool verbose = false; bool print_with_color = false; bool force_rebuild = false; bool quiet = false; static void parse_args(int argc, char* const* argv) { bool unset_operators = true; print_with_color = isatty(1); for (int opt; (opt = getopt(argc, argv, "pyqvc:eEo:C:B")) != -1; ) switch (opt) { case 'p': print_all_and_quit = true; break; case 'y': assume_yes = true; break; case 'q': quiet = true; break; case 'v': verbose = true; break; case 'c': command = optarg; break; case 'E': use_operators.ternary = true; // fallthrough case 'e': { use_operators.not = true; use_operators.or = true; use_operators.and = true; use_operators.orn = true; use_operators.nor = true; use_operators.andn = true; use_operators.nand = true; use_operators. xor = true; use_operators.nxor = true; unset_operators = false; break; } case 'o': { unset_operators = false; for (char* moving; (moving = strtok_r(NULL, ",", &optarg)); ) { if (!strcmp(moving, "not") || !strcmp(moving, "!")) use_operators.not = true; else if (!strcmp(moving, "or") || !strcmp(moving, "||")) use_operators.or = true; else if (!strcmp(moving, "orn") || !strcmp(moving, "|!")) use_operators.orn = true; else if (!strcmp(moving, "nor") || !strcmp(moving, "!|")) use_operators.nor = true; else if (!strcmp(moving, "and") || !strcmp(moving, "&&")) use_operators.and = true; else if (!strcmp(moving, "andn") || !strcmp(moving, "&!")) use_operators.andn = true; else if (!strcmp(moving, "nand") || !strcmp(moving, "!&")) use_operators.nand = true; else if (!strcmp(moving, "xor") || !strcmp(moving, "!=")) use_operators.xor = true; else if (!strcmp(moving, "nxor") || !strcmp(moving, "==")) use_operators.nxor = true; else if (!strcmp(moving, "lt") || !strcmp(moving, "<")) use_operators.lt = true; else if (!strcmp(moving, "lte") || !strcmp(moving, "<=")) use_operators.lte = true; else if (!strcmp(moving, "gt") || !strcmp(moving, ">")) use_operators.gt = true; else if (!strcmp(moving, "gte") || !strcmp(moving, ">=")) use_operators.gte = true; else if (!strcmp(moving, "ternary") || !strcmp(moving, "?:")) use_operators.ternary = true; else { assert(!"TODO"); } } break; } case 'C': { if (!strcmp(optarg, "yes") || !strcmp(optarg, "on")) print_with_color = true; else if (!strcmp(optarg, "no") || !strcmp(optarg, "off")) print_with_color = false; else if (!strcmp(optarg, "auto")) print_with_color = isatty(1); else { assert(!"TODO"); } break; } case 'B': { force_rebuild = true; break; } default: assert(!"TODO"); break; } if (unset_operators) { use_operators.not = true; use_operators.or = true; use_operators.and = true; } } #define W 0b0101010101010101 #define X 0b0011001100110011 #define Y 0b0000111100001111 #define Z 0b0000000011111111 #define M 0b1111111111111111 #define N (65536) enum kind { ek_unreachable, ek_0, ek_1, ek_W, ek_X, ek_Y, ek_Z, ek_not, ek_or, ek_and, ek_orn, ek_nor, ek_andn, ek_nand, ek_xor, ek_nxor, ek_lt, ek_lte, ek_gt, ek_gte, ek_ternary, }; struct expr { enum kind kind; uint16_t cond, left, right; int cost; } lookup[N]; static void print(uint16_t truthtable, int depth) { #define LITERAL_ESCAPE "\e[38;2;200;200;100m" #define VARIABLE_ESCAPE "\e[38;2;100;100;200m" #define RESET_ESCAPE "\e[0m" static const char* const operator_colors[10] = { "\e[38;2;204;0;0m", "\e[38;2;204;122;0m", "\e[38;2;163;204;0m", "\e[38;2;40;204;0m", "\e[38;2;0;204;81m", "\e[38;2;0;204;204m", "\e[38;2;0;81;204m", "\e[38;2;40;0;204m", "\e[38;2;163;0;204m", "\e[38;2;204;0;122m", }; const struct expr* e = &lookup[truthtable]; switch (e->kind) { case ek_unreachable: { assert(!"NOPE"); break; } case ek_0: { printf(print_with_color ? LITERAL_ESCAPE "0" RESET_ESCAPE : "0"); break; } case ek_1: { printf(print_with_color ? LITERAL_ESCAPE "1" RESET_ESCAPE : "1"); break; } case ek_W: { printf(print_with_color ? VARIABLE_ESCAPE "w" RESET_ESCAPE : "w"); break; } case ek_X: { printf(print_with_color ? VARIABLE_ESCAPE "x" RESET_ESCAPE : "x"); break; } case ek_Y: { printf(print_with_color ? VARIABLE_ESCAPE "y" RESET_ESCAPE : "y"); break; } case ek_Z: { printf(print_with_color ? VARIABLE_ESCAPE "z" RESET_ESCAPE : "z"); break; } case ek_not: { const char* start = print_with_color ? operator_colors[depth % 10] : ""; const char* end = print_with_color ? RESET_ESCAPE : ""; printf("%s(!%s", start, end); print(e->left, depth + 1); printf("%s)%s", start, end); break; } #define BINARY_OPERATOR(kind, operatorstring) \ case kind: \ { \ const char *start = "", *end = ""; \ \ if (print_with_color) \ { \ start = operator_colors[depth % 10]; \ \ end = RESET_ESCAPE; \ } \ \ printf("%s(%s", start, end); \ \ print(e->left, depth + 1); \ \ printf("%s%s%s", start, operatorstring, end); \ \ print(e->right, depth + 1); \ \ printf("%s)%s", start, end); \ break; \ } BINARY_OPERATOR(ek_or, " || "); BINARY_OPERATOR(ek_orn, " |! "); BINARY_OPERATOR(ek_nor, " !| "); BINARY_OPERATOR(ek_and, " && "); BINARY_OPERATOR(ek_andn, " &! "); BINARY_OPERATOR(ek_nand, " !& "); BINARY_OPERATOR(ek_xor, " != "); BINARY_OPERATOR(ek_nxor, " == "); BINARY_OPERATOR(ek_lt, " < "); BINARY_OPERATOR(ek_lte, " <= "); BINARY_OPERATOR(ek_gt, " > "); BINARY_OPERATOR(ek_gte, " >= "); #undef BINARY_OPERATOR case ek_ternary: { const char *start = "", *end = ""; if (print_with_color) { start = operator_colors[depth % 10]; end = RESET_ESCAPE; } printf("%s(%s", start, end); print(e->cond, depth + 1); printf(" %s?%s ", start, end); print(e->left, depth + 1); printf(" %s:%s ", start, end); print(e->right, depth + 1); printf("%s)%s", start, end); break; } } } void calculate_simplifications(void) { // init 'lookup': { for (int i = 0; i < N; i++) { lookup[i].kind = ek_unreachable; lookup[i].cost = INT_MAX; } } // heap of truthtables; key = cost struct { uint16_t data[N]; int n; // truthtable -> index in 'todo' int reverse[N]; } todo; // init 'todo': { todo.n = 0; for (int i = 0; i < N; i++) { todo.reverse[i] = -1; } } uint16_t pop(void) { assert(todo.n > 0); uint16_t retval = todo.data[0]; todo.reverse[retval] = -1; uint16_t moving = todo.data[todo.n-- - 1]; int cost = lookup[moving].cost; int index = 0; again: { int left = index * 2 + 1; int right = index * 2 + 2; uint16_t smallest = moving; if (left < todo.n && lookup[todo.data[left]].cost < cost) smallest = todo.data[left]; if (right < todo.n && lookup[todo.data[right]].cost < lookup[smallest].cost) smallest = todo.data[right]; if (smallest == moving) { todo.data[index] = moving; todo.reverse[moving] = index; } else { int new = todo.reverse[smallest]; todo.data[index] = smallest; todo.reverse[smallest] = index; index = new; goto again; } } return retval; } void append(uint16_t truthtable, int cost) { assert(todo.reverse[truthtable] == -1); int index = todo.n++, new_index; while (index > 0 && lookup[todo.data[new_index = (index - 1) / 2]].cost > cost) { todo.data[index] = todo.data[new_index]; todo.reverse[todo.data[new_index]] = index; index = new_index; } todo.data[index] = truthtable; todo.reverse[truthtable] = index; lookup[truthtable].cost = cost; } void update(uint16_t truthtable, int cost) { assert(todo.reverse[truthtable] != -1); assert(cost < lookup[truthtable].cost); int index =todo. reverse[truthtable], new_index; while (index > 0 && lookup[todo.data[new_index = (index - 1) / 2]].cost > cost) { todo.data[index] = todo.data[new_index]; todo.reverse[todo.data[new_index]] = index; index = new_index; } todo.data[index] = truthtable; todo.reverse[truthtable] = index; lookup[truthtable].cost = cost; } // create a list of the "done" truthtables struct { int headtails[N], next[N], head; // for debugging: #if ZDEBUG bool in[N]; #endif } done = {}; // init 'done': { done.head = -1; for (int i = 0; i < N; i++) { done.headtails[i] = -1; #if ZDEBUG done.in[i] = false; #endif } } void insert(int index) { #if ZDEBUG assert(!done.in[index]); #endif int head = index & 1 ? index & ~(index & -index) : index, prevhead = -1; while (done.headtails[head] == -1 || index < done.headtails[head]) { done.headtails[head] = index; prevhead = head, head = head & ~(head & -head); } if (done.headtails[head] < index) { if (prevhead == -1) { assert(done.headtails[head] == head); done.next[head] = index; } else { int tophalftail = done.headtails[prevhead - 1]; assert(tophalftail != -1); done.next[tophalftail] = index; } } else { done.head = index; } int n = ~index & M; int tail = index & 1 ? index : index | (n & -n), prevtail = -1; while (done.headtails[tail] == -1 || done.headtails[tail] < index) { done.headtails[tail] = index; prevtail = tail, tail = tail | (n = ~tail & M, n & -n); } if (index < done.headtails[tail]) { if (prevtail == -1) { assert(done.headtails[tail] == tail); #if ZDEBUG assert(done.in[tail]); #endif done.next[index] = tail; } else { int bottomhalfhead = done.headtails[prevtail + 1]; assert(bottomhalfhead != -1); done.next[index] = bottomhalfhead; } } else { done.next[index] = -1; } #if ZDEBUG done.in[index] = true; #endif } append(W, 0), lookup[W].kind = ek_W; append(X, 0), lookup[X].kind = ek_X; append(Y, 0), lookup[Y].kind = ek_Y; append(Z, 0), lookup[Z].kind = ek_Z; append(0, 1), lookup[0].kind = ek_0; append(M, 1), lookup[M].kind = ek_1; for (int iterations = 1; todo.n && iterations <= N; iterations++) { uint16_t truthtable = pop(); insert(truthtable); int cost = lookup[truthtable].cost; if (verbose) { if (print_with_color) { printf("\e[2K"); } printf("%i of %i (%.2f%%): [%i] ", iterations, N, (100.0 * iterations / N), cost); print(truthtable, 0), puts(""); if (print_with_color) { printf("\e[1A"); } } // consider NOT: if (use_operators.not) { uint16_t not_truthtable = ~truthtable & M; int not_cost = cost + 1; if (not_cost < lookup[not_truthtable].cost) { if (todo.reverse[not_truthtable] == -1) { append(not_truthtable, not_cost); } else { update(not_truthtable, not_cost); } lookup[not_truthtable].kind = ek_not; lookup[not_truthtable].left = truthtable; } } #define BINARY_OPERATOR(ekind, function) \ { \ for (int i = done.head; i != -1; i = done.next[i]) \ { \ { \ uint16_t bin_truthtable = function(truthtable, i) & M; \ \ int bin_cost = 1 + cost + lookup[i].cost; \ \ if (bin_cost < lookup[bin_truthtable].cost) \ { \ if (todo.reverse[bin_truthtable] == -1) \ append(bin_truthtable, bin_cost); \ else \ update(bin_truthtable, bin_cost); \ \ lookup[bin_truthtable].kind = ekind; \ lookup[bin_truthtable].left = truthtable; \ lookup[bin_truthtable].right = i; \ } \ } \ \ { \ uint16_t bin_truthtable = function(i, truthtable) & M; \ \ int bin_cost = 1 + cost + lookup[i].cost; \ \ if (bin_cost < lookup[bin_truthtable].cost) \ { \ if (todo.reverse[bin_truthtable] == -1) \ append(bin_truthtable, bin_cost); \ else \ update(bin_truthtable, bin_cost); \ \ lookup[bin_truthtable].kind = ekind; \ lookup[bin_truthtable].left = i; \ lookup[bin_truthtable].right = truthtable; \ } \ } \ } \ } #define OR(a, b) ((a) | (b)) #define AND(a, b) ((a) & (b)) #define ORN(a, b) (~(a) | (b)) #define NOR(a, b) ~( (a) | (b)) #define ANDN(a, b) (~(a) & (b)) #define NAND(a, b) ~( (a) & (b)) #define XOR(a, b) (( a) ^ (b)) #define NXOR(a, b) ~( (a) ^ (b)) #define LT(a, b) ((~a) & (b)) #define LTE(a, b) ((~a) | (b)) #define GT(a, b) (( a) & ~(b)) #define GTE(a, b) (( a) | ~(b)) if (use_operators.or) { BINARY_OPERATOR(ek_or, OR); } if (use_operators.and) { BINARY_OPERATOR(ek_and, AND); } if (use_operators.orn) { BINARY_OPERATOR(ek_orn, ORN); } if (use_operators.nor) { BINARY_OPERATOR(ek_nor, NOR); } if (use_operators.andn) { BINARY_OPERATOR(ek_andn, ANDN); } if (use_operators.nand) { BINARY_OPERATOR(ek_nand, NAND); } if (use_operators.xor) { BINARY_OPERATOR(ek_xor, XOR); } if (use_operators.nxor) { BINARY_OPERATOR(ek_nxor, NXOR); } if (use_operators.lt) { BINARY_OPERATOR(ek_lt, LT); } if (use_operators.lte) { BINARY_OPERATOR(ek_lte, LTE); } if (use_operators.gt) { BINARY_OPERATOR(ek_gt, GT); } if (use_operators.gte) { BINARY_OPERATOR(ek_gte, GTE); } if (use_operators.ternary) { for (int i = done.head; i != -1; i = done.next[i]) { for (int j = done.head; j != -1; j = done.next[j]) { int ternary_cost = 1 + cost + lookup[i].cost + lookup[j].cost; #define TERNARY(C, T, F) \ { \ uint16_t ternary_truthtable = \ ((C) & (T)) | (~(C) & (F)); \ \ if (ternary_cost < lookup[ternary_truthtable].cost) \ { \ if (todo.reverse[ternary_truthtable] == -1) \ { \ append(ternary_truthtable, ternary_cost); \ } \ else \ { \ update(ternary_truthtable, ternary_cost); \ } \ \ lookup[ternary_truthtable].kind = ek_ternary; \ lookup[ternary_truthtable].cond = (C); \ lookup[ternary_truthtable].left = (T); \ lookup[ternary_truthtable].right = (F); \ } \ } \ TERNARY(truthtable, i, j); TERNARY(i, truthtable, j); TERNARY(i, j, truthtable); } } } } if (verbose && print_with_color) { printf("\e[2K"); } } void get_simplifications(void) { char path[PATH_MAX] = ".simplifier-cache-2"; if (use_operators.not) strcat(path, "-not"); if (use_operators.or) strcat(path, "-or"); if (use_operators.orn) strcat(path, "-orn"); if (use_operators.nor) strcat(path, "-nor"); if (use_operators.and) strcat(path, "-and"); if (use_operators.andn) strcat(path, "-andn"); if (use_operators.nand) strcat(path, "-nand"); if (use_operators.xor) strcat(path, "-xor"); if (use_operators.nxor) strcat(path, "-nxor"); if (use_operators.lt) strcat(path, "-lt"); if (use_operators.lte) strcat(path, "-lte"); if (use_operators.gt) strcat(path, "-gt"); if (use_operators.gte) strcat(path, "-gte"); if (use_operators.ternary) strcat(path, "-ternary"); strcat(path, ".bin"); int fd = -1; if (force_rebuild) { goto rebuild; } else if ((fd = open(path, O_RDONLY)) > 0) { // great, just read it into memory if (read(fd, lookup, sizeof(lookup)) < (ssize_t) sizeof(lookup)) { printf("%s: read() to cache failed: %m\n", argv0); exit(1); } } else if (fd < 0 && errno == ENOENT) { if (!quiet) { puts("" "Oh! looks like you're running this for the first time" "\n" "I'll have to build up my cache of simplifications" "\n" "I'll only have to do this once." "\n" "\n" "This may take a while." "\n" ""); } rebuild: {}; if (!quiet && !verbose) { puts("re-run with '-v' to watch progress"); } if (!quiet && !assume_yes) { puts(""); puts("any input to start:"), getchar(); puts(""); } calculate_simplifications(); fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0664); if (write(fd, lookup, sizeof(lookup)) < (ssize_t) sizeof(lookup)) { printf("%s: write() to cache failed: %m\n", argv0); exit(1); } } else { // something else assert(!"TODO"); } if (fd > 0) { close(fd); } } uint16_t evaluate(const char* text) { enum { tk_uninitialized, tk_0, tk_1, tk_w, tk_x, tk_y, tk_z, tk_oparen, tk_cparen, tk_emark, tk_emarkequals, tk_emarkvbar, tk_emarkampersand, tk_equalsequals, tk_vbarvbar, tk_vbaremark, tk_qmark, tk_less_than, tk_less_than_eq, tk_greater_than, tk_greater_than_eq, tk_ampersandemark, tk_ampersandampersand, tk_colon, tk_EOF, } tokenkind = tk_uninitialized; const char* moving = text; void next_token(void) { while (*moving && *moving == ' ') moving++; switch (*moving) { case 0: tokenkind = tk_EOF; break; case '0': tokenkind = tk_0, moving++; break; case '1': tokenkind = tk_1, moving++; break; case 'w': tokenkind = tk_w, moving++; break; case 'x': tokenkind = tk_x, moving++; break; case 'y': tokenkind = tk_y, moving++; break; case 'z': tokenkind = tk_z, moving++; break; case '(': tokenkind = tk_oparen, moving++; break; case ')': tokenkind = tk_cparen, moving++; break; case '?': tokenkind = tk_qmark, moving++; break; case ':': tokenkind = tk_colon, moving++; break; // either '||' or '|!': case '|': { moving++; switch (*moving) { case '|': tokenkind = tk_vbarvbar, moving++; break; case '!': tokenkind = tk_vbaremark, moving++; break; default: { puts("syntax error"); exit(1); break; } } break; } // either '&&' or '&!': case '&': { moving++; switch (*moving) { case '&': tokenkind = tk_ampersandampersand, moving++; break; case '!': tokenkind = tk_ampersandemark, moving++; break; default: { puts("syntax error"); exit(1); break; } } break; } // either '!' or '!=' or '!&' or '!|' case '!': { moving++; switch (*moving) { case '=': tokenkind = tk_emarkequals, moving++; break; case '|': tokenkind = tk_emarkvbar, moving++; break; case '&': tokenkind = tk_emarkampersand, moving++; break; default: tokenkind = tk_emark; break; } break; } case '<': { moving++; switch (*moving) { case '=': tokenkind = tk_less_than_eq, moving++; break; default: { tokenkind = tk_less_than; break; } } break; } case '>': { moving++; switch (*moving) { case '=': tokenkind = tk_greater_than_eq, moving++; break; default: { tokenkind = tk_greater_than; break; } } break; } // could only be '==': case '=': { moving++; switch (*moving) { case '=': tokenkind = tk_equalsequals, moving++; break; default: { puts("syntax error"); exit(1); break; } } break; } default: assert(!"TODO"); break; } } next_token(); uint16_t parse_root(void) { uint16_t parse_ternary(void) { uint16_t parse_ors(void) { uint16_t parse_ands(void) { uint16_t parse_equals(void) { uint16_t parse_compares(void) { uint16_t parse_prefix(void) { uint16_t parse_primary(void) { uint16_t retval; switch (tokenkind) { case tk_0: retval = 0, next_token(); break; case tk_1: retval = M, next_token(); break; case tk_w: retval = W, next_token(); break; case tk_x: retval = X, next_token(); break; case tk_y: retval = Y, next_token(); break; case tk_z: retval = Z, next_token(); break; case tk_oparen: { next_token(); retval = parse_root(); if (tokenkind != tk_cparen) { assert(!"NOPE"); } next_token(); break; } default: assert(!"TODO"); break; } return retval; } if (tokenkind == tk_emark) { next_token(); return ~parse_prefix(); } else { return parse_primary(); } } uint16_t left = parse_prefix(); again: switch (tokenkind) { case tk_less_than: { next_token(); left = (~left & parse_equals()) & M; goto again; } case tk_less_than_eq: { next_token(); left = (~left | parse_equals()) & M; goto again; } case tk_greater_than_eq: { next_token(); left = ( left | ~parse_equals()) & M; goto again; } case tk_greater_than: { next_token(); left = ( left & ~parse_equals()) & M; goto again; } default: break; } return left; } uint16_t left = parse_compares(); again: switch (tokenkind) { case tk_equalsequals: { next_token(); left = ~(left ^ parse_equals()) & M; goto again; } case tk_emarkequals: { next_token(); left = left ^ parse_equals(); goto again; } default: break; } return left; } uint16_t left = parse_equals(); again: switch (tokenkind) { case tk_ampersandampersand: { next_token(); left = left & parse_equals(); goto again; } case tk_ampersandemark: { next_token(); left = ~left & parse_equals(); goto again; } case tk_emarkampersand: { next_token(); left = ~(left & parse_equals()); goto again; } default: break; } return left; } uint16_t left = parse_ands(); again: switch (tokenkind) { case tk_vbarvbar: { next_token(); left = left | parse_ands(); goto again; } case tk_vbaremark: { next_token(); left = (~left | parse_ands()) & M; goto again; } case tk_emarkvbar: { next_token(); left = ~(left | parse_ands()) & M; goto again; } default: break; } return left; } uint16_t cond = parse_ors(); if (tokenkind == tk_qmark) { next_token(); uint16_t left = parse_ors(); if (tokenkind != tk_colon) { puts("syntax error!"); exit(1); } next_token(); uint16_t right = parse_ternary(); return (cond & left) | (~cond & right); } else { return cond; } } return parse_ternary(); } uint16_t truthtable = parse_root(); if (tokenkind != tk_EOF) { puts("syntax error!"); exit(1); } return truthtable; } int main(int argc, char* const* argv) { parse_args(argc, argv); get_simplifications(); if (print_all_and_quit) { for (int i = 0; i < N; i++) { int cost = lookup[i].cost; if (cost != INT_MAX) { printf("0b%016b: [%2i]: ", i, cost), print(i, 0), puts(""); } } } else if (command) { uint16_t truthtable = evaluate(command); // printf("truthtable = 0b%016b\n", truthtable); if (lookup[truthtable].kind == ek_unreachable) { puts("unreachable"); } else { printf("%2i: ", lookup[truthtable].cost), print(truthtable, 0), puts(""); } } else { // Let's humble-brag just a little. #if 0 { int max_cost = 0; for (int i = 0; i < N; i++) { int cost = lookup[i].cost; if (cost != INT_MAX && max_cost < cost) { max_cost = cost; } } puts(""); printf("I can simplify any tree down to %i operators or less.\n", max_cost); puts(""); } #endif if (!quiet) { puts("Use C-style syntax for boolean operators and expressions."); puts("Available variables: 'w', 'x', 'y' and 'z'."); puts("Extended operators: nor is '!|', orn is '|!', nand is '!&', andn is '&!'."); } for (char* line; (line = readline(">>> ")); free(line)) { if (!*line) continue; add_history(line); uint16_t truthtable = evaluate(line); // printf("truthtable = 0b%016b\n", truthtable); if (lookup[truthtable].kind == ek_unreachable) { puts("unreachable"); } else { printf("%2i: ", lookup[truthtable].cost), print(truthtable, 0), puts(""); } } } return 0; }