removed experimental stuff, since it was not any better than the classic 9 operators. switch from py-parsing to handrolled recursive descent
This commit is contained in:
parent
e3b0af2f12
commit
2499db7c7d
1 changed files with 338 additions and 162 deletions
498
main.py
498
main.py
|
|
@ -6,6 +6,8 @@ import argparse
|
|||
import pickle;
|
||||
import sys;
|
||||
|
||||
from colorsys import hsv_to_rgb;
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(prog = '4-variable-simplifier');
|
||||
|
||||
|
|
@ -24,24 +26,6 @@ def parse_args(argv):
|
|||
You can refer to them using their names or their symbols.
|
||||
''')
|
||||
|
||||
parser.add_argument('-E', '--experimental-operators', action='store_true', \
|
||||
help='''
|
||||
For two boolean variables, there are four possible inputs into a
|
||||
binary operator. For each of those four an operator could return true
|
||||
or false. That would mean in theory there could exist up to 16
|
||||
theoretical operators with different behaviours. Even with the
|
||||
'extended operators' option, we're only introducing 9 possible
|
||||
operators, that's less that 60% of the possibilities!
|
||||
Some of these would of course be useless, like a binary operator that
|
||||
always produces 'false' regardless of the input, for instance, but
|
||||
other may be extremely useful for acting in place of much more complex
|
||||
trees of operators. In theory, the simplifier would produce the
|
||||
smallest most compact expression trees possible.
|
||||
|
||||
Of course, no one would implement something like this and dare
|
||||
to anger the logic gods...
|
||||
''')
|
||||
|
||||
parser.add_argument("-n", '--use-names', help='''
|
||||
Print the operator's names in the expression tree, rather than the
|
||||
symbols.
|
||||
|
|
@ -90,29 +74,6 @@ def determine_available_operators(args):
|
|||
|
||||
extended = ("!|", "!&", "!=", "==", "|!", "&!");
|
||||
|
||||
experimental = (
|
||||
"<00>",
|
||||
"<01>",
|
||||
"<10>",
|
||||
"<11>",
|
||||
|
||||
"<0000>",
|
||||
"<0001>",
|
||||
"<0010>",
|
||||
"<0011>",
|
||||
"<0100>",
|
||||
"<0101>",
|
||||
"<0110>",
|
||||
"<0111>",
|
||||
"<1000>",
|
||||
"<1001>",
|
||||
"<1010>",
|
||||
"<1011>",
|
||||
"<1100>",
|
||||
"<1101>",
|
||||
"<1110>",
|
||||
"<1111>");
|
||||
|
||||
if args.extended_operators:
|
||||
return set(standard + extended);
|
||||
elif args.custom_operators:
|
||||
|
|
@ -129,29 +90,72 @@ def determine_available_operators(args):
|
|||
raise BaseException(f"not an operator: '{o}'");
|
||||
|
||||
return available_operators;
|
||||
elif args.experimental_operators:
|
||||
return set(experimental);
|
||||
else:
|
||||
return set(standard);
|
||||
|
||||
def pretty(exp):
|
||||
LITERAL_ESCAPE = "\033[38;2;200;200;100m";
|
||||
VARIABLE_ESCAPE = "\033[38;2;100;100;200m";
|
||||
|
||||
operator_colors = [];
|
||||
|
||||
for i in range(10):
|
||||
r, g, b = hsv_to_rgb(i / 10, 1.0, 0.8);
|
||||
r, g, b = [int(255 * x) for x in (r, g, b)];
|
||||
operator_colors.append(f"\033[38;2;{r};{g};{b}m");
|
||||
|
||||
RESET_ESCAPE = "\033[0m";
|
||||
|
||||
def pretty(exp, in_color = False, depth = 0):
|
||||
|
||||
start_color, end_color = "", "";
|
||||
|
||||
if in_color:
|
||||
start_color = operator_colors[depth % len(operator_colors)];
|
||||
|
||||
end_color = RESET_ESCAPE;
|
||||
|
||||
retval = "";
|
||||
|
||||
match exp:
|
||||
case ("literal", x):
|
||||
return str(x);
|
||||
retval = str(x);
|
||||
|
||||
if in_color:
|
||||
retval = LITERAL_ESCAPE + retval + RESET_ESCAPE;
|
||||
|
||||
case ("variable", x):
|
||||
return x
|
||||
retval = x
|
||||
|
||||
if in_color:
|
||||
retval = VARIABLE_ESCAPE + retval + RESET_ESCAPE;
|
||||
|
||||
case (op, inner):
|
||||
return f"({op}{pretty(inner)})"
|
||||
retval += start_color + "(" + end_color;
|
||||
|
||||
retval += start_color + op + end_color;
|
||||
|
||||
retval += pretty(inner, in_color, depth + 1);
|
||||
|
||||
retval += start_color + ")" + end_color;
|
||||
|
||||
case (op, left, right):
|
||||
return f"({pretty(left)} {op} {pretty(right)})"
|
||||
retval += start_color + "(" + end_color;
|
||||
|
||||
retval += pretty(left, in_color, depth + 1);
|
||||
|
||||
retval += " " + start_color + op + end_color + " ";
|
||||
|
||||
retval += pretty(right, in_color, depth + 1);
|
||||
|
||||
retval += start_color + ")" + end_color;
|
||||
|
||||
case _:
|
||||
print(exp);
|
||||
assert(not "TODO");
|
||||
|
||||
return retval;
|
||||
|
||||
|
||||
W = 0b0101_0101_0101_0101
|
||||
X = 0b0011_0011_0011_0011
|
||||
Y = 0b0000_1111_0000_1111
|
||||
|
|
@ -205,13 +209,6 @@ def calculate_simplifications(args, available_operators):
|
|||
|
||||
unary_operators = {
|
||||
'!': lambda x: ~x,
|
||||
|
||||
# these were found by running the simplifier with -o xor,and,or,not
|
||||
# 10 - x
|
||||
"<00>": lambda x: 0,
|
||||
"<01>": lambda x: ~x,
|
||||
"<10>": lambda x: x,
|
||||
"<11>": lambda x: M,
|
||||
}
|
||||
|
||||
binary_operators = {
|
||||
|
|
@ -226,26 +223,6 @@ def calculate_simplifications(args, available_operators):
|
|||
|
||||
'!=': lambda x, y: x ^ y, # xor
|
||||
'==': lambda x, y: ~(x ^ y), # nxor
|
||||
|
||||
# these were found by running the simplifier with -o xor,and,or,not
|
||||
# 0011 - x
|
||||
# 0101 - y
|
||||
"<0000>": lambda x, y: 0,
|
||||
"<1000>": lambda x, y: ~(x | y),
|
||||
"<0100>": lambda x, y: x ^ (x | y),
|
||||
"<1100>": lambda x, y: ~x,
|
||||
"<0010>": lambda x, y: x ^ (x & y),
|
||||
"<1010>": lambda x, y: ~y,
|
||||
"<0110>": lambda x, y: x ^ y,
|
||||
"<1110>": lambda x, y: ~(x & y),
|
||||
"<0001>": lambda x, y: x & y,
|
||||
"<1001>": lambda x, y: x ^ ~y,
|
||||
"<0101>": lambda x, y: y,
|
||||
"<1101>": lambda x, y: ~x | y,
|
||||
"<0011>": lambda x, y: x,
|
||||
"<1011>": lambda x, y: x | ~y,
|
||||
"<0111>": lambda x, y: x | y,
|
||||
"<1111>": lambda x, y: M,
|
||||
}
|
||||
|
||||
def print_status():
|
||||
|
|
@ -374,109 +351,288 @@ def get_simplifications(args, available_operators):
|
|||
|
||||
return cache[available_operators];
|
||||
|
||||
def create_parser():
|
||||
from pyparsing import infix_notation, opAssoc, Word, oneOf
|
||||
# def create_parser():
|
||||
# from pyparsing import infixNotation, opAssoc, Word, oneOf
|
||||
#
|
||||
# # this can't handle parenthesis very well...
|
||||
# # do I really have to write my own parser?
|
||||
# root = infixNotation(Word("01wxyz"), [
|
||||
# ('!', 1, opAssoc.RIGHT),
|
||||
# (oneOf('< <= > >='), 2, opAssoc.LEFT),
|
||||
# (oneOf('== !='), 2, opAssoc.LEFT),
|
||||
# (oneOf('&& !& &!'), 2, opAssoc.LEFT),
|
||||
# (oneOf('|| !| |!'), 2, opAssoc.LEFT)]);
|
||||
#
|
||||
# return root;
|
||||
|
||||
# this can't handle parenthesis very well...
|
||||
# do I really have to write my own parser?
|
||||
root = infix_notation(Word("01wxyz"), [
|
||||
('!', 1, opAssoc.RIGHT),
|
||||
(oneOf('< <= > >='), 2, opAssoc.LEFT),
|
||||
(oneOf('== !='), 2, opAssoc.LEFT),
|
||||
(oneOf('&& !& &!'), 2, opAssoc.LEFT),
|
||||
(oneOf('|| !| |!'), 2, opAssoc.LEFT)]);
|
||||
def parse(text):
|
||||
class Tokenizer:
|
||||
def __init__(self, text):
|
||||
self.position = 0;
|
||||
self.text = text;
|
||||
self.tokenkind = None;
|
||||
self.tokendata = None;
|
||||
|
||||
def next(self):
|
||||
# skip whitespace:
|
||||
while self.position < len(self.text) and self.text[self.position].isspace():
|
||||
self.position += 1;
|
||||
|
||||
if self.position == len(self.text):
|
||||
self.tokenkind = "EOF";
|
||||
return;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "0" | "1":
|
||||
self.tokenkind = "literal";
|
||||
self.tokendata = self.text[self.position];
|
||||
self.position += 1;
|
||||
|
||||
case "w" | "x" | "y" | "z":
|
||||
self.tokenkind = "variable";
|
||||
self.tokendata = self.text[self.position];
|
||||
self.position += 1;
|
||||
|
||||
case "(" | ")":
|
||||
self.tokenkind = self.text[self.position];
|
||||
self.position += 1;
|
||||
|
||||
case ">":
|
||||
# could be ">" or ">=":
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "=":
|
||||
self.tokenkind = ">=";
|
||||
self.position += 1;
|
||||
case _:
|
||||
self.tokenkind = ">"
|
||||
|
||||
case "<":
|
||||
# could be "<" or "<=":
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "=":
|
||||
self.tokenkind = "<=";
|
||||
self.position += 1;
|
||||
case _:
|
||||
self.tokenkind = "<"
|
||||
|
||||
case "=":
|
||||
# could only be "=="
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "=":
|
||||
self.tokenkind = "==";
|
||||
self.position += 1;
|
||||
|
||||
case _:
|
||||
raise BaseException("expecting '=' after '='");
|
||||
|
||||
case "!":
|
||||
# could be "!" or "!|" or "&!" or "!="
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "&":
|
||||
self.tokenkind = "!&";
|
||||
self.position += 1;
|
||||
case "|":
|
||||
self.tokenkind = "!|";
|
||||
self.position += 1;
|
||||
case "=":
|
||||
self.tokenkind = "!=";
|
||||
self.position += 1;
|
||||
case _:
|
||||
self.tokenkind = "!"
|
||||
|
||||
case "&":
|
||||
# could be "&&" or "&!"
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "&":
|
||||
self.tokenkind = "&&";
|
||||
self.position += 1;
|
||||
case "!":
|
||||
self.tokenkind = "&!";
|
||||
self.position += 1;
|
||||
case _:
|
||||
raise BaseException("expecting '&' or '!' after '&'");
|
||||
|
||||
case "|":
|
||||
# could be "||" or "|!"
|
||||
self.position += 1;
|
||||
|
||||
match self.text[self.position]:
|
||||
case "|":
|
||||
self.tokenkind = "||";
|
||||
self.position += 1;
|
||||
case "!":
|
||||
self.tokenkind = "|!";
|
||||
self.position += 1;
|
||||
case _:
|
||||
raise BaseException("expecting '|' or '!' after '|'");
|
||||
|
||||
case x:
|
||||
raise BaseException(f'unknown token "{x}"!');
|
||||
|
||||
def parse_primary(tokenizer):
|
||||
match tokenizer.tokenkind:
|
||||
case "literal":
|
||||
ast = ("literal", int(tokenizer.tokendata));
|
||||
tokenizer.next();
|
||||
|
||||
case "variable":
|
||||
ast = ("variable", tokenizer.tokendata);
|
||||
tokenizer.next();
|
||||
|
||||
case "(":
|
||||
tokenizer.next();
|
||||
|
||||
ast = parse_root(tokenizer);
|
||||
|
||||
if tokenizer.tokenkind != ")":
|
||||
raise BaseException(f'expected token ")", found: {tokenizer.tokenkind}');
|
||||
|
||||
tokenizer.next();
|
||||
|
||||
case x:
|
||||
raise BaseException(f'unexpected token "{x}"!');
|
||||
|
||||
return ast;
|
||||
|
||||
def parse_prefix(tokenizer):
|
||||
if tokenizer.tokenkind == "!":
|
||||
tokenizer.next();
|
||||
return ("!", parse_prefix(tokenizer));
|
||||
else:
|
||||
return parse_primary(tokenizer);
|
||||
|
||||
def parse_compares(tokenizer):
|
||||
left = parse_prefix(tokenizer);
|
||||
|
||||
while tokenizer.tokenkind in ("<=", "<", ">", ">="):
|
||||
op = tokenizer.tokenkind;
|
||||
tokenizer.next();
|
||||
left = (op, left, parse_prefix(tokenizer));
|
||||
|
||||
return left;
|
||||
|
||||
def parse_equals(tokenizer):
|
||||
left = parse_compares(tokenizer);
|
||||
|
||||
while tokenizer.tokenkind in ("==", "!="):
|
||||
op = tokenizer.tokenkind;
|
||||
tokenizer.next();
|
||||
left = (op, left, parse_compares(tokenizer));
|
||||
|
||||
return left;
|
||||
|
||||
def parse_ands(tokenizer):
|
||||
left = parse_equals(tokenizer);
|
||||
|
||||
while tokenizer.tokenkind in ("&&", "&!", "!&"):
|
||||
op = tokenizer.tokenkind;
|
||||
tokenizer.next();
|
||||
left = (op, left, parse_equals(tokenizer));
|
||||
|
||||
return left;
|
||||
|
||||
def parse_ors(tokenizer):
|
||||
left = parse_ands(tokenizer);
|
||||
|
||||
while tokenizer.tokenkind in ("||", "|!", "!|"):
|
||||
op = tokenizer.tokenkind;
|
||||
tokenizer.next();
|
||||
left = (op, left, parse_ands(tokenizer));
|
||||
|
||||
return left;
|
||||
|
||||
def parse_root(tokenizer):
|
||||
return parse_ors(tokenizer);
|
||||
|
||||
tokenizer = Tokenizer(text);
|
||||
|
||||
tokenizer.next();
|
||||
|
||||
root = parse_root(tokenizer);
|
||||
|
||||
if tokenizer.tokenkind != "EOF":
|
||||
raise BaseException(
|
||||
f"reached end of expression. "
|
||||
f"Expecting EOF. found: {tokenizer.tokenkind}!");
|
||||
|
||||
return root;
|
||||
|
||||
def parse(parser, text):
|
||||
return parser.parseString(text, parseAll = True).asList()[0]
|
||||
|
||||
def evaluate(expr):
|
||||
match expr:
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return M;
|
||||
case "w":
|
||||
return W;
|
||||
case "x":
|
||||
return X;
|
||||
case "y":
|
||||
return Y;
|
||||
case "z":
|
||||
return Z;
|
||||
case ("!", subexp):
|
||||
return ~evaluate(subexp) & M;
|
||||
case (first, "||", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = result | evaluate(subexpr);
|
||||
return result;
|
||||
case (first, "!|", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = ~(result | evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "|!", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (~result | evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "&&", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = result & evaluate(subexpr);
|
||||
return result;
|
||||
case (first, "!&", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = ~(result & evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "&!", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (~result & evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "<", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (~result & evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "<=", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (~result | evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, ">", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (result & ~evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, ">=", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (result | ~evaluate(subexpr)) & M;
|
||||
return result;
|
||||
case (first, "!=", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = (result ^ evaluate(subexpr)) & M
|
||||
return result;
|
||||
case (first, "==", *rest):
|
||||
result = evaluate(first);
|
||||
for subexpr in rest[::2]: result = ~(result ^ evaluate(subexpr)) & M
|
||||
return result;
|
||||
case ("literal", 0): return 0;
|
||||
case ("literal", 1): return M;
|
||||
|
||||
case ("variable", "w"): return W;
|
||||
case ("variable", "x"): return X;
|
||||
case ("variable", "y"): return Y;
|
||||
case ("variable", "z"): return Z;
|
||||
|
||||
case ("!", inner):
|
||||
return ~evaluate(inner) & M;
|
||||
|
||||
case ("<", left, right):
|
||||
return (~evaluate(left) & evaluate(right)) & M;
|
||||
case ("<=", left, right):
|
||||
return (~evaluate(left) | evaluate(right)) & M;
|
||||
case (">=", left, right):
|
||||
return ( evaluate(left) | ~evaluate(right)) & M;
|
||||
case (">", left, right):
|
||||
return ( evaluate(left) & ~evaluate(right)) & M;
|
||||
|
||||
case ("!=", left, right):
|
||||
return (evaluate(left) ^ evaluate(right)) & M;
|
||||
case ("==", left, right):
|
||||
return ~(evaluate(left) ^ evaluate(right)) & M;
|
||||
|
||||
case ("||", left, right):
|
||||
return evaluate(left) | evaluate(right);
|
||||
case ("|!", left, right):
|
||||
return (~evaluate(left) | evaluate(right)) & M;
|
||||
case ("!|", left, right):
|
||||
return ~(evaluate(left) | evaluate(right)) & M;
|
||||
|
||||
case ("&&", left, right):
|
||||
return evaluate(left) & evaluate(right);
|
||||
case ("&!", left, right):
|
||||
return (~evaluate(left) & evaluate(right)) & M;
|
||||
case ("!&", left, right):
|
||||
return ~(evaluate(left) & evaluate(right)) & M;
|
||||
|
||||
case _:
|
||||
print(expr);
|
||||
print(f'expr = {expr}');
|
||||
assert(not "TODO");
|
||||
|
||||
def repl(args, cost, lookup):
|
||||
import readline;
|
||||
print("Give a boolean expression using the variables 'w', 'x', 'y' and 'z'.");
|
||||
print("Operations: not ('!'), or ('||'), and ('&&').""");
|
||||
print("Operators: not ('!'), or ('||'), and ('&&').");
|
||||
|
||||
print();
|
||||
|
||||
print("The py-parser struggles with deeply-nested parentheses.");
|
||||
print("I may have to just write my recursive-descent parser.");
|
||||
|
||||
print();
|
||||
|
||||
if args.extended_operators:
|
||||
print("Extended operations: nor ('!|'), orn ('|!'), nand ('!&'), ");
|
||||
print("Extended operators: nor ('!|'), orn ('|!'), nand ('!&'), ");
|
||||
print("andn ('&!'), xor ('!='), and nxor ('==')");
|
||||
|
||||
print();
|
||||
|
||||
print(f'I can do anything in {max(cost.values())} operations.')
|
||||
print(f'I can simplify any tree down to {max(cost.values())} operators or less.')
|
||||
print();
|
||||
|
||||
parser = create_parser();
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input(">>> ");
|
||||
|
|
@ -487,10 +643,24 @@ def repl(args, cost, lookup):
|
|||
|
||||
if not line: continue;
|
||||
|
||||
truthtable = evaluate(parse(parser, line));
|
||||
try:
|
||||
parsed = parse(line);
|
||||
except BaseException as e:
|
||||
print(f"syntax error: {e}");
|
||||
continue;
|
||||
|
||||
truthtable = evaluate(parsed);
|
||||
|
||||
if truthtable in lookup:
|
||||
print(f'{cost[truthtable]}: {pretty(lookup[truthtable])}')
|
||||
text = "";
|
||||
|
||||
c = cost[truthtable];
|
||||
|
||||
LITERAL_ESCAPE = "\033[38;2;200;200;100m";
|
||||
|
||||
print(f'{cost[truthtable]}: {pretty(lookup[truthtable], args.color)}')
|
||||
|
||||
print(text);
|
||||
else:
|
||||
print('unreachable.')
|
||||
|
||||
|
|
@ -506,12 +676,18 @@ def main(args):
|
|||
|
||||
if args.printout:
|
||||
for truthtable, exp in sorted(lookup.items()):
|
||||
print(f'{truthtable:016b}: {pretty(exp)}');
|
||||
print(f'{truthtable:016b}: {pretty(exp, args.color)}');
|
||||
elif args.command:
|
||||
truthtable = evaluate(parse(create_parser(), args.command));
|
||||
try:
|
||||
parsed = parse(args.command);
|
||||
except BaseException as e:
|
||||
print(f"syntax error: {e}");
|
||||
return 1;
|
||||
|
||||
truthtable = evaluate(parsed);
|
||||
|
||||
if truthtable in lookup:
|
||||
print(pretty(lookup[truthtable]));
|
||||
print(pretty(lookup[truthtable], args.color));
|
||||
else:
|
||||
print('unreachable.')
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in a new issue