コード例 #1
0
    def test_029func_arg_string(self):
        """
        Test both definining and calling a function that accepts a string
        argument and returns a string.
        """
        module_context = imp.new_module('for_func_arg_string')
        str_func_arg_code = """
function string_arg(string a_str) {
    a = debug(a_str)
    return "I returned a string"
}
b = string_arg("THIS IS A STRING ARG with a known function name userfunc_time")
"""
        code_block = CodeBlockGenerator.wrap_code_block(
            "func_arg_string", module_context, str_func_arg_code, self.functionmap)
        code_block.load(['operator', 'math'])
        sym_tables = {"globals": SymbolTable(), "locals": SymbolTable()}
        code_block.run(sym_tables)
        self.assertEqual(sym_tables['locals']['a'],
                         "THIS IS A STRING ARG with a known function name userfunc_time")
        self.assertEqual(sym_tables['locals']['b'], "I returned a string")
コード例 #2
0
 def test_005valid_assignment(self):
     """Test variable assignments to both local and global tables."""
     simple_line = "x = 49"
     code_block = CodeBlockGenerator.wrap_code_block(
         "goodassignment", self.module_context, simple_line, self.functionmap)
     code_block.load(['operator', 'math'])
     sym_tables = {"globals": SymbolTable(), "locals": SymbolTable()}
     code_block.run(sym_tables)
     dump_symtables(sym_tables)
     self.assertTrue(sym_tables['locals']['x'] == 49)
     simple_line2 = "global y = 49"
     code_block = CodeBlockGenerator.wrap_code_block(
         "goodassignment2", self.module_context, simple_line2, self.functionmap)
     code_block.load(['operator', 'math'])
     sym_tables = {"globals": SymbolTable(), "locals": SymbolTable()}
     code_block.run(sym_tables)
     dump_symtables(sym_tables)
     self.assertTrue(sym_tables['globals']['y'] == 49)
     a_string = "mystr = \"This is a string\""
     code_block = CodeBlockGenerator.wrap_code_block(
         "goodstringassignment", self.module_context, a_string, self.functionmap)
     code_block.load(['operator', 'math'])
     sym_tables = {"globals": SymbolTable(), "locals": SymbolTable()}
     code_block.run(sym_tables)
     dump_symtables(sym_tables)
     self.assertTrue(sym_tables['locals']['mystr'] == "This is a string")
コード例 #3
0
    def __init__(self, kind, screen_dims, new_id, settings=None, **kwargs):
        """
        Initialize a SimpleObjectInstance.

        :param kind: The object type of this new instance
        :type kind: :py:class:`~pygame_maker.actors.object_type.ObjectType`
        :param screen_dims: Width, height of the surface this instance will be
            drawn to.  Allows boundary collisions to be detected.
        :type screen_dims: [int, int]
        :param new_id: A unique integer ID for this instance
        :type new_id: int
        :param settings: Used along with kwargs for settings attributes
            (allows attributes to be set that have a '.' character, which
            cannot be set in kwargs).  Known attributes are the same as for
            kwargs.
        :type settings: None or dict
        :param kwargs:
            Supply alternatives to instance attributes

            * position (list of float or pygame.Rect): Upper left XY coordinate.
              If not integers, each will be rounded to the next highest
              integer [(0,0)]

        """
        # call base class init
        super(SimpleObjectInstance, self).__init__(type(self).__name__)
        #: Name the instance based on the ObjectType's name and the ID
        self.name = "{}{}".format(kind.name, new_id)
        #: The ObjectType this SimpleObjectInstance belongs to
        self.kind = kind
        #: Keep a handle to the game engine for handling certain actions
        self.game_engine = kind.game_engine
        #: Keep track of the screen boundaries for collision detection
        self.screen_dims = list(screen_dims[0:2])
        #: Unique ID for this SimpleObjectInstance
        self.inst_id = new_id
        # rect for storing the instance's position
        self.rect = pygame.Rect(0, 0, 0, 0)
        # Symbols tracked by ObjectInstances
        self._symbols = {
            "parent": None,
            "children": [],
            "position": coordinate.Coordinate(0, 0,
                                              self._update_position_x,
                                              self._update_position_y)
        }
        #: Subclasses override this class variable to add their known symbols
        self._symbols.update(self.INSTANCE_SYMBOLS)
        #: Symbol table
        self.symbols = SymbolTable()
        for sym in self._symbols.keys():
            self.symbols[sym] = self._symbols[sym]

        attr_values = {}
        if settings is not None:
            attr_values.update(settings)
        attr_values.update(kwargs)
        if len(attr_values.keys()) > 0:
            self._apply_kwargs(attr_values)
        # print("Initial symbols:")
        # self.symbols.dumpVars()

        self.action_name_to_method_map = {
            'debug': self.print_debug,
            'execute_code': self.execute_code,
            'if_variable_value': self.if_variable_value,
            'set_variable_value': self.set_variable_value,
            'destroy_object': self.destroy_object,
        }
        self._code_block_id = 0
