#!/usr/bin/python
# Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

import sys


class IDLNode(object):
  """Base class for all IDL elements.
  IDLNode may contain various child nodes, and have properties. Examples
  of IDLNode are modules, interfaces, interface members, function arguments,
  etc.
  """

  def __init__(self, ast):
    """Initializes an IDLNode from a PegParser AST output."""
    self.id = self._find_first(ast, 'Id') if ast is not None else None

  def __repr__(self):
    """Generates string of the form <class id extra extra ... 0x12345678>."""
    extras = self._extra_repr()
    if isinstance(extras, list):
      extras = ' '.join([str(e) for e in extras])
    try:
      if self.id:
        return '<%s %s 0x%x>' % (
            type(self).__name__,
            ('%s %s' % (self.id, extras)).strip(),
            hash(self))
      return '<%s %s 0x%x>' % (
          type(self).__name__,
          extras,
          hash(self))
    except Exception, e:
      return "can't convert to string: %s" % e

  def _extra_repr(self):
    """Returns string of extra info for __repr__()."""
    return ''

  def __cmp__(self, other):
    """Override default compare operation.
    IDLNodes are equal if all their properties are equal."""
    if other is None or not isinstance(other, IDLNode):
      return 1
    return self.__dict__.__cmp__(other.__dict__)

  def all(self, type_filter=None):
    """Returns a list containing this node and all it child nodes
    (recursive).

    Args:
      type_filter -- can be used to limit the results to a specific
        node type (e.g. IDLOperation).
    """
    res = []
    if type_filter is None or isinstance(self, type_filter):
      res.append(self)
    for (k, v) in self.__dict__.items():
      if isinstance(v, IDLNode):
        res.extend(v.all(type_filter))
      elif isinstance(v, list):
        for item in v:
          if isinstance(item, IDLNode):
            res.extend(item.all(type_filter))
    return res

  def to_dict(self):
    """Converts the IDLNode and its children into a dictionary.
    This method is useful mostly for debugging and pretty printing.
    """
    res = {}
    for (k, v) in self.__dict__.items():
      if v == None or v == False or v == [] or v == {}:
        # Skip empty/false members.
        continue
      elif isinstance(v, IDLDictNode) and not len(v):
        # Skip empty dict sub-nodes.
        continue
      elif isinstance(v, list):
        # Convert lists:
        new_v = []
        for sub_node in v:
          if isinstance(sub_node, IDLNode):
            # Convert sub-node:
            new_v.append(sub_node.to_dict())
          else:
            new_v.append(sub_node)
        v = new_v
      elif isinstance(v, IDLNode):
        # Convert sub-node:
        v = v.to_dict()
      res[k] = v
    return res

  def _find_all(self, ast, label, max_results=sys.maxint):
    """Searches the AST for tuples with a given label. The PegParser
    output is composed of lists and tuples, where the tuple 1st argument
    is a label. If ast root is a list, will search recursively inside each
    member in the list.

    Args:
      ast -- the AST to search.
      label -- the label to look for.
      res -- results are put into this list.
      max_results -- maximum number of results.
    """
    res = []
    if max_results <= 0:
      return res

    if isinstance(ast, list):
      for childAst in ast:
        sub_res = self._find_all(childAst, label,
                     max_results - len(res))
        res.extend(sub_res)
    elif isinstance(ast, tuple):
      (nodeLabel, value) = ast
      if nodeLabel == label:
        res.append(value)
    return res

  def _find_first(self, ast, label):
    """Convenience method for _find_all(..., max_results=1).
    Returns a single element instead of a list, or None if nothing
    is found."""
    res = self._find_all(ast, label, max_results=1)
    if len(res):
      return res[0]
    return None

  def _has(self, ast, label):
    """Returns true if an element with the given label is
    in the AST by searching for it."""
    return len(self._find_all(ast, label, max_results=1)) == 1

  def _convert_all(self, ast, label, idlnode_ctor):
    """Converts AST elements into IDLNode elements.
    Uses _find_all to find elements with a given label and converts
    them into IDLNodes with a given constructor.
    Returns:
      A list of the converted nodes.
    Args:
      ast -- the ast element to start a search at.
      label -- the element label to look for.
      idlnode_ctor -- a constructor function of one of the IDLNode
        sub-classes.
    """
    res = []
    found = self._find_all(ast, label)
    if not found:
      return res
    if not isinstance(found, list):
      raise RuntimeError("Expected list but %s found" % type(found))
    for childAst in found:
      converted = idlnode_ctor(childAst)
      res.append(converted)
    return res

  def _convert_first(self, ast, label, idlnode_ctor):
    """Like _convert_all, but only converts the first found results."""
    childAst = self._find_first(ast, label)
    if not childAst:
      return None
    return idlnode_ctor(childAst)

  def _convert_ext_attrs(self, ast):
    """Helper method for uniform conversion of extended attributes."""
    self.ext_attrs = IDLExtAttrs(ast)

  def _convert_annotations(self, ast):
    """Helper method for uniform conversion of annotations."""
    self.annotations = IDLAnnotations(ast)


