summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/bindings/scripts/idl_definitions.py
blob: 11f6846eef251b8d488a44cd9ca024e1901b30ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# pylint: disable=relative-import
"""Blink IDL Intermediate Representation (IR) classes.

Classes are primarily constructors, which build an IdlDefinitions object
(and various contained objects) from an AST (produced by blink_idl_parser).

IR stores typedefs and they are resolved by the code generator.

Typedef resolution uses some auxiliary classes and OOP techniques to make this
a generic call. See TypedefResolver class in code_generator_v8.py.

Class hierarchy (mostly containment, '<' for inheritance):

IdlDefinitions
    IdlCallbackFunction < TypedObject
    IdlEnum :: FIXME: remove, just use a dict for enums
    IdlInterface
        IdlAttribute < TypedObject
        IdlConstant < TypedObject
        IdlLiteral
        IdlOperation < TypedObject
            IdlArgument < TypedObject
        IdlStringifier
        IdlIterable < IdlIterableOrMaplikeOrSetlike
        IdlMaplike < IdlIterableOrMaplikeOrSetlike
        IdlSetlike < IdlIterableOrMaplikeOrSetlike

TypedObject :: Object with one or more attributes that is a type.

IdlArgument is 'picklable', as it is stored in interfaces_info.

Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
"""

import abc

from idl_types import IdlAnnotatedType
from idl_types import IdlFrozenArrayType
from idl_types import IdlNullableType
from idl_types import IdlRecordType
from idl_types import IdlSequenceType
from idl_types import IdlType
from idl_types import IdlUnionType

SPECIAL_KEYWORD_LIST = ['GETTER', 'SETTER', 'DELETER']

################################################################################
# TypedObject
################################################################################


class TypedObject(object):
    """Object with a type, such as an Attribute or Operation (return value).

    The type can be an actual type, or can be a typedef, which must be resolved
    by the TypedefResolver before passing data to the code generator.
    """
    __metaclass__ = abc.ABCMeta
    idl_type_attributes = ('idl_type', )


################################################################################
# Definitions (main container class)
################################################################################


class IdlDefinitions(object):
    def __init__(self, node):
        """Args: node: AST root node, class == 'File'"""
        self.callback_functions = {}
        self.dictionaries = {}
        self.enumerations = {}
        self.includes = []
        self.interfaces = {}
        self.first_name = None
        self.typedefs = {}

        node_class = node.GetClass()
        if node_class != 'File':
            raise ValueError('Unrecognized node class: %s' % node_class)

        children = node.GetChildren()
        for child in children:
            child_class = child.GetClass()
            if child_class == 'Interface':
                interface = IdlInterface(child)
                self.interfaces[interface.name] = interface
                if not self.first_name:
                    self.first_name = interface.name
            elif child_class == 'Typedef':
                typedef = IdlTypedef(child)
                self.typedefs[typedef.name] = typedef
            elif child_class == 'Enum':
                enumeration = IdlEnum(child)
                self.enumerations[enumeration.name] = enumeration
            elif child_class == 'Callback':
                callback_function = IdlCallbackFunction(child)
                self.callback_functions[callback_function.
                                        name] = callback_function
            elif child_class == 'Includes':
                self.includes.append(IdlIncludes(child))
            elif child_class == 'Dictionary':
                dictionary = IdlDictionary(child)
                self.dictionaries[dictionary.name] = dictionary
                if not self.first_name:
                    self.first_name = dictionary.name
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

    def accept(self, visitor):
        visitor.visit_definitions(self)
        for interface in self.interfaces.values():
            interface.accept(visitor)
        for callback_function in self.callback_functions.values():
            callback_function.accept(visitor)
        for dictionary in self.dictionaries.values():
            dictionary.accept(visitor)
        for enumeration in self.enumerations.values():
            enumeration.accept(visitor)
        for include in self.includes:
            include.accept(visitor)
        for typedef in self.typedefs.values():
            typedef.accept(visitor)

    def update(self, other):
        """Update with additional IdlDefinitions."""
        for interface_name, new_interface in other.interfaces.items():
            if not new_interface.is_partial:
                # Add as new interface
                self.interfaces[interface_name] = new_interface
                continue

            # Merge partial to existing interface
            try:
                self.interfaces[interface_name].merge(new_interface)
            except KeyError:
                raise Exception('Tried to merge partial interface for {0}, '
                                'but no existing interface by that name'.
                                format(interface_name))

            # Merge callbacks and enumerations
            self.enumerations.update(other.enumerations)
            self.callback_functions.update(other.callback_functions)


################################################################################
# Callback Functions
################################################################################


