summary refs log tree commit diff
path: root/calculator.py
blob: 9f8ef751bf629ab00a4612043bb0a44f7405be7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
from dataclasses import dataclass,make_dataclass
from collections import deque
from math import factorial

@dataclass
class Number:
    _: int

ParenClose = make_dataclass('ParenClose',[])
ParenOpen = make_dataclass('ParenOpen',[])
AddOp = make_dataclass('AddOp',[])
SubOp = make_dataclass('SubOp',[])
MulOp = make_dataclass('MulOp',[])
DivOp = make_dataclass('DivOp',[])
PowOp = make_dataclass('PowOp',[])
NegOp = make_dataclass('NegOp',[])
ModOp = make_dataclass('ModOp',[])
FacOp = make_dataclass('FacOp',[])
AvgOp = make_dataclass('AvgOp',[])
MaxOp = make_dataclass('MaxOp',[])
MinOp = make_dataclass('MinOp',[])

Token = Number | AddOp | SubOp | MulOp | DivOp | PowOp | NegOp | ModOp | FacOp | AvgOp | MaxOp | MinOp | ParenClose | ParenOpen

Characters = {
    '(': ParenOpen,
    ')': ParenClose,
    '+': AddOp,
    '-': SubOp,
    '*': MulOp,
    '/': DivOp,
    '^': PowOp,
    '~': NegOp,
    '%': ModOp,
    '!': FacOp,
    '@': AvgOp,
    '$': MaxOp,
    '&': MinOp
}

from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
from io import BytesIO
def Tokenize(s: str) -> [Token]:
    g = tokenize(BytesIO(s.encode('utf-8')).readline)
    r = []
    for n, v, _, _, _ in g:
        match (n,v):
            case (2,x):
                r.append(Number(float(x)))
            case (54|60,x) if x in Characters:
                r.append(Characters[x]())
    return r

def Expression(e:  [Token]) -> (int, [Token]):
    r, e = Term(e)
    while len(e) > 0:
        match e.popleft():
            case AddOp():
                x, e = Term(e)
                r += x
            case SubOp():
                x, e = Term(e)
                r -= x
            case x:
                e.insert(0, x)
                break
    return r, e

def Term(e: [Token]) -> (int, [Token]):
    r, e = Factor(e)
    while len(e) > 0:
        match e.popleft():
            case ModOp():
                x, e = Factor(e)
                r %= x
            case AvgOp():
                x, e = Factor(e)
                r = (x + r) / 2
            case MinOp():
                x, e = Factor(e)
                r = min(x, r)
            case MaxOp():
                x, e = Factor(e)
                r = max(x,r)
            case MulOp():
                x, e = Factor(e)
                r *= x
            case DivOp():
                x, e = Factor(e)
                r /= x
            case PowOp():
                x, e = Factor(e)
                r **= x
            case x:
                e.insert(0, x)
                break
    return r,e

def Factor(e: [Token]) -> (int, [Token]):
    assert len(e) > 0
    match e.popleft():
        case NegOp():
            r, e = Factor(e)
            return -r, e
        case Number(n):
            if e and isinstance(e[0], FacOp):
                e.popleft()
                return float(factorial(int(n))), e
            return n, e
        case ParenOpen():
            r, e = Expression(e)
            assert isinstance(e.popleft(), ParenClose)
            if e and isinstance(e[0], FacOp):
                e.popleft()
                r = float(factorial(int(r)))
            return r, e
        case e:
            raise SyntaxError(f"invalid syntax: {e}")

# where is my compose
Evaluate = lambda x: Expression(deque(Tokenize(x)))[0]

if __name__ == '__main__':
    while True:
        try:
            print(Evaluate(input("> ")))
        except Exception as e:
            print(e)