xpcom/idl-parser/xpidl.py

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 #!/usr/bin/env python
     2 # xpidl.py - A parser for cross-platform IDL (XPIDL) files.
     3 #
     4 # This Source Code Form is subject to the terms of the Mozilla Public
     5 # License, v. 2.0. If a copy of the MPL was not distributed with this
     6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     8 """A parser for cross-platform IDL (XPIDL) files."""
    10 import sys, os.path, re
    11 from ply import lex, yacc
    13 """A type conforms to the following pattern:
    15     def isScriptable(self):
    16         'returns True or False'
    18     def nativeType(self, calltype):
    19         'returns a string representation of the native type
    20         calltype must be 'in', 'out', or 'inout'
    22 Interface members const/method/attribute conform to the following pattern:
    24     name = 'string'
    26     def toIDL(self):
    27         'returns the member signature as IDL'
    28 """
    30 def attlistToIDL(attlist):
    31     if len(attlist) == 0:
    32         return ''
    34     attlist = list(attlist)
    35     attlist.sort(cmp=lambda a,b: cmp(a[0], b[0]))
    37     return '[%s] ' % ','.join(["%s%s" % (name, value is not None and '(%s)' % value or '')
    38                               for name, value, aloc in attlist])
    40 _paramsHardcode = {
    41     2: ('array', 'shared', 'iid_is', 'size_is', 'retval'),
    42     3: ('array', 'size_is', 'const'),
    43 }
    45 def paramAttlistToIDL(attlist):
    46     if len(attlist) == 0:
    47         return ''
    49     # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode
    50     # quirk
    51     attlist = list(attlist)
    52     sorted = []
    53     if len(attlist) in _paramsHardcode:
    54         for p in _paramsHardcode[len(attlist)]:
    55             i = 0
    56             while i < len(attlist):
    57                 if attlist[i][0] == p:
    58                     sorted.append(attlist[i])
    59                     del attlist[i]
    60                     continue
    62                 i += 1
    64     sorted.extend(attlist)
    66     return '[%s] ' % ', '.join(["%s%s" % (name, value is not None and ' (%s)' % value or '')
    67                                 for name, value, aloc in sorted])
    69 def unaliasType(t):
    70     while t.kind == 'typedef':
    71         t = t.realtype
    72     assert t is not None
    73     return t
    75 def getBuiltinOrNativeTypeName(t):
    76     t = unaliasType(t)
    77     if t.kind == 'builtin':
    78         return t.name
    79     elif t.kind == 'native':
    80         assert t.specialtype is not None
    81         return '[%s]' % t.specialtype
    82     else:
    83         return None
    85 class BuiltinLocation(object):
    86     def get(self):
    87         return "<builtin type>"
    89     def __str__(self):
    90         return self.get()
    92 class Builtin(object):
    93     kind = 'builtin'
    94     location = BuiltinLocation
    96     def __init__(self, name, nativename, signed=False, maybeConst=False):
    97         self.name = name
    98         self.nativename = nativename
    99         self.signed = signed
   100         self.maybeConst = maybeConst
   102     def isScriptable(self):
   103         return True
   105     def nativeType(self, calltype, shared=False, const=False):
   106         if const:
   107             print >>sys.stderr, IDLError("[const] doesn't make sense on builtin types.", self.location, warning=True)
   108             const = 'const '
   109         elif calltype == 'in' and self.nativename.endswith('*'):
   110             const = 'const '
   111         elif shared:
   112             if not self.nativename.endswith('*'):
   113                 raise IDLError("[shared] not applicable to non-pointer types.", self.location)
   114             const = 'const '
   115         else:
   116             const = ''
   117         return "%s%s %s" % (const, self.nativename,
   118                             calltype != 'in' and '*' or '')
   120 builtinNames = [
   121     Builtin('boolean', 'bool'),
   122     Builtin('void', 'void'),
   123     Builtin('octet', 'uint8_t'),
   124     Builtin('short', 'int16_t', True, True),
   125     Builtin('long', 'int32_t', True, True),
   126     Builtin('long long', 'int64_t', True, False),
   127     Builtin('unsigned short', 'uint16_t', False, True),
   128     Builtin('unsigned long', 'uint32_t', False, True),
   129     Builtin('unsigned long long', 'uint64_t', False, False),
   130     Builtin('float', 'float', True, False),
   131     Builtin('double', 'double', True, False),
   132     Builtin('char', 'char', True, False),
   133     Builtin('string', 'char *', False, False),
   134     Builtin('wchar', 'char16_t', False, False),
   135     Builtin('wstring', 'char16_t *', False, False),
   136 ]
   138 builtinMap = {}
   139 for b in builtinNames:
   140     builtinMap[b.name] = b
   142 class Location(object):
   143     _line = None
   145     def __init__(self, lexer, lineno, lexpos):
   146         self._lineno = lineno
   147         self._lexpos = lexpos
   148         self._lexdata = lexer.lexdata
   149         self._file = getattr(lexer, 'filename', "<unknown>")
   151     def __eq__(self, other):
   152         return self._lexpos == other._lexpos and \
   153                self._file == other._file
   155     def resolve(self):
   156         if self._line:
   157             return
   159         startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1
   160         endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80)
   161         self._line = self._lexdata[startofline:endofline]
   162         self._colno = self._lexpos - startofline
   164     def pointerline(self):
   165         def i():
   166             for i in xrange(0, self._colno):
   167                 yield " "
   168             yield "^"
   170         return "".join(i())
   172     def get(self):
   173         self.resolve()
   174         return "%s line %s:%s" % (self._file, self._lineno, self._colno)
   176     def __str__(self):
   177         self.resolve()
   178         return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno,
   179                                           self._line, self.pointerline())
   181 class NameMap(object):
   182     """Map of name -> object. Each object must have a .name and .location property.
   183     Setting the same name twice throws an error."""
   184     def __init__(self):
   185         self._d = {}
   187     def __getitem__(self, key):
   188         if key in builtinMap:
   189             return builtinMap[key]
   190         return self._d[key]
   192     def __iter__(self):
   193         return self._d.itervalues()
   195     def __contains__(self, key):
   196         return key in builtinMap or key in self._d
   198     def set(self, object):
   199         if object.name in builtinMap:
   200             raise IDLError("name '%s' is a builtin and cannot be redeclared" % (object.name), object.location)
   201         if object.name.startswith("_"):
   202             object.name = object.name[1:]
   203         if object.name in self._d:
   204             old = self._d[object.name]
   205             if old == object: return
   206             if isinstance(old, Forward) and isinstance(object, Interface):
   207                 self._d[object.name] = object
   208             elif isinstance(old, Interface) and isinstance(object, Forward):
   209                 pass
   210             else:
   211                 raise IDLError("name '%s' specified twice. Previous location: %s" % (object.name, self._d[object.name].location), object.location)
   212         else:
   213             self._d[object.name] = object
   215     def get(self, id, location):
   216         try:
   217             return self[id]
   218         except KeyError:
   219             raise IDLError("Name '%s' not found", location)
   221 class IDLError(Exception):
   222     def __init__(self, message, location, warning=False):
   223         self.message = message
   224         self.location = location
   225         self.warning = warning
   227     def __str__(self):
   228         return "%s: %s, %s" % (self.warning and 'warning' or 'error',
   229                                self.message, self.location)
   231 class Include(object):
   232     kind = 'include'
   234     def __init__(self, filename, location):
   235         self.filename = filename
   236         self.location = location
   238     def __str__(self):
   239         return "".join(["include '%s'\n" % self.filename])
   241     def resolve(self, parent):
   242         def incfiles():
   243             yield self.filename
   244             for dir in parent.incdirs:
   245                 yield os.path.join(dir, self.filename)
   247         for file in incfiles():
   248             if not os.path.exists(file): continue
   250             self.IDL = parent.parser.parse(open(file).read(), filename=file)
   251             self.IDL.resolve(parent.incdirs, parent.parser)
   252             for type in self.IDL.getNames():
   253                 parent.setName(type)
   254             parent.deps.extend(self.IDL.deps)
   255             return
   257         raise IDLError("File '%s' not found" % self.filename, self.location)
   259 class IDL(object):
   260     def __init__(self, productions):
   261         self.productions = productions
   262         self.deps = []
   264     def setName(self, object):
   265         self.namemap.set(object)
   267     def getName(self, id, location):
   268         try:
   269             return self.namemap[id]
   270         except KeyError:
   271             raise IDLError("type '%s' not found" % id, location)
   273     def hasName(self, id):
   274         return id in self.namemap
   276     def getNames(self):
   277         return iter(self.namemap)
   279     def __str__(self):
   280         return "".join([str(p) for p in self.productions])
   282     def resolve(self, incdirs, parser):
   283         self.namemap = NameMap()
   284         self.incdirs = incdirs
   285         self.parser = parser
   286         for p in self.productions:
   287             p.resolve(self)
   289     def includes(self):
   290         for p in self.productions:
   291             if p.kind == 'include':
   292                 yield p
   294     def needsJSTypes(self):
   295         for p in self.productions:
   296             if p.kind == 'interface' and p.needsJSTypes():
   297                 return True
   298         return False
   300 class CDATA(object):
   301     kind = 'cdata'
   302     _re = re.compile(r'\n+')
   304     def __init__(self, data, location):
   305         self.data = self._re.sub('\n', data)
   306         self.location = location
   308     def resolve(self, parent):
   309         pass
   311     def __str__(self):
   312         return "cdata: %s\n\t%r\n" % (self.location.get(), self.data)
   314     def count(self):
   315         return 0
   317 class Typedef(object):
   318     kind = 'typedef'
   320     def __init__(self, type, name, location, doccomments):
   321         self.type = type
   322         self.name = name
   323         self.location = location
   324         self.doccomments = doccomments
   326     def __eq__(self, other):
   327         return self.name == other.name and self.type == other.type
   329     def resolve(self, parent):
   330         parent.setName(self)
   331         self.realtype = parent.getName(self.type, self.location)
   333     def isScriptable(self):
   334         return self.realtype.isScriptable()
   336     def nativeType(self, calltype):
   337         return "%s %s" % (self.name,
   338                           calltype != 'in' and '*' or '')
   340     def __str__(self):
   341         return "typedef %s %s\n" % (self.type, self.name)
   343 class Forward(object):
   344     kind = 'forward'
   346     def __init__(self, name, location, doccomments):
   347         self.name = name
   348         self.location = location
   349         self.doccomments = doccomments
   351     def __eq__(self, other):
   352         return other.kind == 'forward' and other.name == self.name
   354     def resolve(self, parent):
   355         # Hack alert: if an identifier is already present, move the doccomments
   356         # forward.
   357         if parent.hasName(self.name):
   358             for i in xrange(0, len(parent.productions)):
   359                 if parent.productions[i] is self: break
   360             for i in xrange(i + 1, len(parent.productions)):
   361                 if hasattr(parent.productions[i], 'doccomments'):
   362                     parent.productions[i].doccomments[0:0] = self.doccomments
   363                     break
   365         parent.setName(self)
   367     def isScriptable(self):
   368         return True
   370     def nativeType(self, calltype):
   371         return "%s %s" % (self.name,
   372                           calltype != 'in' and '* *' or '*')
   374     def __str__(self):
   375         return "forward-declared %s\n" % self.name
   377 class Native(object):
   378     kind = 'native'
   380     modifier = None
   381     specialtype = None
   383     specialtypes = {
   384         'nsid': None,
   385         'domstring': 'nsAString',
   386         'utf8string': 'nsACString',
   387         'cstring': 'nsACString',
   388         'astring': 'nsAString',
   389         'jsval': 'JS::Value'
   390         }
   392     def __init__(self, name, nativename, attlist, location):
   393         self.name = name
   394         self.nativename = nativename
   395         self.location = location
   397         for name, value, aloc in attlist:
   398             if value is not None:
   399                 raise IDLError("Unexpected attribute value", aloc)
   400             if name in ('ptr', 'ref'):
   401                 if self.modifier is not None:
   402                     raise IDLError("More than one ptr/ref modifier", aloc)
   403                 self.modifier = name
   404             elif name in self.specialtypes.keys():
   405                 if self.specialtype is not None:
   406                     raise IDLError("More than one special type", aloc)
   407                 self.specialtype = name
   408                 if self.specialtypes[name] is not None:
   409                     self.nativename = self.specialtypes[name]
   410             else:
   411                 raise IDLError("Unexpected attribute", aloc)
   413     def __eq__(self, other):
   414         return self.name == other.name and \
   415                self.nativename == other.nativename and \
   416                self.modifier == other.modifier and \
   417                self.specialtype == other.specialtype
   419     def resolve(self, parent):
   420         parent.setName(self)
   422     def isScriptable(self):
   423         if self.specialtype is None:
   424             return False
   426         if self.specialtype == 'nsid':
   427             return self.modifier is not None
   429         return self.modifier == 'ref'
   431     def isPtr(self, calltype):
   432         return self.modifier == 'ptr'
   434     def isRef(self, calltype):
   435         return self.modifier == 'ref'
   437     def nativeType(self, calltype, const=False, shared=False):
   438         if shared:
   439             if calltype != 'out':
   440                 raise IDLError("[shared] only applies to out parameters.")
   441             const = True
   443         if self.specialtype is not None and calltype == 'in':
   444             const = True
   446         if self.specialtype == 'jsval':
   447             if calltype == 'out' or calltype == 'inout':
   448                 return "JS::MutableHandleValue "
   449             return "JS::HandleValue "
   451         if self.isRef(calltype):
   452             m = '& '
   453         elif self.isPtr(calltype):
   454             m = '*' + ((self.modifier == 'ptr' and calltype != 'in') and '*' or '')
   455         else:
   456             m = calltype != 'in' and '*' or ''
   457         return "%s%s %s" % (const and 'const ' or '', self.nativename, m)
   459     def __str__(self):
   460         return "native %s(%s)\n" % (self.name, self.nativename)
   462 class Interface(object):
   463     kind = 'interface'
   465     def __init__(self, name, attlist, base, members, location, doccomments):
   466         self.name = name
   467         self.attributes = InterfaceAttributes(attlist, location)
   468         self.base = base
   469         self.members = members
   470         self.location = location
   471         self.namemap = NameMap()
   472         self.doccomments = doccomments
   473         self.nativename = name
   475         for m in members:
   476             if not isinstance(m, CDATA):
   477                 self.namemap.set(m)
   479     def __eq__(self, other):
   480         return self.name == other.name and self.location == other.location
   482     def resolve(self, parent):
   483         self.idl = parent
   485         # Hack alert: if an identifier is already present, libIDL assigns
   486         # doc comments incorrectly. This is quirks-mode extraordinaire!
   487         if parent.hasName(self.name):
   488             for member in self.members:
   489                 if hasattr(member, 'doccomments'):
   490                     member.doccomments[0:0] = self.doccomments
   491                     break
   492             self.doccomments = parent.getName(self.name, None).doccomments
   494         if self.attributes.function:
   495             has_method = False
   496             for member in self.members:
   497                 if member.kind is 'method':
   498                     if has_method:
   499                         raise IDLError("interface '%s' has multiple methods, but marked 'function'" % self.name, self.location)
   500                     else:
   501                         has_method = True
   503         parent.setName(self)
   504         if self.base is not None:
   505             realbase = parent.getName(self.base, self.location)
   506             if realbase.kind != 'interface':
   507                 raise IDLError("interface '%s' inherits from non-interface type '%s'" % (self.name, self.base), self.location)
   509             if self.attributes.scriptable and not realbase.attributes.scriptable:
   510                 print >>sys.stderr, IDLError("interface '%s' is scriptable but derives from non-scriptable '%s'" % (self.name, self.base), self.location, warning=True)
   512             if self.attributes.scriptable and realbase.attributes.builtinclass and not self.attributes.builtinclass:
   513                 raise IDLError("interface '%s' is not builtinclass but derives from builtinclass '%s'" % (self.name, self.base), self.location)
   515         for member in self.members:
   516             member.resolve(self)
   518         # The number 250 is NOT arbitrary; this number is the maximum number of
   519         # stub entries defined in xpcom/reflect/xptcall/public/genstubs.pl
   520         # Do not increase this value without increasing the number in that
   521         # location, or you WILL cause otherwise unknown problems!
   522         if self.countEntries() > 250 and not self.attributes.builtinclass:
   523             raise IDLError("interface '%s' has too many entries" % self.name,
   524                 self.location)
   526     def isScriptable(self):
   527         # NOTE: this is not whether *this* interface is scriptable... it's
   528         # whether, when used as a type, it's scriptable, which is true of all
   529         # interfaces.
   530         return True
   532     def nativeType(self, calltype, const=False):
   533         return "%s%s %s" % (const and 'const ' or '',
   534                             self.name,
   535                             calltype != 'in' and '* *' or '*')
   537     def __str__(self):
   538         l = ["interface %s\n" % self.name]
   539         if self.base is not None:
   540             l.append("\tbase %s\n" % self.base)
   541         l.append(str(self.attributes))
   542         if self.members is None:
   543             l.append("\tincomplete type\n")
   544         else:
   545             for m in self.members:
   546                 l.append(str(m))
   547         return "".join(l)
   549     def getConst(self, name, location):
   550         # The constant may be in a base class
   551         iface = self
   552         while name not in iface.namemap and iface is not None:
   553             iface = self.idl.getName(self.base, self.location)
   554         if iface is None:
   555             raise IDLError("cannot find symbol '%s'" % name, c.location)
   556         c = iface.namemap.get(name, location)
   557         if c.kind != 'const':
   558             raise IDLError("symbol '%s' is not a constant", c.location)
   560         return c.getValue()
   562     def needsJSTypes(self):
   563         for m in self.members:
   564             if m.kind == "attribute" and m.type == "jsval":
   565                 return True
   566             if m.kind == "method" and m.needsJSTypes():
   567                 return True
   568         return False
   570     def countEntries(self):
   571         ''' Returns the number of entries in the vtable for this interface. '''
   572         total = sum(member.count() for member in self.members)
   573         if self.base is not None:
   574             realbase = self.idl.getName(self.base, self.location)
   575             total += realbase.countEntries()
   576         return total
   578 class InterfaceAttributes(object):
   579     uuid = None
   580     scriptable = False
   581     builtinclass = False
   582     function = False
   583     deprecated = False
   584     noscript = False
   586     def setuuid(self, value):
   587         self.uuid = value.lower()
   589     def setscriptable(self):
   590         self.scriptable = True
   592     def setfunction(self):
   593         self.function = True
   595     def setnoscript(self):
   596         self.noscript = True
   598     def setbuiltinclass(self):
   599         self.builtinclass = True
   601     def setdeprecated(self):
   602         self.deprecated = True
   604     actions = {
   605         'uuid':       (True, setuuid),
   606         'scriptable': (False, setscriptable),
   607         'builtinclass': (False, setbuiltinclass),
   608         'function':   (False, setfunction),
   609         'noscript':   (False, setnoscript),
   610         'deprecated': (False, setdeprecated),
   611         'object':     (False, lambda self: True),
   612         }
   614     def __init__(self, attlist, location):
   615         def badattribute(self):
   616             raise IDLError("Unexpected interface attribute '%s'" % name, location)
   618         for name, val, aloc in attlist:
   619             hasval, action = self.actions.get(name, (False, badattribute))
   620             if hasval:
   621                 if val is None:
   622                     raise IDLError("Expected value for attribute '%s'" % name,
   623                                    aloc)
   625                 action(self, val)
   626             else:
   627                 if val is not None:
   628                     raise IDLError("Unexpected value for attribute '%s'" % name,
   629                                    aloc)
   631                 action(self)
   633         if self.uuid is None:
   634             raise IDLError("interface has no uuid", location)
   636     def __str__(self):
   637         l = []
   638         if self.uuid:
   639             l.append("\tuuid: %s\n" % self.uuid)
   640         if self.scriptable:
   641             l.append("\tscriptable\n")
   642         if self.builtinclass:
   643             l.append("\tbuiltinclass\n")
   644         if self.function:
   645             l.append("\tfunction\n")
   646         return "".join(l)
   648 class ConstMember(object):
   649     kind = 'const'
   650     def __init__(self, type, name, value, location, doccomments):
   651         self.type = type
   652         self.name = name
   653         self.value = value
   654         self.location = location
   655         self.doccomments = doccomments
   657     def resolve(self, parent):
   658         self.realtype = parent.idl.getName(self.type, self.location)
   659         self.iface = parent
   660         basetype = self.realtype
   661         while isinstance(basetype, Typedef):
   662             basetype = basetype.realtype
   663         if not isinstance(basetype, Builtin) or not basetype.maybeConst:
   664             raise IDLError("const may only be a short or long type, not %s" % self.type, self.location)
   666         self.basetype = basetype
   668     def getValue(self):
   669         return self.value(self.iface)
   671     def __str__(self):
   672         return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue())
   674     def count(self):
   675         return 0
   677 class Attribute(object):
   678     kind = 'attribute'
   679     noscript = False
   680     readonly = False
   681     implicit_jscontext = False
   682     nostdcall = False
   683     binaryname = None
   684     null = None
   685     undefined = None
   686     deprecated = False
   687     infallible = False
   689     def __init__(self, type, name, attlist, readonly, location, doccomments):
   690         self.type = type
   691         self.name = name
   692         self.attlist = attlist
   693         self.readonly = readonly
   694         self.location = location
   695         self.doccomments = doccomments
   697         for name, value, aloc in attlist:
   698             if name == 'binaryname':
   699                 if value is None:
   700                     raise IDLError("binaryname attribute requires a value",
   701                                    aloc)
   703                 self.binaryname = value
   704                 continue
   706             if name == 'Null':
   707                 if value is None:
   708                     raise IDLError("'Null' attribute requires a value", aloc)
   709                 if readonly:
   710                     raise IDLError("'Null' attribute only makes sense for setters",
   711                                    aloc);
   712                 if value not in ('Empty', 'Null', 'Stringify'):
   713                     raise IDLError("'Null' attribute value must be 'Empty', 'Null' or 'Stringify'",
   714                                    aloc);
   715                 self.null = value
   716             elif name == 'Undefined':
   717                 if value is None:
   718                     raise IDLError("'Undefined' attribute requires a value", aloc)
   719                 if readonly:
   720                     raise IDLError("'Undefined' attribute only makes sense for setters",
   721                                    aloc);
   722                 if value not in ('Empty', 'Null'):
   723                     raise IDLError("'Undefined' attribute value must be 'Empty' or 'Null'",
   724                                    aloc);
   725                 self.undefined = value
   726             else:
   727                 if value is not None:
   728                     raise IDLError("Unexpected attribute value", aloc)
   730                 if name == 'noscript':
   731                     self.noscript = True
   732                 elif name == 'implicit_jscontext':
   733                     self.implicit_jscontext = True
   734                 elif name == 'deprecated':
   735                     self.deprecated = True
   736                 elif name == 'nostdcall':
   737                     self.nostdcall = True
   738                 elif name == 'infallible':
   739                     self.infallible = True
   740                 else:
   741                     raise IDLError("Unexpected attribute '%s'" % name, aloc)
   743     def resolve(self, iface):
   744         self.iface = iface
   745         self.realtype = iface.idl.getName(self.type, self.location)
   746         if (self.null is not None and
   747             getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'):
   748             raise IDLError("'Null' attribute can only be used on DOMString",
   749                            self.location)
   750         if (self.undefined is not None and
   751             getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'):
   752             raise IDLError("'Undefined' attribute can only be used on DOMString",
   753                            self.location)
   754         if self.infallible and not self.realtype.kind == 'builtin':
   755             raise IDLError('[infallible] only works on builtin types '
   756                            '(numbers, booleans, and raw char types)',
   757                            self.location)
   758         if self.infallible and not iface.attributes.builtinclass:
   759             raise IDLError('[infallible] attributes are only allowed on '
   760                            '[builtinclass] interfaces',
   761                            self.location)
   764     def toIDL(self):
   765         attribs = attlistToIDL(self.attlist)
   766         readonly = self.readonly and 'readonly ' or ''
   767         return "%s%sattribute %s %s;" % (attribs, readonly, self.type, self.name)
   769     def isScriptable(self):
   770         if not self.iface.attributes.scriptable: return False
   771         return not self.noscript
   773     def __str__(self):
   774         return "\t%sattribute %s %s\n" % (self.readonly and 'readonly ' or '',
   775                                           self.type, self.name)
   777     def count(self):
   778         return self.readonly and 1 or 2
   780 class Method(object):
   781     kind = 'method'
   782     noscript = False
   783     notxpcom = False
   784     binaryname = None
   785     implicit_jscontext = False
   786     nostdcall = False
   787     optional_argc = False
   788     deprecated = False
   790     def __init__(self, type, name, attlist, paramlist, location, doccomments, raises):
   791         self.type = type
   792         self.name = name
   793         self.attlist = attlist
   794         self.params = paramlist
   795         self.location = location
   796         self.doccomments = doccomments
   797         self.raises = raises
   799         for name, value, aloc in attlist:
   800             if name == 'binaryname':
   801                 if value is None:
   802                     raise IDLError("binaryname attribute requires a value",
   803                                    aloc)
   805                 self.binaryname = value
   806                 continue
   808             if value is not None:
   809                 raise IDLError("Unexpected attribute value", aloc)
   811             if name == 'noscript':
   812                 self.noscript = True
   813             elif name == 'notxpcom':
   814                 self.notxpcom = True
   815             elif name == 'implicit_jscontext':
   816                 self.implicit_jscontext = True
   817             elif name == 'optional_argc':
   818                 self.optional_argc = True
   819             elif name == 'deprecated':
   820                 self.deprecated = True
   821             elif name == 'nostdcall':
   822                 self.nostdcall = True
   823             else:
   824                 raise IDLError("Unexpected attribute '%s'" % name, aloc)
   826         self.namemap = NameMap()
   827         for p in paramlist:
   828             self.namemap.set(p)
   830     def resolve(self, iface):
   831         self.iface = iface
   832         self.realtype = self.iface.idl.getName(self.type, self.location)
   833         for p in self.params:
   834             p.resolve(self)
   835         for p in self.params:
   836             if p.retval and p != self.params[-1]:
   837                 raise IDLError("'retval' parameter '%s' is not the last parameter" % p.name, self.location)
   838             if p.size_is:
   839                 found_size_param = False
   840                 for size_param in self.params:
   841                     if p.size_is == size_param.name:
   842                         found_size_param = True
   843                         if getBuiltinOrNativeTypeName(size_param.realtype) != 'unsigned long':
   844                             raise IDLError("is_size parameter must have type 'unsigned long'", self.location)
   845                 if not found_size_param:
   846                     raise IDLError("could not find is_size parameter '%s'" % p.size_is, self.location)
   848     def isScriptable(self):
   849         if not self.iface.attributes.scriptable: return False
   850         return not (self.noscript or self.notxpcom)
   852     def __str__(self):
   853         return "\t%s %s(%s)\n" % (self.type, self.name, ", ".join([p.name for p in self.params]))
   855     def toIDL(self):
   856         if len(self.raises):
   857             raises = ' raises (%s)' % ','.join(self.raises)
   858         else:
   859             raises = ''
   861         return "%s%s %s (%s)%s;" % (attlistToIDL(self.attlist),
   862                                     self.type,
   863                                     self.name,
   864                                     ", ".join([p.toIDL()
   865                                                for p in self.params]),
   866                                     raises)
   868     def needsJSTypes(self):
   869         if self.implicit_jscontext:
   870             return True
   871         if self.type == "jsval":
   872             return True
   873         for p in self.params:
   874             t = p.realtype
   875             if isinstance(t, Native) and t.specialtype == "jsval":
   876                 return True
   877         return False
   879     def count(self):
   880         return 1
   882 class Param(object):
   883     size_is = None
   884     iid_is = None
   885     const = False
   886     array = False
   887     retval = False
   888     shared = False
   889     optional = False
   890     null = None
   891     undefined = None
   893     def __init__(self, paramtype, type, name, attlist, location, realtype=None):
   894         self.paramtype = paramtype
   895         self.type = type
   896         self.name = name
   897         self.attlist = attlist
   898         self.location = location
   899         self.realtype = realtype
   901         for name, value, aloc in attlist:
   902             # Put the value-taking attributes first!
   903             if name == 'size_is':
   904                 if value is None:
   905                     raise IDLError("'size_is' must specify a parameter", aloc)
   906                 self.size_is = value
   907             elif name == 'iid_is':
   908                 if value is None:
   909                     raise IDLError("'iid_is' must specify a parameter", aloc)
   910                 self.iid_is = value
   911             elif name == 'Null':
   912                 if value is None:
   913                     raise IDLError("'Null' must specify a parameter", aloc)
   914                 if value not in ('Empty', 'Null', 'Stringify'):
   915                     raise IDLError("'Null' parameter value must be 'Empty', 'Null', or 'Stringify'",
   916                                    aloc);
   917                 self.null = value
   918             elif name == 'Undefined':
   919                 if value is None:
   920                     raise IDLError("'Undefined' must specify a parameter", aloc)
   921                 if value not in ('Empty', 'Null'):
   922                     raise IDLError("'Undefined' parameter value must be 'Empty' or 'Null'",
   923                                    aloc);
   924                 self.undefined = value
   925             else:
   926                 if value is not None:
   927                     raise IDLError("Unexpected value for attribute '%s'" % name,
   928                                    aloc)
   930                 if name == 'const':
   931                     self.const = True
   932                 elif name == 'array':
   933                     self.array = True
   934                 elif name == 'retval':
   935                     self.retval = True
   936                 elif name == 'shared':
   937                     self.shared = True
   938                 elif name == 'optional':
   939                     self.optional = True
   940                 else:
   941                     raise IDLError("Unexpected attribute '%s'" % name, aloc)
   943     def resolve(self, method):
   944         self.realtype = method.iface.idl.getName(self.type, self.location)
   945         if self.array:
   946             self.realtype = Array(self.realtype)
   947         if (self.null is not None and
   948             getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'):
   949             raise IDLError("'Null' attribute can only be used on DOMString",
   950                            self.location)
   951         if (self.undefined is not None and
   952             getBuiltinOrNativeTypeName(self.realtype) != '[domstring]'):
   953             raise IDLError("'Undefined' attribute can only be used on DOMString",
   954                            self.location)
   956     def nativeType(self):
   957         kwargs = {}
   958         if self.shared: kwargs['shared'] = True
   959         if self.const: kwargs['const'] = True
   961         try:
   962             return self.realtype.nativeType(self.paramtype, **kwargs)
   963         except IDLError, e:
   964             raise IDLError(e.message, self.location)
   965         except TypeError, e:
   966             raise IDLError("Unexpected parameter attribute", self.location)
   968     def toIDL(self):
   969         return "%s%s %s %s" % (paramAttlistToIDL(self.attlist),
   970                                self.paramtype,
   971                                self.type,
   972                                self.name)
   974 class Array(object):
   975     def __init__(self, basetype):
   976         self.type = basetype
   978     def isScriptable(self):
   979         return self.type.isScriptable()
   981     def nativeType(self, calltype, const=False):
   982         return "%s%s*" % (const and 'const ' or '',
   983                           self.type.nativeType(calltype))
   985 class IDLParser(object):
   986     keywords = {
   987         'const': 'CONST',
   988         'interface': 'INTERFACE',
   989         'in': 'IN',
   990         'inout': 'INOUT',
   991         'out': 'OUT',
   992         'attribute': 'ATTRIBUTE',
   993         'raises': 'RAISES',
   994         'readonly': 'READONLY',
   995         'native': 'NATIVE',
   996         'typedef': 'TYPEDEF',
   997         'Infinity': 'INFINITY'
   998         }
  1000     tokens = [
  1001         'IDENTIFIER',
  1002         'CDATA',
  1003         'INCLUDE',
  1004         'IID',
  1005         'NUMBER',
  1006         'HEXNUM',
  1007         'LSHIFT',
  1008         'RSHIFT',
  1009         'NATIVEID',
  1012     tokens.extend(keywords.values())
  1014     states = (
  1015         ('nativeid', 'exclusive'),
  1018     hexchar = r'[a-fA-F0-9]'
  1020     t_NUMBER = r'-?\d+'
  1021     t_HEXNUM = r'0x%s+' % hexchar
  1022     t_LSHIFT = r'<<'
  1023     t_RSHIFT=  r'>>'
  1025     literals = '"(){}[],;:=|+-*'
  1027     t_ignore = ' \t'
  1029     def t_multilinecomment(self, t):
  1030         r'/\*(?s).*?\*/'
  1031         t.lexer.lineno += t.value.count('\n')
  1032         if t.value.startswith("/**"):
  1033             self._doccomments.append(t.value)
  1035     def t_singlelinecomment(self, t):
  1036         r'(?m)//.*?$'
  1038     def t_IID(self, t):
  1039         return t
  1040     t_IID.__doc__ = r'%(c)s{8}-%(c)s{4}-%(c)s{4}-%(c)s{4}-%(c)s{12}' % {'c': hexchar}
  1042     def t_IDENTIFIER(self, t):
  1043         r'(unsigned\ long\ long|unsigned\ short|unsigned\ long|long\ long)(?!_?[A-Za-z][A-Za-z_0-9])|_?[A-Za-z][A-Za-z_0-9]*'
  1044         t.type = self.keywords.get(t.value, 'IDENTIFIER')
  1045         return t
  1047     def t_LCDATA(self, t):
  1048         r'(?s)%\{[ ]*C\+\+[ ]*\n(?P<cdata>.*?\n?)%\}[ ]*(C\+\+)?'
  1049         t.type = 'CDATA'
  1050         t.value = t.lexer.lexmatch.group('cdata')
  1051         t.lexer.lineno += t.value.count('\n')
  1052         return t
  1054     def t_INCLUDE(self, t):
  1055         r'\#include[ \t]+"[^"\n]+"'
  1056         inc, value, end = t.value.split('"')
  1057         t.value = value
  1058         return t
  1060     def t_directive(self, t):
  1061         r'\#(?P<directive>[a-zA-Z]+)[^\n]+'
  1062         raise IDLError("Unrecognized directive %s" % t.lexer.lexmatch.group('directive'),
  1063                        Location(lexer=self.lexer, lineno=self.lexer.lineno,
  1064                                 lexpos=self.lexer.lexpos))
  1066     def t_newline(self, t):
  1067         r'\n+'
  1068         t.lexer.lineno += len(t.value)
  1070     def t_nativeid_NATIVEID(self, t):
  1071         r'[^()\n]+(?=\))'
  1072         t.lexer.begin('INITIAL')
  1073         return t
  1075     t_nativeid_ignore = ''
  1077     def t_ANY_error(self, t):
  1078         raise IDLError("unrecognized input",
  1079                        Location(lexer=self.lexer,
  1080                                 lineno=self.lexer.lineno,
  1081                                 lexpos=self.lexer.lexpos))
  1083     precedence = (
  1084         ('left', '|'),
  1085         ('left', 'LSHIFT', 'RSHIFT'),
  1086         ('left', '+', '-'),
  1087         ('left', '*'),
  1088         ('left', 'UMINUS'),
  1091     def p_idlfile(self, p):
  1092         """idlfile : productions"""
  1093         p[0] = IDL(p[1])
  1095     def p_productions_start(self, p):
  1096         """productions : """
  1097         p[0] = []
  1099     def p_productions_cdata(self, p):
  1100         """productions : CDATA productions"""
  1101         p[0] = list(p[2])
  1102         p[0].insert(0, CDATA(p[1], self.getLocation(p, 1)))
  1104     def p_productions_include(self, p):
  1105         """productions : INCLUDE productions"""
  1106         p[0] = list(p[2])
  1107         p[0].insert(0, Include(p[1], self.getLocation(p, 1)))
  1109     def p_productions_interface(self, p):
  1110         """productions : interface productions
  1111                        | typedef productions
  1112                        | native productions"""
  1113         p[0] = list(p[2])
  1114         p[0].insert(0, p[1])
  1116     def p_typedef(self, p):
  1117         """typedef : TYPEDEF IDENTIFIER IDENTIFIER ';'"""
  1118         p[0] = Typedef(type=p[2],
  1119                        name=p[3],
  1120                        location=self.getLocation(p, 1),
  1121                        doccomments=p.slice[1].doccomments)
  1123     def p_native(self, p):
  1124         """native : attributes NATIVE IDENTIFIER afternativeid '(' NATIVEID ')' ';'"""
  1125         p[0] = Native(name=p[3],
  1126                       nativename=p[6],
  1127                       attlist=p[1]['attlist'],
  1128                       location=self.getLocation(p, 2))
  1130     def p_afternativeid(self, p):
  1131         """afternativeid : """
  1132         # this is a place marker: we switch the lexer into literal identifier
  1133         # mode here, to slurp up everything until the closeparen
  1134         self.lexer.begin('nativeid')
  1136     def p_anyident(self, p):
  1137         """anyident : IDENTIFIER
  1138                     | CONST"""
  1139         p[0] = {'value': p[1],
  1140                 'location': self.getLocation(p, 1)}
  1142     def p_attributes(self, p):
  1143         """attributes : '[' attlist ']'
  1144                       | """
  1145         if len(p) == 1:
  1146             p[0] = {'attlist': []}
  1147         else:
  1148             p[0] = {'attlist': p[2],
  1149                     'doccomments': p.slice[1].doccomments}
  1151     def p_attlist_start(self, p):
  1152         """attlist : attribute"""
  1153         p[0] = [p[1]]
  1155     def p_attlist_continue(self, p):
  1156         """attlist : attribute ',' attlist"""
  1157         p[0] = list(p[3])
  1158         p[0].insert(0, p[1])
  1160     def p_attribute(self, p):
  1161         """attribute : anyident attributeval"""
  1162         p[0] = (p[1]['value'], p[2], p[1]['location'])
  1164     def p_attributeval(self, p):
  1165         """attributeval : '(' IDENTIFIER ')'
  1166                         | '(' IID ')'
  1167                         | """
  1168         if len(p) > 1:
  1169             p[0] = p[2]
  1171     def p_interface(self, p):
  1172         """interface : attributes INTERFACE IDENTIFIER ifacebase ifacebody ';'"""
  1173         atts, INTERFACE, name, base, body, SEMI = p[1:]
  1174         attlist = atts['attlist']
  1175         doccomments = []
  1176         if 'doccomments' in atts:
  1177             doccomments.extend(atts['doccomments'])
  1178         doccomments.extend(p.slice[2].doccomments)
  1180         l = lambda: self.getLocation(p, 2)
  1182         if body is None:
  1183             # forward-declared interface... must not have attributes!
  1184             if len(attlist) != 0:
  1185                 raise IDLError("Forward-declared interface must not have attributes",
  1186                                list[0][3])
  1188             if base is not None:
  1189                 raise IDLError("Forward-declared interface must not have a base",
  1190                                l())
  1191             p[0] = Forward(name=name, location=l(), doccomments=doccomments)
  1192         else:
  1193             p[0] = Interface(name=name,
  1194                              attlist=attlist,
  1195                              base=base,
  1196                              members=body,
  1197                              location=l(),
  1198                              doccomments=doccomments)
  1200     def p_ifacebody(self, p):
  1201         """ifacebody : '{' members '}'
  1202                      | """
  1203         if len(p) > 1:
  1204             p[0] = p[2]
  1206     def p_ifacebase(self, p):
  1207         """ifacebase : ':' IDENTIFIER
  1208                      | """
  1209         if len(p) == 3:
  1210             p[0] = p[2]
  1212     def p_members_start(self, p):
  1213         """members : """
  1214         p[0] = []
  1216     def p_members_continue(self, p):
  1217         """members : member members"""
  1218         p[0] = list(p[2])
  1219         p[0].insert(0, p[1])
  1221     def p_member_cdata(self, p):
  1222         """member : CDATA"""
  1223         p[0] = CDATA(p[1], self.getLocation(p, 1))
  1225     def p_member_const(self, p):
  1226         """member : CONST IDENTIFIER IDENTIFIER '=' number ';' """
  1227         p[0] = ConstMember(type=p[2], name=p[3],
  1228                            value=p[5], location=self.getLocation(p, 1),
  1229                            doccomments=p.slice[1].doccomments)
  1231 # All "number" products return a function(interface)
  1233     def p_number_decimal(self, p):
  1234         """number : NUMBER"""
  1235         n = int(p[1])
  1236         p[0] = lambda i: n
  1238     def p_number_hex(self, p):
  1239         """number : HEXNUM"""
  1240         n = int(p[1], 16)
  1241         p[0] = lambda i: n
  1243     def p_number_identifier(self, p):
  1244         """number : IDENTIFIER"""
  1245         id = p[1]
  1246         loc = self.getLocation(p, 1)
  1247         p[0] = lambda i: i.getConst(id, loc)
  1249     def p_number_paren(self, p):
  1250         """number : '(' number ')'"""
  1251         p[0] = p[2]
  1253     def p_number_neg(self, p):
  1254         """number : '-' number %prec UMINUS"""
  1255         n = p[2]
  1256         p[0] = lambda i: - n(i)
  1258     def p_number_add(self, p):
  1259         """number : number '+' number
  1260                   | number '-' number
  1261                   | number '*' number"""
  1262         n1 = p[1]
  1263         n2 = p[3]
  1264         if p[2] == '+':
  1265             p[0] = lambda i: n1(i) + n2(i)
  1266         elif p[2] == '-':
  1267             p[0] = lambda i: n1(i) - n2(i)
  1268         else:
  1269             p[0] = lambda i: n1(i) * n2(i)
  1271     def p_number_shift(self, p):
  1272         """number : number LSHIFT number
  1273                   | number RSHIFT number"""
  1274         n1 = p[1]
  1275         n2 = p[3]
  1276         if p[2] == '<<':
  1277             p[0] = lambda i: n1(i) << n2(i)
  1278         else:
  1279             p[0] = lambda i: n1(i) >> n2(i)
  1281     def p_number_bitor(self, p):
  1282         """number : number '|' number"""
  1283         n1 = p[1]
  1284         n2 = p[3]
  1285         p[0] = lambda i: n1(i) | n2(i)
  1287     def p_member_att(self, p):
  1288         """member : attributes optreadonly ATTRIBUTE IDENTIFIER IDENTIFIER ';'"""
  1289         if 'doccomments' in p[1]:
  1290             doccomments = p[1]['doccomments']
  1291         elif p[2] is not None:
  1292             doccomments = p[2]
  1293         else:
  1294             doccomments = p.slice[3].doccomments
  1296         p[0] = Attribute(type=p[4],
  1297                          name=p[5],
  1298                          attlist=p[1]['attlist'],
  1299                          readonly=p[2] is not None,
  1300                          location=self.getLocation(p, 3),
  1301                          doccomments=doccomments)
  1303     def p_member_method(self, p):
  1304         """member : attributes IDENTIFIER IDENTIFIER '(' paramlist ')' raises ';'"""
  1305         if 'doccomments' in p[1]:
  1306             doccomments = p[1]['doccomments']
  1307         else:
  1308             doccomments = p.slice[2].doccomments
  1310         p[0] = Method(type=p[2],
  1311                       name=p[3],
  1312                       attlist=p[1]['attlist'],
  1313                       paramlist=p[5],
  1314                       location=self.getLocation(p, 3),
  1315                       doccomments=doccomments,
  1316                       raises=p[7])
  1318     def p_paramlist(self, p):
  1319         """paramlist : param moreparams
  1320                      | """
  1321         if len(p) == 1:
  1322             p[0] = []
  1323         else:
  1324             p[0] = list(p[2])
  1325             p[0].insert(0, p[1])
  1327     def p_moreparams_start(self, p):
  1328         """moreparams :"""
  1329         p[0] = []
  1331     def p_moreparams_continue(self, p):
  1332         """moreparams : ',' param moreparams"""
  1333         p[0] = list(p[3])
  1334         p[0].insert(0, p[2])
  1336     def p_param(self, p):
  1337         """param : attributes paramtype IDENTIFIER IDENTIFIER"""
  1338         p[0] = Param(paramtype=p[2],
  1339                      type=p[3],
  1340                      name=p[4],
  1341                      attlist=p[1]['attlist'],
  1342                      location=self.getLocation(p, 3))
  1344     def p_paramtype(self, p):
  1345         """paramtype : IN
  1346                      | INOUT
  1347                      | OUT"""
  1348         p[0] = p[1]
  1350     def p_optreadonly(self, p):
  1351         """optreadonly : READONLY
  1352                        | """
  1353         if len(p) > 1:
  1354             p[0] = p.slice[1].doccomments
  1355         else:
  1356             p[0] = None
  1358     def p_raises(self, p):
  1359         """raises : RAISES '(' idlist ')'
  1360                   | """
  1361         if len(p) == 1:
  1362             p[0] = []
  1363         else:
  1364             p[0] = p[3]
  1366     def p_idlist(self, p):
  1367         """idlist : IDENTIFIER"""
  1368         p[0] = [p[1]]
  1370     def p_idlist_continue(self, p):
  1371         """idlist : IDENTIFIER ',' idlist"""
  1372         p[0] = list(p[3])
  1373         p[0].insert(0, p[1])
  1375     def p_error(self, t):
  1376         if not t:
  1377             raise IDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", None)
  1378         else:
  1379             location = Location(self.lexer, t.lineno, t.lexpos)
  1380             raise IDLError("invalid syntax", location)
  1382     def __init__(self, outputdir=''):
  1383         self._doccomments = []
  1384         self.lexer = lex.lex(object=self,
  1385                              outputdir=outputdir,
  1386                              lextab='xpidllex',
  1387                              optimize=1)
  1388         self.parser = yacc.yacc(module=self,
  1389                                 outputdir=outputdir,
  1390                                 debug=0,
  1391                                 tabmodule='xpidlyacc',
  1392                                 optimize=1)
  1394     def clearComments(self):
  1395         self._doccomments = []
  1397     def token(self):
  1398         t = self.lexer.token()
  1399         if t is not None and t.type != 'CDATA':
  1400             t.doccomments = self._doccomments
  1401             self._doccomments = []
  1402         return t
  1404     def parse(self, data, filename=None):
  1405         if filename is not None:
  1406             self.lexer.filename = filename
  1407         self.lexer.lineno = 1
  1408         self.lexer.input(data)
  1409         idl = self.parser.parse(lexer=self)
  1410         if filename is not None:
  1411             idl.deps.append(filename)
  1412         return idl
  1414     def getLocation(self, p, i):
  1415         return Location(self.lexer, p.lineno(i), p.lexpos(i))
  1417 if __name__ == '__main__':
  1418     p = IDLParser()
  1419     for f in sys.argv[1:]:
  1420         print "Parsing %s" % f
  1421         p.parse(open(f).read(), filename=f)

mercurial