Quick actions

cmd+k|ctrl+k

Navigation

Languages

elmacyc proto 1

Snippet info

Language

Python

Visibility

public

Author

electromagneticcyclone

Created

2025-03-07T18:53:16.26559Z

Updated

2025-03-08T10:47:42.143979Z

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)
INFO