class IdlCallbackFunction(TypedObject):
    def __init__(self, node):
        children = node.GetChildren()
        num_children = len(children)
        if num_children < 2 or num_children > 3:
            raise ValueError('Expected 2 or 3 children, got %s' % num_children)
        type_node = children[0]
        arguments_node = children[1]
        if num_children == 3:
            ext_attributes_node = children[2]
            self.extended_attributes = (
                ext_attributes_node_to_extended_attributes(ext_attributes_node)
            )
        else:
            self.extended_attributes = {}
        arguments_node_class = arguments_node.GetClass()
        if arguments_node_class != 'Arguments':
            raise ValueError(
                'Expected Arguments node, got %s' % arguments_node_class)

        self.name = node.GetName()
        self.idl_type = type_node_to_type(type_node)
        self.arguments = arguments_node_to_arguments(arguments_node)

    def accept(self, visitor):
        visitor.visit_callback_function(self)
        for argument in self.arguments:
            argument.accept(visitor)


################################################################################
# Dictionary
################################################################################


class IdlDictionary(object):
    def __init__(self, node):
        self.extended_attributes = {}
        self.is_partial = bool(node.GetProperty('PARTIAL'))
        self.name = node.GetName()
        self.members = []
        self.parent = None
        for child in node.GetChildren():
            child_class = child.GetClass()
            if child_class == 'Inherit':
                self.parent = child.GetName()
            elif child_class == 'Key':
                self.members.append(IdlDictionaryMember(child))
            elif child_class == 'ExtAttributes':
                self.extended_attributes = (
                    ext_attributes_node_to_extended_attributes(child))
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

    def accept(self, visitor):
        visitor.visit_dictionary(self)
        for member in self.members:
            member.accept(visitor)


class IdlDictionaryMember(TypedObject):
    def __init__(self, node):
        self.default_value = None
        self.extended_attributes = {}
        self.idl_type = None
        self.is_required = bool(node.GetProperty('REQUIRED'))
        self.name = node.GetName()
        for child in node.GetChildren():
            child_class = child.GetClass()
            if child_class == 'Type':
                self.idl_type = type_node_to_type(child)
            elif child_class == 'Default':
                self.default_value = default_node_to_idl_literal(child)
            elif child_class == 'ExtAttributes':
                self.extended_attributes = (
                    ext_attributes_node_to_extended_attributes(child))
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

    def accept(self, visitor):
        visitor.visit_dictionary_member(self)


################################################################################
# Enumerations
################################################################################


class IdlEnum(object):
    def __init__(self, node):
        self.name = node.GetName()
        self.values = []
        for child in node.GetChildren():
            self.values.append(child.GetName())

    def accept(self, visitor):
        visitor.visit_enumeration(self)


################################################################################
# Typedefs
################################################################################


class IdlTypedef(object):
    idl_type_attributes = ('idl_type', )

    def __init__(self, node):
        self.name = node.GetName()
        self.idl_type = typedef_node_to_type(node)

    def accept(self, visitor):
        visitor.visit_typedef(self)


################################################################################
# Interfaces
################################################################################


