Skip to main content
Rollback to Revision 5
Source Link
Mast
  • 13.5k
  • 12
  • 54
  • 119
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            tryif self.is_digit(item):
                result_stack.append(int(item))
            except ValueErrorelse:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))

         return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two


    def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            try:
                result_stack.append(int(item))
            except ValueError:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))

         return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            if self.is_digit(item):
                result_stack.append(item)
            else:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))
        return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
added 68 characters in body
Source Link
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two


        def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            try:
                result_stack.append(int(item))
            except ValueError:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))

        return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two


        def get_expression_result(self):
    result_stack: list = []

    while self.rpn_stack:
        item = self.rpn_stack.popleft()
        try:
            result_stack.append(int(item))
        except ValueError:
            second, first = result_stack.pop(), result_stack.pop()
            result_stack.append(self.calculate_this(first, second, item))

    return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two


    def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            try:
                result_stack.append(int(item))
            except ValueError:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))

        return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
deleted 355 characters in body
Source Link
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

        def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            if self.is_digit(item)try:
                result_stack.append(int(item))
           except elseValueError:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))
     
    return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    def get_expression_result(self):
        result_stack: list = []

        while self.rpn_stack:
            item = self.rpn_stack.popleft()
            if self.is_digit(item):
                result_stack.append(item)
            else:
                second, first = result_stack.pop(), result_stack.pop()
                result_stack.append(self.calculate_this(first, second, item))
        return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
from string import ascii_letters
from collections import deque


