from fractions import Fraction
from functools import reduce
from math import sin, pi
def todo():
raise InterruptedError("Not implemented")
# AFAIK ` is unused in TeX, so it's used to emulate Compose key
# https://habr.com/ru/articles/80091/
def pseudoCompose(s: str) -> str:
# TODO don't iterate dict items
_dict = {
"oo": "°",
"xx": "×",
# Greek
"GW": "Ω",
"gm": "μ",
"gp": "π",
}
for k, v in _dict.items():
s = s.replace(f"`{k}", v)
return s
class Unit:
_units = {
"g", # Gram
"m", # Meter
"A", # Ampere
"K", # Kelvin
"mol", # Mole
"cd", # Candela
"s", # Second
}
_prefixes = list(zip((
"y", # yocto
"z", # zepto
"a", # atto
"f", # femto
"p", # pico
"n", # nano
"μ", # micro
"m", # milli
"k", # kilo
"M", # mega
"G", # giga
"T", # tera
"P", # peta
"E", # exa
"Z", # zetta
"Y", # yotta
), range(-24, 25, 3))) + [
("c", -2), # centi
("d", -1), # deci
("da", 1), # deca
("h", 2), # hecto
]
_bin_prefixes = list(zip((
"Ki", # kibi
"Mi", # mebi
"Gi", # gibi
"Ti", # tebi
"Pi", # pebi
"Ei", # exbi
"Zi", # zebi
"Yi", # yobi
), range(10, 81, 10)))
_conversions = {
"N": (3, [("g", 1), ("m", 1)], [("s", 2)]), # Newton
"Pa": (3, [("g", 1)], [("m", 1), ("s", 2)]), # Pascal
"J": (3, [("g", 1), ("m", 2)], [("s", 2)]), # Joule
"W": (3, [("g", 1), ("m", 2)], [("s", 3)]), # Watt
"C": (1, [("A", 1), ("s", 1)], []), # Coulomb
"V": (3, [("g", 1), ("m", 2)], [("A", 1), ("s", 3)]), # Volt
"Ω": (3, [("g", 1), ("m", 2)], [("A", 2), ("s", 3)]), # Ohm
"Hz": (1, [], [("s", 1)]), # Hertz
"L": (-3, [("m", 3)], []), # Liter
}
def __init__(self):
print(self._prefixes)
class Integer:
def __init__(self, value: Fraction, unit: Unit):
if value.denominator != 1:
raise ValueError("Non-integer value")
self.value = value
self.unit = unit
def __repr__(self) -> str:
return f"{self.value!r} {self.unit!r})"
class Natural(Integer):
def __init__(self, value: Fraction, unit: Unit):
if not (value > 0):
raise ValueError('Non-natural value')
super().__init__()
def __repr__(self) -> str:
return f"{self.value!r} {self.unit!r})"
class Statement:
_ops = {
# Operations:
# * Priority (0 = function)
# * Number of args (None = inf)
# * Calc function
'+': (1, None, lambda x: sum(x)),
'-': (1, 2, lambda x: x[0] - x[1]),
'*': (2, None, lambda x: reduce(lambda a, b: a * b, x)),
'/': (2, 2, lambda x: x[0] / x[1]),
'**': (3, 2, lambda x: x[0] ** x[1]),
'sin': (0, 1, lambda x: sin(x[0])),
# Add your own functions
'inc': (0, 1, lambda x: x[0] + 1),
}
def __init__(self, elm):
self.elm = elm
def __repr__(self, e = None, prev = (0, True)) -> str:
if e is None:
e = self.elm
if type(e) not in (list, tuple):
return str(e)
match e[0]:
case '=':
return " = ".join(self.__repr__(i) for i in e[1:])
case 'var':
return e[1]
case _ if e[0] in self._ops.keys():
if (self._ops[e[0]][1] is not None) and (len(e) - 1 != self._ops[e[0]][1]):
raise ValueError('Invalid statement')
cur = self._ops[e[0]]
if cur[0] != 0:
br = (cur[0] < prev[0]) or \
((cur[0] == prev[0]) and prev[1] is not None)
return ('(' if br else '') + (" " + e[0] + " ").join(self.__repr__(i, cur) for i in e[1:]) + (')' if br else '')
return e[0] + "(" + ", ".join(self.__repr__(i, cur) for i in e[1:]) + ")"
def reduce(self, e = None):
if e is None:
e = self.elm
if e[0] == 'var':
return e
# Convert '-' → '+'; '/' → '*'
r = None
if e[0] == '-':
e[0] = '+'
r = [
'*',
e[2],
-1,
]
if e[0] == '/':
e[0] = '*'
r = [
'**',
e[2],
-1,
]
if r is not None:
e.pop(2)
e.insert(2, r)
# Reduce each element (+ previous conversion)
to_remove = dict()
for i in range(1, len(e)):
if type(e[i]) is list:
r = [self.reduce(e[i])]
to_remove[i] = r[::-1]
for i in tuple(to_remove.keys())[::-1]:
e.pop(i)
for ri in to_remove[i]:
e.insert(i, ri)
# Unwrap nested operations with infinite args
to_remove = dict()
for i in range(1, len(e)):
if type(e[i]) is list:
if (e[i][0] == e[0]) and (e[0] in filter(lambda x: self._ops[x][1] == None, self._ops.keys())):
r = list(map(lambda x: self.reduce(x) if type(x) is list else x, e[i][1:]))
to_remove[i] = r[::-1]
for i in tuple(to_remove.keys())[::-1]:
e.pop(i)
for ri in to_remove[i]:
e.insert(i, ri)
# Find calculated numbers
nums = tuple(filter(lambda x: type(x) is not list and type(x) is not str, e[1:]))
# Reduce numbers if every argument is a number or there are some numbers in infinite-arg operation
red = None
if (e[0] in self._ops) and (((self._ops[e[0]][1] is None) and (len(nums) > 1)) or (self._ops[e[0]][1] == len(nums))):
red = self._ops[e[0]][2](nums)
# Substitute calculated numbers with reduced value
if red is not None:
e = [e[0]] + list(filter(lambda x: x not in nums, e[1:])) + [red]
# Return elm or it's only argument
if len(e) == 2:
return e[1]
return e
class Store:
_store = dict()
def __init__(self):
pass
def __repr__(self) -> str:
return f"{self._store!r}"
def var(self, v: str) -> str:
if v not in self._store:
self._store[v] = None
if self._store[v] is None:
return ['var', v]
return self._store[v]
def set(self, v: str, n):
self._store[v] = n
store = Store()
s = Statement([
'=',
[
'/',
[
'*',
store.var('x'),
2,
[
'-',
2,
[
'+',
2,
[
'-',
2,
1,
],
],
],
[
'**',
2,
2,
],
],
8,
],
[
'+',
[
'+',
[
'-',
[
'*',
2,
store.var('x'),
],
1,
],
[
'*',
4,
1,
],
],
2,
[
'sin',
[
'/',
pi,
2,
],
],
],
])
print(s.elm)
print(s)
s.reduce()
print(s.elm)
print(s)