class IDLDictNode(IDLNode):
  """Base class for dictionary-like IDL nodes such as extended attributes
  and annotations. The base class implements various dict interfaces."""

  def __init__(self, ast):
    IDLNode.__init__(self, None)
    if ast is not None and isinstance(ast, dict):
      self.__map = ast
    else:
      self.__map = {}

  def __len__(self):
    return len(self.__map)

  def __getitem__(self, key):
    return self.__map[key]

  def __setitem__(self, key, value):
    self.__map[key] = value

  def __delitem__(self, key):
    del self.__map[key]

  def __contains__(self, key):
    return key in self.__map

  def __iter__(self):
    return self.__map.__iter__()

  def get(self, key, default=None):
    return self.__map.get(key, default)

  def items(self):
    return self.__map.items()

  def keys(self):
    return self.__map.keys()

  def values(self):
    return self.__map.values()

  def clear(self):
    self.__map = {}

  def to_dict(self):
    """Overrides the default IDLNode.to_dict behavior.
    The IDLDictNode members are copied into a new dictionary, and
    IDLNode members are recursively converted into dicts as well.
    """
    res = {}
    for (k, v) in self.__map.items():
      if isinstance(v, IDLNode):
        v = v.to_dict()
      res[k] = v
    return res

  def all(self, node_filter):
    """Overrides the default IDLNode.all recursive operator."""
    if node_filter is None or isinstance(self, node_filter):
      return [self]
    return []


class IDLFile(IDLNode):
  """IDLFile is the top-level node in each IDL file. It may contain
  modules or interfaces."""

  def __init__(self, ast, filename=None):
    IDLNode.__init__(self, ast)
    self.filename = filename
    self.modules = self._convert_all(ast, 'Module', IDLModule)
    self.interfaces = self._convert_all(ast, 'Interface', IDLInterface)


class IDLModule(IDLNode):
  """IDLModule has an id, and may contain interfaces, type defs and
  implements statements."""
  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self._convert_ext_attrs(ast)
    self._convert_annotations(ast)
    self.interfaces = self._convert_all(ast, 'Interface', IDLInterface)
    self.typeDefs = self._convert_all(ast, 'TypeDef', IDLTypeDef)
    self.implementsStatements = self._convert_all(ast, 'ImplStmt',
      IDLImplementsStatement)


class IDLExtAttrs(IDLDictNode):
  """IDLExtAttrs is an IDLDictNode that stores IDL Extended Attributes.
  Modules, interfaces, members and arguments can all own IDLExtAttrs."""
  def __init__(self, ast=None):
    IDLDictNode.__init__(self, None)
    if not ast:
      return
    ext_attrs_ast = self._find_first(ast, 'ExtAttrs')
    if not ext_attrs_ast:
      return
    for ext_attr in self._find_all(ext_attrs_ast, 'ExtAttr'):
      name = self._find_first(ext_attr, 'Id')
      value = self._find_first(ext_attr, 'ExtAttrValue')
      func_value = self._find_first(value, 'ExtAttrFunctionValue')
      if func_value:
        # TODO: Parse ExtAttrFunctionValue.  It is used only for
        # NamedConstructor which is ignored.
        pass
      else:
        self[name] = value


class IDLType(IDLNode):
  """IDLType is used to describe constants, attributes and operations'
  return and input types. IDLType matches AST labels such as ScopedName,
  StringType, VoidType, IntegerType, etc."""

  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    # Search for a 'ScopedName' or any label ending with 'Type'.
    if isinstance(ast, list):
      self.id = self._find_first(ast, 'ScopedName')
      if not self.id:
        # FIXME: use regexp search instead
        for childAst in ast:
          (label, childAst) = childAst
          if label.endswith('Type'):
            self.id = self.__label_to_type(label, ast)
            break
    elif isinstance(ast, tuple):
      (label, value) = ast
      if label == 'ScopedName':
        self.id = value
      else:
        self.id = self.__label_to_type(label, ast)
    elif isinstance(ast, str):
      self.id = ast
    if not self.id:
      raise SyntaxError('Could not parse type %s' % (ast))

  def __label_to_type(self, label, ast):
    if label.endswith('Type'):
      # Omit 'Type' suffix and lowercase the rest.
      label = '%s%s' % (label[0].lower(), label[1:-4])
      if label == 'longLong':
        # Special case for LongLongType:
        label = 'long long'
    # Add unsigned qualifier.
    if self._has(ast, 'Unsigned'):
      label = 'unsigned %s' % label
    return label


class IDLTypeDef(IDLNode):
  """IDLNode for 'typedef [type] [id]' declarations."""
  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self._convert_annotations(ast)
    self.type = self._convert_first(ast, 'Type', IDLType)


