#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import math
import sys
import readline

# Супер-крутой консольный калькулятор - только стандартная библиотека
# Поддерживает: скобки, + - * / // % ^, унарный -, функции math, переменные, история, ans

OPS = {
    '+': (1, 'left'),
    '-': (1, 'left'),
    '*': (2, 'left'),
    '/': (2, 'left'),
    '//': (2, 'left'),
    '%': (2, 'left'),
    '^': (3, 'right'),
}

UNARY = 'u-'  # токен для унарного минуса

FUNCTIONS = {
    'sin': math.sin,
    'cos': math.cos,
    'tan': math.tan,
    'asin': math.asin,
    'acos': math.acos,
    'atan': math.atan,
    'sqrt': math.sqrt,
    'log': lambda x, base=math.e: math.log(x, base) if base != math.e else math.log(x),
    'ln': math.log,
    'log10': math.log10,
    'exp': math.exp,
    'pow': math.pow,
    'abs': abs,
    'floor': math.floor,
    'ceil': math.ceil,
    'round': round,
    'factorial': math.factorial,
}

CONSTS = {
    'pi': math.pi,
    'e': math.e,
}

# токенайзер
def tokenize(s):
    tokens = []
    i = 0
    L = len(s)
    while i < L:
        c = s[i]
        if c.isspace():
            i += 1
            continue
        if c.isdigit() or (c == '.' and i+1 < L and s[i+1].isdigit()):
            j = i
            has_dot = False
            while j < L and (s[j].isdigit() or s[j] == '.'):
                if s[j] == '.':
                    if has_dot:
                        break
                    has_dot = True
                j += 1
            tokens.append(s[i:j])
            i = j
            continue
        if c.isalpha() or c == '_':
            j = i
            while j < L and (s[j].isalnum() or s[j] == '_'):
                j += 1
            tokens.append(s[i:j])
            i = j
            continue
        # многосимвольные операторы
        if s.startswith('//', i):
            tokens.append('//'); i += 2; continue
        # одиночные операторы и скобки
        if c in '+-*/%^(),=%':
            tokens.append(c)
            i += 1
            continue
        if c == '=':
            tokens.append('='); i += 1; continue
        raise ValueError(f"Неизвестный символ: {c}")
    return tokens

# шантинг-ярд -> ОПЗ (RPN)
def to_rpn(tokens):
    out = []
    stack = []
    prev = None
    i = 0
    while i < len(tokens):
        tok = tokens[i]
        if is_number(tok) or is_variable(tok):
            out.append(tok)
        elif tok in FUNCTIONS:
            stack.append(tok)
        elif tok == ',':
            while stack and stack[-1] != '(':
                out.append(stack.pop())
            if not stack:
                raise ValueError("Неправильно размещённая запятая или несоответствие скобок")
        elif tok == '(':
            stack.append(tok)
        elif tok == ')':
            while stack and stack[-1] != '(':
                out.append(stack.pop())
            if not stack:
                raise ValueError("Несоответствие скобок")
            stack.pop()
            if stack and stack[-1] in FUNCTIONS:
                out.append(stack.pop())
        elif tok in OPS or tok == '-':
            # определить унарный минус
            if tok == '-' and (prev is None or (prev in OPS or prev in ('(', ','))):
                op = UNARY
            else:
                op = tok
            if op == UNARY:
                prec = 4
                assoc = 'right'
            else:
                prec, assoc = OPS.get(op, (None, None))
                if prec is None:
                    raise ValueError(f"Неизвестный оператор: {op}")
            while stack:
                top = stack[-1]
                if top == '(':
                    break
                if top == UNARY:
                    top_prec = 4; top_assoc = 'right'
                else:
                    if top in OPS:
                        top_prec, top_assoc = OPS[top]
                    else:
                        top_prec = 5; top_assoc = 'left'
                if (assoc == 'left' and prec <= top_prec) or (assoc == 'right' and prec < top_prec):
                    out.append(stack.pop())
                else:
                    break
            stack.append(op)
        else:
            raise ValueError(f"Неизвестный токен в выражении: {tok}")
        prev = tok
        i += 1
    while stack:
        t = stack.pop()
        if t in ('(', ')'):
            raise ValueError("Несоответствие скобок")
        out.append(t)
    return out

def is_number(tok):
    try:
        float(tok)
        return True
    except:
        return False

def is_variable(tok):
    if tok in FUNCTIONS or tok in ('(', ')', ',',) or tok in OPS or tok == UNARY:
        return False
    if tok == 'ans':
        return True
    return tok.isidentifier()