class SyntacticalAnalyzer:
    `"""
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
Addition and subtraction operations are allowed.
Commands begin with a slash and can be: /exit and /help
    """`
    _digits_tags: str = '1234567890'
    _commands = ['/exit', '/help']
    left_part: str = None
    expression_stack: list = []
    operator_priority: dict = {
        '(': 0,
        '+': 2,
        '-': 2,
        '*': 3,
        '/': 3,
        '^': 4,
        ')': 0
    }
    rpn_stack: deque = deque()

    @staticmethod
    def check_ascii(name):
        for letter in name:
            if letter not in ascii_letters:
                return False
        return True

    @property
    def checked_string(self) -> str:
        return self.__checked_string

    @checked_string.setter
    def checked_string(self, value: str):
        self.__checked_string = value

    @property
    def check_result(self):
        return self.res

    def __init__(self):
        self.__checked_string: str = ''
        self._state: str = 'assignment operator'
        # scan chain bypass rules
        self.chain_rules: dict = dict(skip=False,
                                      off=False
                                      )
        # check status
        self._status = dict(checker='',
                            error=None,
                            check_res=False,
                            )
        # object passed to the wrapper class
        self.res = dict(error=None,
                        state=None,
                        command=None,
                        left=None,
                        rpn_expression=None
                        )
        # list of test functions
        self._check_chain: list = [self.check_not_empty,
                                   self.check_command_tag,
                                   self.check_command_incorrectness,
                                   self.check_equality_tag,
                                   self.check_left_part,
                                   self.check_right_part,
                                   self.to_rpn
                                   ]

    def notify(self, checker: str, check_res: bool):
        """
       Passes the function name and the result of its work to the self._status object
     @param checker: name of the function passed
     @type checker: str
     @param check_res: result of the function passed
     @type check_res: bool
     @return: None
        """
        self._status['checker'] = checker
        self._status['check_res'] = check_res

    def check_status_handler(self):
        """
        Reads the modified self._status object, sets self._state
         and modifies the self.chain_rules object
        @return: None
        """

        if self._status['checker'] == 'check_not_empty' and not self._status['check_res']:
            self._status['error'] = 'empty'
            self._state = 'empty'
        if self._status['checker'] == 'check_command_tag':
            if self._status['check_res']:
                self._state = 'command'
            else:
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_command_incorrectness':
            if not self._status['check_res']:
                self._status['error'] = self.add_command()
                self.chain_rules['off'] = True
            else:
                self.chain_rules['off'] = True
        if self._status['checker'] == 'check_equality_tag':
            if not self._status['check_res']:
                self._state = 'expression'
                self.chain_rules['skip'] = True

        if self._status['checker'] == 'check_left_part':
            if not self._status['check_res']:
                self._status['error'] = 'Invalid identifier'

        if self._status['checker'] == 'check_right_part':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                    self.chain_rules['skip'] = True
                else:
                    self._status['error'] = 'Invalid identifier'
                    self.chain_rules['skip'] = True
        if self._status['checker'] == 'to_rpn':
            if not self._status['check_res']:
                if self._state == 'assignment operator':
                    self._status['error'] = 'Invalid assignment'
                else:
                    self._status['error'] = 'Invalid expression'

    def perform_res(self):
        """
        Checks self._state and self._status. Fills out the dictionary self.res
        @return: None
        """
        self.res['state'] = self._state
        if self._state == 'empty':
            self.res['error'] = 'empty'
            self.chain_rules['off'] = True
        if self._state == 'command':
            if self._status['error'] is None:
                self.res['command'] = self.add_command()
            else:
                self.res['error'] = self._status['error']
        if self._state == 'assignment operator':
            if self._status['error'] is None:
                self.res['left'] = self.left_part
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']
        if self._state == 'expression':
            if self._status['error'] is None:
                self.res['rpn_expression'] = self.rpn_stack
            else:
                self.res['error'] = self._status['error']

    def clear_init_fields(self):
        """
        Clears all constructor fields before checking for a new line
        @return: None
        """
        self._state = 'assignment operator'
        self._status['checker'] = ''
        self._status['error'] = None
        self._status['check_res'] = False
        self.chain_rules['skip'] = False
        self.chain_rules['off'] = False

        for key, value in self.res.items():
            if type(value) != dict:
                self.res[key] = None
        self.expression_stack = []

    def run_check_chain(self):
        """
        Starts a string check chain. Reads objects self.chain_rules and
        self._status, if the skip == True property skips the next check,
         if the property off == True or one of the checks has completed
         with an error, terminates its work
        @return: None
        """
        j = -1
        self.clear_init_fields()

        for i, check in enumerate(self._check_chain):
            if j == i:
                self.chain_rules['skip'] = False
            if self.chain_rules['skip']:
                j = i + 1 if i + 1 < len(self._check_chain) else -1
                continue
            self.run_check(check)
            self.check_status_handler()
            if self._status['error'] is not None:
                break
            if self.chain_rules['off']:
                break
        self.perform_res()

    def run_check(self, check_func):
        """
        @type check_func: function
        """
        result = check_func()
        self.notify(check_func.__name__, result)

    def check_not_empty(self):
        return self.checked_string != ''

    def check_command_tag(self):
        return self.checked_string.startswith('/')

    def check_command_incorrectness(self):
        return self.checked_string in self._commands

    def add_command(self) -> str:
        for _command in self._commands:
            if self.checked_string == _command:
                return _command
        return 'Unknown command'

    def check_equality_tag(self) -> bool:
        return '=' in self.checked_string

    def is_variable(self, name: str) -> bool:
        return all([len(name) >= 1, self.check_ascii(name)])

    def check_left_part(self):
        if self._state == 'assignment operator':
            self.left_part = self.checked_string.split('=')[0].strip()
            return self.is_variable(self.left_part)

    @staticmethod
    def get_fragment_params(value: str, end):
        out_str = ''
        pos = 0
        sym = value[0]
        while sym not in end:
            out_str += sym
            try:
                pos += 1
                sym = value[pos]
            except IndexError:
                return out_str, None
        return out_str, pos

    @staticmethod
    def is_operator(item: str):
        item_list: list = item.strip().split(' ')
        my_str = ''.join(item_list)
        if my_str[0] in '+-':
            for el in my_str:
                if el not in '+-':
                    return False
        if my_str[0] in '/*^':
            if len(my_str) > 1:
                return False
        return True

    @staticmethod
    def is_digit(item: str):
        if item[0] == '0':
            if len(item) != 1:
                return False
            return True
        for el in item:
            if el not in '1234567890':
                return False
        return True

    @staticmethod
    def is_left_parenthesis(item: str):
        for el in item:
            if el not in '(':
                return False
        return True

    @staticmethod
    def is_right_parenthesis(item: str):
        for el in item:
            if el not in ')':
                return False
        return True

    @staticmethod
    def get_first(value: str):
        return value[0] if value else None

    @staticmethod
    def get_tag(letter: str):
        if letter in ascii_letters:
            return 'variable'
        if letter in '-+/*^':
            return 'operator'
        if letter in '1234567890':
            return 'digit'
        if letter in '()':
            return 'left parenthesis' if letter == '(' else 'right parenthesis'

    @staticmethod
    def get_end_tag(tag: str) -> str:
        if tag == 'variable':
            return ' )+-/*^'
        if tag == 'operator':
            return '(0123456789' + ascii_letters
        if tag == 'digit':
            return ' )+-/*^'
        if tag == 'left parenthesis':
            return ' 0123456789' + ascii_letters + '+-'
        if tag == 'right parenthesis':
            return ' +-/*^'

    @staticmethod
    def transform_operator(el: str):
        if '-' in el or '+' in el:
            minus_cnt = el.count('-')
            if minus_cnt:
                return '-' if minus_cnt % 2 != 0 else '+'
            return '+'
        return el

    @staticmethod
    def transform_parenthesis(el: str):
        return list(el)

    def transform_element(self, el: str, tag: str):
        if tag == 'operator':
            return self.transform_operator(el)
        if tag in ['left parenthesis', 'right parenthesis']:
            return self.transform_parenthesis(el.rstrip())
        return el.rstrip()

    @staticmethod
    def add_el(container: list, el):
        if type(el) == list:
            container += el
        else:
            container.append(el.rstrip())

    def check_right_part(self):
        next_pos = 0
        if self._state == 'assignment operator':
            input_str = self.checked_string.split('=', 1)[1].strip()
        else:
            input_str = self.checked_string.strip()
        if not input_str:
            return False
        while True:
            current: str = input_str[next_pos:]
            sym: str = self.get_first(current)
            name = self.get_tag(sym)
            end_tag = self.get_end_tag(name)
            el, offset = self.get_fragment_params(value=current, end=end_tag)

            conditions = [
                self.is_variable(el),
                self.is_operator(el),
                self.is_digit(el),
                self.is_left_parenthesis(el),
                self.is_right_parenthesis(el)
            ]
            if not any(conditions):
                return False
            el = self.transform_element(el=el, tag=name)
            if not self.expression_stack or self.expression_stack[-1] == '(':
                if el in '+-':
                    self.expression_stack.append('0')
            self.add_el(self.expression_stack, el)
            if offset is None:
                return True
            temp = current[offset:]
            offset += temp.find(temp.lstrip())
            next_pos += offset

    def to_rpn(self):
        f = False
        operators: list = []
        for item in self.expression_stack:
            if self.is_digit(item) or self.is_variable(item):
                self.rpn_stack.append(item)
            else:
                if not operators:
                    operators.append(item)
                else:
                    if item == '(' or self.operator_priority[item] > self.operator_priority[operators[-1]]:
                        operators.append(item)
                    else:
                        if not operators:
                            return False
                        while operators:
                            operator = operators.pop()
                            if operator == '(':
                                f = True
                                break
                            self.rpn_stack.append(operator)
                        if item == ')' and not f:
                            return False
                        if item != ')':
                            operators.append(item)

        if operators:
            if '(' in operators:
                return False
            else:
                while operators:
                    self.rpn_stack.append(operators.pop())
        return True

    # End of class SyntacticalAnalyzer