class IDLInterface(IDLNode):
  """IDLInterface node contains operations, attributes, constants,
  as well as parent references."""

  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self._convert_ext_attrs(ast)
    self._convert_annotations(ast)
    self.parents = self._convert_all(ast, 'ParentInterface',
      IDLParentInterface)
    self.operations = self._convert_all(ast, 'Operation', IDLOperation)
    self.attributes = self._convert_all(ast, 'Attribute', IDLAttribute)
    self.constants = self._convert_all(ast, 'Const', IDLConstant)
    self.snippets = self._convert_all(ast, 'Snippet', IDLSnippet)
    self.is_supplemental = 'Supplemental' in self.ext_attrs
    self.is_no_interface_object = 'NoInterfaceObject' in self.ext_attrs
    self.is_fc_suppressed = 'Suppressed' in self.ext_attrs


class IDLParentInterface(IDLNode):
  """This IDLNode specialization is for 'Interface Child : Parent {}'
  declarations."""
  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self._convert_annotations(ast)
    self.type = self._convert_first(ast, 'InterfaceType', IDLType)


class IDLMember(IDLNode):
  """A base class for constants, attributes and operations."""

  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self.type = self._convert_first(ast, 'Type', IDLType)
    self._convert_ext_attrs(ast)
    self._convert_annotations(ast)
    self.is_fc_suppressed = 'Suppressed' in self.ext_attrs


class IDLOperation(IDLMember):
  """IDLNode specialization for 'type name(args)' declarations."""
  def __init__(self, ast):
    IDLMember.__init__(self, ast)
    self.type = self._convert_first(ast, 'ReturnType', IDLType)
    self.arguments = self._convert_all(ast, 'Argument', IDLArgument)
    self.raises = self._convert_first(ast, 'Raises', IDLType)
    self.specials = self._find_all(ast, 'Special')
    self.is_stringifier = self._has(ast, 'Stringifier')
  def _extra_repr(self):
    return [self.arguments]


class IDLAttribute(IDLMember):
  """IDLNode specialization for 'attribute type name' declarations."""
  def __init__(self, ast):
    IDLMember.__init__(self, ast)
    self.is_read_only = self._has(ast, 'ReadOnly')
    # There are various ways to define exceptions for attributes:
    self.raises = self._convert_first(ast, 'Raises', IDLType)
    self.get_raises = self.raises \
      or self._convert_first(ast, 'GetRaises', IDLType)
    self.set_raises = self.raises \
      or self._convert_first(ast, 'SetRaises', IDLType)
    # FremontCut IDL syntax defines getters and setters separately:
    self.is_fc_getter = self._has(ast, 'AttrGetter')
    self.is_fc_setter = self._has(ast, 'AttrSetter')
  def _extra_repr(self):
    extra = []
    if self.is_fc_getter: extra.append('get')
    if self.is_fc_setter: extra.append('set')
    if self.is_read_only: extra.append('readonly')
    if self.raises: extra.append('raises')
    return extra

class IDLConstant(IDLMember):
  """IDLNode specialization for 'const type name = value' declarations."""
  def __init__(self, ast):
    IDLMember.__init__(self, ast)
    self.value = self._find_first(ast, 'ConstExpr')


class IDLSnippet(IDLMember):
  """IDLNode specialization for 'snippet { data };"""
  def __init__(self, ast):
    IDLMember.__init__(self, ast)
    self._convert_annotations(ast)
    self.text = self._find_first(ast, 'SnippetText')


class IDLArgument(IDLNode):
  """IDLNode specialization for operation arguments."""
  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self.type = self._convert_first(ast, 'Type', IDLType)
    self._convert_ext_attrs(ast)
    # WebKit and Web IDL differ in how Optional is declared:
    self.is_optional = self._has(ast, 'Optional') \
      or ('Optional' in self.ext_attrs)


class IDLImplementsStatement(IDLNode):
  """IDLNode specialization for 'X implements Y' declarations."""
  def __init__(self, ast):
    IDLNode.__init__(self, ast)
    self.implementor = self._convert_first(ast, 'ImplStmtImplementor',
      IDLType)
    self.implemented = self._convert_first(ast, 'ImplStmtImplemented',
      IDLType)


class IDLAnnotations(IDLDictNode):
  """IDLDictNode specialization for a list of FremontCut annotations."""
  def __init__(self, ast=None):
    IDLDictNode.__init__(self, ast)
    self.id = None
    if not ast:
      return
    for annotation in self._find_all(ast, 'Annotation'):
      name = self._find_first(annotation, 'Id')
      value = IDLAnnotation(annotation)
      self[name] = value


class IDLAnnotation(IDLDictNode):
  """IDLDictNode specialization for one annotation."""
  def __init__(self, ast=None):
    IDLDictNode.__init__(self, ast)
    self.id = None
    if not ast:
      return
    for arg in self._find_all(ast, 'AnnotationArg'):
      name = self._find_first(arg, 'Id')
      value = self._find_first(arg, 'AnnotationArgValue')
      self[name] = value
