Beispiel #1
0
    def __init__(self):
        self.parser = Parser()
        self.environment = Environment()
        self.current_statement_index = 0
        self.statements = []
        self.sub_return_locations = []
        self.array_parser = ArrayParser()

        self.__program_path = None
        self.prog_args = []
        self.__subroutine_body_locations = {}
Beispiel #2
0
    def __init__(self):
        self.parser = Parser()
        self.environment = Environment()
        self.current_statement_index = 0
        self.statements = []
        self.sub_return_locations = []
        self.array_parser = ArrayParser()

        self.__program_path = None
        self.prog_args = []
        self.__subroutine_body_locations = {}
Beispiel #3
0
class Interpreter:
    """This class is used to execute Microsoft Small Basic code."""
    def __init__(self):
        self.parser = Parser()
        self.environment = Environment()
        self.current_statement_index = 0
        self.statements = []
        self.sub_return_locations = []
        self.array_parser = ArrayParser()

        self.__program_path = None
        self.prog_args = []
        self.__subroutine_body_locations = {}

    def execute_code(self, code, args=None, program_path=None):
        """
        Executes the given Microsoft Small Basic code, given as a string.

        :param code: The string containing Microsoft Small Basic code.
        :param args: A list of arguments to the Microsoft Small Basic program.
        """
        self.__init_tk()

        if args is None:
            self.prog_args = []
        else:
            self.prog_args = args

        self.statements = self.parser.parse(code)
        if self.statements:
            self.__scan_statements()
            if program_path:
                self.__program_path = os.path.join(
                    os.path.dirname(program_path),
                    '')  # .join to ensure trailing slash
            else:
                self.__program_path = ""
            self.__tk_root.after(1, self.__start_main_thread)
            self.__tk_root.mainloop()
        self._exit()

    def execute_file(self, file_path, args=None):
        """
        Executes the given Microsoft Small Basic source code file.

        :param file_path: The string path to a Microsoft Small Basic source code file.
        :param args: A list of arguments to the Microsoft Small Basic program.
        """
        with open(file_path) as code_file:
            code = code_file.read()
            self.execute_code(code, args, code_file.name)

    @property
    def program_path(self):
        """Returns the path of the directory containing the currently executing script, or the empty string if there is
        no currently executing script or the script is being executed from a string."""
        return self.__program_path

    def __init_tk(self):
        self.__tk_root = tk.Tk()
        self.__tk_root.withdraw()
        self.msb_objects = {
            "Clock": modules.Clock(self),
            "Math": modules.Math(self),
            "TextWindow": modules.TextWindow(self, self.__tk_root),
            "GraphicsWindow": modules.GraphicsWindow(self, self.__tk_root),
            "Text": modules.Text(self),
            "Stack": modules.Stack(self),
            "Network": modules.Network(self),
            "File": modules.FileModule(self),
            "Desktop": modules.Desktop(self, self.__tk_root),
            "Array": modules.Array(self),
            "Program": modules.Program(self),
            "Timer": modules.Timer(self),
            "Mouse": modules.Mouse(self),
        }

        self.__threads = []

    def __scan_statements(self):
        # Save the line numbers for each subroutine body
        self.__subroutine_body_locations.clear()
        for line_number, statement in enumerate(self.statements):
            if isinstance(statement, ast.SubStatement):
                self.__subroutine_body_locations[
                    statement.sub_name.lower()] = line_number + 1

    def __start_main_thread(self):
        p = InterpreterThread(self, 0)
        self.__threads.append(p)
        p.start()
        self.__tk_root.after(1, self.__check_threads_finished)

    def _call_subroutine_in_new_thread(self, sub_name):
        line_number = self.__subroutine_body_locations[sub_name.lower()]
        p = InterpreterThread(self, line_number)
        self.__threads.append(p)
        p.start()

    def __check_threads_finished(self):
        for thread in self.__threads[:]:
            if not thread.is_alive():
                self.__threads.remove(thread)
        if not self.__threads:
            if not (self.msb_objects["GraphicsWindow"].is_visible()
                    or self.msb_objects["TextWindow"].is_visible()):
                self._exit()
        else:
            self.__tk_root.after(100, self.__check_threads_finished)

    def _exit(self, status=None):
        # TODO: make this able to close all running interpreter threads
        self.__tk_root.quit()
        self.__program_path = ""

    def _assign(self, destination_ast, value_ast, line_number=None):

        if isinstance(destination_ast, ast.UserVariable):
            # Assigning to variable as an array using one or more indices
            if destination_ast.array_indices:
                array_string = self.environment.get_variable(
                    destination_ast.variable_name)
                index_values = [
                    str(self._evaluate_expression_ast(index_ast))
                    for index_ast in destination_ast.array_indices
                ]
                value = str(self._evaluate_expression_ast(value_ast))

                # TODO: raise error when trying to assign to a subroutine name or subroutine call.

                new_array_string = self.array_parser.set_value(
                    array_string, index_values, value)
                self.environment.bind(destination_ast.variable_name,
                                      new_array_string)

            # Just overwriting variable
            else:
                self.environment.bind(destination_ast.variable_name,
                                      self._evaluate_expression_ast(value_ast))

        if isinstance(destination_ast, ast.MsbObjectField):
            msb_object = self.msb_objects[modules.utilities.capitalize(
                destination_ast.msb_object)]
            member_name = modules.utilities.capitalize(
                destination_ast.msb_object_field_name)

            # Determine if this is a function or an event
            info = modules.utilities.get_msb_builtin_info(
                destination_ast.msb_object, member_name)
            if info.type == "event":
                msb_object.set_event_sub(member_name, value_ast.variable_name)
            else:
                value = str(self._evaluate_expression_ast(value_ast))
                setattr(msb_object, member_name, value)

    def _increment_value(self, var_ast):
        # Increments the variable that has the given name by 1.  If not defined, this defines var_name to be 1.
        if isinstance(var_ast, ast.UserVariable):
            val = self._evaluate_operation("+", var_ast, ast.LiteralValue(1))
            self.environment.bind(var_ast.variable_name, val)
        else:
            raise errors.PyMsbRuntimeError(
                "Internal error - tried to increment non-UserVariable in Interpreter.increment_value"
            )

    def _evaluate_comparison_ast(self, val_ast):
        # Important - returns "True" or "False" if this is actually a comparison
        # Otherwise, evaluates as an expression and returns the string result (e.g. "10" or "concatstr" or even "true")
        if isinstance(val_ast, ast.Comparison):
            return self._evaluate_comparison(val_ast.comparator, val_ast.left,
                                             val_ast.right)
        return self._evaluate_expression_ast(val_ast)

    def _evaluate_expression_ast(self, val_ast):
        if isinstance(val_ast, ast.LiteralValue):
            return val_ast.value  # always a string

        if isinstance(val_ast, ast.UserVariable):
            value = self.environment.get_variable(val_ast.variable_name)

            # If accessing variable as an array
            if val_ast.array_indices:
                index_values = [
                    str(self._evaluate_expression_ast(i))
                    for i in val_ast.array_indices
                ]
                return self.array_parser.get_value(value, index_values)

            # Just accessing variable
            return value

        if isinstance(val_ast, ast.MsbObjectField):
            return self._evaluate_object_field(val_ast.msb_object,
                                               val_ast.msb_object_field_name)

        if isinstance(val_ast, ast.Operation):
            return self._evaluate_operation(val_ast.operator, val_ast.left,
                                            val_ast.right)

        if isinstance(val_ast, ast.MsbObjectFunctionCall):
            return self._execute_function_call(val_ast.msb_object,
                                               val_ast.msb_object_function,
                                               val_ast.parameter_asts)

        raise NotImplementedError(val_ast)

    def _execute_function_call(self, obj_name, fn_name, arg_asts):
        arg_vals = [
            str(self._evaluate_expression_ast(arg_ast)) for arg_ast in arg_asts
        ]
        fn = getattr(self.msb_objects[modules.utilities.capitalize(obj_name)],
                     modules.utilities.capitalize(fn_name))
        return fn.__call__(*arg_vals)

    def _evaluate_object_field(self, obj_name, field_name):
        return getattr(
            self.msb_objects[modules.utilities.capitalize(obj_name)],
            modules.utilities.capitalize(field_name))

    def _evaluate_comparison(self, comp, left, right):
        # returns "True" or "False" - VERY IMPORTANT NOTE: returns the strings and not boolean values.
        # possible comp values are strings "<=", ">=", "<", ">", "<>", "="
        # left, right are asts

        left = self._evaluate_comparison_ast(left)
        right = self._evaluate_comparison_ast(right)
        if comp == "=":
            return str(left == right)
        if comp == "<>":
            return str(left != right)
        if comp.lower() == "and":
            return str(
                str(left).lower() == "true" and str(right).lower() == "true")
        if comp.lower() == "or":
            return str(
                str(left).lower() == "true" or str(right).lower() == "true")

        # At this point, anything that is non-numerical is treated like 0
        left = modules.utilities.numericize(left, True)
        right = modules.utilities.numericize(right, True)

        return str(
            ((comp == "<" and left < right) or (comp == "<=" and left <= right)
             or (comp == ">" and left > right)
             or (comp == ">=" and left >= right)))

    # FIXME: fix this so ("x is " + "00") returns "x is 00" and not "x is 0"
    def _evaluate_operation(self, op, left, right):
        # op is "+", "-", "*" or "/"
        # left, right are expression asts
        left = self._evaluate_expression_ast(left)
        if isinstance(left, str):
            left = modules.utilities.numericize(left, op != "+")
        right = self._evaluate_expression_ast(right)
        if isinstance(right, str):
            right = modules.utilities.numericize(right, op != "+")

        if op == "+":
            try:
                return str(left + right)
            except TypeError:
                return str(left) + str(right)

        if op == "-":
            return left - right
        if op == "*":
            return left * right
        if op == "/":
            return left / right
