text: #!/usr/bin/env python
  
  #    This file is part of Diamond.
  #
  #    Diamond is free software: you can redistribute it and/or modify
  #    it under the terms of the GNU General Public License as published by
  #    the Free Software Foundation, either version 3 of the License, or
  #    (at your option) any later version.
  #
  #    Diamond is distributed in the hope that it will be useful,
  #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  #    GNU General Public License for more details.
  #
  #    You should have received a copy of the GNU General Public License
  #    along with Diamond.  If not, see <http://www.gnu.org/licenses/>.
  
  import plist
  
  class MixedTree:
    def __init__(self, parent, child):
      """
      The .doc and .attrs comes from parent, and the .data comes from child. This
      is used to hide integer_value etc. from the left hand side, but for its data
      entry to show up on the right.
      """
  
      self.parent = parent
      self.child = child
  
      self.name = parent.name
      self.schemaname = parent.schemaname
  
      excluded_attrs = ["shape"]
      self.attrs = dict(self.parent.attrs.items() + [x for x in self.child.attrs.items() if x[0] not in excluded_attrs])
  
      self.children = parent.children
      self.datatype = child.datatype
      self.data = child.data
      self.doc = parent.doc
      self.active = parent.active
  
      return
  
    def set_attr(self, attr, val):
      if attr in self.parent.attrs:
        self.parent.set_attr(attr, val)
      elif attr in self.child.attrs:
        self.child.set_attr(attr, val)
      else:
        raise Exception, "Attribute not present in either parent or child!"
  
      return
  
    def get_attr(self, attr):
      if attr in self.parent.attrs:
        self.parent.get_attr(attr)
      elif attr in self.child.attrs:
        self.child.get_attr(attr)
      else:
        raise Exception, "Attribute not present in either parent or child!"
  
    def set_data(self, data):
      self.child.set_data(data)
      self.datatype = self.child.datatype
      self.data = self.child.data
  
      return
  
    def valid_data(self, datatype, data):
      return self.child.valid_data(datatype, data)
  
    def validity_check(self, datatype, data):
      return self.child.validity_check(datatype, data)
  
    def matches(self, text, case_sensitive = False):
      old_parent_data = self.parent.data
      self.parent.data = None
      parent_matches = self.parent.matches(text, case_sensitive)
      self.parent.data = old_parent_data
  
      if parent_matches:
        return True
  
      if case_sensitive:
        text_re = re.compile(text)
      else:
        text_re = re.compile(text, re.IGNORECASE)
  
      if not self.child.data is None and not text_re.search(self.child.data) is None:
        return True
      else:
        return False
  
    def is_comment(self):
      return self.parent.is_comment()
  
    def get_comment(self):
      return self.parent.get_comment()
  
    def is_tensor(self, geometry_dim_tree):
      """
      Perform a series of tests on the current MixedTree, to determine if
      it is intended to be used to store tensor or vector data.
      """
  
      # Check that a geometry is defined
      if geometry_dim_tree is None:
        return False
  
      # Check that this element has calculable and positive dimensions
        try:
          dim1, dim2 = self.tensor_shape(geometry_dim_tree)
          assert dim1 > 0
          assert dim2 > 0
        except AssertionError:
          return False
  
      # The element must have dim1, rank and shape attributes
      if "dim1" not in self.child.attrs.keys() \
         or "rank" not in self.child.attrs.keys() \
         or "shape" not in self.child.attrs.keys():
        return False
  
      # The dim1 and rank attributes must be of fixed type
      if self.child.attrs["dim1"][0] != "fixed" or self.child.attrs["rank"][0] != "fixed":
        return False
  
      if "dim2" in self.child.attrs.keys():
        # If a dim2 attribute is specified, it must be of fixed type and the rank must be 2
        # Also, the shape attribute must be a list of integers with cardinality equal to the rank
        if self.child.attrs["dim2"][0] != "fixed" \
           or self.child.attrs["rank"][1] != "2" \
           or not isinstance(self.child.attrs["shape"][0], plist.List) \
           or self.child.attrs["shape"][0].datatype is not int \
           or str(self.child.attrs["shape"][0].cardinality) != self.child.attrs["rank"][1]:
          return False
  
      # Otherwise, the rank must be one and the shape an integer
      elif self.child.attrs["rank"][1] != "1" or self.child.attrs["shape"][0] is not int:
        return False
  
      # The data for the element must be a list of one or more
      if not isinstance(self.datatype, plist.List) or self.datatype.cardinality != "+":
        return False
  
      # If the shape has been set, check that it has a valid value
      if self.child.attrs["shape"][1] != None:
        if geometry_dim_tree.data is None:
          return False
  
        dim1, dim2 = self.tensor_shape(geometry_dim_tree)
        if "dim2" in self.child.attrs.keys():
          if self.child.attrs["shape"][1] != str(dim1) + " " + str(dim2):
            return False
        elif self.child.attrs["shape"][1] != str(dim1):
          return False
  
      return True
  
    def tensor_shape(self, dimension):
      """
      Read the tensor shape for tensor or vector data in the current MixedTree.
      """
  
      if not isinstance(dimension, int):
        dimension = int(dimension.data)
  
      dim1 = 1
      dim2 = 1
      if "dim1" in self.child.attrs.keys():
        dim1 = int(eval(self.child.attrs["dim1"][1], {"dim":dimension}))
        if "dim2" in self.child.attrs.keys():
          dim2 = int(eval(self.child.attrs["dim2"][1], {"dim":dimension}))
  
      return (dim1, dim2)
  
    def is_symmetric_tensor(self, geometry):
      """
      Read if the tensor data in the current MixedTree is symmetric.
      """
  
      dim1, dim2 = self.tensor_shape(geometry)
  
      return dim1 == dim2 and "symmetric" in self.child.attrs.keys() and self.child.attrs["symmetric"][1] == "true"
  
    def is_code(self):
      """
      Perform a series of tests on the current MixedTree, to determine if
      it is intended to be used to store code data.
      """
      try:
        lang = self.get_attr("language")
        if lang == "python":
          return True
      except:
        pass
  
      if self.datatype is not str:
        return False
  
      if "type" in self.child.attrs.keys():
        return self.child.attrs["type"][1] == "code"
      
      return False
  
    def get_code_language(self):
      """
      Assuming this is a code snippet return the language.
      """
  
      if not self.is_code():
        return "python"
  
      try:
        lang = self.child.get_attr("language")
        return lang
      except:
        try:
          lang = self.get_attr("language")
          return lang
        except:   
          return "python"
  
    def get_name_path(self, leaf = True):
      return self.parent.get_name_path(leaf)
  
    def is_sliceable(self):
      return True
  
    def all_attrs_fixed(self):
      for attr in self.attrs:
        if self.attrs[attr][0] != "fixed":
          return False
  
      return True
  