Bumping manifests a=b2g-bump
[gecko.git] / xpcom / idl-parser / xpidl.py
blob829469b4e175c84227814e0af56731a99c913185
1 #!/usr/bin/env python
2 # xpidl.py - A parser for cross-platform IDL (XPIDL) files.
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'),
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),
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'
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/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),
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'
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)