<gh_stars>1-10
import Python_sml_ClientInterface as sml

### Note: Helper class used by extract_wm_graph

class WMNode:
    """ Represents a node in the working memory graph wrapping an Identifier and containing links to child wmes

        node.id = root_id (Identifier)
        node.symbol = string (The root_id symbol e.g. O34)
        node['attr'] = WMNode   # for identifiers
        node['attr'] = constant # for string, double, or int value
        node['attr'] = [ val1, val2, ... ] # for multi-valued attributes (values can be constants or WMNodes)
    """

    def __init__(self, soar_id):
        self.id = soar_id
        self.symbol = soar_id.GetValueAsString()
        self.children = {}

    def attributes(self):
        """ Returns a list of all child wme attribute strings """
        return list(self.children.keys())

    # Supports dictionary syntax (read only)
    def __getitem__(self, attr):
        """ Returns the value of the wme (node, attr, val)
            where a value can be a int, double, string, WMNode,
            or a list of such values for a multi-valued attribute """
        return self.children.get(attr, None)

    def __str__(self):
        """ Returns a nicely formatted string representation of the node and all its children 
            (Warning: will be a lot of text for large graphs) """
        return self.__str_helper__("", set())

    def __str_helper__(self, indent, ignore_ids):
        var = "<" + self.symbol + ">"
        if self.symbol in ignore_ids or len(self.children) == 0:
            return var

        ignore_ids.add(self.symbol)

        s = var + " {\n"
        for a, v in self.children.items():
            s += indent + "  " + a + ": " + _wm_value_to_str(v, indent + "  ", ignore_ids) + "\n"
        s += indent + "}"
        return s

    def _extract_children(self, max_depth, node_map):
        """ Internal helper method to recursively extract graph structure for a node's children """
        if max_depth == 0:
            return

        for index in range(self.id.GetNumberChildren()):
            wme = self.id.GetChild(index)
            attr = wme.GetAttribute()
            if wme.IsIdentifier():
                child_id = wme.ConvertToIdentifier()
                child_sym = child_id.GetValueAsString()
                # First check if the child id is already in the node map
                if child_sym in node_map:
                    wme_val = node_map[child_sym]
                else:
                    # If not, recursively create and extract the children
                    wme_val = WMNode(child_id)
                    node_map[wme_val.symbol] = wme_val
                    wme_val._extract_children(max_depth-1, node_map)

            elif wme.GetValueType() == "int":
                wme_val = wme.ConvertToIntElement().GetValue()
            elif wme.GetValueType() == "double":
                wme_val = wme.ConvertToFloatElement().GetValue()
            else:
                wme_val = wme.GetValueAsString()

            self._add_child_wme(attr, wme_val)

    def _add_child_wme(self, attr, value):
        """ Adds the child wme to the children dictionary
            If there are multiple values for a given attr, move them into a list instead of replacing """
        if attr in self.children:
            cur_val = self.children[attr]
            if isinstance(cur_val, list):
                # Child is already a list, just append
                cur_val.append(value)
            else:
                # This is the second value for the attr, replace current value with a list
                self.children[attr] = [ cur_val, value ]
        else:
            # First time we've seen this attr, just add to dictionary
            self.children[attr] = value

def _wm_value_to_str(val, indent, ignore_ids):
    """
    recursive helper function which returns a string representation of any given value type

    :param val: The value to convert to a string (can be str, int, float, list, WMNode)
    :param indent: a string of spaces to indent the current level
    :param ignore_ids: A set of Identifier symbols to not print
    """
    if isinstance(val, str):
        return val
    if isinstance(val, int):
        return str(val)
    if isinstance(val, float):
        return str(val)
    if isinstance(val, list):
        return "[ " + ", ".join(_wm_value_to_str(i, indent, ignore_ids) for i in val) + " ]"
    if isinstance(val, WMNode):
        return val.__str_helper__(indent, ignore_ids)
    return ""



import Python_sml_ClientInterface as sml

### Note: Helper class used by extract_wm_graph

