4-variable-simplifier/main.c
2025-06-10 21:03:26 -05:00

1194 lines
32 KiB
C

#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <limits.h>
#include <readline/readline.h>
#include <readline/history.h>
#define argv0 program_invocation_name
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 ternary;
} use_operators;
bool assume_yes = false;
bool verbose = false;
static void parse_args(int argc, char* const* argv)
{
bool unset_operators = true;
for (int opt; (opt = getopt(argc, argv, "pyvc:eEo:")) != -1; ) switch (opt)
{
case 'p':
print_all_and_quit = true;
break;
case 'y':
assume_yes = 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"))
use_operators.not = true;
else if (!strcmp(moving, "or"))
use_operators.or = true;
else if (!strcmp(moving, "orn"))
use_operators.orn = true;
else if (!strcmp(moving, "nor"))
use_operators.nor = true;
else if (!strcmp(moving, "and"))
use_operators.and = true;
else if (!strcmp(moving, "andn"))
use_operators.andn = true;
else if (!strcmp(moving, "nand"))
use_operators.nand = true;
else if (!strcmp(moving, "xor"))
use_operators.xor = true;
else if (!strcmp(moving, "nxor"))
use_operators.nxor = true;
else
{
assert(!"TODO");
}
}
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_undef,
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_ternary,
};
struct expr {
enum kind kind;
uint16_t cond, left, right;
} lookup[N];
// truthtable -> cost
int costs[N] = {};
static void print(uint16_t truthtable)
{
const struct expr* e = &lookup[truthtable];
switch (e->kind)
{
case ek_undef:
assert(!"NOPE");
break;
case ek_0: printf("0"); break;
case ek_1: printf("1"); break;
case ek_W: printf("w"); break;
case ek_X: printf("x"); break;
case ek_Y: printf("y"); break;
case ek_Z: printf("z"); break;
case ek_not:
printf("(!"), print(e->left), printf(")");
break;
case ek_or:
printf("("), print(e->left), printf(" || "), print(e->right), printf(")");
break;
case ek_orn:
printf("("), print(e->left), printf(" |! "), print(e->right), printf(")");
break;
case ek_nor:
printf("("), print(e->left), printf(" !| "), print(e->right), printf(")");
break;
case ek_and:
printf("("), print(e->left), printf(" && "), print(e->right), printf(")");
break;
case ek_andn:
printf("("), print(e->left), printf(" &! "), print(e->right), printf(")");
break;
case ek_nand:
printf("("), print(e->left), printf(" !& "), print(e->right), printf(")");
break;
case ek_xor:
printf("("), print(e->left), printf(" != "), print(e->right), printf(")");
break;
case ek_nxor:
printf("("), print(e->left), printf(" == "), print(e->right), printf(")");
break;
case ek_ternary:
printf("("), print(e->cond), printf(" ? "),
print(e->left), printf(" : "), print(e->right), printf(")");
break;
}
}
void calculate_simplifications(void)
{
// heap of truthtables; key = cost
uint16_t todo[N];
int todo_n = 0;
// truthtable -> index in 'todo'
int reverse[N];
// init:
for (int i = 0; i < N; i++)
{
lookup[i].kind = ek_undef;
reverse[i] = -1;
costs[i] = INT_MAX;
}
uint16_t pop(void)
{
assert(todo_n > 0);
uint16_t retval = todo[0];
reverse[retval] = -1;
uint16_t moving = todo[todo_n-- - 1];
int cost = costs[moving];
int index = 0;
again:
{
int left = index * 2 + 1;
int right = index * 2 + 2;
uint16_t smallest = moving;
if (left < todo_n && costs[todo[left]] < cost)
smallest = todo[left];
if (right < todo_n && costs[todo[right]] < costs[smallest])
smallest = todo[right];
if (smallest == moving)
{
todo[index] = moving;
reverse[moving] = index;
}
else
{
int new = reverse[smallest];
todo[index] = smallest;
reverse[smallest] = index;
index = new;
goto again;
}
}
return retval;
}
void append(uint16_t truthtable, int cost)
{
assert(reverse[truthtable] == -1);
int index = todo_n++, new_index;
while (index > 0 && costs[todo[new_index = (index - 1) / 2]] > cost)
{
todo[index] = todo[new_index];
reverse[todo[new_index]] = index;
index = new_index;
}
todo[index] = truthtable;
reverse[truthtable] = index;
costs[truthtable] = cost;
}
void update(uint16_t truthtable, int cost)
{
assert(reverse[truthtable] != -1);
assert(cost < costs[truthtable]);
int index = reverse[truthtable], new_index;
while (index > 0 && costs[todo[new_index = (index - 1) / 2]] > cost)
{
todo[index] = todo[new_index];
reverse[todo[new_index]] = index;
index = new_index;
}
todo[index] = truthtable;
reverse[truthtable] = index;
costs[truthtable] = cost;
}
append(W, 1), lookup[W].kind = ek_W;
append(X, 1), lookup[X].kind = ek_X;
append(Y, 1), lookup[Y].kind = ek_Y;
append(Z, 1), lookup[Z].kind = ek_Z;
append(0, 1), lookup[0].kind = ek_0;
append(M, 1), lookup[M].kind = ek_1;
while (todo_n)
{
uint16_t truthtable = pop();
int cost = costs[truthtable];
if (verbose)
{
int left = N - todo_n;
printf("%i of %i (%.2f%%): [%i] ", left, N, (100.0 * left / N), cost);
print(truthtable), puts("");
}
// consider NOT:
if (use_operators.not)
{
uint16_t not_truthtable = ~truthtable & M;
int not_cost = cost + 1;
if (not_cost < costs[not_truthtable])
{
if (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 = 0; i < N; i++) \
{ \
if (costs[i] != INT_MAX) \
{ \
{ \
uint16_t bin_truthtable = function(truthtable, i) & M; \
\
int bin_cost = 1 + cost + costs[i]; \
\
if (bin_cost < costs[bin_truthtable]) \
{ \
if (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 + costs[i]; \
\
if (bin_cost < costs[bin_truthtable]) \
{ \
if (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))
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.ternary)
{
for (int i = 0; i < N; i++)
{
if (costs[i] != INT_MAX)
{
for (int j = 0; j < N; j++)
{
if (costs[j] != INT_MAX)
{
int ternary_cost = 1 + cost + costs[i] + costs[j];
#define TERNARY(C, T, F) \
{ \
uint16_t ternary_truthtable = \
((C) & (T)) | (~(C) & (F)); \
\
if (ternary_cost < costs[ternary_truthtable]) \
{ \
if (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);
}
}
}
}
}
}
}
void get_simplifications(void)
{
char path[PATH_MAX] = ".simplifier-cache";
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.ternary)
strcat(path, "-ternary");
strcat(path, ".bin");
int fd = open(path, O_RDONLY);
if (fd > 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);
}
if (read(fd, costs, sizeof(costs)) < (ssize_t) sizeof(costs))
{
printf("%s: read() to cache failed: %m\n", argv0);
exit(1);
}
}
else if (fd < 0 && errno == ENOENT)
{
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"
"");
if (!verbose)
{
puts("re-run with '-v' to watch progress");
}
if (!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);
}
if (write(fd, costs, sizeof(costs)) < (ssize_t) sizeof(costs))
{
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:
assert(!"TODO");
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 = costs[i];
if (cost != INT_MAX)
{
printf("0b%016b: [%2i]: ", i, cost), print(i), puts("");
}
}
}
else if (command)
{
uint16_t truthtable = evaluate(command);
// printf("truthtable = 0b%016b\n", truthtable);
if (costs[truthtable] == INT_MAX)
{
puts("unreachable");
}
else
{
printf("%2i: ", costs[truthtable]), print(truthtable), puts("");
}
}
else for (char* line; (line = readline(">>> ")); free(line))
{
uint16_t truthtable = evaluate(line);
// printf("truthtable = 0b%016b\n", truthtable);
if (costs[truthtable] == INT_MAX)
{
puts("unreachable");
}
else
{
printf("%2i: ", costs[truthtable]), print(truthtable), puts("");
}
}
return 0;
}