2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
12 from json_parse
import OrderedDict
15 # This file is a peer to json_schema.py. Each of these files understands a
16 # certain format describing APIs (either JSON or IDL), reads files written
17 # in that format into memory, and emits them as a Python array of objects
18 # corresponding to those APIs, where the objects are formatted in a way that
19 # the JSON schema compiler understands. compiler.py drives both idl_schema.py
22 # idl_parser expects to be able to import certain files in its directory,
23 # so let's set things up the way it wants.
24 _idl_generators_path
= os
.path
.join(os
.path
.dirname(os
.path
.realpath(__file__
)),
25 os
.pardir
, os
.pardir
, 'ppapi', 'generators')
26 if _idl_generators_path
in sys
.path
:
29 sys
.path
.insert(0, _idl_generators_path
)
35 def ProcessComment(comment
):
37 Convert a comment into a parent comment and a list of parameter comments.
39 Function comments are of the form:
40 Function documentation. May contain HTML and multiple lines.
42 |arg1_name|: Description of arg1. Use <var>argument</var> to refer
44 |arg2_name|: Description of arg2...
46 Newlines are removed, and leading and trailing whitespace is stripped.
49 comment: The string from a Comment node.
51 Returns: A tuple that looks like:
53 "The processed comment, minus all |parameter| mentions.",
55 'parameter_name_1': "The comment that followed |parameter_name_1|:",
60 # Find all the parameter comments of the form '|name|: comment'.
61 parameter_starts
= list(re
.finditer(r
' *\|([^|]*)\| *: *', comment
))
63 # Get the parent comment (everything before the first parameter comment.
64 first_parameter_location
= (parameter_starts
[0].start()
65 if parameter_starts
else len(comment
))
66 parent_comment
= comment
[:first_parameter_location
]
68 # We replace \n\n with <br/><br/> here and below, because the documentation
69 # needs to know where the newlines should be, and this is easier than
71 parent_comment
= (parent_comment
.strip().replace('\n\n', '<br/><br/>')
74 params
= OrderedDict()
75 for (cur_param
, next_param
) in itertools
.izip_longest(parameter_starts
,
76 parameter_starts
[1:]):
77 param_name
= cur_param
.group(1)
79 # A parameter's comment goes from the end of its introduction to the
80 # beginning of the next parameter's introduction.
81 param_comment_start
= cur_param
.end()
82 param_comment_end
= next_param
.start() if next_param
else len(comment
)
83 params
[param_name
] = (comment
[param_comment_start
:param_comment_end
84 ].strip().replace('\n\n', '<br/><br/>')
86 return (parent_comment
, params
)
88 class Callspec(object):
90 Given a Callspec node representing an IDL function declaration, converts into
92 (name, list of function parameters, return type)
94 def __init__(self
, callspec_node
, comment
):
95 self
.node
= callspec_node
96 self
.comment
= comment
98 def process(self
, callbacks
):
101 if self
.node
.GetProperty('TYPEREF') not in ('void', None):
102 return_type
= Typeref(self
.node
.GetProperty('TYPEREF'),
104 {'name': self
.node
.GetName()}).process(callbacks
)
105 # The IDL parser doesn't allow specifying return types as optional.
106 # Instead we infer any object return values to be optional.
107 # TODO(asargent): fix the IDL parser to support optional return types.
108 if return_type
.get('type') == 'object' or '$ref' in return_type
:
109 return_type
['optional'] = True;
110 for node
in self
.node
.children
:
111 parameter
= Param(node
).process(callbacks
)
112 if parameter
['name'] in self
.comment
:
113 parameter
['description'] = self
.comment
[parameter
['name']]
114 parameters
.append(parameter
)
115 return (self
.node
.GetName(), parameters
, return_type
)
119 Given a Param node representing a function parameter, converts into a Python
120 dictionary that the JSON schema compiler expects to see.
122 def __init__(self
, param_node
):
123 self
.node
= param_node
125 def process(self
, callbacks
):
126 return Typeref(self
.node
.GetProperty('TYPEREF'),
128 {'name': self
.node
.GetName()}).process(callbacks
)
130 class Dictionary(object):
132 Given an IDL Dictionary node, converts into a Python dictionary that the JSON
133 schema compiler expects to see.
135 def __init__(self
, dictionary_node
):
136 self
.node
= dictionary_node
138 def process(self
, callbacks
):
139 properties
= OrderedDict()
140 for node
in self
.node
.children
:
141 if node
.cls
== 'Member':
142 k
, v
= Member(node
).process(callbacks
)
144 result
= {'id': self
.node
.GetName(),
145 'properties': properties
,
147 if self
.node
.GetProperty('inline_doc'):
148 result
['inline_doc'] = True
152 class Member(object):
154 Given an IDL dictionary or interface member, converts into a name/value pair
155 where the value is a Python dictionary that the JSON schema compiler expects
158 def __init__(self
, member_node
):
159 self
.node
= member_node
161 def process(self
, callbacks
):
162 properties
= OrderedDict()
163 name
= self
.node
.GetName()
164 for property_name
in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'):
165 if self
.node
.GetProperty(property_name
):
166 properties
[property_name
.lower()] = True
167 for option_name
, sanitizer
in [
168 ('maxListeners', int),
169 ('supportsFilters', lambda s
: s
== 'true'),
170 ('supportsListeners', lambda s
: s
== 'true'),
171 ('supportsRules', lambda s
: s
== 'true')]:
172 if self
.node
.GetProperty(option_name
):
173 if 'options' not in properties
:
174 properties
['options'] = {}
175 properties
['options'][option_name
] = sanitizer(self
.node
.GetProperty(
178 parameter_comments
= OrderedDict()
179 for node
in self
.node
.children
:
180 if node
.cls
== 'Comment':
181 (parent_comment
, parameter_comments
) = ProcessComment(node
.GetName())
182 properties
['description'] = parent_comment
183 elif node
.cls
== 'Callspec':
185 name
, parameters
, return_type
= (Callspec(node
, parameter_comments
)
187 properties
['parameters'] = parameters
188 if return_type
is not None:
189 properties
['returns'] = return_type
190 properties
['name'] = name
192 properties
['type'] = 'function'
194 properties
= Typeref(self
.node
.GetProperty('TYPEREF'),
195 self
.node
, properties
).process(callbacks
)
196 enum_values
= self
.node
.GetProperty('legalValues')
198 if properties
['type'] == 'integer':
199 enum_values
= map(int, enum_values
)
200 elif properties
['type'] == 'double':
201 enum_values
= map(float, enum_values
)
202 properties
['enum'] = enum_values
203 return name
, properties
205 class Typeref(object):
207 Given a TYPEREF property representing the type of dictionary member or
208 function parameter, converts into a Python dictionary that the JSON schema
209 compiler expects to see.
211 def __init__(self
, typeref
, parent
, additional_properties
=OrderedDict()):
212 self
.typeref
= typeref
214 self
.additional_properties
= additional_properties
216 def process(self
, callbacks
):
217 properties
= self
.additional_properties
220 if self
.parent
.GetProperty('OPTIONAL', False):
221 properties
['optional'] = True
223 # The IDL parser denotes array types by adding a child 'Array' node onto
224 # the Param node in the Callspec.
225 for sibling
in self
.parent
.GetChildren():
226 if sibling
.cls
== 'Array' and sibling
.GetName() == self
.parent
.GetName():
227 properties
['type'] = 'array'
228 properties
['items'] = OrderedDict()
229 properties
= properties
['items']
232 if self
.typeref
== 'DOMString':
233 properties
['type'] = 'string'
234 elif self
.typeref
== 'boolean':
235 properties
['type'] = 'boolean'
236 elif self
.typeref
== 'double':
237 properties
['type'] = 'number'
238 elif self
.typeref
== 'long':
239 properties
['type'] = 'integer'
240 elif self
.typeref
== 'any':
241 properties
['type'] = 'any'
242 elif self
.typeref
== 'object':
243 properties
['type'] = 'object'
244 if 'additionalProperties' not in properties
:
245 properties
['additionalProperties'] = OrderedDict()
246 properties
['additionalProperties']['type'] = 'any'
247 instance_of
= self
.parent
.GetProperty('instanceOf')
249 properties
['isInstanceOf'] = instance_of
250 elif self
.typeref
== 'ArrayBuffer':
251 properties
['type'] = 'binary'
252 properties
['isInstanceOf'] = 'ArrayBuffer'
253 elif self
.typeref
== 'FileEntry':
254 properties
['type'] = 'object'
255 properties
['isInstanceOf'] = 'FileEntry'
256 if 'additionalProperties' not in properties
:
257 properties
['additionalProperties'] = OrderedDict()
258 properties
['additionalProperties']['type'] = 'any'
259 elif self
.typeref
is None:
260 properties
['type'] = 'function'
262 if self
.typeref
in callbacks
:
263 # Do not override name and description if they are already specified.
264 name
= properties
.get('name', None)
265 description
= properties
.get('description', None)
266 properties
.update(callbacks
[self
.typeref
])
267 if description
is not None:
268 properties
['description'] = description
270 properties
['name'] = name
272 properties
['$ref'] = self
.typeref
278 Given an IDL Enum node, converts into a Python dictionary that the JSON
279 schema compiler expects to see.
281 def __init__(self
, enum_node
):
282 self
.node
= enum_node
283 self
.description
= ''
285 def process(self
, callbacks
):
287 for node
in self
.node
.children
:
288 if node
.cls
== 'EnumItem':
289 enum
.append(node
.GetName())
290 elif node
.cls
== 'Comment':
291 self
.description
= ProcessComment(node
.GetName())[0]
293 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
294 result
= {'id' : self
.node
.GetName(),
295 'description': self
.description
,
298 for property_name
in ('inline_doc', 'nodoc'):
299 if self
.node
.GetProperty(property_name
):
300 result
[property_name
] = True
304 class Namespace(object):
306 Given an IDLNode representing an IDL namespace, converts into a Python
307 dictionary that the JSON schema compiler expects to see.
310 def __init__(self
, namespace_node
, nodoc
=False, permissions
=None,
312 self
.namespace
= namespace_node
314 self
.internal
= internal
318 self
.callbacks
= OrderedDict()
319 self
.permissions
= permissions
or []
322 for node
in self
.namespace
.children
:
323 if node
.cls
== 'Dictionary':
324 self
.types
.append(Dictionary(node
).process(self
.callbacks
))
325 elif node
.cls
== 'Callback':
326 k
, v
= Member(node
).process(self
.callbacks
)
327 self
.callbacks
[k
] = v
328 elif node
.cls
== 'Interface' and node
.GetName() == 'Functions':
329 self
.functions
= self
.process_interface(node
)
330 elif node
.cls
== 'Interface' and node
.GetName() == 'Events':
331 self
.events
= self
.process_interface(node
)
332 elif node
.cls
== 'Enum':
333 self
.types
.append(Enum(node
).process(self
.callbacks
))
335 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
336 return {'namespace': self
.namespace
.GetName(),
338 'documentation_permissions_required': self
.permissions
,
340 'functions': self
.functions
,
341 'internal': self
.internal
,
342 'events': self
.events
}
344 def process_interface(self
, node
):
346 for member
in node
.children
:
347 if member
.cls
== 'Member':
348 name
, properties
= Member(member
).process(self
.callbacks
)
349 members
.append(properties
)
352 class IDLSchema(object):
354 Given a list of IDLNodes and IDLAttributes, converts into a Python list
355 of api_defs that the JSON schema compiler expects to see.
358 def __init__(self
, idl
):
366 for node
in self
.idl
:
367 if node
.cls
== 'Namespace':
368 namespace
= Namespace(node
, nodoc
, permissions
, internal
)
369 namespaces
.append(namespace
.process())
372 elif node
.cls
== 'Copyright':
374 elif node
.cls
== 'Comment':
376 elif node
.cls
== 'ExtAttribute':
377 if node
.name
== 'nodoc':
378 nodoc
= bool(node
.value
)
379 elif node
.name
== 'permissions':
380 permission
= node
.value
.split(',')
381 elif node
.name
== 'internal':
382 internal
= bool(node
.value
)
386 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
391 Given the filename of an IDL file, parses it and returns an equivalent
392 Python dictionary in a format that the JSON schema compiler expects to see.
395 f
= open(filename
, 'r')
399 idl
= idl_parser
.IDLParser().ParseData(contents
, filename
)
400 idl_schema
= IDLSchema(idl
)
401 return idl_schema
.process()
405 Dump a json serialization of parse result for the IDL files whose names
406 were passed in on the command line.
408 for filename
in sys
.argv
[1:]:
409 schema
= Load(filename
)
410 print json
.dumps(schema
, indent
=2)
412 if __name__
== '__main__':