class UnknownDataTypeError(BaseInterpreterError):
    def __str__(self):
        return 'Unknown data type error{}'.format(self.on_line) class StringError(BaseInterpreterError):
    def __init__(self, source, index):
        self._source = source
        self._index = index

    def __str__(self):
        return 'Invalid index {} on string "{}"{}'.format(self._index, self._source, self.on_line) class VariableAlreadyDefinedError(BaseInterpreterError):
    def __str__(self):
        return 'Variable already defined{}'.format(self.on_line) class EmptyDataStackError(BaseInterpreterError):
    def __str__(self):
        return 'Empty data stack error{}'.format(self.on_line) class UndefinedVariableError(BaseInterpreterError):
    def __init__(self, name, frame):
        self.name = name
        self.frame = frame

    def __str__(self):
        return 'Undefined variable {}@{}{}'.format(self.frame, self.name, self.on_line) class UndeclaredVariableError(BaseInterpreterError):
    def __init__(self, name, frame):
        self.name = name
        self.frame = frame

    def __str__(self):
        return 'Undeclared variable {}@{}{}'.format(self.frame, self.name, self.on_line) class FrameError(BaseInterpreterError):
    def __str__(self):
        return 'Frame error{}'.format(self.on_line) class UnknownLabelError(BaseInterpreterError):
    def __init__(self, label_name):
        self.label_name = label_name

    def __str__(self):
        return 'Unknown label {}{}'.format(self.label_name, self.on_line) class InvalidReturnError(BaseInterpreterError):
    def __str__(self):
        return 'Invalid return error{}'.format(self.on_line) class InvalidOperandTypeError(BaseInterpreterError):
    def __str__(self):
        return 'Invalid operand type{}'.format(self.on_line) class Operand(object):
    type = None
    # constant
    value = None
    # variable
    frame = None
    name = None
    # data type
    data_type = None
    # label
    label = None

    CONSTANT_MAPPING = {
        'bool': bool,
        'int': int,
        'float': float,
        'string': str,
    }

    CONSTANT_MAPPING_REVERSE = {k: v for v, k in CONSTANT_MAPPING.items()}

    BOOL_LITERAL_MAPPING = {'true': True, 'false': False}

    def __init__(self, value):
        # type: (str) -> None
        constant_match = CONSTANT_RE.match(value)
        if constant_match:
            self._resolve_constant(constant_match)
            return
        variable_match = VARIABLE_RE.match(value)
        if variable_match:
            self._resolve_variable(variable_match)
            return
        type_match = TYPE_RE.match(value)
        if type_match:
            self._resolve_type(type_match)
            return
        label_match = LABEL_RE.match(value)
        if label_match:
            # is label
            self.label = value
            self.type = TypeOperand.LABEL
            return

        raise InvalidCodeException(InvalidCodeException.INVALID_OPERAND)

    def _resolve_constant(self, constant_match: Match[str]) -> None:
        type_, value = constant_match.groups()  # type: str, str
        type_ = type_.lower().strip()
        try:
            self.value = self.CONSTANT_MAPPING.get(type_)(value)
            if type_ == self.CONSTANT_MAPPING_REVERSE.get(bool):
                self.value = self.BOOL_LITERAL_MAPPING.get(value.lower())
            elif type_ == self.CONSTANT_MAPPING_REVERSE.get(str):
                self.value = unquote_escape_sequences(value=self.value)
        except ValueError:
            pass
        if self.value is None:
            raise InvalidCodeException(type_=InvalidCodeException.INVALID_OPERAND)
        self.type = TypeOperand.CONSTANT

    def _resolve_variable(self, variable_match: Match[str]) -> None:
        # type: (Match) -> None
        frame, name = variable_match.groups()
        if not (frame and name):
            raise InvalidCodeException(type_=InvalidCodeException.INVALID_OPERAND)
        self.frame = frame
        self.name = name
        self.type = TypeOperand.VARIABLE

    def _resolve_type(self, type_match: Match[str]) -> None:
        # type: (Match[str]) -> None
        self.data_type = type_match.group(1).lower()
        if self.data_type not in self.CONSTANT_MAPPING:
            raise InvalidCodeException(type_=InvalidCodeException.INVALID_OPERAND)
        self.type = TypeOperand.DATA_TYPE

    def __str__(self):
        return 'Operand({})'.format(
            self.value or self.name or self.label
        ) class TypeOperand(IntEnum):
    VARIABLE = 1
    CONSTANT = 2
    LABEL = 3
    DATA_TYPE = 4 class InstructionPrices(object):
    _prices_table = {
        0: 'LABEL,BREAK,DPRINT,GROOT'.split(','),
        1: 'MOVE,DEFVAR,PUSHS,POPS,INT2FLOATS,FLOAT2INTS,FLOAT2R2EINTS,FLOAT2R2OINTS,INT2CHARS,STRI2INTS,JUMPIFEQS,'
           'JUMPIFNEQS'.split(','),
        2: 'PUSHFRAME,POPFRAME,CREATEFRAME,CLEARS,INT2FLOAT,FLOAT2INT,FLOAT2R2EINT,FLOAT2R2OINT,INT2CHAR,STRI2INT,'
           'JUMP,JUMPIFEQ,JUMPIFNEQ,TYPE'.split(','),
        3: 'ADDS,SUBS,MULS,DIVS,LTS,GTS,EQS,ANDS,ORS,NOTS'.split(','),
        4: 'ADD,SUB,MUL,DIV,LT,GT,EQ,AND,OR,NOT,READ,WRITE,CONCAT,STRLEN,GETCHAR,SETCHAR'.split(','),
        5: 'CALL,RETURN'.split(','),
    }

    INSTRUCTIONS = {
        name: value for value, names in _prices_table.items() for name in names
    }

    OPERAND_CONSTANT = 1
    OPERAND_STACK = 2
    OPERAND_VARIABLE = 4