# вычисление ОПЗ
def eval_rpn(rpn, variables):
    st = []
    for tok in rpn:
        if is_number(tok):
            st.append(float(tok))
        elif tok in variables:
            st.append(variables[tok])
        elif tok in CONSTS:
            st.append(CONSTS[tok])
        elif tok == UNARY:
            if not st:
                raise ValueError("Отсутствует операнд для унарного '-'")
            a = st.pop()
            st.append(-a)
        elif tok in OPS:
            if len(st) < 2:
                raise ValueError(f"Отсутствует операнд для оператора {tok}")
            b = st.pop(); a = st.pop()
            st.append(apply_op(a, b, tok))
        elif tok in FUNCTIONS:
            if tok == 'log' and len(st) >= 2:
                b = st.pop(); a = st.pop()
                st.append(FUNCTIONS[tok](a, b))
            elif tok == 'pow' and len(st) >= 2:
                b = st.pop(); a = st.pop()
                st.append(FUNCTIONS[tok](a, b))
            else:
                if not st:
                    raise ValueError(f"Отсутствует операнд для функции {tok}")
                a = st.pop()
                if tok == 'factorial':
                    if not float(a).is_integer() or a < 0:
                        raise ValueError("factorial() определена только для неотрицательных целых")
                    st.append(FUNCTIONS[tok](int(a)))
                else:
                    st.append(FUNCTIONS[tok](a))
        else:
            raise ValueError(f"Неизвестный токен при вычислении: {tok}")
    if len(st) != 1:
        raise ValueError("Неверное выражение")
    return st[0]

def apply_op(a, b, op):
    if op == '+': return a + b
    if op == '-': return a - b
    if op == '*': return a * b
    if op == '/':
        if b == 0:
            raise ZeroDivisionError("деление на ноль")
        return a / b
    if op == '//':
        if b == 0:
            raise ZeroDivisionError("деление на ноль")
        return a // b
    if op == '%':
        if b == 0:
            raise ZeroDivisionError("деление по модулю на ноль")
        return a % b
    if op == '^':
        return a ** b
    raise ValueError(f"Неподдерживаемый оператор: {op}")

# верхнеуровневое вычисление, поддерживает присваивание name = expr
def evaluate(expr, variables):
    tokens = tokenize(expr)
    if not tokens:
        raise ValueError("Пустое выражение")
    # присваивание
    if len(tokens) >= 3 and tokens[1] == '=' and is_variable(tokens[0]):
        name = tokens[0]
        rhs = tokens[2:]
        rpn = to_rpn(rhs)
        val = eval_rpn(rpn, variables)
        variables[name] = val
        variables['ans'] = val
        return val, f"{name} = {val}"
    else:
        rpn = to_rpn(tokens)
        val = eval_rpn(rpn, variables)
        variables['ans'] = val
        return val, str(val)

def pretty(x):
    if isinstance(x, float):
        if x.is_integer():
            return str(int(x))
        else:
            return f"{x:.12g}"
    return str(x)

def banner():
    print("SuperKalk v1 - консольный калькулятор")
    print("Команды: exit, quit, help, vars, history, clear, load <файл>")
    print("Поддерживаемые функции: " + ", ".join(sorted(FUNCTIONS.keys())))
    print("Константы: " + ", ".join(f"{k}={v}" for k, v in CONSTS.items()))
    print("Пример: x = 2 * (3 + sqrt(4))")
    print()

def repl():
    variables = {'ans': 0.0}
    history = []
    banner()
    while True:
        try:
            line = input('>>> ').strip()
        except (EOFError, KeyboardInterrupt):
            print()
            break
        if not line:
            continue
        history.append(line)
        low = line.lower()
        if low in ('exit', 'quit'):
            break
        if low == 'help':
            print("Вводите выражения. Примеры:")
            print("  2 + 3*4")
            print("  sin(pi/2) + ln(e)")
            print("  x = 5")
            print("  ans * 2")
            continue
        if low == 'vars':
            for k, v in variables.items():
                print(f"{k} = {pretty(v)}")
            continue
        if low == 'history':
            for i, h in enumerate(history, 1):
                print(f"{i}: {h}")
            continue
        if low == 'clear':
            variables = {'ans': 0.0}
            history = []
            print("Переменные и история очищены.")
            continue
        if low.startswith('load '):
            fn = line.split(' ', 1)[1]
            try:
                with open(fn, 'r', encoding='utf-8') as f:
                    for l in f:
                        l = l.strip()
                        if not l or l.startswith('#'):
                            continue
                        print('> ' + l)
                        try:
                            val, out = evaluate(l, variables)
                            print(pretty(val))
                        except Exception as e:
                            print("Ошибка:", e)
            except Exception as e:
                print("Не удалось загрузить файл:", e)
            continue
        try:
            val, out = evaluate(line, variables)
            print(pretty(val))
        except Exception as e:
            print("Ошибка:", e)

if __name__ == '__main__':
    repl()