class Interpreter:
    bye_string = 'Bye!'
    help_string = 'The program calculates expressions using addition, subtraction, multiplication, integer division' \
                  ' and exponentiation over a set of integers, and also uses variables.'

    def __init__(self, obj):
        self.variables: dict = {}
        self.obj = obj
        self.error: str = None
        self.res: int = None
        self.rpn_stack: deque = deque()

    def execute(self):
        if not self.analysis_handler():
            return False
        return True

    def analysis_handler(self):
        """
        Читает self.obj.
        @return:
        """
        self.rpn_stack = deque()
        self.res = None
        self.error = None
        if self.obj['state'] == 'empty':
            pass
        if self.obj['state'] == 'command':
            if not self.command_handler(self.obj['command']):
                return False
        if self.obj['state'] == 'expression':
            if not self.expression_handler():
                print(self.error)
            else:
                print(self.res)

        if self.obj['state'] == 'assignment operator':
            if not self.assignment_handler():
                print(self.error)
        return True

    def command_handler(self, param: str) -> bool:
        if param == '/exit':
            print(self.bye_string)
            return False
        if param == '/help':
            print(self.help_string)
            return True

    def expression_handler(self):
        if not self.check_variables():
            return False
        self.res = self.get_expression_result()
        return True

    @staticmethod
    def calculate_this(one, two, sign):
        one, two = [int(x) for x in [one, two]]
        if sign == '+':
            return one + two
        if sign == '-':
            return one - two
        if sign == '*':
            return one * two
        if sign == '/':
            return one // two
        if sign == '^':
            return one ** two


        def get_expression_result(self):
    result_stack: list = []

    while self.rpn_stack:
        item = self.rpn_stack.popleft()
        try:
            result_stack.append(int(item))
        except ValueError:
            second, first = result_stack.pop(), result_stack.pop()
            result_stack.append(self.calculate_this(first, second, item))
 
    return result_stack[0]

    def assignment_handler(self):
        if not self.expression_handler():
            return False
        left = self.obj['left']
        self.variables[left] = self.res
        return True

    def check_variables(self):
        self.rpn_stack = self.obj['rpn_expression']
        for i, item in enumerate(self.rpn_stack):
            if item in self.variables:
                self.rpn_stack[i] = self.variables[item]
            else:
                if item[0] in ascii_letters:
                    return False
        return True