import logging
import re
from io import StringIO
from typing import Optional, Union
from ifj2017.interpreter.exceptions import UnknownDataTypeError, StringError, VariableAlreadyDefinedError
from .exceptions import EmptyDataStackError, UndefinedVariableError, UndeclaredVariableError, \
    FrameError, UnknownLabelError, InvalidReturnError, InvalidOperandTypeError
from .operand import Operand, TypeOperand
from .prices import InstructionPrices
        frame[to.name] = self.get_value(what)

        # explicit variable access, only declaration
        self.operand_price += InstructionPrices.OPERAND_VARIABLE

    def define_variable(self, variable):
        # type: (Operand) -> None
        frame = self.frame(variable.frame)
        if variable.name in frame:
            raise VariableAlreadyDefinedError()

        frame[variable.name] = None
        self.operand_price += InstructionPrices.OPERAND_VARIABLE

    def call(self, op):
        # type: (Operand, Operand) -> None
        if op.label not in self.labels:
            raise UnknownLabelError(op.label)

        self.call_stack.append(self.program_counter)
        self.program_counter = self.labels.get(op.label)

    def return_(self):
        if not self.call_stack:
            raise InvalidReturnError()
        self.program_counter = self.call_stack[-1] + 1
        self.call_stack = self.call_stack[:-1]

    def jump(self, op):
        # type: (Operand) -> None
        if op.label not in self.labels:
            raise UnknownLabelError(op.label)

        self.program_counter = self.labels.get(op.label)

    def push_stack(self, op):
        value = self.get_value(op)
        logging.debug("Push {} to stack.".format(value))
        self.data_stack.append(value)
        self.operand_price += InstructionPrices.OPERAND_STACK

    def pop_stack(self, op=None):
        # type: (Operand) -> object
        if not self.data_stack:
            raise EmptyDataStackError()

        value = self.data_stack[-1]
        logging.debug("Pop {} from stack.".format(value))
        if op:
            # for operand is set, without is only returned
            self.set_value(op, value)

        self.operand_price += InstructionPrices.OPERAND_STACK
        self.data_stack = self.data_stack[:-1]
        return value

    def jump_if(self, op0, op1, op2, positive=True):
        equal = self.get_value(op1) == self.get_value(op2)
        if positive == equal:
            self.jump(op0)

    def set_char(self, where, index, from_):
        changed = self.get_value(where)
        changed[self.get_value(index)] = self.get_value(from_)[0]
        self.set_value(where, changed)

    def get_char(self, target, string, index):
        source = self.get_value(string)
        try:
            self.set_value(
                target,
                source[self.get_value(index)]
            )
        except IndexError:
            raise StringError(source, self.get_value(index))

    def str_len(self, target, string):
        return self.set_value(target, len(self.get_value(string)))

    def read(self, to, type_):
        # type: (Operand, Operand) -> None
        loaded = []
        input_ = self.stdin.readline().strip()  # type: str
        input_len = len(input_)
        if type_.data_type == Operand.CONSTANT_MAPPING_REVERSE.get(str):
            # is string
            i = input_[0] == "\""
            while i < input_len and input_[i] != '"':
                loaded.append(input_[i])
                i += 1
            try:
                self.set_value(to, ''.join(loaded))
            except ValueError:
                self.set_value(to, "")
        elif type_.data_type == Operand.CONSTANT_MAPPING_REVERSE.get(int):
            # is string
            i = 0
            while i < input_len and input_[i].isdecimal():
                loaded.append(input_[i])
                i += 1
            try:
                self.set_value(to, int(''.join(loaded)))
            except ValueError:
                self.set_value(to, 0)
        elif type_.data_type == Operand.CONSTANT_MAPPING_REVERSE.get(Operand.CONSTANT_MAPPING.get('float')):
            float_re = re.compile(r'^(\d+\.\d+)|(\d+[Ee][+-]?\d+)|(\d+)')
            match = float_re.match(input_)
            assert match
            try:
                self.set_value(to, Operand.CONSTANT_MAPPING.get('float')(match.group(0)))
            except ValueError:
                self.set_value(to, .0)
        elif type_.data_type == Operand.CONSTANT_MAPPING_REVERSE.get(bool):
            bool_re = re.compile(r'^(true|false)', re.IGNORECASE)
            match = bool_re.match(input_)
            if match:
                self.set_value(to, Operand.BOOL_LITERAL_MAPPING.get(match.group(0).lower()))
            else:
                self.set_value(to, False)
        else:

            raise UnknownDataTypeError()