restructure project + unit tests
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
from .parser import Parser, tokenize
|
||||
from .printer import format_poly
|
||||
|
||||
def convert(expr: str) -> str:
|
||||
poly = Parser(tokenize(expr)).parse()
|
||||
return format_poly(poly)
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from . import convert
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) > 1:
|
||||
expr = " ".join(sys.argv[1:])
|
||||
else:
|
||||
expr = input("Enter formula: ").strip()
|
||||
|
||||
print(convert(expr))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import FrozenSet
|
||||
|
||||
Monomial = FrozenSet[str]
|
||||
Poly = set[Monomial]
|
||||
|
||||
|
||||
def zero() -> Poly:
|
||||
return set()
|
||||
|
||||
|
||||
def one() -> Poly:
|
||||
return {frozenset()}
|
||||
|
||||
|
||||
def var(name: str) -> Poly:
|
||||
return {frozenset([name])}
|
||||
|
||||
|
||||
def poly_xor(a: Poly, b: Poly) -> Poly:
|
||||
return a ^ b
|
||||
|
||||
|
||||
def poly_and(a: Poly, b: Poly) -> Poly:
|
||||
out: Poly = set()
|
||||
for ma in a:
|
||||
for mb in b:
|
||||
m = frozenset(set(ma) | set(mb))
|
||||
if m in out:
|
||||
out.remove(m)
|
||||
else:
|
||||
out.add(m)
|
||||
return out
|
||||
|
||||
|
||||
def poly_not(a: Poly) -> Poly:
|
||||
return poly_xor(a, one())
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
from .core import Poly, poly_and, poly_not, poly_xor, one, var, zero
|
||||
|
||||
TOKEN_RE = re.compile(
|
||||
r"""
|
||||
\s*(
|
||||
\(|\)|
|
||||
!|
|
||||
\+|\*|
|
||||
AND|OR|
|
||||
0|1|
|
||||
X\d+|
|
||||
[A-Za-z_][A-Za-z0-9_]*
|
||||
)
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def tokenize(s: str) -> List[str]:
|
||||
tokens = TOKEN_RE.findall(s)
|
||||
stripped = re.sub(r"\s+", "", s)
|
||||
if "".join(tokens) != stripped:
|
||||
raise ValueError(f"Invalid token near: {s!r}")
|
||||
return tokens
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self, tokens: List[str]):
|
||||
self.tokens = tokens
|
||||
self.i = 0
|
||||
|
||||
def peek(self) -> str | None:
|
||||
return self.tokens[self.i] if self.i < len(self.tokens) else None
|
||||
|
||||
def eat(self, expected: str | None = None) -> str:
|
||||
tok = self.peek()
|
||||
if tok is None:
|
||||
raise ValueError("Unexpected end of input")
|
||||
if expected is not None and tok != expected:
|
||||
raise ValueError(f"Expected {expected!r}, got {tok!r}")
|
||||
self.i += 1
|
||||
return tok
|
||||
|
||||
def parse(self) -> Poly:
|
||||
p = self.parse_or()
|
||||
if self.peek() is not None:
|
||||
raise ValueError(f"Unexpected token: {self.peek()!r}")
|
||||
return p
|
||||
|
||||
def parse_or(self) -> Poly:
|
||||
left = self.parse_and()
|
||||
while self.peek() in ("OR", "+"):
|
||||
self.eat()
|
||||
right = self.parse_and()
|
||||
left = poly_xor(poly_xor(left, right), poly_and(left, right))
|
||||
return left
|
||||
|
||||
def parse_and(self) -> Poly:
|
||||
left = self.parse_not()
|
||||
while self.peek() in ("AND", "*"):
|
||||
self.eat()
|
||||
right = self.parse_not()
|
||||
left = poly_and(left, right)
|
||||
return left
|
||||
|
||||
def parse_not(self) -> Poly:
|
||||
if self.peek() == "!":
|
||||
self.eat("!")
|
||||
return poly_not(self.parse_not())
|
||||
return self.parse_atom()
|
||||
|
||||
def parse_atom(self) -> Poly:
|
||||
tok = self.peek()
|
||||
if tok is None:
|
||||
raise ValueError("Unexpected end of input")
|
||||
|
||||
if tok == "(":
|
||||
self.eat("(")
|
||||
inside = self.parse_or()
|
||||
self.eat(")")
|
||||
return inside
|
||||
|
||||
if tok == "0":
|
||||
self.eat("0")
|
||||
return zero()
|
||||
|
||||
if tok == "1":
|
||||
self.eat("1")
|
||||
return one()
|
||||
|
||||
if re.fullmatch(r"X\d+|[A-Za-z_][A-Za-z0-9_]*", tok):
|
||||
self.eat()
|
||||
return var(tok)
|
||||
|
||||
raise ValueError(f"Unexpected token: {tok!r}")
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .core import Monomial, Poly
|
||||
|
||||
|
||||
def monomial_key(m: Monomial):
|
||||
return (len(m), tuple(sorted(m)))
|
||||
|
||||
|
||||
def format_monomial(m: Monomial) -> str:
|
||||
if len(m) == 0:
|
||||
return "1"
|
||||
if len(m) == 1:
|
||||
return next(iter(m))
|
||||
parts = sorted(m, key=lambda x: (len(x), x))
|
||||
return "(" + "⸱".join(parts) + ")"
|
||||
|
||||
|
||||
def format_poly(p: Poly) -> str:
|
||||
if not p:
|
||||
return "0"
|
||||
|
||||
monos = sorted(p, key=monomial_key)
|
||||
return "⊕".join(format_monomial(m) for m in monos)
|
||||
Reference in New Issue
Block a user