]>
Commit | Line | Data |
---|---|---|
ed1e77d3 A |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Copyright (c) 2014 Apple Inc. All rights reserved. | |
4 | # Copyright (c) 2014 University of Washington. All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # 1. Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # 2. Redistributions in binary form must reproduce the above copyright | |
12 | # notice, this list of conditions and the following disclaimer in the | |
13 | # documentation and/or other materials provided with the distribution. | |
14 | # | |
15 | # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | |
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
17 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
18 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | |
19 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
20 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
21 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
22 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
23 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
24 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
25 | # THE POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | import logging | |
28 | import os.path | |
29 | import re | |
30 | from string import Template | |
31 | ||
32 | from generator_templates import GeneratorTemplates as Templates | |
33 | from models import PrimitiveType, ObjectType, ArrayType, EnumType, AliasedType, Frameworks | |
34 | ||
35 | log = logging.getLogger('global') | |
36 | ||
37 | ||
38 | def ucfirst(str): | |
39 | return str[:1].upper() + str[1:] | |
40 | ||
41 | _ALWAYS_UPPERCASED_ENUM_VALUE_SUBSTRINGS = set(['API', 'CSS', 'DOM', 'HTML', 'XHR', 'XML']) | |
42 | ||
43 | # These objects are built manually by creating and setting InspectorValues. | |
44 | # Before sending these over the protocol, their shapes are checked against the specification. | |
45 | # So, any types referenced by these types require debug-only assertions that check values. | |
46 | # Calculating necessary assertions is annoying, and adds a lot of complexity to the generator. | |
47 | ||
48 | # FIXME: This should be converted into a property in JSON. | |
49 | _TYPES_NEEDING_RUNTIME_CASTS = set([ | |
50 | "Runtime.RemoteObject", | |
51 | "Runtime.PropertyDescriptor", | |
52 | "Runtime.InternalPropertyDescriptor", | |
53 | "Runtime.CollectionEntry", | |
54 | "Debugger.FunctionDetails", | |
55 | "Debugger.CallFrame", | |
56 | "Canvas.TraceLog", | |
57 | "Canvas.ResourceInfo", | |
58 | "Canvas.ResourceState", | |
59 | # This should be a temporary hack. TimelineEvent should be created via generated C++ API. | |
60 | "Timeline.TimelineEvent", | |
61 | # For testing purposes only. | |
62 | "Test.TypeNeedingCast" | |
63 | ]) | |
64 | ||
65 | # FIXME: This should be converted into a property in JSON. | |
66 | _TYPES_WITH_OPEN_FIELDS = set([ | |
67 | "Timeline.TimelineEvent", | |
68 | # InspectorStyleSheet not only creates this property but wants to read it and modify it. | |
69 | "CSS.CSSProperty", | |
70 | # InspectorResourceAgent needs to update mime-type. | |
71 | "Network.Response", | |
72 | # For testing purposes only. | |
73 | "Test.OpenParameterBundle" | |
74 | ]) | |
75 | ||
76 | ||
77 | class Generator: | |
78 | def __init__(self, model, input_filepath): | |
79 | self._model = model | |
80 | self._input_filepath = input_filepath | |
81 | ||
82 | def model(self): | |
83 | return self._model | |
84 | ||
85 | def generate_license(self): | |
86 | return Template(Templates.CopyrightBlock).substitute(None, inputFilename=os.path.basename(self._input_filepath)) | |
87 | ||
88 | # These methods are overridden by subclasses. | |
89 | def non_supplemental_domains(self): | |
90 | return filter(lambda domain: not domain.is_supplemental, self.model().domains) | |
91 | ||
92 | def domains_to_generate(self): | |
93 | return self.non_supplemental_domains() | |
94 | ||
95 | def generate_output(self): | |
96 | pass | |
97 | ||
98 | def output_filename(self): | |
99 | pass | |
100 | ||
101 | def encoding_for_enum_value(self, enum_value): | |
102 | if not hasattr(self, "_assigned_enum_values"): | |
103 | self._traverse_and_assign_enum_values() | |
104 | ||
105 | return self._enum_value_encodings[enum_value] | |
106 | ||
107 | def assigned_enum_values(self): | |
108 | if not hasattr(self, "_assigned_enum_values"): | |
109 | self._traverse_and_assign_enum_values() | |
110 | ||
111 | return self._assigned_enum_values[:] # Slice. | |
112 | ||
113 | @staticmethod | |
114 | def type_needs_runtime_casts(_type): | |
115 | return _type.qualified_name() in _TYPES_NEEDING_RUNTIME_CASTS | |
116 | ||
117 | @staticmethod | |
118 | def type_has_open_fields(_type): | |
119 | return _type.qualified_name() in _TYPES_WITH_OPEN_FIELDS | |
120 | ||
121 | def type_needs_shape_assertions(self, _type): | |
122 | if not hasattr(self, "_types_needing_shape_assertions"): | |
123 | self.calculate_types_requiring_shape_assertions(self.model().domains) | |
124 | ||
125 | return _type in self._types_needing_shape_assertions | |
126 | ||
127 | # To restrict the domains over which we compute types needing assertions, call this method | |
128 | # before generating any output with the desired domains parameter. The computed | |
129 | # set of types will not be automatically regenerated on subsequent calls to | |
130 | # Generator.types_needing_shape_assertions(). | |
131 | def calculate_types_requiring_shape_assertions(self, domains): | |
132 | domain_names = map(lambda domain: domain.domain_name, domains) | |
133 | log.debug("> Calculating types that need shape assertions (eligible domains: %s)" % ", ".join(domain_names)) | |
134 | ||
135 | # Mutates the passed-in set; this simplifies checks to prevent infinite recursion. | |
136 | def gather_transitively_referenced_types(_type, gathered_types): | |
137 | if _type in gathered_types: | |
138 | return | |
139 | ||
140 | if isinstance(_type, ObjectType): | |
141 | log.debug("> Adding type %s to list of types needing shape assertions." % _type.qualified_name()) | |
142 | gathered_types.add(_type) | |
143 | for type_member in _type.members: | |
144 | gather_transitively_referenced_types(type_member.type, gathered_types) | |
145 | elif isinstance(_type, EnumType): | |
146 | log.debug("> Adding type %s to list of types needing shape assertions." % _type.qualified_name()) | |
147 | gathered_types.add(_type) | |
148 | elif isinstance(_type, AliasedType): | |
149 | gather_transitively_referenced_types(_type.aliased_type, gathered_types) | |
150 | elif isinstance(_type, ArrayType): | |
151 | gather_transitively_referenced_types(_type.element_type, gathered_types) | |
152 | ||
153 | found_types = set() | |
154 | for domain in domains: | |
155 | for declaration in domain.type_declarations: | |
156 | if declaration.type.qualified_name() in _TYPES_NEEDING_RUNTIME_CASTS: | |
157 | log.debug("> Gathering types referenced by %s to generate shape assertions." % declaration.type.qualified_name()) | |
158 | gather_transitively_referenced_types(declaration.type, found_types) | |
159 | ||
160 | self._types_needing_shape_assertions = found_types | |
161 | ||
162 | # Private helper instance methods. | |
163 | def _traverse_and_assign_enum_values(self): | |
164 | self._enum_value_encodings = {} | |
165 | self._assigned_enum_values = [] | |
166 | all_types = [] | |
167 | ||
168 | domains = self.non_supplemental_domains() | |
169 | ||
170 | for domain in domains: | |
171 | for type_declaration in domain.type_declarations: | |
172 | all_types.append(type_declaration.type) | |
173 | for type_member in type_declaration.type_members: | |
174 | all_types.append(type_member.type) | |
175 | ||
176 | for domain in domains: | |
177 | for event in domain.events: | |
178 | all_types.extend([parameter.type for parameter in event.event_parameters]) | |
179 | ||
180 | for domain in domains: | |
181 | for command in domain.commands: | |
182 | all_types.extend([parameter.type for parameter in command.call_parameters]) | |
183 | all_types.extend([parameter.type for parameter in command.return_parameters]) | |
184 | ||
185 | for _type in all_types: | |
186 | if not isinstance(_type, EnumType): | |
187 | continue | |
188 | map(self._assign_encoding_for_enum_value, _type.enum_values()) | |
189 | ||
190 | def _assign_encoding_for_enum_value(self, enum_value): | |
191 | if enum_value in self._enum_value_encodings: | |
192 | return | |
193 | ||
194 | self._enum_value_encodings[enum_value] = len(self._assigned_enum_values) | |
195 | self._assigned_enum_values.append(enum_value) | |
196 | ||
197 | # Miscellaneous text manipulation routines. | |
198 | def wrap_with_guard_for_domain(self, domain, text): | |
199 | if self.model().framework is Frameworks.WebInspector: | |
200 | return text | |
201 | guard = domain.feature_guard | |
202 | if guard: | |
203 | return Generator.wrap_with_guard(guard, text) | |
204 | return text | |
205 | ||
206 | @staticmethod | |
207 | def wrap_with_guard(guard, text): | |
208 | return '\n'.join([ | |
209 | '#if %s' % guard, | |
210 | text, | |
211 | '#endif // %s' % guard, | |
212 | ]) | |
213 | ||
214 | @staticmethod | |
215 | def stylized_name_for_enum_value(enum_value): | |
216 | regex = '(%s)' % "|".join(_ALWAYS_UPPERCASED_ENUM_VALUE_SUBSTRINGS) | |
217 | ||
218 | def replaceCallback(match): | |
219 | return match.group(1).upper() | |
220 | ||
221 | # Split on hyphen, introduce camelcase, and force uppercasing of acronyms. | |
222 | subwords = map(ucfirst, enum_value.split('-')) | |
223 | return re.sub(re.compile(regex, re.IGNORECASE), replaceCallback, "".join(subwords)) | |
224 | ||
225 | @staticmethod | |
226 | def js_name_for_parameter_type(_type): | |
227 | _type = _type | |
228 | if isinstance(_type, AliasedType): | |
229 | _type = _type.aliased_type # Fall through. | |
230 | if isinstance(_type, EnumType): | |
231 | _type = _type.primitive_type # Fall through. | |
232 | ||
233 | if isinstance(_type, (ArrayType, ObjectType)): | |
234 | return 'object' | |
235 | if isinstance(_type, PrimitiveType): | |
236 | if _type.qualified_name() in ['object', 'any']: | |
237 | return 'object' | |
238 | elif _type.qualified_name() in ['integer', 'number']: | |
239 | return 'number' | |
240 | else: | |
241 | return _type.qualified_name() |