Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- Simplified VM code which works for some cases.
- You need extend/rewrite code to pass all cases.
- """
- import builtins
- import dis
- import types
- import typing as tp
- # CO_VARARGS = 4
- # # CO_VARKEYWORDS = 8
- # #
- # # ERR_TOO_MANY_POS_ARGS = 'Too many positional arguments'
- # # ERR_TOO_MANY_KW_ARGS = 'Too many keyword arguments'
- # # ERR_MULT_VALUES_FOR_ARG = 'Multiple values for arguments'
- # # ERR_MISSING_POS_ARGS = 'Missing positional arguments'
- # # ERR_MISSING_KWONLY_ARGS = 'Missing keyword-only arguments'
- # #
- # #
- # # def bind_args(func: tp.Any, *args: tp.Any, **kwargs: tp.Any) -> tp.Dict[str, tp.Any]:
- # # """Bind values from `args` and `kwargs` to corresponding arguments of `func`
- # #
- # # :param func: function to be inspected
- # # :param args: positional arguments to be bound
- # # :param kwargs: keyword arguments to be bound
- # # :return: `dict[argument_name] = argument_value` if binding was successful,
- # # raise TypeError with one of `ERR_*` error descriptions otherwise
- # # """
- # # code_obj = func.__code__
- # # varargs_flag = code_obj.co_flags & CO_VARARGS
- # # kwargs_flag = code_obj.co_flags & CO_VARKEYWORDS
- # #
- # # all_variables = list(code_obj.co_varnames)
- # # num_positional_args: int = code_obj.co_argcount
- # # if len(args) > num_positional_args and not varargs_flag:
- # # raise TypeError(ERR_TOO_MANY_POS_ARGS)
- # #
- # # num_kwonly_args: int = code_obj.co_kwonlyargcount
- # # default_values: tp.Tuple[tp.Any] = tp.cast(tp.Tuple[tp.Any], func.__defaults__)
- # # kw_default_values: tp.Dict[str, tp.Any] = func.__kwdefaults__
- # #
- # # # Firstly, trying to bind positional arguments
- # # positional_args_binding: tp.Dict[str, tp.Any] = dict()
- # # all_positional_args = all_variables[:num_positional_args].copy()
- # # not_binded_positional_vars = all_variables[:num_positional_args].copy()
- # # # Simply bind `args` to positional arguments
- # # for var_name, value in zip(all_variables[:num_positional_args], args):
- # # positional_args_binding[var_name] = value
- # # not_binded_positional_vars.remove(var_name)
- # #
- # # # Then if `kwargs` was given check positional args and bind them if possible
- # # pos_kwargs = {kw: val for kw, val in kwargs.items() if kw in all_positional_args}
- # # for kwarg_name, value in pos_kwargs.items():
- # # if kwarg_name in positional_args_binding:
- # # raise TypeError(ERR_MULT_VALUES_FOR_ARG)
- # # positional_args_binding[kwarg_name] = value
- # # not_binded_positional_vars.remove(kwarg_name)
- # # kwargs.pop(kwarg_name)
- # #
- # # # Trying to bind positional arguments to their default values if they exists
- # # if default_values:
- # # # At this moment we already binded all positional arguments without default values
- # # # That's why we remove this args from `not_binded_positional_vars`
- # # for arg in all_positional_args[:num_positional_args - len(default_values)]:
- # # if arg in not_binded_positional_vars:
- # # not_binded_positional_vars.remove(arg)
- # # for var_name, default_value in zip(not_binded_positional_vars.copy(), default_values):
- # # if var_name not in positional_args_binding:
- # # positional_args_binding[var_name] = default_value
- # # not_binded_positional_vars.remove(var_name)
- # #
- # # # At this moment we must bind all positional arguments with their default/kwargs/args values
- # # if len(positional_args_binding) < num_positional_args:
- # # raise TypeError(ERR_MISSING_POS_ARGS)
- # #
- # # arg_binding: tp.Dict[str, tp.Any] = {**positional_args_binding}
- # # # Now we need to bind keyword arguments
- # # kwargs_binding: tp.Dict[str, tp.Any] = dict()
- # # # Let's go parse kw default values and merge with passed kwargs
- # # if kw_default_values:
- # # for kwarg_name, value in kw_default_values.items():
- # # # Don't overwrite value passed in kwargs
- # # if kwarg_name not in kwargs:
- # # kwargs[kwarg_name] = value
- # # # At this moment we remove all kwargs that corresponds to the positional argument
- # # if len(kwargs) < num_kwonly_args:
- # # raise TypeError(ERR_MISSING_KWONLY_ARGS)
- # # for kwarg_name, value in kwargs.items():
- # # if kwarg_name in all_variables[num_positional_args:]:
- # # kwargs_binding[kwarg_name] = value
- # # # Merge kwargs_binding to the existing bindings
- # # arg_binding = {**kwargs_binding, **arg_binding}
- # #
- # # # After all black magic maybe we need to parse remains arguments
- # # # But what remains? We just binds all possible positional and keyword arguments
- # # # We need to pass values to args and kwargs if they was given
- # # # Remove all already parsed values from args and kwargs
- # # if len(all_variables) > num_positional_args + num_kwonly_args:
- # # args_copy: tp.List[tp.Any] = list(args).copy()
- # # kwargs_copy: tp.Dict[str, tp.Any] = kwargs.copy()
- # # for val in positional_args_binding.values():
- # # if val in args_copy:
- # # args_copy.remove(val)
- # # for kwarg_name, value in kwargs_binding.items():
- # # if kwarg_name in kwargs_copy:
- # # kwargs_copy.pop(kwarg_name)
- # #
- # # if varargs_flag and not kwargs_flag:
- # # arg_binding[all_variables[-1]] = tuple(args_copy)
- # # elif kwargs_flag and not varargs_flag:
- # # arg_binding[all_variables[-1]] = kwargs_copy
- # # else:
- # # arg_binding[all_variables[-2]] = tuple(args_copy)
- # # arg_binding[all_variables[-1]] = kwargs_copy
- # # return arg_binding
- class Frame:
- """
- Frame header in cpython with description
- https://github.com/python/cpython/blob/3.6/Include/frameobject.h#L17
- Text description of frame parameters
- https://docs.python.org/3/library/inspect.html?highlight=frame#types-and-members
- """
- def __init__(self,
- frame_code: types.CodeType,
- frame_builtins: tp.Dict[str, tp.Any],
- frame_globals: tp.Dict[str, tp.Any],
- frame_locals: tp.Dict[str, tp.Any]) -> None:
- self.code = frame_code
- self.builtins = frame_builtins
- self.globals = frame_globals
- self.locals = frame_locals
- self.data_stack: tp.Any = []
- self.instruction_mapping: tp.Any = {instr.offset: instr for instr in dis.get_instructions(self.code)}
- self.max_offset = (len(self.instruction_mapping) - 1) * 2
- self.current_offset: int = 0
- self.loop_iterator_indexes: tp.Any = []
- self.loop_endings: tp.Any = []
- self.allow_change_offset_in_run: bool = True
- self.return_value = None
- def top(self) -> tp.Any:
- return self.data_stack[-1]
- def top1(self) -> tp.Any:
- return self.data_stack[-2]
- def top2(self) -> tp.Any:
- return self.data_stack[-3]
- def pop(self) -> tp.Any:
- return self.data_stack.pop()
- def push(self, *values: tp.Any) -> None:
- self.data_stack.extend(values)
- def popn(self, n: int) -> tp.Any:
- """
- Pop a number of values from the value stack.
- A list of n values is returned, the deepest value first.
- """
- if n > 0:
- returned = self.data_stack[-n:]
- self.data_stack[-n:] = []
- return returned
- else:
- return []
- def run(self) -> tp.Any:
- while self.current_offset <= self.max_offset:
- instruction = self.instruction_mapping[self.current_offset]
- getattr(self, instruction.opname.lower() + "_op")(instruction.argval)
- if self.allow_change_offset_in_run:
- self.current_offset = instruction.offset + 2
- else:
- self.allow_change_offset_in_run = True
- return self.return_value
- def call_function_op(self, arg: int) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-CALL_FUNCTION
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L3121
- """
- arguments = self.popn(arg)
- f = self.pop()
- self.push(f(*arguments))
- def load_name_op(self, arg: str) -> None:
- """
- Partial realization
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-LOAD_NAME
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L2057
- """
- if arg in self.locals:
- self.push(self.locals[arg])
- elif arg in self.globals:
- self.push(self.globals[arg])
- elif arg in self.builtins:
- self.push(self.builtins[arg])
- def load_method_op(self, method_name: str) -> None:
- obj = self.pop()
- if hasattr(obj, method_name):
- self.push(getattr(obj, method_name))
- else:
- raise AttributeError
- def call_method_op(self, arg: str) -> None:
- method_pos_args = int(arg)
- args = self.popn(method_pos_args)
- method = self.pop()
- self.push(method(*args))
- def load_fast_op(self, arg: str) -> None:
- self.push(self.locals[arg])
- def load_global_op(self, arg: str) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-LOAD_GLOBAL
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L2108
- """
- if arg in self.globals:
- self.push(self.globals[arg])
- elif arg in self.builtins:
- self.push(self.builtins[arg])
- def load_const_op(self, arg: tp.Any) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-LOAD_CONST
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L1088
- """
- self.push(arg)
- def return_value_op(self, arg: tp.Any) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-RETURN_VALUE
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L1641
- """
- self.return_value = self.pop()
- def pop_top_op(self, arg: tp.Any) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-POP_TOP
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L1102
- """
- self.pop()
- def make_function_op(self, arg: int) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-MAKE_FUNCTION
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L3203
- Parse stack:
- https://github.com/python/cpython/blob/3.7/Objects/call.c#L158
- Call function in cpython:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L4554
- """
- name = self.pop() # the qualified name of the function (at TOS) # noqa
- code = self.pop() # the code associated with the function (at TOS1)
- # TODO: use arg to parse function defaults
- def f(*args: tp.Any, **kwargs: tp.Any) -> tp.Any:
- # TODO: parse input arguments using code attributes such as co_argcount
- parsed_args: tp.Dict[str, tp.Any] = {}
- for i in range(code.co_argcount):
- parsed_args[code.co_varnames[i]] = args[i]
- f_locals = dict(self.locals)
- f_locals.update(parsed_args)
- frame = Frame(code, self.builtins, self.globals, f_locals) # Run code in prepared environment
- return frame.run()
- self.push(f)
- def store_name_op(self, arg: str) -> None:
- """
- Operation description:
- https://docs.python.org/3/library/dis.html#opcode-STORE_NAME
- Operation realization:
- https://github.com/python/cpython/blob/3.7/Python/ceval.c#L1923
- """
- const = self.pop()
- self.locals[arg] = const
- def delete_global_op(self, arg: str) -> None:
- if arg in self.globals:
- self.globals.pop(arg)
- elif arg in self.builtins:
- self.builtins.pop(arg)
- else:
- raise NameError
- def store_fast_op(self, arg: str) -> None:
- self.locals[arg] = self.pop()
- def delete_fast_op(self, arg: str) -> None:
- if arg in self.locals:
- self.locals.pop(arg)
- else:
- raise NameError
- def store_global_op(self, arg: str) -> None:
- global_op_name = self.pop()
- self.globals[arg] = global_op_name
- def inplace_add_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] += right_operand
- def binary_add_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand + right_operand)
- def inplace_subtract_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] -= right_operand
- def binary_subtract_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand - right_operand)
- def inplace_multiply_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] *= right_operand
- def binary_multiply_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand * right_operand)
- def inplace_floor_divide_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] //= right_operand
- def binary_floor_divide_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand // right_operand)
- def inplace_true_divide_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] /= right_operand
- def binary_true_divide_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand / right_operand)
- def inplace_lshift_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] <<= right_operand
- def binary_lshift_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand << right_operand)
- def inplace_rshift_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] >>= right_operand
- def binary_rshift_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand >> right_operand)
- def inplace_modulo_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] %= right_operand
- def binary_modulo_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand % right_operand)
- def inplace_power_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] **= right_operand
- def binary_power_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand ** right_operand)
- def inplace_and_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] &= right_operand
- def binary_and_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand & right_operand)
- def inplace_or_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] |= right_operand
- def binary_or_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand | right_operand)
- def inplace_xor_op(self, arg: str) -> None:
- right_operand = self.pop()
- self.data_stack[-1] ^= right_operand
- def binary_xor_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- self.push(left_operand ^ right_operand)
- def get_iter_op(self, arg: str) -> None:
- self.data_stack[-1] = iter(self.top())
- self.loop_iterator_indexes.append(len(self.data_stack) - 1)
- def for_iter_op(self, out_loop_offset: int) -> None:
- iterator_stack_idx = self.loop_iterator_indexes[-1]
- iterator = self.data_stack[iterator_stack_idx]
- try:
- iter_res = next(iterator)
- self.push(iter_res)
- except StopIteration:
- self.pop()
- self.current_offset = self.loop_endings.pop()
- self.allow_change_offset_in_run = False
- def jump_absolute_op(self, offset: int) -> None:
- self.current_offset = offset
- self.allow_change_offset_in_run = False
- def setup_loop_op(self, arg: str):
- self.loop_endings.append(int(arg))
- def compare_op_op(self, arg: str) -> None:
- right_operand = self.pop()
- left_operand = self.pop()
- cmp_result = False
- if arg == "==":
- cmp_result = left_operand == right_operand
- elif arg == "!=":
- cmp_result = left_operand != right_operand
- elif arg == "in":
- cmp_result = left_operand in right_operand
- elif arg == "not in":
- cmp_result = left_operand not in right_operand
- elif arg == ">":
- cmp_result = left_operand > right_operand
- elif arg == ">=":
- cmp_result = left_operand >= right_operand
- elif arg == "<":
- cmp_result = left_operand < right_operand
- elif arg == "<=":
- cmp_result = left_operand <= right_operand
- else:
- raise NotImplementedError
- self.push(cmp_result)
- def pop_jump_if_false_op(self, arg: str) -> None:
- condition = self.pop()
- if not condition:
- self.current_offset = int(arg)
- self.allow_change_offset_in_run = False
- def pop_jump_if_true_op(self, arg: str) -> None:
- condition = self.pop()
- if condition:
- self.current_offset = int(arg)
- self.allow_change_offset_in_run = False
- def extended_arg_op(self, arg: str) -> None:
- pass
- def unpack_sequence_op(self, arg: str) -> None:
- sequence = list(self.pop())
- self.data_stack.extend(sequence[::-1])
- def unary_positive_op(self, arg: str) -> None:
- value = self.top()
- self.data_stack[-1] = +value
- def unary_negative_op(self, arg: str) -> None:
- value = self.top()
- self.data_stack[-1] = -value
- def unary_invert_op(self, arg: str) -> None:
- value = self.top()
- self.data_stack[-1] = ~value
- def unary_not_op(self, arg: str) -> None:
- value = self.top()
- self.data_stack[-1] = not value
- def build_list_op(self, arg: str) -> None:
- num_values = int(arg)
- values = list(self.popn(num_values))
- self.push(values)
- def build_tuple_op(self, arg: str) -> None:
- num_values = int(arg)
- values = tuple(self.popn(num_values))
- self.push(values)
- def build_set_op(self, arg: str) -> None:
- num_values = int(arg)
- values = set(self.popn(num_values))
- self.push(values)
- def build_const_key_map_op(self, arg: str) -> None:
- num_values = int(arg)
- keys = self.pop()
- result_dict = dict.fromkeys(keys)
- values = self.popn(num_values)
- for i in range(num_values):
- result_dict[keys[i]] = values[i]
- self.push(result_dict)
- def build_map_op(self, arg_count: int) -> None:
- values = self.popn(arg_count)
- keys = self.popn(arg_count)
- result_map = dict()
- for i in range(arg_count):
- result_map[keys[i]] = values[i]
- self.push(result_map)
- def build_slice_op(self, num_slice_args: int) -> None:
- if num_slice_args == 2:
- start, end = self.popn(num_slice_args)
- self.push(slice(start, end))
- else:
- start, end, step = self.popn(num_slice_args)
- self.push(slice(start, end, step))
- def store_subscr_op(self, arg: str) -> None:
- subscr_value = self.top()
- subscr_obj = self.top1()
- value_to_assign = self.top2()
- subscr_obj[subscr_value] = value_to_assign
- def delete_subscr_op(self, arg: str) -> None:
- subscr_value = self.top()
- subscr_obj = self.top1()
- del subscr_obj[subscr_value]
- def binary_subscr_op(self, arg: str) -> None:
- subscr_value = self.pop()
- obj = self.pop()
- self.push(obj[subscr_value])
- class VirtualMachine:
- def run(self, code_obj: types.CodeType) -> None:
- """
- :param code_obj: code for interpreting
- """
- globals_context: tp.Dict[str, tp.Any] = {}
- frame = Frame(code_obj, builtins.globals()['__builtins__'], globals_context, globals_context)
- return frame.run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement