#!/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 re
import subprocess
import tempfile

from pegparser import *

# IDL grammar variants.
WEBIDL_SYNTAX = 0
WEBKIT_SYNTAX = 1
FREMONTCUT_SYNTAX = 2


class IDLParser(object):
  """IDLParser is a PEG based IDL files parser."""

  def __init__(self, syntax=WEBIDL_SYNTAX):
    """Constructor.

    Initializes the IDLParser by defining the grammar and initializing
    a PEGParserinstance.

    Args:
      syntax -- supports either WEBIDL_SYNTAX (0) or WEBKIT_SYNTAX (1)
    """
    self._syntax = syntax
    self._pegparser = PegParser(self._idl_grammar(),
      self._whitespace_grammar(),
      strings_are_tokens=True)

  def _idl_grammar(self):
    """Returns the PEG grammar for IDL parsing."""

    # utilities:
    def syntax_switch(w3c_syntax, webkit_syntax, fremontcut_syntax=None):
      """Returns w3c_syntax or web_syntax, depending on the current
      configuration.
      """
      if self._syntax == WEBIDL_SYNTAX:
        return w3c_syntax
      elif self._syntax == WEBKIT_SYNTAX:
        return webkit_syntax
      elif self._syntax == FREMONTCUT_SYNTAX:
        if fremontcut_syntax is not None:
          return fremontcut_syntax
        return w3c_syntax
      else:
        raise RuntimeError('unsupported IDL syntax %s' % syntax)

    # The following grammar is based on the Web IDL's LL(1) grammar
    # (specified in: http://dev.w3.org/2006/webapi/WebIDL/#idl-grammar).
    # It is adjusted to PEG grammar, as well as to also support
    # WebKit IDL and FremontCut grammar.

    ###################### BEGIN GRAMMAR #####################

    def Id():
      return re.compile(r'[\w\_]+')

    def _Definitions():
      return MAYBE(MANY(_Definition))

    def _Definition():
      return syntax_switch(
        # Web IDL:
        OR(Module, Interface, ExceptionDef, TypeDef, ImplStmt,
           ValueTypeDef, Const),
        # WebKit:
        OR(Module, Interface))

    def Module():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), 'module', Id, '{', _Definitions, '}',
         MAYBE(';')],
        # WebKit:
        ['module', MAYBE(ExtAttrs), Id, '{', _Definitions, '}',
         MAYBE(';')],
        # FremontCut:
        [MAYBE(_Annotations), MAYBE(ExtAttrs), 'module', Id,
         '{', _Definitions, '}', MAYBE(';')])

    def Interface():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), 'interface', Id, MAYBE(_ParentInterfaces),
         MAYBE(['{', MAYBE(MANY(_Member)), '}']), ';'],
        # WebKit:
        ['interface', MAYBE(ExtAttrs), Id, MAYBE(_ParentInterfaces),
         MAYBE(['{', MAYBE(MANY(_Member)), '}']), MAYBE(';')],
        # FremontCut:
        [MAYBE(_Annotations), MAYBE(ExtAttrs), 'interface',
         Id, MAYBE(_ParentInterfaces), MAYBE(['{', MAYBE(MANY(_Member)),
         '}']), ';'])

    def _Member():
      return syntax_switch(
        # Web IDL:
        OR(Const, Attribute, Operation, ExtAttrs),
        # WebKit:
        OR(Const, Attribute, Operation),
        # FremontCut:
        OR(Const, Attribute, Operation, Snippet))

    # Interface inheritance:
    def _ParentInterfaces():
      return [':', MANY(ParentInterface, separator=',')]

    def ParentInterface():
      return syntax_switch(
        # Web IDL:
        [InterfaceType],
        # WebKit:
        [InterfaceType],
        # FremontCut:
        [MAYBE(_Annotations), InterfaceType])

    # TypeDef (Web IDL):
    def TypeDef():
      return ['typedef', Type, Id, ';']

    # TypeDef (Old-school W3C IDLs)
    def ValueTypeDef():
      return ['valuetype', Id, Type, ';']

    # Implements Statement (Web IDL):
    def ImplStmt():
      return [ImplStmtImplementor, 'implements', ImplStmtImplemented,
          ';']

    def ImplStmtImplementor():
      return ScopedName

    def ImplStmtImplemented():
      return ScopedName

    # Constants:
    def Const():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), 'const', Type, Id, '=', ConstExpr, ';'],
        # WebKit:
        ['const', MAYBE(ExtAttrs), Type, Id, '=', ConstExpr, ';'],
        # FremontCut:
        [MAYBE(_Annotations), MAYBE(ExtAttrs), 'const', Type, Id, '=',
         ConstExpr, ';'])

    def ConstExpr():
      return OR(_BooleanLiteral,
            _IntegerLiteral,
            _FloatLiteral)

    def _BooleanLiteral():
      return re.compile(r'true|false')

    def _IntegerLiteral():
      return OR(re.compile(r'(0x)?[0-9ABCDEF]+'),
            re.compile(r'[0-9]+'))

    def _FloatLiteral():
      return re.compile(r'[0-9]+\.[0-9]*')

    # Attributes:
    def Attribute():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), MAYBE(Stringifier), MAYBE(ReadOnly),
         'attribute', Type, Id, MAYBE(_AttrRaises), ';'],
        # WebKit:
        [MAYBE(Stringifier), MAYBE(ReadOnly), 'attribute',
         MAYBE(ExtAttrs), Type, Id, MAYBE(_AttrRaises), ';'],
        # FremontCut:
        [MAYBE(_Annotations), MAYBE(ExtAttrs),
         MAYBE(_AttrGetterSetter), MAYBE(Stringifier), MAYBE(ReadOnly),
         'attribute', Type, Id, MAYBE(_AttrRaises), ';'])

    def _AttrRaises():
      return syntax_switch(
        # Web IDL:
        MANY(OR(GetRaises, SetRaises)),
        # WebKit:
        MANY(OR(GetRaises, SetRaises, Raises), separator=','))

    # Special fremontcut feature:
    def _AttrGetterSetter():
      return OR(AttrGetter, AttrSetter)

    def AttrGetter():
      return 'getter'

    def AttrSetter():
      return 'setter'

    def ReadOnly():
      return 'readonly'

    def GetRaises():
      return syntax_switch(
        # Web IDL:
        ['getraises', '(', _ScopedNames, ')'],
        # WebKit:
        ['getter', 'raises', '(', _ScopedNames, ')'])

    def SetRaises():
      return syntax_switch(
        # Web IDL:
        ['setraises', '(', _ScopedNames, ')'],
        # WebKit:
        ['setter', 'raises', '(', _ScopedNames, ')'])

    # Operation:
    def Operation():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), MAYBE(Stringifier), MAYBE(_Specials),
         ReturnType, MAYBE(Id), '(', _Arguments, ')', MAYBE(Raises),
         ';'],
        # WebKit:
        [MAYBE(ExtAttrs), ReturnType, MAYBE(Id), '(', _Arguments, ')',
         MAYBE(Raises), ';'],
        # FremontCut:
        [MAYBE(_Annotations), MAYBE(ExtAttrs), MAYBE(Stringifier),
         MAYBE(_Specials), ReturnType, MAYBE(Id), '(', _Arguments, ')',
         MAYBE(Raises), ';'])

    def _Specials():
      return MANY(Special)

    def Special():
      return re.compile(r'getter|setter|creator|deleter|caller')

    def Stringifier():
      return 'stringifier'

    def Raises():
      return ['raises', '(', _ScopedNames, ')']

    # Operation arguments:
    def _Arguments():
      return MAYBE(MANY(Argument, ','))

    def Argument():
      return syntax_switch(
        # Web IDL:
        [MAYBE(ExtAttrs), MAYBE(Optional), MAYBE('in'),
         MAYBE(Optional), Type, MAYBE(AnEllipsis), Id],
        # WebKit:
        [MAYBE(Optional), MAYBE('in'), MAYBE(Optional),
         MAYBE(ExtAttrs), Type, Id])

    def Optional():
      return 'optional'

    def AnEllipsis():
      return '...'

    # Exceptions (Web IDL).
    def ExceptionDef():
      return ['exception', Id, '{', MAYBE(MANY(_ExceptionMember)), '}',
          ';']

    def _ExceptionMember():
      return OR(Const, ExceptionField, ExtAttrs)

    def ExceptionField():
      return [Type, Id, ';']

    # Types:
    def Type():
      return _Type

    def ReturnType():
      return OR(VoidType, _Type)

    def InterfaceType():
      return ScopedName

    def _Type():
      return OR(AnyType, ObjectType, _NullableType)

    def _NullableType():
      return [OR(_IntegerType, BooleanType, OctetType, FloatType,
             DoubleType, SequenceType, ScopedName),
          MAYBE(Nullable)]

    def Nullable():
      return '?'

    def SequenceType():
      return ['sequence', '<', Type, '>']

    def AnyType():
      return 'any'

    def ObjectType():
      return 'object'

    def VoidType():
      return 'void'

    def _IntegerType():
      return [MAYBE(Unsigned), OR(ByteType, IntType, LongLongType,
                    LongType, OctetType, ShortType)]

    def Unsigned():
      return 'unsigned'

    def ShortType():
      return 'short'

    def LongLongType():
      return ['long', 'long']

    def LongType():
      return 'long'

    def IntType():
      return 'int'

    def ByteType():
      return 'byte'

    def OctetType():
      return 'octet'

    def BooleanType():
      return 'boolean'

    def FloatType():
      return 'float'

    def DoubleType():
      return 'double'

    def _ScopedNames():
      return MANY(ScopedName, separator=',')

    def ScopedName():
      return re.compile(r'[\w\_\:\.\<\>]+')

    # Extended Attributes:
    def ExtAttrs():
      return ['[', MANY(ExtAttr, ','), ']']

    def ExtAttr():
      return [Id, MAYBE(OR(['=', ExtAttrValue], ExtAttrArgList))]

    def ExtAttrValue():
      return OR(ExtAttrFunctionValue, re.compile(r'[\w&0-9:\-\|]+'))

    def ExtAttrFunctionValue():
      return [Id, ExtAttrArgList]

    def ExtAttrArgList():
      return ['(', MAYBE(MANY(Argument, ',')), ')']

    # Annotations - used in the FremontCut IDL grammar:
    def _Annotations():
      return MANY(Annotation)

    def Annotation():
      return ['@', Id, MAYBE(_AnnotationBody)]

    def _AnnotationBody():
      return ['(', MAYBE(MANY(AnnotationArg, ',')), ')']

    def AnnotationArg():
      return [Id, MAYBE(['=', AnnotationArgValue])]

    def AnnotationArgValue():
      return re.compile(r'[\w&0-9:/\-\.]+')

    # Snippets - used in the FremontCut IDL grammer:
    def Snippet():
      return [MAYBE(_Annotations), 'snippet', '{', SnippetText, '}', ';']

    def SnippetText():
      return re.compile(r'[^\}]*')

    ###################### END GRAMMAR #####################

    # Return the grammar's root rule:
    return MANY(_Definition)

  def _whitespace_grammar(self):
    return OR(re.compile(r'\s+'),
          re.compile(r'//.*'),
          re.compile(r'#.*'),
          re.compile(r'/\*.*?\*/', re.S))

  def _pre_process(self, content, defines, includePaths):
    """Pre-processes the content using gcc.

    WebKit IDLs require pre-processing by gcc. This is done by invoking
    gcc in a sub-process and capturing the results.

    Returns:
      The result of running gcc on the content.

    Args:
      content -- text to process.
      defines -- an array of pre-processor defines.
      includePaths -- an array of path strings.
    """
    # FIXME: Handle gcc not found, or any other processing errors
    gcc = '/usr/bin/gcc'
    cmd = [gcc, '-E', '-P', '-C', '-x', 'c++'];
    for define in defines:
      cmd.append('-D%s' % define)
    cmd.append('-')
    pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (content, stderr) = pipe.communicate(content)
    return content

  def parse(self, content, defines=[], includePaths=[]):
    """Parse the give content string.

    The WebKit IDL syntax also allows gcc pre-processing instructions.
    Lists of defined variables and include paths can be provided.

    Returns:
      An abstract syntax tree (AST).

    Args:
      content -- text to parse.
      defines -- an array of pre-processor defines.
      includePaths -- an array of path strings used by the
        gcc pre-processor.
    """
    if self._syntax == WEBKIT_SYNTAX:
      content = self._pre_process(content, defines, includePaths)

    return self._pegparser.parse(content)
