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:
27 'returns the member signature as IDL'
30 def attlistToIDL(attlist
):
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
])
41 2: ('array', 'shared', 'iid_is', 'size_is', 'retval'),
42 3: ('array', 'size_is', 'const'),
45 def paramAttlistToIDL(attlist
):
49 # Hack alert: g_hash_table_foreach is pretty much unimitatable... hardcode
51 attlist
= list(attlist
)
53 if len(attlist
) in _paramsHardcode
:
54 for p
in _paramsHardcode
[len(attlist
)]:
56 while i
< len(attlist
):
57 if attlist
[i
][0] == p
:
58 sorted.append(attlist
[i
])
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])
70 while t
.kind
== 'typedef':
75 def getBuiltinOrNativeTypeName(t
):
77 if t
.kind
== 'builtin':
79 elif t
.kind
== 'native':
80 assert t
.specialtype
is not None
81 return '[%s]' % t
.specialtype
85 class BuiltinLocation(object):
87 return "<builtin type>"
92 class Builtin(object):
94 location
= BuiltinLocation
96 def __init__(self
, name
, nativename
, signed
=False, maybeConst
=False):
98 self
.nativename
= nativename
100 self
.maybeConst
= maybeConst
102 def isScriptable(self
):
105 def nativeType(self
, calltype
, shared
=False, const
=False):
107 print >>sys
.stderr
, IDLError("[const] doesn't make sense on builtin types.", self
.location
, warning
=True)
109 elif calltype
== 'in' and self
.nativename
.endswith('*'):
112 if not self
.nativename
.endswith('*'):
113 raise IDLError("[shared] not applicable to non-pointer types.", self
.location
)
117 return "%s%s %s" % (const
, self
.nativename
,
118 calltype
!= 'in' and '*' or '')
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),
139 for b
in builtinNames
:
140 builtinMap
[b
.name
] = b
142 class Location(object):
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
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
):
166 for i
in xrange(0, self
._colno
):
174 return "%s line %s:%s" % (self
._file
, self
._lineno
, self
._colno
)
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."""
187 def __getitem__(self
, key
):
188 if key
in builtinMap
:
189 return builtinMap
[key
]
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
):
211 raise IDLError("name '%s' specified twice. Previous location: %s" % (object.name
, self
._d
[object.name
].location
), object.location
)
213 self
._d
[object.name
] = object
215 def get(self
, id, location
):
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
228 return "%s: %s, %s" % (self
.warning
and 'warning' or 'error',
229 self
.message
, self
.location
)
231 class Include(object):
234 def __init__(self
, filename
, location
):
235 self
.filename
= filename
236 self
.location
= location
239 return "".join(["include '%s'\n" % self
.filename
])
241 def resolve(self
, parent
):
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():
254 parent
.deps
.extend(self
.IDL
.deps
)
257 raise IDLError("File '%s' not found" % self
.filename
, self
.location
)
260 def __init__(self
, productions
):
261 self
.productions
= productions
264 def setName(self
, object):
265 self
.namemap
.set(object)
267 def getName(self
, id, location
):
269 return self
.namemap
[id]
271 raise IDLError("type '%s' not found" % id, location
)
273 def hasName(self
, id):
274 return id in self
.namemap
277 return iter(self
.namemap
)
280 return "".join([str(p
) for p
in self
.productions
])
282 def resolve(self
, incdirs
, parser
):
283 self
.namemap
= NameMap()
284 self
.incdirs
= incdirs
286 for p
in self
.productions
:
290 for p
in self
.productions
:
291 if p
.kind
== 'include':
294 def needsJSTypes(self
):
295 for p
in self
.productions
:
296 if p
.kind
== 'interface' and p
.needsJSTypes():
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
):
312 return "cdata: %s\n\t%r\n" % (self
.location
.get(), self
.data
)
317 class Typedef(object):
320 def __init__(self
, type, name
, location
, doccomments
):
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
):
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 '')
341 return "typedef %s %s\n" % (self
.type, self
.name
)
343 class Forward(object):
346 def __init__(self
, name
, location
, doccomments
):
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
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
367 def isScriptable(self
):
370 def nativeType(self
, calltype
):
371 return "%s %s" % (self
.name
,
372 calltype
!= 'in' and '* *' or '*')
375 return "forward-declared %s\n" % self
.name
377 class Native(object):
385 'domstring': 'nsAString',
386 'utf8string': 'nsACString',
387 'cstring': 'nsACString',
388 'astring': 'nsAString',
392 def __init__(self
, name
, nativename
, attlist
, location
):
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
)
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
]
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
):
422 def isScriptable(self
):
423 if self
.specialtype
is None:
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):
439 if calltype
!= 'out':
440 raise IDLError("[shared] only applies to out parameters.")
443 if self
.specialtype
is not None and calltype
== 'in':
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
):
453 elif self
.isPtr(calltype
):
454 m
= '*' + ((self
.modifier
== 'ptr' and calltype
!= 'in') and '*' or '')
456 m
= calltype
!= 'in' and '*' or ''
457 return "%s%s %s" % (const
and 'const ' or '', self
.nativename
, m
)
460 return "native %s(%s)\n" % (self
.name
, self
.nativename
)
462 class Interface(object):
465 def __init__(self
, name
, attlist
, base
, members
, location
, doccomments
):
467 self
.attributes
= InterfaceAttributes(attlist
, location
)
469 self
.members
= members
470 self
.location
= location
471 self
.namemap
= NameMap()
472 self
.doccomments
= doccomments
473 self
.nativename
= name
476 if not isinstance(m
, CDATA
):
479 def __eq__(self
, other
):
480 return self
.name
== other
.name
and self
.location
== other
.location
482 def resolve(self
, 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
492 self
.doccomments
= parent
.getName(self
.name
, None).doccomments
494 if self
.attributes
.function
:
496 for member
in self
.members
:
497 if member
.kind
is 'method':
499 raise IDLError("interface '%s' has multiple methods, but marked 'function'" % self
.name
, self
.location
)
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
:
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
,
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
532 def nativeType(self
, calltype
, const
=False):
533 return "%s%s %s" % (const
and 'const ' or '',
535 calltype
!= 'in' and '* *' or '*')
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")
545 for m
in self
.members
:
549 def getConst(self
, name
, location
):
550 # The constant may be in a base class
552 while name
not in iface
.namemap
and iface
is not None:
553 iface
= self
.idl
.getName(self
.base
, self
.location
)
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
)
562 def needsJSTypes(self
):
563 for m
in self
.members
:
564 if m
.kind
== "attribute" and m
.type == "jsval":
566 if m
.kind
== "method" and m
.needsJSTypes():
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()
578 class InterfaceAttributes(object):
586 def setuuid(self
, value
):
587 self
.uuid
= value
.lower()
589 def setscriptable(self
):
590 self
.scriptable
= True
592 def setfunction(self
):
595 def setnoscript(self
):
598 def setbuiltinclass(self
):
599 self
.builtinclass
= True
601 def setdeprecated(self
):
602 self
.deprecated
= True
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
))
622 raise IDLError("Expected value for attribute '%s'" % name
,
628 raise IDLError("Unexpected value for attribute '%s'" % name
,
633 if self
.uuid
is None:
634 raise IDLError("interface has no uuid", location
)
639 l
.append("\tuuid: %s\n" % self
.uuid
)
641 l
.append("\tscriptable\n")
642 if self
.builtinclass
:
643 l
.append("\tbuiltinclass\n")
645 l
.append("\tfunction\n")
648 class ConstMember(object):
650 def __init__(self
, type, name
, value
, location
, doccomments
):
654 self
.location
= location
655 self
.doccomments
= doccomments
657 def resolve(self
, parent
):
658 self
.realtype
= parent
.idl
.getName(self
.type, self
.location
)
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
669 return self
.value(self
.iface
)
672 return "\tconst %s %s = %s\n" % (self
.type, self
.name
, self
.getValue())
677 class Attribute(object):
681 implicit_jscontext
= False
689 def __init__(self
, type, name
, attlist
, readonly
, location
, doccomments
):
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':
700 raise IDLError("binaryname attribute requires a value",
703 self
.binaryname
= value
708 raise IDLError("'Null' attribute requires a value", aloc
)
710 raise IDLError("'Null' attribute only makes sense for setters",
712 if value
not in ('Empty', 'Null', 'Stringify'):
713 raise IDLError("'Null' attribute value must be 'Empty', 'Null' or 'Stringify'",
716 elif name
== 'Undefined':
718 raise IDLError("'Undefined' attribute requires a value", aloc
)
720 raise IDLError("'Undefined' attribute only makes sense for setters",
722 if value
not in ('Empty', 'Null'):
723 raise IDLError("'Undefined' attribute value must be 'Empty' or 'Null'",
725 self
.undefined
= value
727 if value
is not None:
728 raise IDLError("Unexpected attribute value", aloc
)
730 if name
== 'noscript':
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
741 raise IDLError("Unexpected attribute '%s'" % name
, aloc
)
743 def resolve(self
, 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",
750 if (self
.undefined
is not None and
751 getBuiltinOrNativeTypeName(self
.realtype
) != '[domstring]'):
752 raise IDLError("'Undefined' attribute can only be used on DOMString",
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)',
758 if self
.infallible
and not iface
.attributes
.builtinclass
:
759 raise IDLError('[infallible] attributes are only allowed on '
760 '[builtinclass] interfaces',
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
774 return "\t%sattribute %s %s\n" % (self
.readonly
and 'readonly ' or '',
775 self
.type, self
.name
)
778 return self
.readonly
and 1 or 2
780 class Method(object):
785 implicit_jscontext
= False
787 optional_argc
= False
790 def __init__(self
, type, name
, attlist
, paramlist
, location
, doccomments
, raises
):
793 self
.attlist
= attlist
794 self
.params
= paramlist
795 self
.location
= location
796 self
.doccomments
= doccomments
799 for name
, value
, aloc
in attlist
:
800 if name
== 'binaryname':
802 raise IDLError("binaryname attribute requires a value",
805 self
.binaryname
= value
808 if value
is not None:
809 raise IDLError("Unexpected attribute value", aloc
)
811 if name
== 'noscript':
813 elif name
== 'notxpcom':
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
824 raise IDLError("Unexpected attribute '%s'" % name
, aloc
)
826 self
.namemap
= NameMap()
830 def resolve(self
, iface
):
832 self
.realtype
= self
.iface
.idl
.getName(self
.type, self
.location
)
833 for p
in self
.params
:
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
)
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
)
853 return "\t%s %s(%s)\n" % (self
.type, self
.name
, ", ".join([p
.name
for p
in self
.params
]))
857 raises
= ' raises (%s)' % ','.join(self
.raises
)
861 return "%s%s %s (%s)%s;" % (attlistToIDL(self
.attlist
),
865 for p
in self
.params
]),
868 def needsJSTypes(self
):
869 if self
.implicit_jscontext
:
871 if self
.type == "jsval":
873 for p
in self
.params
:
875 if isinstance(t
, Native
) and t
.specialtype
== "jsval":
893 def __init__(self
, paramtype
, type, name
, attlist
, location
, realtype
=None):
894 self
.paramtype
= paramtype
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':
905 raise IDLError("'size_is' must specify a parameter", aloc
)
907 elif name
== 'iid_is':
909 raise IDLError("'iid_is' must specify a parameter", aloc
)
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'",
918 elif name
== 'Undefined':
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'",
924 self
.undefined
= value
926 if value
is not None:
927 raise IDLError("Unexpected value for attribute '%s'" % name
,
932 elif name
== 'array':
934 elif name
== 'retval':
936 elif name
== 'shared':
938 elif name
== 'optional':
941 raise IDLError("Unexpected attribute '%s'" % name
, aloc
)
943 def resolve(self
, method
):
944 self
.realtype
= method
.iface
.idl
.getName(self
.type, self
.location
)
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",
951 if (self
.undefined
is not None and
952 getBuiltinOrNativeTypeName(self
.realtype
) != '[domstring]'):
953 raise IDLError("'Undefined' attribute can only be used on DOMString",
956 def nativeType(self
):
958 if self
.shared
: kwargs
['shared'] = True
959 if self
.const
: kwargs
['const'] = True
962 return self
.realtype
.nativeType(self
.paramtype
, **kwargs
)
964 raise IDLError(e
.message
, self
.location
)
966 raise IDLError("Unexpected parameter attribute", self
.location
)
969 return "%s%s %s %s" % (paramAttlistToIDL(self
.attlist
),
975 def __init__(self
, 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):
988 'interface': 'INTERFACE',
992 'attribute': 'ATTRIBUTE',
994 'readonly': 'READONLY',
996 'typedef': 'TYPEDEF',
997 'Infinity': 'INFINITY'
1012 tokens
.extend(keywords
.values())
1015 ('nativeid', 'exclusive'),
1018 hexchar
= r
'[a-fA-F0-9]'
1021 t_HEXNUM
= r
'0x%s+' % hexchar
1025 literals
= '"(){}[],;:=|+-*'
1029 def t_multilinecomment(self
, t
):
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
):
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')
1047 def t_LCDATA(self
, t
):
1048 r
'(?s)%\{[ ]*C\+\+[ ]*\n(?P<cdata>.*?\n?)%\}[ ]*(C\+\+)?'
1050 t
.value
= t
.lexer
.lexmatch
.group('cdata')
1051 t
.lexer
.lineno
+= t
.value
.count('\n')
1054 def t_INCLUDE(self
, t
):
1055 r
'\#include[ \t]+"[^"\n]+"'
1056 inc
, value
, end
= t
.value
.split('"')
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
):
1068 t
.lexer
.lineno
+= len(t
.value
)
1070 def t_nativeid_NATIVEID(self
, t
):
1072 t
.lexer
.begin('INITIAL')
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
))
1085 ('left', 'LSHIFT', 'RSHIFT'),
1091 def p_idlfile(self
, p
):
1092 """idlfile : productions"""
1095 def p_productions_start(self
, p
):
1096 """productions : """
1099 def p_productions_cdata(self
, p
):
1100 """productions : CDATA productions"""
1102 p
[0].insert(0, CDATA(p
[1], self
.getLocation(p
, 1)))
1104 def p_productions_include(self
, p
):
1105 """productions : INCLUDE productions"""
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"""
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],
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],
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
1139 p
[0] = {'value': p
[1],
1140 'location': self
.getLocation(p
, 1)}
1142 def p_attributes(self
, p
):
1143 """attributes : '[' attlist ']'
1146 p
[0] = {'attlist': []}
1148 p
[0] = {'attlist': p
[2],
1149 'doccomments': p
.slice[1].doccomments
}
1151 def p_attlist_start(self
, p
):
1152 """attlist : attribute"""
1155 def p_attlist_continue(self
, p
):
1156 """attlist : attribute ',' attlist"""
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 ')'
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']
1176 if 'doccomments' in atts
:
1177 doccomments
.extend(atts
['doccomments'])
1178 doccomments
.extend(p
.slice[2].doccomments
)
1180 l
= lambda: self
.getLocation(p
, 2)
1183 # forward-declared interface... must not have attributes!
1184 if len(attlist
) != 0:
1185 raise IDLError("Forward-declared interface must not have attributes",
1188 if base
is not None:
1189 raise IDLError("Forward-declared interface must not have a base",
1191 p
[0] = Forward(name
=name
, location
=l(), doccomments
=doccomments
)
1193 p
[0] = Interface(name
=name
,
1198 doccomments
=doccomments
)
1200 def p_ifacebody(self
, p
):
1201 """ifacebody : '{' members '}'
1206 def p_ifacebase(self
, p
):
1207 """ifacebase : ':' IDENTIFIER
1212 def p_members_start(self
, p
):
1216 def p_members_continue(self
, p
):
1217 """members : member members"""
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"""
1238 def p_number_hex(self
, p
):
1239 """number : HEXNUM"""
1243 def p_number_identifier(self
, p
):
1244 """number : IDENTIFIER"""
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 ')'"""
1253 def p_number_neg(self
, p
):
1254 """number : '-' number %prec UMINUS"""
1256 p
[0] = lambda i
: - n(i
)
1258 def p_number_add(self
, p
):
1259 """number : number '+' number
1261 | number '*' number"""
1265 p
[0] = lambda i
: n1(i
) + n2(i
)
1267 p
[0] = lambda i
: n1(i
) - n2(i
)
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"""
1277 p
[0] = lambda i
: n1(i
) << n2(i
)
1279 p
[0] = lambda i
: n1(i
) >> n2(i
)
1281 def p_number_bitor(self
, p
):
1282 """number : number '|' number"""
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:
1294 doccomments
= p
.slice[3].doccomments
1296 p
[0] = Attribute(type=p
[4],
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']
1308 doccomments
= p
.slice[2].doccomments
1310 p
[0] = Method(type=p
[2],
1312 attlist
=p
[1]['attlist'],
1314 location
=self
.getLocation(p
, 3),
1315 doccomments
=doccomments
,
1318 def p_paramlist(self
, p
):
1319 """paramlist : param moreparams
1325 p
[0].insert(0, p
[1])
1327 def p_moreparams_start(self
, p
):
1331 def p_moreparams_continue(self
, p
):
1332 """moreparams : ',' param moreparams"""
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],
1341 attlist
=p
[1]['attlist'],
1342 location
=self
.getLocation(p
, 3))
1344 def p_paramtype(self
, p
):
1350 def p_optreadonly(self
, p
):
1351 """optreadonly : READONLY
1354 p
[0] = p
.slice[1].doccomments
1358 def p_raises(self
, p
):
1359 """raises : RAISES '(' idlist ')'
1366 def p_idlist(self
, p
):
1367 """idlist : IDENTIFIER"""
1370 def p_idlist_continue(self
, p
):
1371 """idlist : IDENTIFIER ',' idlist"""
1373 p
[0].insert(0, p
[1])
1375 def p_error(self
, t
):
1377 raise IDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", None)
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
,
1388 self
.parser
= yacc
.yacc(module
=self
,
1389 outputdir
=outputdir
,
1391 tabmodule
='xpidlyacc',
1394 def clearComments(self
):
1395 self
._doccomments
= []
1398 t
= self
.lexer
.token()
1399 if t
is not None and t
.type != 'CDATA':
1400 t
.doccomments
= self
._doccomments
1401 self
._doccomments
= []
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
)
1414 def getLocation(self
, p
, i
):
1415 return Location(self
.lexer
, p
.lineno(i
), p
.lexpos(i
))
1417 if __name__
== '__main__':
1419 for f
in sys
.argv
[1:]:
1420 print "Parsing %s" % f
1421 p
.parse(open(f
).read(), filename
=f
)