class IdlInterface(object):
    def __init__(self, node):
        self.attributes = []
        self.constants = []
        self.constructors = []
        self.custom_constructors = []
        self.extended_attributes = {}
        self.operations = []
        self.parent = None
        self.stringifier = None
        self.iterable = None
        self.has_indexed_elements = False
        self.has_named_property_getter = False
        self.maplike = None
        self.setlike = None
        self.original_interface = None
        self.partial_interfaces = []

        self.is_callback = bool(node.GetProperty('CALLBACK'))
        self.is_partial = bool(node.GetProperty('PARTIAL'))
        self.is_mixin = bool(node.GetProperty('MIXIN'))
        self.name = node.GetName()
        self.idl_type = IdlType(self.name)

        has_indexed_property_getter = False
        has_integer_typed_length = False

        # These are used to support both constructor operations and old style
        # [Constructor] extended attributes. Ideally we should do refactoring
        # for constructor code generation but we will use a new code generator
        # soon so this kind of workaround should be fine.
        constructor_operations = []
        custom_constructor_operations = []
        constructor_operations_extended_attributes = {}

        def is_blacklisted_attribute_type(idl_type):
            return idl_type.is_callback_function or \
                idl_type.is_dictionary or \
                idl_type.is_record_type or \
                idl_type.is_sequence_type

        children = node.GetChildren()
        for child in children:
            child_class = child.GetClass()
            if child_class == 'Attribute':
                attr = IdlAttribute(child)
                if is_blacklisted_attribute_type(attr.idl_type):
                    raise ValueError(
                        'Type "%s" cannot be used as an attribute.' %
                        attr.idl_type)
                if attr.idl_type.is_integer_type and attr.name == 'length':
                    has_integer_typed_length = True
                self.attributes.append(attr)
            elif child_class == 'Const':
                self.constants.append(IdlConstant(child))
            elif child_class == 'ExtAttributes':
                extended_attributes = ext_attributes_node_to_extended_attributes(
                    child)
                self.constructors, self.custom_constructors = (
                    extended_attributes_to_constructors(extended_attributes))
                clear_constructor_attributes(extended_attributes)
                self.extended_attributes = extended_attributes
            elif child_class == 'Operation':
                op = IdlOperation(child)
                if 'getter' in op.specials:
                    if str(op.arguments[0].idl_type) == 'unsigned long':
                        has_indexed_property_getter = True
                    elif str(op.arguments[0].idl_type) == 'DOMString':
                        self.has_named_property_getter = True
                self.operations.append(op)
            elif child_class == 'Constructor':
                operation = constructor_operation_from_node(child)
                if operation.is_custom:
                    custom_constructor_operations.append(operation.constructor)
                else:
                    # Check extended attributes consistency when we previously
                    # handle constructor operations.
                    if constructor_operations:
                        check_constructor_operations_extended_attributes(
                            constructor_operations_extended_attributes,
                            operation.extended_attributes)
                    constructor_operations.append(operation.constructor)
                    constructor_operations_extended_attributes.update(
                        operation.extended_attributes)
            elif child_class == 'Inherit':
                self.parent = child.GetName()
            elif child_class == 'Stringifier':
                self.stringifier = IdlStringifier(child)
                self.process_stringifier()
            elif child_class == 'Iterable':
                self.iterable = IdlIterable(child)
            elif child_class == 'Maplike':
                self.maplike = IdlMaplike(child)
            elif child_class == 'Setlike':
                self.setlike = IdlSetlike(child)
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

        if len(filter(None, [self.iterable, self.maplike, self.setlike])) > 1:
            raise ValueError(
                'Interface can only have one of iterable<>, maplike<> and setlike<>.'
            )

        # TODO(rakuco): This validation logic should be in v8_interface according to bashi@.
        # At the moment, doing so does not work because several IDL files are partial Window
        # interface definitions, and interface_dependency_resolver.py doesn't seem to have any logic
        # to prevent these partial interfaces from resetting has_named_property to False.
        if 'LegacyUnenumerableNamedProperties' in self.extended_attributes and \
           not self.has_named_property_getter:
            raise ValueError(
                '[LegacyUnenumerableNamedProperties] can be used only in interfaces '
                'that support named properties.')

        if has_integer_typed_length and has_indexed_property_getter:
            self.has_indexed_elements = True
        else:
            if self.iterable is not None and self.iterable.key_type is None:
                raise ValueError(
                    'Value iterators (iterable<V>) must be accompanied by an indexed '
                    'property getter and an integer-typed length attribute.')

        if 'Unforgeable' in self.extended_attributes:
            raise ValueError('[Unforgeable] cannot appear on interfaces.')

        if constructor_operations or custom_constructor_operations:
            if self.constructors or self.custom_constructors:
                raise ValueError('Detected mixed [Constructor] and consructor '
                                 'operations. Do not use both in a single '
                                 'interface.')
            extended_attributes = (
                convert_constructor_operations_extended_attributes(
                    constructor_operations_extended_attributes))
            if any(name in extended_attributes.keys()
                   for name in self.extended_attributes.keys()):
                raise ValueError('Detected mixed extended attributes for '
                                 'both [Constructor] and constructor '
                                 'operations. Do not use both in a single '
                                 'interface')
            self.constructors = constructor_operations
            self.custom_constructors = custom_constructor_operations
            self.extended_attributes.update(extended_attributes)

    def accept(self, visitor):
        visitor.visit_interface(self)
        for attribute in self.attributes:
            attribute.accept(visitor)
        for constant in self.constants:
            constant.accept(visitor)
        for constructor in self.constructors:
            constructor.accept(visitor)
        for custom_constructor in self.custom_constructors:
            custom_constructor.accept(visitor)
        for operation in self.operations:
            operation.accept(visitor)
        if self.iterable:
            self.iterable.accept(visitor)
        elif self.maplike:
            self.maplike.accept(visitor)
        elif self.setlike:
            self.setlike.accept(visitor)

    def process_stringifier(self):
        """Add the stringifier's attribute or named operation child, if it has
        one, as a regular attribute/operation of this interface."""
        if self.stringifier.attribute:
            self.attributes.append(self.stringifier.attribute)
        elif self.stringifier.operation:
            self.operations.append(self.stringifier.operation)

    def merge(self, other):
        """Merge in another interface's members (e.g., partial interface)"""
        self.attributes.extend(other.attributes)
        self.constants.extend(other.constants)
        self.operations.extend(other.operations)
        if self.stringifier is None:
            self.stringifier = other.stringifier