コード例 #4
0
class SimpleObjectInstance(logging_object.LoggingObject):
    """
    SimpleObjectInstances wrap instances of ObjectType for use by types without
    sprites.

    The most useful features of this class is allowing the logic engine to
    access symbols, and supporting variable and code execution type actions.
    """
    INSTANCE_SYMBOLS = {}
    # Regex for searching for symbol interpolations in debug strings
    INTERPOLATION_REGEX = re.compile("{([^}]*)}")

    def __init__(self, kind, screen_dims, new_id, settings=None, **kwargs):
        """
        Initialize a SimpleObjectInstance.

        :param kind: The object type of this new instance
        :type kind: :py:class:`~pygame_maker.actors.object_type.ObjectType`
        :param screen_dims: Width, height of the surface this instance will be
            drawn to.  Allows boundary collisions to be detected.
        :type screen_dims: [int, int]
        :param new_id: A unique integer ID for this instance
        :type new_id: int
        :param settings: Used along with kwargs for settings attributes
            (allows attributes to be set that have a '.' character, which
            cannot be set in kwargs).  Known attributes are the same as for
            kwargs.
        :type settings: None or dict
        :param kwargs:
            Supply alternatives to instance attributes

            * position (list of float or pygame.Rect): Upper left XY coordinate.
              If not integers, each will be rounded to the next highest
              integer [(0,0)]

        """
        # call base class init
        super(SimpleObjectInstance, self).__init__(type(self).__name__)
        #: Name the instance based on the ObjectType's name and the ID
        self.name = "{}{}".format(kind.name, new_id)
        #: The ObjectType this SimpleObjectInstance belongs to
        self.kind = kind
        #: Keep a handle to the game engine for handling certain actions
        self.game_engine = kind.game_engine
        #: Keep track of the screen boundaries for collision detection
        self.screen_dims = list(screen_dims[0:2])
        #: Unique ID for this SimpleObjectInstance
        self.inst_id = new_id
        # rect for storing the instance's position
        self.rect = pygame.Rect(0, 0, 0, 0)
        # Symbols tracked by ObjectInstances
        self._symbols = {
            "parent": None,
            "children": [],
            "position": coordinate.Coordinate(0, 0,
                                              self._update_position_x,
                                              self._update_position_y)
        }
        #: Subclasses override this class variable to add their known symbols
        self._symbols.update(self.INSTANCE_SYMBOLS)
        #: Symbol table
        self.symbols = SymbolTable()
        for sym in self._symbols.keys():
            self.symbols[sym] = self._symbols[sym]

        attr_values = {}
        if settings is not None:
            attr_values.update(settings)
        attr_values.update(kwargs)
        if len(attr_values.keys()) > 0:
            self._apply_kwargs(attr_values)
        # print("Initial symbols:")
        # self.symbols.dumpVars()

        self.action_name_to_method_map = {
            'debug': self.print_debug,
            'execute_code': self.execute_code,
            'if_variable_value': self.if_variable_value,
            'set_variable_value': self.set_variable_value,
            'destroy_object': self.destroy_object,
        }
        self._code_block_id = 0

    def _update_position_x(self):
        # Automatically called when the X coordinate of the position changes
        self.debug("_update_position_x():")
        self._round_position_x_to_rect_x()
        #pylint: disable=no-member
        self.symbols['position.x'] = self.position.x
        #pylint: enable=no-member

    def _update_position_y(self):
        # Automatically called when the Y coordinate of the position changes
        self.debug("_update_position_y():")
        self._round_position_y_to_rect_y()
        #pylint: disable=no-member
        self.symbols['position.y'] = self.position.y
        #pylint: enable=no-member

    def _round_position_x_to_rect_x(self):
        # Called when the x coordinate of the position changes, to round
        # the floating-point value to the nearest integer and place it
        # in rect.x for the draw() method.
        self.debug("_round_position_x_to_rect_x():")
        #pylint: disable=no-member
        self.rect.x = math.floor(self.position.x + 0.5)
        #pylint: enable=no-member

    def _round_position_y_to_rect_y(self):
        # _round_position_y_to_rect_y():
        #  Called when the y coordinate of the position changes, to round
        #  the floating-point value to the nearest integer and place it
        #  in rect.y for the draw() method.
        self.debug("_round_position_y_to_rect_y():")
        #pylint: disable=no-member
        self.rect.y = math.floor(self.position.y + 0.5)
        #pylint: enable=no-member

    @property
    def code_block_id(self):
        """Return a unique code block id."""
        self._code_block_id += 1
        return self._code_block_id

    @property
    def position(self):
        """Position of this instance.  Set a new position using an x, y list."""
        return self.symbols['position']

    @position.setter
    def position(self, new_coord):
        if len(new_coord) >= 2:
            self.debug("Set {}'s position to {}".format(self.name, new_coord))
            self.symbols['position'].x = new_coord[0]
            self.symbols['position'].y = new_coord[1]

    def _apply_kwargs(self, kwargs):
        # Apply the kwargs dict mappings to the instance's properties.

        # Any keys that don't refer to built-in properties (speed, direction,
        # etc) will instead be tracked in the local symbol table to support
        # code execution actions.
        # Parameters themselves can have attributes up to 1 level, to support
        #     position.x and position.y

        # :param kwargs: A dictionary containing the new attributes to be
        #     applied
        # :type kwargs: dict
        self.debug("_apply_kwargs(kwargs={}):".format(str(kwargs)))
        relative = False
        if "relative" in kwargs.keys():
            relative = kwargs["relative"]
        for kwarg in kwargs.keys():
            if kwarg == 'relative':
                # 'relative' is not an attribute, it instead determines how the
                #  other attributes are applied.
                continue
            # Attributes can themselves have attributes, but only 1 level deep
            # is currently supported.  This facilitates setting the position.x
            #  and position.y attributes.
            attrs = kwarg.split('.')
            if hasattr(self, attrs[0]):
                new_val = kwargs[kwarg]
                if len(attrs) == 1:
                    old_val = getattr(self, kwarg)
                    if relative:
                        new_val += getattr(self, kwarg)
                    if new_val != old_val:
                        setattr(self, kwarg, new_val)
                elif len(attrs) == 2:
                    main_attr = getattr(self, attrs[0])
                    old_val = getattr(main_attr, attrs[1])
                    if relative:
                        new_val += old_val
                    if new_val != old_val:
                        setattr(main_attr, attrs[1], new_val)
            else:
                # keep track of local symbols created by code blocks
                self.symbols[kwarg] = kwargs[kwarg]

    def _symbol_change_callback(self, sym, new_value):
        # Callback for the SymbolTable.

        # Called whenever a symbol changes while running the language engine.

        # :param sym: The symbol's name
        # :type sym: str
        # :param new_value: The symbol's new value
        self.debug("_symbol_change_callback(sym={}, new_value={}):".format(sym,
                                                                           new_value))
        handled_change = False
        if hasattr(self, sym):
            setattr(self, sym, new_value)
            handled_change = True
        elif sym == 'position.x':
            self.position.x = new_value
            handled_change = True
        elif sym == 'position.y':
            self.position.y = new_value
            handled_change = True
        return handled_change

    def execute_code(self, action, keep_code_block=True):
        """
        Handle the execute_code action.

        Puts local variables into the symbols attribute, which is a symbol
        table.  Applies any built-in local variable changes for the instance.

        :param action: The Action instance that triggered this method
        :type action: :py:class:`~pygame_maker.actions.action.Action`
        :param keep_code_block: Specify whether the code block will be re-used,
            and so shouldn't be deleted after execution
        :type keep_code_block: bool
        """
        self.debug("execute_code(action={}, keep_code_block={}):".format(action,
                                                                         keep_code_block))
        if len(action.action_data['code']) > 0:
            instance_handle_name = "obj_{}_block{}".format(self.kind.name, self.code_block_id)
            if 'language_engine_handle' not in action.runtime_data:
                action['language_engine_handle'] = instance_handle_name
                # print("action {} runtime: '{}'".format(action, action.runtime_data))
                self.game_engine.language_engine.register_code_block(
                    instance_handle_name, action.action_data['code']
                )
            local_symbols = SymbolTable(self.symbols,
                                        lambda s, v: self._symbol_change_callback(s, v))
            # future: allow references to this instance in user code (E.G. set
            #  as parent, add as child instance).
            local_symbols.set_constant("self", self)
            self.debug("{} inst {} syms before code block: {}".format(self.kind.name,
                                                                      self.inst_id,
                                                                      local_symbols.vars))
            self.game_engine.language_engine.execute_code_block(
                action['language_engine_handle'], local_symbols
            )
            self.debug("  syms after code block: {}".format(local_symbols.vars))
            if not keep_code_block:
                # support one-shot actions
                self.game_engine.language_engine.unregister_code_block(
                    action['language_engine_handle']
                )
                del action.runtime_data['language_engine_handle']

    def print_debug(self, action):
        """
        Handle the debug action.

        Debug messages are treated as format strings, where {symbol} will be
        replaced with actual values from the symbol tables.

        :param action: The Action instance that triggered this method
        :type action: :py:class:`~pygame_maker.actions.action.Action`
        """
        message_parts = ["DEBUG FROM {} instance #{}: ".format(self.kind.name, self.inst_id)]
        if len(action['message']) > 0:
            interpolations = self.INTERPOLATION_REGEX.findall(action['message'])
            msg_str = action['message']
            if len(interpolations) > 0:
                inter_set = set(interpolations)
                for inter in inter_set:
                    inter_str = "{" + inter + "}"
                    rstr = "UNKNOWN"
                    if inter in self.symbols.keys():
                        rstr = self.symbols[inter]
                    elif inter in self.game_engine.language_engine.global_symbol_table.keys():
                        rstr = self.game_engine.language_engine.global_symbol_table[inter]
                    msg_str = re.sub(inter_str, str(rstr), msg_str)
            message_parts.append(msg_str)
        else:
            message_parts.append("\nlocal symbol table entries:\n")
            for lsym in self.symbols.keys():
                if lsym in ["parent", "children", "position"]:
                    continue
                message_parts.append("\t{:30s} = {:30s}\n".format(lsym, str(self.symbols[lsym])))
            message_parts.append("global symbol table entries:\n")
            for gsym in self.game_engine.language_engine.global_symbol_table.keys():
                if gsym in ["pi", "e"]:
                    continue
                symname = str(self.game_engine.language_engine.global_symbol_table[gsym])
                message_parts.append("\t{:30s} = {:30s}\n".format(gsym, symname))
        print "".join(message_parts)

    def if_variable_value(self, action):
        """
        Handle the if_variable_value action.

        Makes use of both the local symbol table in self.symbols, and the
        global symbol table managed by the language engine.

        :param action: The Action instance that triggered this method
        :type action: :py:class:`~pygame_maker.actions.action.Action`
        """
        self.debug("if_variable_value(action={}):".format(action))
        # look in symbol tables for the answer, local table first
        var_val = self.symbols.DEFAULT_UNINITIALIZED_VALUE
        test_val = action['value']
        test_result = False
        if action['variable'] in self.symbols.keys():
            var_val = self.symbols[action['variable']]
        elif action['variable'] in self.game_engine.language_engine.global_symbol_table.keys():
            var_val = self.game_engine.language_engine.global_symbol_table[action['variable']]
        if isinstance(action['value'], str):
            # replace a string with a symbol value, if the string is in a symbol table
            if action['value'] in self.symbols.keys():
                test_val = self.symbols[action['value']]
            elif action['value'] in self.game_engine.language_engine.global_symbol_table.keys():
                test_val = self.game_engine.language_engine.global_symbol_table[action['value']]
        if action['test'] == "equals":
            test_result = (var_val == test_val)
        if action['test'] == "not_equals":
            test_result = (var_val != test_val)
        if action['test'] == "less_than_or_equals":
            test_result = (var_val <= test_val)
        if action['test'] == "less_than":
            test_result = (var_val < test_val)
        if action['test'] == "greater_than_or_equals":
            test_result = (var_val >= test_val)
        if action['test'] == "greater_than":
            test_result = (var_val > test_val)
        self.debug("  {} inst {}: if {} {} {} is {}".format(self.kind.name, self.inst_id,
                                                            action['variable'],
                                                            action['test'],
                                                            test_val, test_result))
        # update the action's action_result attribute, so that the
        # action sequence can choose the right conditional path
        action.action_result = test_result

    def set_variable_value(self, action):
        """
        Handle the set_variable_value action.

        :param action: The Action instance that triggered this method
        :type action: :py:class:`~pygame_maker.actions.action.Action`
        """
        self.debug("set_variable_value(action={}):".format(action))
        gsymtable = self.game_engine.language_engine.global_symbol_table
        if action['is_global']:
            value_result = action.get_parameter_expression_result('value',
                                                                  gsymtable,
                                                                  self.game_engine.language_engine)
            self.debug("  {} inst {}: set global var {} to {}".format(self.kind.name,
                                                                      self.inst_id,
                                                                      action['variable'],
                                                                      value_result))
            self.game_engine.language_engine.global_symbol_table[action['variable']] = value_result
        else:
            value_result = action.get_parameter_expression_result('value', self.symbols,
                                                                  self.game_engine.language_engine)
            self.debug("  {} inst {}: set local var '{}' to {}".format(self.kind.name,
                                                                       self.inst_id,
                                                                       action['variable'],
                                                                       value_result))
            self.symbols[action['variable']] = value_result

    def set_parent_instance(self, parent):
        """
        Set or replace this instance's parent, for forwarding 'child' events.
        """
        if not isinstance(parent, SimpleObjectInstance):
            self.warn("set_parent_instance() passed non-instance '{}'".format(parent))
            return
        # if this instance already has a parent, remove it from the parent's child instances
        if self.symbols["parent"] is not None:
            #pylint: disable=no-member
            self.symbols["parent"].remove_child_instance(self)
            #pylint: enable=no-member
        # add this instance as a child of the new parent
        parent.add_child_instance(self)
        self.symbols["parent"] = parent

    def remove_parent_instance(self):
        """Remove the parent instance, for example when it is destroyed"""
        self.debug("remove_parent_instance():")
        self.symbols["parent"] = None

    def add_child_instance(self, child):
        """Add a child instance to this one, for forwarding 'parent' events"""
        if not isinstance(child, SimpleObjectInstance):
            self.warn("add_child_instance() passed non-instance '{}'".format(child))
            return
        self.debug("add_child_instance(child={} inst {}):".format(child.kind.name, child.inst_id))
        #pylint: disable=unsupported-membership-test
        if child not in self.symbols["children"]:
            #pylint: enable=unsupported-membership-test
            #pylint: disable=no-member
            self.symbols["children"].append(child)
            #pylint: enable=no-member
        else:
            self.info("add_child_instance() called with already existing child instance")

    def remove_child_instance(self, child):
        """
            Remove a child instance from this one, to disconnect it from
            'parent' events
        """
        if not isinstance(child, SimpleObjectInstance):
            self.warn("add_child_instance() passed non-instance '{}'".format(child))
            return
        self.debug("remove_child_instance(child={} inst {}):".
                   format(child.kind.name, child.inst_id))
        #pylint: disable=unsupported-membership-test
        if child in self.symbols["children"]:
            #pylint: enable=unsupported-membership-test
            #pylint: disable=no-member
            self.symbols["children"].remove(child)
            #pylint: enable=no-member
        else:
            self.info("remove_child_instance() called with non-existent child instance")

    def destroy_object(self, action, no_destroy_event=False):
        """
            Queue and transmit the destroy event for this instance, then
            schedule it for removal from its object type.

            Also handles parent and child connections, sending "destroy_child"
            events to parent instances, and destroy any child instances.
            Remove any remaining references to the instance so it can be GC'd.
        """
        self.debug("destroy_object(action={}):".format(action))
        self.game_engine.event_engine.queue_event(
            event.ObjectStateEvent("destroy", {"type": self.kind, "instance": self})
        )
        if len(self.symbols["children"]) > 0:
            # destroy all child instances
            #pylint: disable=not-an-iterable
            for child_instance in self.symbols["children"]:
                #pylint: enable=not-an-iterable
                # no need to queue destroy_child events for ourself..
                child_instance.remove_parent_instance()
                child_instance.destroy_object(action, True)
        # break connection with parent (if any)
        if self.symbols["parent"] is not None:
            parent = self.symbols["parent"]
            # queue destroy_child event to parent instance
            #pylint: disable=no-member
            new_ev = event.ObjectStateEvent("destroy_child", {"type": parent.kind,
                                                              "instance": parent,
                                                              "child_type": self.kind})
            #pylint: enable=no-member
            self.game_engine.event_engine.queue_event(new_ev)
            #pylint: disable=no-member
            parent.remove_child_instance(self)
            #pylint: enable=no-member
            self.game_engine.event_engine.transmit_event("destroy_child")
        if not no_destroy_event:
            # only transmit the event once; child instances can skip this
            self.game_engine.event_engine.transmit_event("destroy")
        self.kind.add_instance_to_delete_list(self)

    def execute_action(self, action, an_event):
        """
        Perform an action in an action sequence, in response to an event.

        :param action: The Action instance that triggered this method
        :type action: :py:class:`~pygame_maker.actions.action.Action`
        :param an_event: The Event instance that triggered this method
        :type an_event: :py:class:`~pygame_maker.events.event.Event`
        :return: A tuple containing the action parameters (``apply_to`` will
            be filtered out), and True/False based on whether the action was
            handled here in the base class or not.
        :rtype: (dict, bool)
        """
        # Apply any setting names that match property names found in the
        #  action_data.  For some actions, this is enough.
        # common exceptions:
        #  apply_to: assumed to have directed the action to this instance
        #  relative: add to instead of replace property settings
        self.debug("execute_action(action={}, an_event={}):".format(action, an_event))
        action_params = {}
        handled_action = False
        # check for expressions that need to be executed
        for param in action.action_data.keys():
            if param == 'apply_to':
                continue
            action_params[param] = action.get_parameter_expression_result(
                param, self.symbols, self.game_engine.language_engine)
        if action.name in self.action_name_to_method_map.keys():
            self.action_name_to_method_map[action.name](action)
            handled_action = True
            self.debug("  {} inst {} execute_action {} handled".format(self.kind.name,
                                                                       self.inst_id,
                                                                       action.name))
        return (action_params, handled_action)

    def __repr__(self):
        return "<{} {:03d}>".format(type(self).__name__, self.inst_id)