class WMNode:
    """ Represents a node in the working memory graph wrapping an Identifier and containing links to child wmes

        node.id = root_id (Identifier)
        node.symbol = string (The root_id symbol e.g. O34)
        node['attr'] = WMNode   # for identifiers
        node['attr'] = constant # for string, double, or int value
        node['attr'] = [ val1, val2, ... ] # for multi-valued attributes (values can be constants or WMNodes)
    """

    def __init__(self, soar_id):
        self.id = soar_id
        self.symbol = soar_id.GetValueAsString()
        self.children = {}

    def attributes(self):
        """ Returns a list of all child wme attribute strings """
        return list(self.children.keys())

    # Supports dictionary syntax (read only)
    def __getitem__(self, attr):
        """ Returns the value of the wme (node, attr, val)
            where a value can be a int, double, string, WMNode,
            or a list of such values for a multi-valued attribute """
        return self.children.get(attr, None)

    def __str__(self):
        """ Returns a nicely formatted string representation of the node and all its children 
            (Warning: will be a lot of text for large graphs) """
        return self.__str_helper__("", set())

    def __str_helper__(self, indent, ignore_ids):
        var = "<" + self.symbol + ">"
        if self.symbol in ignore_ids or len(self.children) == 0:
            return var

        ignore_ids.add(self.symbol)

        s = var + " {\n"
        for a, v in self.children.items():
            s += indent + "  " + a + ": " + _wm_value_to_str(v, indent + "  ", ignore_ids) + "\n"
        s += indent + "}"
        return s

    def _extract_children(self, max_depth, node_map):
        """ Internal helper method to recursively extract graph structure for a node's children """
        if max_depth == 0:
            return

        for index in range(self.id.GetNumberChildren()):
            wme = self.id.GetChild(index)
            attr = wme.GetAttribute()
            if wme.IsIdentifier():
                child_id = wme.ConvertToIdentifier()
                child_sym = child_id.GetValueAsString()
                # First check if the child id is already in the node map
                if child_sym in node_map:
                    wme_val = node_map[child_sym]
                else:
                    # If not, recursively create and extract the children
                    wme_val = WMNode(child_id)
                    node_map[wme_val.symbol] = wme_val
                    wme_val._extract_children(max_depth-1, node_map)

            elif wme.GetValueType() == "int":
                wme_val = wme.ConvertToIntElement().GetValue()
            elif wme.GetValueType() == "double":
                wme_val = wme.ConvertToFloatElement().GetValue()
            else:
                wme_val = wme.GetValueAsString()

            self._add_child_wme(attr, wme_val)

    def _add_child_wme(self, attr, value):
        """ Adds the child wme to the children dictionary
            If there are multiple values for a given attr, move them into a list instead of replacing """
        if attr in self.children:
            cur_val = self.children[attr]
            if isinstance(cur_val, list):
                # Child is already a list, just append
                cur_val.append(value)
            else:
                # This is the second value for the attr, replace current value with a list
                self.children[attr] = [ cur_val, value ]
        else:
            # First time we've seen this attr, just add to dictionary
            self.children[attr] = value

def _wm_value_to_str(val, indent, ignore_ids):
    """
    recursive helper function which returns a string representation of any given value type

    :param val: The value to convert to a string (can be str, int, float, list, WMNode)
    :param indent: a string of spaces to indent the current level
    :param ignore_ids: A set of Identifier symbols to not print
    """
    if isinstance(val, str):
        return val
    if isinstance(val, int):
        return str(val)
    if isinstance(val, float):
        return str(val)
    if isinstance(val, list):
        return "[ " + ", ".join(_wm_value_to_str(i, indent, ignore_ids) for i in val) + " ]"
    if isinstance(val, WMNode):
        return val.__str_helper__(indent, ignore_ids)
    return ""
### Note: Helper class used by extract_wm_graph Represents a node in the working memory graph wrapping an Identifier and containing links to child wmes          node.id = root_id (Identifier)         node.symbol = string (The root_id symbol e.g. O34)         node['attr'] = WMNode   # for identifiers         node['attr'] = constant # for string, double, or int value         node['attr'] = [ val1, val2, ... ] # for multi-valued attributes (values can be constants or WMNodes) Returns a list of all child wme attribute strings # Supports dictionary syntax (read only) Returns the value of the wme (node, attr, val)             where a value can be a int, double, string, WMNode,             or a list of such values for a multi-valued attribute Returns a nicely formatted string representation of the node and all its children              (Warning: will be a lot of text for large graphs) Internal helper method to recursively extract graph structure for a node's children # First check if the child id is already in the node map # If not, recursively create and extract the children Adds the child wme to the children dictionary             If there are multiple values for a given attr, move them into a list instead of replacing # Child is already a list, just append # This is the second value for the attr, replace current value with a list # First time we've seen this attr, just add to dictionary recursive helper function which returns a string representation of any given value type      :param val: The value to convert to a string (can be str, int, float, list, WMNode)     :param indent: a string of spaces to indent the current level     :param ignore_ids: A set of Identifier symbols to not print