################################################################################
# Attributes
################################################################################


class IdlAttribute(TypedObject):
    def __init__(self, node=None):
        self.is_read_only = bool(
            node.GetProperty('READONLY')) if node else False
        self.is_static = bool(node.GetProperty('STATIC')) if node else False
        self.name = node.GetName() if node else None
        self.idl_type = None
        self.extended_attributes = {}
        # In what interface the attribute is (originally) defined when the
        # attribute is inherited from an ancestor interface.
        self.defined_in = None

        if node:
            children = node.GetChildren()
            for child in children:
                child_class = child.GetClass()
                if child_class == 'Type':
                    self.idl_type = type_node_to_type(child)
                elif child_class == 'ExtAttributes':
                    self.extended_attributes = ext_attributes_node_to_extended_attributes(
                        child)
                else:
                    raise ValueError(
                        'Unrecognized node class: %s' % child_class)

        if 'Unforgeable' in self.extended_attributes and self.is_static:
            raise ValueError(
                '[Unforgeable] cannot appear on static attributes.')

    def accept(self, visitor):
        visitor.visit_attribute(self)


################################################################################
# Constants
################################################################################


class IdlConstant(TypedObject):
    def __init__(self, node):
        children = node.GetChildren()
        num_children = len(children)
        if num_children < 2 or num_children > 3:
            raise ValueError('Expected 2 or 3 children, got %s' % num_children)
        type_node = children[0]
        value_node = children[1]
        value_node_class = value_node.GetClass()
        if value_node_class != 'Value':
            raise ValueError('Expected Value node, got %s' % value_node_class)

        self.name = node.GetName()
        # ConstType is more limited than Type, so subtree is smaller and
        # we don't use the full type_node_to_type function.
        self.idl_type = type_node_inner_to_type(type_node)
        self.value = value_node.GetProperty('VALUE')
        # In what interface the attribute is (originally) defined when the
        # attribute is inherited from an ancestor interface.
        self.defined_in = None

        if num_children == 3:
            ext_attributes_node = children[2]
            self.extended_attributes = ext_attributes_node_to_extended_attributes(
                ext_attributes_node)
        else:
            self.extended_attributes = {}

    def accept(self, visitor):
        visitor.visit_constant(self)


################################################################################
# Literals
################################################################################


class IdlLiteral(object):
    def __init__(self, idl_type, value):
        self.idl_type = idl_type
        self.value = value
        self.is_null = False

    def __str__(self):
        if self.idl_type == 'DOMString':
            if self.value:
                return '"%s"' % self.value
            else:
                return 'WTF::g_empty_string'
        if self.idl_type == 'integer':
            return '%d' % self.value
        if self.idl_type == 'float':
            return '%g' % self.value
        if self.idl_type == 'boolean':
            return 'true' if self.value else 'false'
        if self.idl_type == 'dictionary':
            return self.value
        raise ValueError('Unsupported literal type: %s' % self.idl_type)


class IdlLiteralNull(IdlLiteral):
    def __init__(self):
        self.idl_type = 'NULL'
        self.value = None
        self.is_null = True

    def __str__(self):
        return 'nullptr'


def default_node_to_idl_literal(node):
    idl_type = node.GetProperty('TYPE')
    value = node.GetProperty('VALUE')
    if idl_type == 'DOMString':
        if '"' in value or '\\' in value:
            raise ValueError('Unsupported string value: %r' % value)
        return IdlLiteral(idl_type, value)
    if idl_type == 'integer':
        return IdlLiteral(idl_type, int(value, base=0))
    if idl_type == 'float':
        return IdlLiteral(idl_type, float(value))
    if idl_type in ['boolean', 'sequence']:
        return IdlLiteral(idl_type, value)
    if idl_type == 'NULL':
        return IdlLiteralNull()
    if idl_type == 'dictionary':
        return IdlLiteral(idl_type, value)
    raise ValueError('Unrecognized default value type: %s' % idl_type)


################################################################################
# Operations
################################################################################