Beispiel #4
0
class Interpreter:
    """This class is used to execute Microsoft Small Basic code."""
    def __init__(self):
        self.parser = Parser()
        self.environment = Environment()
        self.current_statement_index = 0
        self.statements = []
        self.sub_return_locations = []
        self.array_parser = ArrayParser()

        self.__program_path = None
        self.prog_args = []
        self.__subroutine_body_locations = {}

    def execute_code(self, code, args=None, program_path=None):
        """
        Executes the given Microsoft Small Basic code, given as a string.

        :param code: The string containing Microsoft Small Basic code.
        :param args: A list of arguments to the Microsoft Small Basic program.
        """
        self.__init_tk()

        if args is None:
            self.prog_args = []
        else:
            self.prog_args = args

        self.statements = self.parser.parse(code)
        if self.statements:
            self.__scan_statements()
            if program_path:
                self.__program_path = os.path.join(os.path.dirname(program_path), '')  # .join to ensure trailing slash
            else:
                self.__program_path = ""
            self.__tk_root.after(1, self.__start_main_thread)
            self.__tk_root.mainloop()
        self._exit()

    def execute_file(self, file_path, args=None):
        """
        Executes the given Microsoft Small Basic source code file.

        :param file_path: The string path to a Microsoft Small Basic source code file.
        :param args: A list of arguments to the Microsoft Small Basic program.
        """
        with open(file_path) as code_file:
            code = code_file.read()
            self.execute_code(code, args, code_file.name)

    @property
    def program_path(self):
        """Returns the path of the directory containing the currently executing script, or the empty string if there is
        no currently executing script or the script is being executed from a string."""
        return self.__program_path

    def __init_tk(self):
        self.__tk_root = tk.Tk()
        self.__tk_root.withdraw()
        self.msb_objects = {
            "Clock": modules.Clock(self),
            "Math": modules.Math(self),
            "TextWindow": modules.TextWindow(self, self.__tk_root),
            "GraphicsWindow": modules.GraphicsWindow(self, self.__tk_root),
            "Text": modules.Text(self),
            "Stack": modules.Stack(self),
            "Network": modules.Network(self),
            "File": modules.FileModule(self),
            "Desktop": modules.Desktop(self, self.__tk_root),
            "Array": modules.Array(self),
            "Program": modules.Program(self),
            "Timer": modules.Timer(self),
            "Mouse": modules.Mouse(self),
        }

        self.__threads = []

    def __scan_statements(self):
        # Save the line numbers for each subroutine body
        self.__subroutine_body_locations.clear()
        for line_number, statement in enumerate(self.statements):
            if isinstance(statement, ast.SubStatement):
                self.__subroutine_body_locations[statement.sub_name.lower()] = line_number + 1

    def __start_main_thread(self):
        p = InterpreterThread(self, 0)
        self.__threads.append(p)
        p.start()
        self.__tk_root.after(1, self.__check_threads_finished)

    def _call_subroutine_in_new_thread(self, sub_name):
        line_number = self.__subroutine_body_locations[sub_name.lower()]
        p = InterpreterThread(self, line_number)
        self.__threads.append(p)
        p.start()

    def __check_threads_finished(self):
        for thread in self.__threads[:]:
            if not thread.is_alive():
                self.__threads.remove(thread)
        if not self.__threads:
            if not (self.msb_objects["GraphicsWindow"].is_visible() or self.msb_objects["TextWindow"].is_visible()):
                self._exit()
        else:
            self.__tk_root.after(100, self.__check_threads_finished)

    def _exit(self, status=None):
        # TODO: make this able to close all running interpreter threads
        self.__tk_root.quit()
        self.__program_path = ""

    def _assign(self, destination_ast, value_ast, line_number=None):

        if isinstance(destination_ast, ast.UserVariable):
            # Assigning to variable as an array using one or more indices
            if destination_ast.array_indices:
                array_string = self.environment.get_variable(destination_ast.variable_name)
                index_values = [str(self._evaluate_expression_ast(index_ast)) for index_ast in destination_ast.array_indices]
                value = str(self._evaluate_expression_ast(value_ast))

                # TODO: raise error when trying to assign to a subroutine name or subroutine call.

                new_array_string = self.array_parser.set_value(array_string, index_values, value)
                self.environment.bind(destination_ast.variable_name, new_array_string)

            # Just overwriting variable
            else:
                self.environment.bind(destination_ast.variable_name, self._evaluate_expression_ast(value_ast))

        if isinstance(destination_ast, ast.MsbObjectField):
            msb_object = self.msb_objects[modules.utilities.capitalize(destination_ast.msb_object)]
            member_name = modules.utilities.capitalize(destination_ast.msb_object_field_name)

            # Determine if this is a function or an event
            info = modules.utilities.get_msb_builtin_info(destination_ast.msb_object, member_name)
            if info.type == "event":
                msb_object.set_event_sub(member_name, value_ast.variable_name)
            else:
                value = str(self._evaluate_expression_ast(value_ast))
                setattr(msb_object,
                        member_name,
                        value)

    def _increment_value(self, var_ast):
        # Increments the variable that has the given name by 1.  If not defined, this defines var_name to be 1.
        if isinstance(var_ast, ast.UserVariable):
            val = self._evaluate_operation("+", var_ast, ast.LiteralValue(1))
            self.environment.bind(var_ast.variable_name, val)
        else:
            raise errors.PyMsbRuntimeError(
                "Internal error - tried to increment non-UserVariable in Interpreter.increment_value")

    def _evaluate_comparison_ast(self, val_ast):
        # Important - returns "True" or "False" if this is actually a comparison
        # Otherwise, evaluates as an expression and returns the string result (e.g. "10" or "concatstr" or even "true")
        if isinstance(val_ast, ast.Comparison):
            return self._evaluate_comparison(val_ast.comparator, val_ast.left, val_ast.right)
        return self._evaluate_expression_ast(val_ast)

    def _evaluate_expression_ast(self, val_ast):
        if isinstance(val_ast, ast.LiteralValue):
            return val_ast.value  # always a string

        if isinstance(val_ast, ast.UserVariable):
            value = self.environment.get_variable(val_ast.variable_name)

            # If accessing variable as an array
            if val_ast.array_indices:
                index_values = [str(self._evaluate_expression_ast(i)) for i in val_ast.array_indices]
                return self.array_parser.get_value(value, index_values)

            # Just accessing variable
            return value

        if isinstance(val_ast, ast.MsbObjectField):
            return self._evaluate_object_field(val_ast.msb_object, val_ast.msb_object_field_name)

        if isinstance(val_ast, ast.Operation):
            return self._evaluate_operation(val_ast.operator, val_ast.left, val_ast.right)

        if isinstance(val_ast, ast.MsbObjectFunctionCall):
            return self._execute_function_call(val_ast.msb_object,
                                              val_ast.msb_object_function,
                                              val_ast.parameter_asts)

        raise NotImplementedError(val_ast)

    def _execute_function_call(self, obj_name, fn_name, arg_asts):
        arg_vals = [str(self._evaluate_expression_ast(arg_ast)) for arg_ast in arg_asts]
        fn = getattr(self.msb_objects[modules.utilities.capitalize(obj_name)], modules.utilities.capitalize(fn_name))
        return fn.__call__(*arg_vals)

    def _evaluate_object_field(self, obj_name, field_name):
        return getattr(self.msb_objects[modules.utilities.capitalize(obj_name)], modules.utilities.capitalize(field_name))

    def _evaluate_comparison(self, comp, left, right):
        # returns "True" or "False" - VERY IMPORTANT NOTE: returns the strings and not boolean values.
        # possible comp values are strings "<=", ">=", "<", ">", "<>", "="
        # left, right are asts

        left = self._evaluate_comparison_ast(left)
        right = self._evaluate_comparison_ast(right)
        if comp == "=":
            return str(left == right)
        if comp == "<>":
            return str(left != right)
        if comp.lower() == "and":
            return str(str(left).lower() == "true" and str(right).lower() == "true")
        if comp.lower() == "or":
            return str(str(left).lower() == "true" or str(right).lower() == "true")

        # At this point, anything that is non-numerical is treated like 0
        left = modules.utilities.numericize(left, True)
        right = modules.utilities.numericize(right, True)

        return str(((comp == "<" and left < right) or
                    (comp == "<=" and left <= right) or
                    (comp == ">" and left > right) or
                    (comp == ">=" and left >= right)))

    # FIXME: fix this so ("x is " + "00") returns "x is 00" and not "x is 0"
    def _evaluate_operation(self, op, left, right):
        # op is "+", "-", "*" or "/"
        # left, right are expression asts
        left = self._evaluate_expression_ast(left)
        if isinstance(left, str):
            left = modules.utilities.numericize(left, op != "+")
        right = self._evaluate_expression_ast(right)
        if isinstance(right, str):
            right = modules.utilities.numericize(right, op != "+")

        if op == "+":
            try:
                return str(left + right)
            except TypeError:
                return str(left) + str(right)

        if op == "-":
            return left - right
        if op == "*":
            return left * right
        if op == "/":
            return left / right