1# Copyright (C) 2013 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29"""Blink IDL Intermediate Representation (IR) classes.
30
31Classes are primarily constructors, which build an IdlDefinitions object
32(and various contained objects) from an AST (produced by blink_idl_parser).
33
34This is in two steps:
35* Constructors walk the AST, creating objects.
36* Typedef resolution.
37
38Typedefs are all resolved here, and not stored in IR.
39
40Typedef resolution uses some auxiliary classes and OOP techniques to make this
41a generic call, via the resolve_typedefs() method.
42
43Class hierarchy (mostly containment, '<' for inheritance):
44
45IdlDefinitions
46    IdlCallbackFunction < TypedObject
47    IdlEnum :: FIXME: remove, just use a dict for enums
48    IdlInterface
49        IdlAttribute < TypedObject
50        IdlConstant < TypedObject
51        IdlLiteral
52        IdlOperation < TypedObject
53            IdlArgument < TypedObject
54        IdlStringifier
55    IdlException < IdlInterface
56        (same contents as IdlInterface)
57
58TypedObject :: mixin for typedef resolution
59
60Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
61"""
62
63import abc
64
65from idl_types import IdlType, IdlUnionType, IdlArrayType, IdlSequenceType, IdlNullableType
66
67SPECIAL_KEYWORD_LIST = ['GETTER', 'SETTER', 'DELETER']
68STANDARD_TYPEDEFS = {
69    # http://www.w3.org/TR/WebIDL/#common-DOMTimeStamp
70    'DOMTimeStamp': 'unsigned long long',
71}
72
73
74################################################################################
75# TypedObject (mixin for typedef resolution)
76################################################################################
77
78class TypedObject(object):
79    """Object with a type, such as an Attribute or Operation (return value).
80
81    The type can be an actual type, or can be a typedef, which must be resolved
82    before passing data to the code generator.
83    """
84    __metaclass__ = abc.ABCMeta
85    idl_type = None
86
87    def resolve_typedefs(self, typedefs):
88        """Resolve typedefs to actual types in the object."""
89        # Constructors don't have their own return type, because it's the
90        # interface itself.
91        if not self.idl_type:
92            return
93        # Need to re-assign self.idl_type, not just mutate idl_type,
94        # since type(idl_type) may change.
95        self.idl_type = self.idl_type.resolve_typedefs(typedefs)
96
97
98################################################################################
99# Definitions (main container class)
100################################################################################
101
102class IdlDefinitions(object):
103    def __init__(self, idl_name, node):
104        """Args: node: AST root node, class == 'File'"""
105        self.callback_functions = {}
106        self.dictionaries = {}
107        self.enumerations = {}
108        self.interfaces = {}
109        self.idl_name = idl_name
110
111        node_class = node.GetClass()
112        if node_class != 'File':
113            raise ValueError('Unrecognized node class: %s' % node_class)
114
115        typedefs = dict((typedef_name, IdlType(type_name))
116                        for typedef_name, type_name in
117                        STANDARD_TYPEDEFS.iteritems())
118
119        children = node.GetChildren()
120        for child in children:
121            child_class = child.GetClass()
122            if child_class == 'Interface':
123                interface = IdlInterface(idl_name, child)
124                self.interfaces[interface.name] = interface
125            elif child_class == 'Exception':
126                exception = IdlException(idl_name, child)
127                # For simplicity, treat exceptions as interfaces
128                self.interfaces[exception.name] = exception
129            elif child_class == 'Typedef':
130                type_name = child.GetName()
131                typedefs[type_name] = typedef_node_to_type(child)
132            elif child_class == 'Enum':
133                enumeration = IdlEnum(idl_name, child)
134                self.enumerations[enumeration.name] = enumeration
135            elif child_class == 'Callback':
136                callback_function = IdlCallbackFunction(idl_name, child)
137                self.callback_functions[callback_function.name] = callback_function
138            elif child_class == 'Implements':
139                # Implements is handled at the interface merging step
140                pass
141            elif child_class == 'Dictionary':
142                dictionary = IdlDictionary(idl_name, child)
143                self.dictionaries[dictionary.name] = dictionary
144            else:
145                raise ValueError('Unrecognized node class: %s' % child_class)
146
147        # Typedefs are not stored in IR:
148        # Resolve typedefs with the actual types and then discard the Typedefs.
149        # http://www.w3.org/TR/WebIDL/#idl-typedefs
150        self.resolve_typedefs(typedefs)
151
152    def resolve_typedefs(self, typedefs):
153        for callback_function in self.callback_functions.itervalues():
154            callback_function.resolve_typedefs(typedefs)
155        for interface in self.interfaces.itervalues():
156            interface.resolve_typedefs(typedefs)
157
158    def update(self, other):
159        """Update with additional IdlDefinitions."""
160        for interface_name, new_interface in other.interfaces.iteritems():
161            if not new_interface.is_partial:
162                # Add as new interface
163                self.interfaces[interface_name] = new_interface
164                continue
165
166            # Merge partial to existing interface
167            try:
168                self.interfaces[interface_name].merge(new_interface)
169            except KeyError:
170                raise Exception('Tried to merge partial interface for {0}, '
171                                'but no existing interface by that name'
172                                .format(interface_name))
173
174            # Merge callbacks and enumerations
175            self.enumerations.update(other.enumerations)
176            self.callback_functions.update(other.callback_functions)
177
178
179################################################################################
180# Callback Functions
181################################################################################
182
183class IdlCallbackFunction(TypedObject):
184    def __init__(self, idl_name, node):
185        children = node.GetChildren()
186        num_children = len(children)
187        if num_children != 2:
188            raise ValueError('Expected 2 children, got %s' % num_children)
189        type_node, arguments_node = children
190        arguments_node_class = arguments_node.GetClass()
191        if arguments_node_class != 'Arguments':
192            raise ValueError('Expected Arguments node, got %s' % arguments_node_class)
193
194        self.idl_name = idl_name
195        self.name = node.GetName()
196        self.idl_type = type_node_to_type(type_node)
197        self.arguments = arguments_node_to_arguments(idl_name, arguments_node)
198
199    def resolve_typedefs(self, typedefs):
200        TypedObject.resolve_typedefs(self, typedefs)
201        for argument in self.arguments:
202            argument.resolve_typedefs(typedefs)
203
204
205################################################################################
206# Dictionary
207################################################################################
208
209class IdlDictionary(object):
210    def __init__(self, idl_name, node):
211        self.extended_attributes = {}
212        self.is_partial = node.GetProperty('Partial') or False
213        self.idl_name = idl_name
214        self.name = node.GetName()
215        self.members = []
216        self.parent = None
217        for child in node.GetChildren():
218            child_class = child.GetClass()
219            if child_class == 'Inherit':
220                self.parent = child.GetName()
221            elif child_class == 'Key':
222                self.members.append(IdlDictionaryMember(idl_name, child))
223            elif child_class == 'ExtAttributes':
224                self.extended_attributes = (
225                    ext_attributes_node_to_extended_attributes(idl_name, child))
226            else:
227                raise ValueError('Unrecognized node class: %s' % child_class)
228
229
230class IdlDictionaryMember(object):
231    def __init__(self, idl_name, node):
232        self.default_value = None
233        self.extended_attributes = {}
234        self.idl_type = None
235        self.idl_name = idl_name
236        self.name = node.GetName()
237        for child in node.GetChildren():
238            child_class = child.GetClass()
239            if child_class == 'Type':
240                self.idl_type = type_node_to_type(child)
241            elif child_class == 'Default':
242                self.default_value = default_node_to_idl_literal(child)
243            elif child_class == 'ExtAttributes':
244                self.extended_attributes = (
245                    ext_attributes_node_to_extended_attributes(idl_name, child))
246            else:
247                raise ValueError('Unrecognized node class: %s' % child_class)
248
249
250################################################################################
251# Enumerations
252################################################################################
253
254class IdlEnum(object):
255    # FIXME: remove, just treat enums as a dictionary
256    def __init__(self, idl_name, node):
257        self.idl_name = idl_name
258        self.name = node.GetName()
259        self.values = []
260        for child in node.GetChildren():
261            self.values.append(child.GetName())
262
263
264################################################################################
265# Interfaces and Exceptions
266################################################################################
267
268class IdlInterface(object):
269    def __init__(self, idl_name, node=None):
270        self.attributes = []
271        self.constants = []
272        self.constructors = []
273        self.custom_constructors = []
274        self.extended_attributes = {}
275        self.operations = []
276        self.parent = None
277        self.stringifier = None
278        if not node:  # Early exit for IdlException.__init__
279            return
280
281        self.is_callback = node.GetProperty('CALLBACK') or False
282        self.is_exception = False
283        # FIXME: uppercase 'Partial' => 'PARTIAL' in base IDL parser
284        self.is_partial = node.GetProperty('Partial') or False
285        self.idl_name = idl_name
286        self.name = node.GetName()
287
288        children = node.GetChildren()
289        for child in children:
290            child_class = child.GetClass()
291            if child_class == 'Attribute':
292                self.attributes.append(IdlAttribute(idl_name, child))
293            elif child_class == 'Const':
294                self.constants.append(IdlConstant(idl_name, child))
295            elif child_class == 'ExtAttributes':
296                extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
297                self.constructors, self.custom_constructors = (
298                    extended_attributes_to_constructors(idl_name, extended_attributes))
299                clear_constructor_attributes(extended_attributes)
300                self.extended_attributes = extended_attributes
301            elif child_class == 'Operation':
302                self.operations.append(IdlOperation(idl_name, child))
303            elif child_class == 'Inherit':
304                self.parent = child.GetName()
305            elif child_class == 'Stringifier':
306                self.stringifier = IdlStringifier(idl_name, child)
307                self.process_stringifier()
308            else:
309                raise ValueError('Unrecognized node class: %s' % child_class)
310
311    def resolve_typedefs(self, typedefs):
312        for attribute in self.attributes:
313            attribute.resolve_typedefs(typedefs)
314        for constant in self.constants:
315            constant.resolve_typedefs(typedefs)
316        for constructor in self.constructors:
317            constructor.resolve_typedefs(typedefs)
318        for custom_constructor in self.custom_constructors:
319            custom_constructor.resolve_typedefs(typedefs)
320        for operation in self.operations:
321            operation.resolve_typedefs(typedefs)
322
323    def process_stringifier(self):
324        """Add the stringifier's attribute or named operation child, if it has
325        one, as a regular attribute/operation of this interface."""
326        if self.stringifier.attribute:
327            self.attributes.append(self.stringifier.attribute)
328        elif self.stringifier.operation:
329            self.operations.append(self.stringifier.operation)
330
331    def merge(self, other):
332        """Merge in another interface's members (e.g., partial interface)"""
333        self.attributes.extend(other.attributes)
334        self.constants.extend(other.constants)
335        self.operations.extend(other.operations)
336
337
338class IdlException(IdlInterface):
339    # Properly exceptions and interfaces are distinct, and thus should inherit a
340    # common base class (say, "IdlExceptionOrInterface").
341    # However, there is only one exception (DOMException), and new exceptions
342    # are not expected. Thus it is easier to implement exceptions as a
343    # restricted subclass of interfaces.
344    # http://www.w3.org/TR/WebIDL/#idl-exceptions
345    def __init__(self, idl_name, node):
346        # Exceptions are similar to Interfaces, but simpler
347        IdlInterface.__init__(self, idl_name)
348        self.is_callback = False
349        self.is_exception = True
350        self.is_partial = False
351        self.idl_name = idl_name
352        self.name = node.GetName()
353
354        children = node.GetChildren()
355        for child in children:
356            child_class = child.GetClass()
357            if child_class == 'Attribute':
358                attribute = IdlAttribute(idl_name, child)
359                self.attributes.append(attribute)
360            elif child_class == 'Const':
361                self.constants.append(IdlConstant(idl_name, child))
362            elif child_class == 'ExtAttributes':
363                self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
364            elif child_class == 'ExceptionOperation':
365                self.operations.append(IdlOperation.from_exception_operation_node(idl_name, child))
366            else:
367                raise ValueError('Unrecognized node class: %s' % child_class)
368
369
370################################################################################
371# Attributes
372################################################################################
373
374class IdlAttribute(TypedObject):
375    def __init__(self, idl_name, node):
376        self.is_read_only = node.GetProperty('READONLY') or False
377        self.is_static = node.GetProperty('STATIC') or False
378        self.idl_name = idl_name
379        self.name = node.GetName()
380        # Defaults, overridden below
381        self.idl_type = None
382        self.extended_attributes = {}
383
384        children = node.GetChildren()
385        for child in children:
386            child_class = child.GetClass()
387            if child_class == 'Type':
388                self.idl_type = type_node_to_type(child)
389            elif child_class == 'ExtAttributes':
390                self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
391            else:
392                raise ValueError('Unrecognized node class: %s' % child_class)
393
394
395################################################################################
396# Constants
397################################################################################
398
399class IdlConstant(TypedObject):
400    def __init__(self, idl_name, node):
401        children = node.GetChildren()
402        num_children = len(children)
403        if num_children < 2 or num_children > 3:
404            raise ValueError('Expected 2 or 3 children, got %s' % num_children)
405        type_node = children[0]
406        value_node = children[1]
407        value_node_class = value_node.GetClass()
408        if value_node_class != 'Value':
409            raise ValueError('Expected Value node, got %s' % value_node_class)
410
411        self.idl_name = idl_name
412        self.name = node.GetName()
413        # ConstType is more limited than Type, so subtree is smaller and
414        # we don't use the full type_node_to_type function.
415        self.idl_type = type_node_inner_to_type(type_node)
416        # FIXME: This code is unnecessarily complicated due to the rather
417        # inconsistent way the upstream IDL parser outputs default values.
418        # http://crbug.com/374178
419        if value_node.GetProperty('TYPE') == 'float':
420            self.value = value_node.GetProperty('VALUE')
421        else:
422            self.value = value_node.GetName()
423
424        if num_children == 3:
425            ext_attributes_node = children[2]
426            self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, ext_attributes_node)
427        else:
428            self.extended_attributes = {}
429
430
431################################################################################
432# Literals
433################################################################################
434
435class IdlLiteral(object):
436    def __init__(self, idl_type, value):
437        self.idl_type = idl_type
438        self.value = value
439        self.is_null = False
440
441    def __str__(self):
442        if self.idl_type == 'DOMString':
443            return 'String("%s")' % self.value
444        if self.idl_type == 'integer':
445            return '%d' % self.value
446        if self.idl_type == 'float':
447            return '%g' % self.value
448        if self.idl_type == 'boolean':
449            return 'true' if self.value else 'false'
450        raise ValueError('Unsupported literal type: %s' % self.idl_type)
451
452
453class IdlLiteralNull(IdlLiteral):
454    def __init__(self):
455        self.idl_type = 'NULL'
456        self.value = None
457        self.is_null = True
458
459    def __str__(self):
460        return 'nullptr'
461
462
463def default_node_to_idl_literal(node):
464    # FIXME: This code is unnecessarily complicated due to the rather
465    # inconsistent way the upstream IDL parser outputs default values.
466    # http://crbug.com/374178
467    idl_type = node.GetProperty('TYPE')
468    if idl_type == 'DOMString':
469        value = node.GetProperty('NAME')
470        if '"' in value or '\\' in value:
471            raise ValueError('Unsupported string value: %r' % value)
472        return IdlLiteral(idl_type, value)
473    if idl_type == 'integer':
474        return IdlLiteral(idl_type, int(node.GetProperty('NAME'), base=0))
475    if idl_type == 'float':
476        return IdlLiteral(idl_type, float(node.GetProperty('VALUE')))
477    if idl_type == 'boolean':
478        return IdlLiteral(idl_type, node.GetProperty('VALUE'))
479    if idl_type == 'NULL':
480        return IdlLiteralNull()
481    raise ValueError('Unrecognized default value type: %s' % idl_type)
482
483
484################################################################################
485# Operations
486################################################################################
487
488class IdlOperation(TypedObject):
489    def __init__(self, idl_name, node=None):
490        self.arguments = []
491        self.extended_attributes = {}
492        self.specials = []
493        self.is_constructor = False
494
495        if not node:
496            self.is_static = False
497            return
498        self.idl_name = idl_name
499        self.name = node.GetName()  # FIXME: should just be: or ''
500        # FIXME: AST should use None internally
501        if self.name == '_unnamed_':
502            self.name = ''
503
504        self.is_static = node.GetProperty('STATIC') or False
505        property_dictionary = node.GetProperties()
506        for special_keyword in SPECIAL_KEYWORD_LIST:
507            if special_keyword in property_dictionary:
508                self.specials.append(special_keyword.lower())
509
510        self.idl_type = None
511        children = node.GetChildren()
512        for child in children:
513            child_class = child.GetClass()
514            if child_class == 'Arguments':
515                self.arguments = arguments_node_to_arguments(idl_name, child)
516            elif child_class == 'Type':
517                self.idl_type = type_node_to_type(child)
518            elif child_class == 'ExtAttributes':
519                self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
520            else:
521                raise ValueError('Unrecognized node class: %s' % child_class)
522
523    @classmethod
524    def from_exception_operation_node(cls, idl_name, node):
525        # Needed to handle one case in DOMException.idl:
526        # // Override in a Mozilla compatible format
527        # [NotEnumerable] DOMString toString();
528        # FIXME: can we remove this? replace with a stringifier?
529        operation = cls(idl_name)
530        operation.name = node.GetName()
531        children = node.GetChildren()
532        if len(children) < 1 or len(children) > 2:
533            raise ValueError('ExceptionOperation node with %s children, expected 1 or 2' % len(children))
534
535        type_node = children[0]
536        operation.idl_type = type_node_to_type(type_node)
537
538        if len(children) > 1:
539            ext_attributes_node = children[1]
540            operation.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, ext_attributes_node)
541
542        return operation
543
544    @classmethod
545    def constructor_from_arguments_node(cls, name, idl_name, arguments_node):
546        constructor = cls(idl_name)
547        constructor.name = name
548        constructor.arguments = arguments_node_to_arguments(idl_name, arguments_node)
549        constructor.is_constructor = True
550        return constructor
551
552    def resolve_typedefs(self, typedefs):
553        TypedObject.resolve_typedefs(self, typedefs)
554        for argument in self.arguments:
555            argument.resolve_typedefs(typedefs)
556
557
558################################################################################
559# Arguments
560################################################################################
561
562class IdlArgument(TypedObject):
563    def __init__(self, idl_name, node):
564        self.extended_attributes = {}
565        self.idl_type = None
566        self.is_optional = node.GetProperty('OPTIONAL')  # syntax: (optional T)
567        self.is_variadic = False  # syntax: (T...)
568        self.idl_name = idl_name
569        self.name = node.GetName()
570        self.default_value = None
571
572        children = node.GetChildren()
573        for child in children:
574            child_class = child.GetClass()
575            if child_class == 'Type':
576                self.idl_type = type_node_to_type(child)
577            elif child_class == 'ExtAttributes':
578                self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
579            elif child_class == 'Argument':
580                child_name = child.GetName()
581                if child_name != '...':
582                    raise ValueError('Unrecognized Argument node; expected "...", got "%s"' % child_name)
583                self.is_variadic = child.GetProperty('ELLIPSIS') or False
584            elif child_class == 'Default':
585                self.default_value = default_node_to_idl_literal(child)
586            else:
587                raise ValueError('Unrecognized node class: %s' % child_class)
588
589
590def arguments_node_to_arguments(idl_name, node):
591    # [Constructor] and [CustomConstructor] without arguments (the bare form)
592    # have None instead of an arguments node, but have the same meaning as using
593    # an empty argument list, [Constructor()], so special-case this.
594    # http://www.w3.org/TR/WebIDL/#Constructor
595    if node is None:
596        return []
597    return [IdlArgument(idl_name, argument_node)
598            for argument_node in node.GetChildren()]
599
600
601################################################################################
602# Stringifiers
603################################################################################
604
605class IdlStringifier(object):
606    def __init__(self, idl_name, node):
607        self.attribute = None
608        self.operation = None
609        self.extended_attributes = {}
610        self.idl_name = idl_name
611
612        for child in node.GetChildren():
613            child_class = child.GetClass()
614            if child_class == 'Attribute':
615                self.attribute = IdlAttribute(idl_name, child)
616            elif child_class == 'Operation':
617                operation = IdlOperation(idl_name, child)
618                if operation.name:
619                    self.operation = operation
620            elif child_class == 'ExtAttributes':
621                self.extended_attributes = ext_attributes_node_to_extended_attributes(idl_name, child)
622            else:
623                raise ValueError('Unrecognized node class: %s' % child_class)
624
625        # Copy the stringifier's extended attributes (such as [Unforgable]) onto
626        # the underlying attribute or operation, if there is one.
627        if self.attribute or self.operation:
628            (self.attribute or self.operation).extended_attributes.update(
629                self.extended_attributes)
630
631
632################################################################################
633# Extended attributes
634################################################################################
635
636def ext_attributes_node_to_extended_attributes(idl_name, node):
637    """
638    Returns:
639      Dictionary of {ExtAttributeName: ExtAttributeValue}.
640      Value is usually a string, with these exceptions:
641      Constructors: value is a list of Arguments nodes, corresponding to
642        possible signatures of the constructor.
643      CustomConstructors: value is a list of Arguments nodes, corresponding to
644        possible signatures of the custom constructor.
645      NamedConstructor: value is a Call node, corresponding to the single
646        signature of the named constructor.
647      SetWrapperReferenceTo: value is an Arguments node.
648    """
649    # Primarily just make a dictionary from the children.
650    # The only complexity is handling various types of constructors:
651    # Constructors and Custom Constructors can have duplicate entries due to
652    # overloading, and thus are stored in temporary lists.
653    # However, Named Constructors cannot be overloaded, and thus do not have
654    # a list.
655    # FIXME: move Constructor logic into separate function, instead of modifying
656    #        extended attributes in-place.
657    constructors = []
658    custom_constructors = []
659    extended_attributes = {}
660
661    def child_node(extended_attribute_node):
662        children = extended_attribute_node.GetChildren()
663        if not children:
664            return None
665        if len(children) > 1:
666            raise ValueError('ExtAttributes node with %s children, expected at most 1' % len(children))
667        return children[0]
668
669    extended_attribute_node_list = node.GetChildren()
670    for extended_attribute_node in extended_attribute_node_list:
671        name = extended_attribute_node.GetName()
672        child = child_node(extended_attribute_node)
673        child_class = child and child.GetClass()
674        if name == 'Constructor':
675            if child_class and child_class != 'Arguments':
676                raise ValueError('Constructor only supports Arguments as child, but has child of class: %s' % child_class)
677            constructors.append(child)
678        elif name == 'CustomConstructor':
679            if child_class and child_class != 'Arguments':
680                raise ValueError('[CustomConstructor] only supports Arguments as child, but has child of class: %s' % child_class)
681            custom_constructors.append(child)
682        elif name == 'NamedConstructor':
683            if child_class and child_class != 'Call':
684                raise ValueError('[NamedConstructor] only supports Call as child, but has child of class: %s' % child_class)
685            extended_attributes[name] = child
686        elif name == 'SetWrapperReferenceTo':
687            if not child:
688                raise ValueError('[SetWrapperReferenceTo] requires a child, but has none.')
689            if child_class != 'Arguments':
690                raise ValueError('[SetWrapperReferenceTo] only supports Arguments as child, but has child of class: %s' % child_class)
691            extended_attributes[name] = arguments_node_to_arguments(idl_name, child)
692        elif child:
693            raise ValueError('ExtAttributes node with unexpected children: %s' % name)
694        else:
695            value = extended_attribute_node.GetProperty('VALUE')
696            extended_attributes[name] = value
697
698    # Store constructors and custom constructors in special list attributes,
699    # which are deleted later. Note plural in key.
700    if constructors:
701        extended_attributes['Constructors'] = constructors
702    if custom_constructors:
703        extended_attributes['CustomConstructors'] = custom_constructors
704
705    return extended_attributes
706
707
708def extended_attributes_to_constructors(idl_name, extended_attributes):
709    """Returns constructors and custom_constructors (lists of IdlOperations).
710
711    Auxiliary function for IdlInterface.__init__.
712    """
713
714    constructor_list = extended_attributes.get('Constructors', [])
715    constructors = [
716        IdlOperation.constructor_from_arguments_node('Constructor', idl_name, arguments_node)
717        for arguments_node in constructor_list]
718
719    custom_constructor_list = extended_attributes.get('CustomConstructors', [])
720    custom_constructors = [
721        IdlOperation.constructor_from_arguments_node('CustomConstructor', idl_name, arguments_node)
722        for arguments_node in custom_constructor_list]
723
724    if 'NamedConstructor' in extended_attributes:
725        # FIXME: support overloaded named constructors, and make homogeneous
726        name = 'NamedConstructor'
727        call_node = extended_attributes['NamedConstructor']
728        extended_attributes['NamedConstructor'] = call_node.GetName()
729        children = call_node.GetChildren()
730        if len(children) != 1:
731            raise ValueError('NamedConstructor node expects 1 child, got %s.' % len(children))
732        arguments_node = children[0]
733        named_constructor = IdlOperation.constructor_from_arguments_node('NamedConstructor', idl_name, arguments_node)
734        # FIXME: should return named_constructor separately; appended for Perl
735        constructors.append(named_constructor)
736
737    return constructors, custom_constructors
738
739
740def clear_constructor_attributes(extended_attributes):
741    # Deletes Constructor*s* (plural), sets Constructor (singular)
742    if 'Constructors' in extended_attributes:
743        del extended_attributes['Constructors']
744        extended_attributes['Constructor'] = None
745    if 'CustomConstructors' in extended_attributes:
746        del extended_attributes['CustomConstructors']
747        extended_attributes['CustomConstructor'] = None
748
749
750################################################################################
751# Types
752################################################################################
753
754def type_node_to_type(node):
755    children = node.GetChildren()
756    if len(children) < 1 or len(children) > 2:
757        raise ValueError('Type node expects 1 or 2 children (type + optional array []), got %s (multi-dimensional arrays are not supported).' % len(children))
758
759    base_type = type_node_inner_to_type(children[0])
760
761    if node.GetProperty('NULLABLE'):
762        base_type = IdlNullableType(base_type)
763
764    if len(children) == 2:
765        array_node = children[1]
766        array_node_class = array_node.GetClass()
767        if array_node_class != 'Array':
768            raise ValueError('Expected Array node as TypeSuffix, got %s node.' % array_node_class)
769        array_type = IdlArrayType(base_type)
770        if array_node.GetProperty('NULLABLE'):
771            return IdlNullableType(array_type)
772        return array_type
773
774    return base_type
775
776
777def type_node_inner_to_type(node):
778    node_class = node.GetClass()
779    # Note Type*r*ef, not Typedef, meaning the type is an identifier, thus
780    # either a typedef shorthand (but not a Typedef declaration itself) or an
781    # interface type. We do not distinguish these, and just use the type name.
782    if node_class in ['PrimitiveType', 'Typeref']:
783        # unrestricted syntax: unrestricted double | unrestricted float
784        is_unrestricted = node.GetProperty('UNRESTRICTED') or False
785        return IdlType(node.GetName(), is_unrestricted=is_unrestricted)
786    elif node_class == 'Any':
787        return IdlType('any')
788    elif node_class == 'Sequence':
789        return sequence_node_to_type(node)
790    elif node_class == 'UnionType':
791        return union_type_node_to_idl_union_type(node)
792    raise ValueError('Unrecognized node class: %s' % node_class)
793
794
795def sequence_node_to_type(node):
796    children = node.GetChildren()
797    if len(children) != 1:
798        raise ValueError('Sequence node expects exactly 1 child, got %s' % len(children))
799    sequence_child = children[0]
800    sequence_child_class = sequence_child.GetClass()
801    if sequence_child_class != 'Type':
802        raise ValueError('Unrecognized node class: %s' % sequence_child_class)
803    element_type = type_node_to_type(sequence_child)
804    sequence_type = IdlSequenceType(element_type)
805    if node.GetProperty('NULLABLE'):
806        return IdlNullableType(sequence_type)
807    return sequence_type
808
809
810def typedef_node_to_type(node):
811    children = node.GetChildren()
812    if len(children) != 1:
813        raise ValueError('Typedef node with %s children, expected 1' % len(children))
814    child = children[0]
815    child_class = child.GetClass()
816    if child_class != 'Type':
817        raise ValueError('Unrecognized node class: %s' % child_class)
818    return type_node_to_type(child)
819
820
821def union_type_node_to_idl_union_type(node):
822    member_types = [type_node_to_type(member_type_node)
823                    for member_type_node in node.GetChildren()]
824    return IdlUnionType(member_types)
825