class SmartCalculator:
    """
    The name of a variable (identifier) can contain only Latin letters.
A variable can have a name consisting of more than one letter.
The case is also important; for example, n is not the same as N.
The value can be an integer number or a value of another variable.
It should be possible to set a new value to an existing variable.
To print the value of a variable you should just type its name.
    """
    _analyzer_methods = ['run_check_chain']
    _interpreter_methods = ['execute', 'analysis_handler']

    def __init__(self):
        self._analyzer: SyntacticalAnalyzer = SyntacticalAnalyzer()
        self.analyzer_result: dict = self._analyzer.check_result
        self._interpreter: Interpreter = Interpreter(self.analyzer_result)

    def __getattr__(self, item):
        for item in self._analyzer_methods + self._interpreter_methods:
            if item in self._analyzer_methods:
                return getattr(self._analyzer, item)
            if item in self._interpreter_methods:
                return getattr(self._interpreter, item)

    def run(self):
        while True:
            self._analyzer.checked_string = input().strip()
            self._analyzer.run_check_chain()
            if self._analyzer.res['error'] is not None and self._analyzer.res['error'] != 'empty':
                print(self._analyzer.res['error'])
            else:
                if not self._interpreter.execute():
                    return None


calculator = SmartCalculator()
calculator.run()
added 1 character in body
Source Link
Loading
edited title
Link
Loading
added 42 characters in body; edited tags; edited title
Source Link
Peilonrayz
  • 43.5k
  • 7
  • 76
  • 155
Loading
added 582 characters in body
Source Link
Loading
Source Link
Loading