class IdlOperation(TypedObject):
    def __init__(self, node=None):
        self.arguments = []
        self.extended_attributes = {}
        self.specials = []
        self.is_constructor = False
        self.idl_type = None
        self.is_static = False
        # In what interface the attribute is (originally) defined when the
        # attribute is inherited from an ancestor interface.
        self.defined_in = None

        if not node:
            return

        self.name = node.GetName()

        self.is_static = bool(node.GetProperty('STATIC'))
        property_dictionary = node.GetProperties()
        for special_keyword in SPECIAL_KEYWORD_LIST:
            if special_keyword in property_dictionary:
                self.specials.append(special_keyword.lower())

        children = node.GetChildren()
        for child in children:
            child_class = child.GetClass()
            if child_class == 'Arguments':
                self.arguments = arguments_node_to_arguments(child)
            elif child_class == 'Type':
                self.idl_type = type_node_to_type(child)
            elif child_class == 'ExtAttributes':
                self.extended_attributes = ext_attributes_node_to_extended_attributes(
                    child)
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

        if 'Unforgeable' in self.extended_attributes and self.is_static:
            raise ValueError(
                '[Unforgeable] cannot appear on static operations.')

    @classmethod
    def constructor_from_arguments_node(cls, name, arguments_node):
        constructor = cls()
        constructor.name = name
        constructor.arguments = arguments_node_to_arguments(arguments_node)
        constructor.is_constructor = True
        return constructor

    def accept(self, visitor):
        visitor.visit_operation(self)
        for argument in self.arguments:
            argument.accept(visitor)


################################################################################
# Arguments
################################################################################


class IdlArgument(TypedObject):
    def __init__(self, node=None):
        self.extended_attributes = {}
        self.idl_type = None
        self.is_optional = False  # syntax: (optional T)
        self.is_variadic = False  # syntax: (T...)
        self.default_value = None

        if not node:
            return

        self.is_optional = node.GetProperty('OPTIONAL')
        self.name = node.GetName()

        children = node.GetChildren()
        for child in children:
            child_class = child.GetClass()
            if child_class == 'Type':
                self.idl_type = type_node_to_type(child)
            elif child_class == 'ExtAttributes':
                self.extended_attributes = ext_attributes_node_to_extended_attributes(
                    child)
            elif child_class == 'Argument':
                child_name = child.GetName()
                if child_name != '...':
                    raise ValueError(
                        'Unrecognized Argument node; expected "...", got "%s"'
                        % child_name)
                self.is_variadic = bool(child.GetProperty('ELLIPSIS'))
            elif child_class == 'Default':
                self.default_value = default_node_to_idl_literal(child)
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

    def accept(self, visitor):
        visitor.visit_argument(self)


def arguments_node_to_arguments(node):
    # [Constructor] and [CustomConstructor] without arguments (the bare form)
    # have None instead of an arguments node, but have the same meaning as using
    # an empty argument list, [Constructor()], so special-case this.
    # http://www.w3.org/TR/WebIDL/#Constructor
    if node is None:
        return []
    return [IdlArgument(argument_node) for argument_node in node.GetChildren()]


################################################################################
# Stringifiers
################################################################################


class IdlStringifier(object):
    def __init__(self, node):
        self.attribute = None
        self.operation = None
        self.extended_attributes = {}

        for child in node.GetChildren():
            child_class = child.GetClass()
            if child_class == 'Attribute':
                self.attribute = IdlAttribute(child)
            elif child_class == 'Operation':
                operation = IdlOperation(child)
                if operation.name:
                    self.operation = operation
            elif child_class == 'ExtAttributes':
                self.extended_attributes = ext_attributes_node_to_extended_attributes(
                    child)
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)

        # Copy the stringifier's extended attributes (such as [Unforgable]) onto
        # the underlying attribute or operation, if there is one.
        if self.attribute or self.operation:
            (self.attribute or self.operation).extended_attributes.update(
                self.extended_attributes)


################################################################################
# Iterable, Maplike, Setlike
################################################################################


class IdlIterableOrMaplikeOrSetlike(TypedObject):
    def __init__(self, node):
        self.extended_attributes = {}
        self.type_children = []

        for child in node.GetChildren():
            child_class = child.GetClass()
            if child_class == 'ExtAttributes':
                self.extended_attributes = ext_attributes_node_to_extended_attributes(
                    child)
            elif child_class == 'Type':
                self.type_children.append(child)
            else:
                raise ValueError('Unrecognized node class: %s' % child_class)


class IdlIterable(IdlIterableOrMaplikeOrSetlike):
    idl_type_attributes = ('key_type', 'value_type')

    def __init__(self, node):
        super(IdlIterable, self).__init__(node)

        if len(self.type_children) == 1:
            self.key_type = None
            self.value_type = type_node_to_type(self.type_children[0])
        elif len(self.type_children) == 2:
            self.key_type = type_node_to_type(self.type_children[0])
            self.value_type = type_node_to_type(self.type_children[1])
        else:
            raise ValueError('Unexpected number of type children: %d' % len(
                self.type_children))
        del self.type_children

    def accept(self, visitor):
        visitor.visit_iterable(self)


class IdlMaplike(IdlIterableOrMaplikeOrSetlike):
    idl_type_attributes = ('key_type', 'value_type')

    def __init__(self, node):
        super(IdlMaplike, self).__init__(node)

        self.is_read_only = bool(node.GetProperty('READONLY'))

        if len(self.type_children) == 2:
            self.key_type = type_node_to_type(self.type_children[0])
            self.value_type = type_node_to_type(self.type_children[1])
        else:
            raise ValueError(
                'Unexpected number of children: %d' % len(self.type_children))
        del self.type_children

    def accept(self, visitor):
        visitor.visit_maplike(self)


class IdlSetlike(IdlIterableOrMaplikeOrSetlike):
    idl_type_attributes = ('value_type', )

    def __init__(self, node):
        super(IdlSetlike, self).__init__(node)

        self.is_read_only = bool(node.GetProperty('READONLY'))

        if len(self.type_children) == 1:
            self.value_type = type_node_to_type(self.type_children[0])
        else:
            raise ValueError(
                'Unexpected number of children: %d' % len(self.type_children))
        del self.type_children

    def accept(self, visitor):
        visitor.visit_setlike(self)


################################################################################
# Includes statements
################################################################################


class IdlIncludes(object):
    def __init__(self, node):
        self.interface = node.GetName()
        self.mixin = node.GetProperty('REFERENCE')

    def accept(self, visitor):
        visitor.visit_include(self)


################################################################################
# Extended attributes
################################################################################


class Exposure:
    """An Exposure holds one Exposed or RuntimeEnabled condition.
    Each exposure has two properties: exposed and runtime_enabled.
    Exposure(e, r) corresponds to [Exposed(e r)]. Exposure(e) corresponds to
    [Exposed=e].
    """

    def __init__(self, exposed, runtime_enabled=None):
        self.exposed = exposed
        self.runtime_enabled = runtime_enabled


def ext_attributes_node_to_extended_attributes(node):
    """
    Returns:
      Dictionary of {ExtAttributeName: ExtAttributeValue}.
      Value is usually a string, with these exceptions:
      Constructors: value is a list of Arguments nodes, corresponding to
        possible signatures of the constructor.
      CustomConstructors: value is a list of Arguments nodes, corresponding to
        possible signatures of the custom constructor.
      NamedConstructor: value is a Call node, corresponding to the single
        signature of the named constructor.
    """
    # Primarily just make a dictionary from the children.
    # The only complexity is handling various types of constructors:
    # Constructors and Custom Constructors can have duplicate entries due to
    # overloading, and thus are stored in temporary lists.
    # However, Named Constructors cannot be overloaded, and thus do not have
    # a list.
    # TODO(bashi): Remove |constructors| and |custom_constructors|.
    constructors = []
    custom_constructors = []
    extended_attributes = {}

    def child_node(extended_attribute_node):
        children = extended_attribute_node.GetChildren()
        if not children:
            return None
        if len(children) > 1:
            raise ValueError(
                'ExtAttributes node with %s children, expected at most 1' %
                len(children))
        return children[0]

    extended_attribute_node_list = node.GetChildren()
    for extended_attribute_node in extended_attribute_node_list:
        name = extended_attribute_node.GetName()
        child = child_node(extended_attribute_node)
        child_class = child and child.GetClass()
        if name == 'Constructor':
            raise ValueError('[Constructor] is deprecated. Use constructor '
                             'operations')
        elif name == 'CustomConstructor':
            raise ValueError('[CustomConstructor] is deprecated. Use '
                             'constructor operations with [Custom]')
        elif name == 'NamedConstructor':
            if child_class and child_class != 'Call':
                raise ValueError(
                    '[NamedConstructor] only supports Call as child, but has child of class: %s'
                    % child_class)
            extended_attributes[name] = child
        elif name == 'Exposed':
            if child_class and child_class != 'Arguments':
                raise ValueError(
                    '[Exposed] only supports Arguments as child, but has child of class: %s'
                    % child_class)
            exposures = []
            if child_class == 'Arguments':
                exposures = [
                    Exposure(
                        exposed=str(arg.idl_type), runtime_enabled=arg.name)
                    for arg in arguments_node_to_arguments(child)
                ]
            else:
                value = extended_attribute_node.GetProperty('VALUE')
                if type(value) is str:
                    exposures = [Exposure(exposed=value)]
                else:
                    exposures = [Exposure(exposed=v) for v in value]
            extended_attributes[name] = exposures
        elif child:
            raise ValueError(
                'ExtAttributes node with unexpected children: %s' % name)
        else:
            value = extended_attribute_node.GetProperty('VALUE')
            extended_attributes[name] = value

    # Store constructors and custom constructors in special list attributes,
    # which are deleted later. Note plural in key.
    if constructors:
        extended_attributes['Constructors'] = constructors
    if custom_constructors:
        extended_attributes['CustomConstructors'] = custom_constructors

    return extended_attributes


def extended_attributes_to_constructors(extended_attributes):
    """Returns constructors and custom_constructors (lists of IdlOperations).

    Auxiliary function for IdlInterface.__init__.
    """

    # TODO(bashi): Remove 'Constructors' and 'CustomConstructors'.

    constructor_list = extended_attributes.get('Constructors', [])
    constructors = [
        IdlOperation.constructor_from_arguments_node('Constructor',
                                                     arguments_node)
        for arguments_node in constructor_list
    ]

    custom_constructor_list = extended_attributes.get('CustomConstructors', [])
    custom_constructors = [
        IdlOperation.constructor_from_arguments_node('CustomConstructor',
                                                     arguments_node)
        for arguments_node in custom_constructor_list
    ]

    if 'NamedConstructor' in extended_attributes:
        # FIXME: support overloaded named constructors, and make homogeneous
        name = 'NamedConstructor'
        call_node = extended_attributes['NamedConstructor']
        extended_attributes['NamedConstructor'] = call_node.GetName()
        children = call_node.GetChildren()
        if len(children) != 1:
            raise ValueError('NamedConstructor node expects 1 child, got %s.' %
                             len(children))
        arguments_node = children[0]
        named_constructor = IdlOperation.constructor_from_arguments_node(
            'NamedConstructor', arguments_node)
        # FIXME: should return named_constructor separately; appended for Perl
        constructors.append(named_constructor)

    return constructors, custom_constructors


class ConstructorOperation(object):
    """Represents a constructor operation. This is a tentative object used to
    create constructors in IdlInterface.
    """

    def __init__(self, constructor, extended_attributes, is_custom):
        self.constructor = constructor
        self.extended_attributes = extended_attributes
        self.is_custom = is_custom


def constructor_operation_from_node(node):
    """Creates a ConstructorOperation from the given |node|.
    """

    arguments_node = None
    extended_attributes = {}

    for child in node.GetChildren():
        child_class = child.GetClass()
        if child_class == 'Arguments':
            arguments_node = child
        elif child_class == 'ExtAttributes':
            extended_attributes = ext_attributes_node_to_extended_attributes(
                child)
        else:
            raise ValueError('Unrecognized node class: %s' % child_class)

    if not arguments_node:
        raise ValueError('Expected Arguments node for constructor operation')

    if 'Custom' in extended_attributes:
        if extended_attributes['Custom']:
            raise ValueError('[Custom] should not have a value on constructor '
                             'operations')
        del extended_attributes['Custom']
        constructor = IdlOperation.constructor_from_arguments_node(
            'CustomConstructor', arguments_node)
        return ConstructorOperation(
            constructor, extended_attributes, is_custom=True)
    else:
        constructor = IdlOperation.constructor_from_arguments_node(
            'Constructor', arguments_node)
        return ConstructorOperation(
            constructor, extended_attributes, is_custom=False)


def check_constructor_operations_extended_attributes(current_attrs, new_attrs):
    """Raises a ValueError if two extended attribute lists have different values
    of constructor related attributes.
    """

    attrs_to_check = ['CallWith', 'RaisesException']
    for attr in attrs_to_check:
        if current_attrs.get(attr) != new_attrs.get(attr):
            raise ValueError('[{}] should have the same value on all '
                             'constructor operations'.format(attr))


def convert_constructor_operations_extended_attributes(extended_attributes):
    """Converts extended attributes specified on constructor operations to
    extended attributes for an interface definition (e.g. [ConstructorCallWith])
    """

    converted = {}
    for name, value in extended_attributes.items():
        if name == "CallWith":
            converted["ConstructorCallWith"] = value
        elif name == "RaisesException":
            if value:
                raise ValueError(
                    '[RaisesException] should not have a value on '
                    'constructor operations')
            converted["RaisesException"] = 'Constructor'
        elif name == "MeasureAs":
            converted["MeasureAs"] = value
        elif name == "Measure":
            converted["Measure"] = None
        else:
            raise ValueError(
                '[{}] is not supported on constructor operations'.format(name))

    return converted


def clear_constructor_attributes(extended_attributes):
    # Deletes Constructor*s* (plural), sets Constructor (singular)
    if 'Constructors' in extended_attributes:
        del extended_attributes['Constructors']
        extended_attributes['Constructor'] = None
    if 'CustomConstructors' in extended_attributes:
        del extended_attributes['CustomConstructors']
        extended_attributes['CustomConstructor'] = None


################################################################################
# Types
################################################################################


def type_node_to_type(node):
    children = node.GetChildren()
    if len(children) != 1 and len(children) != 2:
        raise ValueError(
            'Type node expects 1 or 2 child(ren), got %d.' % len(children))

    base_type = type_node_inner_to_type(children[0])
    if len(children) == 2:
        extended_attributes = ext_attributes_node_to_extended_attributes(
            children[1])
        base_type = IdlAnnotatedType(base_type, extended_attributes)

    if node.GetProperty('NULLABLE'):
        base_type = IdlNullableType(base_type)

    return base_type


def type_node_inner_to_type(node):
    node_class = node.GetClass()
    # Note Type*r*ef, not Typedef, meaning the type is an identifier, thus
    # either a typedef shorthand (but not a Typedef declaration itself) or an
    # interface type. We do not distinguish these, and just use the type name.
    if node_class in ['PrimitiveType', 'StringType', 'Typeref']:
        # unrestricted syntax: unrestricted double | unrestricted float
        is_unrestricted = bool(node.GetProperty('UNRESTRICTED'))
        return IdlType(node.GetName(), is_unrestricted=is_unrestricted)
    elif node_class == 'Any':
        return IdlType('any')
    elif node_class in ['Sequence', 'FrozenArray']:
        return sequence_node_to_type(node)
    elif node_class == 'UnionType':
        return union_type_node_to_idl_union_type(node)
    elif node_class == 'Promise':
        return IdlType('Promise')
    elif node_class == 'Record':
        return record_node_to_type(node)
    raise ValueError('Unrecognized node class: %s' % node_class)


def record_node_to_type(node):
    children = node.GetChildren()
    if len(children) != 2:
        raise ValueError('record<K,V> node expects exactly 2 children, got %d'
                         % (len(children)))
    key_child = children[0]
    value_child = children[1]
    if key_child.GetClass() != 'StringType':
        raise ValueError('Keys in record<K,V> nodes must be string types.')
    if value_child.GetClass() != 'Type':
        raise ValueError('Unrecognized node class for record<K,V> value: %s' %
                         value_child.GetClass())
    return IdlRecordType(
        IdlType(key_child.GetName()), type_node_to_type(value_child))


def sequence_node_to_type(node):
    children = node.GetChildren()
    class_name = node.GetClass()
    if len(children) != 1:
        raise ValueError('%s node expects exactly 1 child, got %s' %
                         (class_name, len(children)))
    sequence_child = children[0]
    sequence_child_class = sequence_child.GetClass()
    if sequence_child_class != 'Type':
        raise ValueError('Unrecognized node class: %s' % sequence_child_class)
    element_type = type_node_to_type(sequence_child)
    if class_name == 'Sequence':
        sequence_type = IdlSequenceType(element_type)
    elif class_name == 'FrozenArray':
        sequence_type = IdlFrozenArrayType(element_type)
    else:
        raise ValueError('Unexpected node: %s' % class_name)
    if node.GetProperty('NULLABLE'):
        return IdlNullableType(sequence_type)
    return sequence_type


def typedef_node_to_type(node):
    children = node.GetChildren()
    if len(children) != 1:
        raise ValueError(
            'Typedef node with %s children, expected 1' % len(children))
    child = children[0]
    child_class = child.GetClass()
    if child_class != 'Type':
        raise ValueError('Unrecognized node class: %s' % child_class)
    return type_node_to_type(child)


def union_type_node_to_idl_union_type(node):
    member_types = [
        type_node_to_type(member_type_node)
        for member_type_node in node.GetChildren()
    ]
    return IdlUnionType(member_types)


################################################################################
# Visitor
################################################################################


class Visitor(object):
    """Abstract visitor class for IDL definitions traverse."""

    def visit_definitions(self, definitions):
        pass

    def visit_typed_object(self, typed_object):
        pass

    def visit_callback_function(self, callback_function):
        self.visit_typed_object(callback_function)

    def visit_dictionary(self, dictionary):
        pass

    def visit_dictionary_member(self, member):
        self.visit_typed_object(member)

    def visit_enumeration(self, enumeration):
        pass

    def visit_include(self, include):
        pass

    def visit_interface(self, interface):
        pass

    def visit_typedef(self, typedef):
        self.visit_typed_object(typedef)

    def visit_attribute(self, attribute):
        self.visit_typed_object(attribute)

    def visit_constant(self, constant):
        self.visit_typed_object(constant)

    def visit_operation(self, operation):
        self.visit_typed_object(operation)

    def visit_argument(self, argument):
        self.visit_typed_object(argument)

    def visit_iterable(self, iterable):
        self.visit_typed_object(iterable)

    def visit_maplike(self, maplike):
        self.visit_typed_object(maplike)

    def visit_setlike(self, setlike):
        self.visit_typed_object(setlike)