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
14 # This file is a peer to json_schema.py. Each of these files understands a
15 # certain format describing APIs (either JSON or IDL), reads files written
16 # in that format into memory, and emits them as a Python array of objects
17 # corresponding to those APIs, where the objects are formatted in a way that
18 # the JSON schema compiler understands. compiler.py drives both idl_schema.py
21 # idl_parser expects to be able to import certain files in its directory,
22 # so let's set things up the way it wants.
23 _idl_generators_path
= os
.path
.join(os
.path
.dirname(os
.path
.realpath(__file__
)),
24 os
.pardir
, os
.pardir
, 'ppapi', 'generators')
25 if _idl_generators_path
in sys
.path
:
28 sys
.path
.insert(0, _idl_generators_path
)
34 def ProcessComment(comment
):
36 Convert a comment into a parent comment and a list of parameter comments.
38 Function comments are of the form:
39 Function documentation. May contain HTML and multiple lines.
41 |arg1_name|: Description of arg1. Use <var>argument</var> to refer
43 |arg2_name|: Description of arg2...
45 Newlines are removed, and leading and trailing whitespace is stripped.
48 comment: The string from a Comment node.
50 Returns: A tuple that looks like:
52 "The processed comment, minus all |parameter| mentions.",
54 'parameter_name_1': "The comment that followed |parameter_name_1|:",
59 def add_paragraphs(content
):
60 paragraphs
= content
.split('\n\n')
61 if len(paragraphs
) < 2:
63 return '<p>' + '</p><p>'.join(p
.strip() for p
in paragraphs
) + '</p>'
65 # Find all the parameter comments of the form '|name|: comment'.
66 parameter_starts
= list(re
.finditer(r
' *\|([^|]*)\| *: *', comment
))
68 # Get the parent comment (everything before the first parameter comment.
69 first_parameter_location
= (parameter_starts
[0].start()
70 if parameter_starts
else len(comment
))
71 parent_comment
= (add_paragraphs(comment
[:first_parameter_location
].strip())
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
] = (
84 add_paragraphs(comment
[param_comment_start
:param_comment_end
].strip())
87 return (parent_comment
, params
)
90 class Callspec(object):
92 Given a Callspec node representing an IDL function declaration, converts into
94 (name, list of function parameters, return type)
96 def __init__(self
, callspec_node
, comment
):
97 self
.node
= callspec_node
98 self
.comment
= comment
100 def process(self
, callbacks
):
103 if self
.node
.GetProperty('TYPEREF') not in ('void', None):
104 return_type
= Typeref(self
.node
.GetProperty('TYPEREF'),
106 {'name': self
.node
.GetName()}).process(callbacks
)
107 # The IDL parser doesn't allow specifying return types as optional.
108 # Instead we infer any object return values to be optional.
109 # TODO(asargent): fix the IDL parser to support optional return types.
110 if return_type
.get('type') == 'object' or '$ref' in return_type
:
111 return_type
['optional'] = True
112 for node
in self
.node
.GetChildren():
113 parameter
= Param(node
).process(callbacks
)
114 if parameter
['name'] in self
.comment
:
115 parameter
['description'] = self
.comment
[parameter
['name']]
116 parameters
.append(parameter
)
117 return (self
.node
.GetName(), parameters
, return_type
)
122 Given a Param node representing a function parameter, converts into a Python
123 dictionary that the JSON schema compiler expects to see.
125 def __init__(self
, param_node
):
126 self
.node
= param_node
128 def process(self
, callbacks
):
129 return Typeref(self
.node
.GetProperty('TYPEREF'),
131 {'name': self
.node
.GetName()}).process(callbacks
)
134 class Dictionary(object):
136 Given an IDL Dictionary node, converts into a Python dictionary that the JSON
137 schema compiler expects to see.
139 def __init__(self
, dictionary_node
):
140 self
.node
= dictionary_node
142 def process(self
, callbacks
):
143 properties
= OrderedDict()
144 for node
in self
.node
.GetChildren():
145 if node
.cls
== 'Member':
146 k
, v
= Member(node
).process(callbacks
)
148 result
= {'id': self
.node
.GetName(),
149 'properties': properties
,
151 if self
.node
.GetProperty('nodoc'):
152 result
['nodoc'] = True
153 elif self
.node
.GetProperty('inline_doc'):
154 result
['inline_doc'] = True
155 elif self
.node
.GetProperty('noinline_doc'):
156 result
['noinline_doc'] = True
161 class Member(object):
163 Given an IDL dictionary or interface member, converts into a name/value pair
164 where the value is a Python dictionary that the JSON schema compiler expects
167 def __init__(self
, member_node
):
168 self
.node
= member_node
170 def process(self
, callbacks
):
171 properties
= OrderedDict()
172 name
= self
.node
.GetName()
173 if self
.node
.GetProperty('deprecated'):
174 properties
['deprecated'] = self
.node
.GetProperty('deprecated')
175 if self
.node
.GetProperty('allowAmbiguousOptionalArguments'):
176 properties
['allowAmbiguousOptionalArguments'] = True
177 for property_name
in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'):
178 if self
.node
.GetProperty(property_name
):
179 properties
[property_name
.lower()] = True
180 for option_name
, sanitizer
in [
181 ('maxListeners', int),
182 ('supportsFilters', lambda s
: s
== 'true'),
183 ('supportsListeners', lambda s
: s
== 'true'),
184 ('supportsRules', lambda s
: s
== 'true')]:
185 if self
.node
.GetProperty(option_name
):
186 if 'options' not in properties
:
187 properties
['options'] = {}
188 properties
['options'][option_name
] = sanitizer(self
.node
.GetProperty(
191 parameter_comments
= OrderedDict()
192 for node
in self
.node
.GetChildren():
193 if node
.cls
== 'Comment':
194 (parent_comment
, parameter_comments
) = ProcessComment(node
.GetName())
195 properties
['description'] = parent_comment
196 elif node
.cls
== 'Callspec':
198 name
, parameters
, return_type
= (Callspec(node
, parameter_comments
)
200 properties
['parameters'] = parameters
201 if return_type
is not None:
202 properties
['returns'] = return_type
203 properties
['name'] = name
205 properties
['type'] = 'function'
207 properties
= Typeref(self
.node
.GetProperty('TYPEREF'),
208 self
.node
, properties
).process(callbacks
)
209 enum_values
= self
.node
.GetProperty('legalValues')
211 if properties
['type'] == 'integer':
212 enum_values
= map(int, enum_values
)
213 elif properties
['type'] == 'double':
214 enum_values
= map(float, enum_values
)
215 properties
['enum'] = enum_values
216 return name
, properties
219 class Typeref(object):
221 Given a TYPEREF property representing the type of dictionary member or
222 function parameter, converts into a Python dictionary that the JSON schema
223 compiler expects to see.
225 def __init__(self
, typeref
, parent
, additional_properties
):
226 self
.typeref
= typeref
228 self
.additional_properties
= additional_properties
230 def process(self
, callbacks
):
231 properties
= self
.additional_properties
234 if self
.parent
.GetPropertyLocal('OPTIONAL'):
235 properties
['optional'] = True
237 # The IDL parser denotes array types by adding a child 'Array' node onto
238 # the Param node in the Callspec.
239 for sibling
in self
.parent
.GetChildren():
240 if sibling
.cls
== 'Array' and sibling
.GetName() == self
.parent
.GetName():
241 properties
['type'] = 'array'
242 properties
['items'] = OrderedDict()
243 properties
= properties
['items']
246 if self
.typeref
== 'DOMString':
247 properties
['type'] = 'string'
248 elif self
.typeref
== 'boolean':
249 properties
['type'] = 'boolean'
250 elif self
.typeref
== 'double':
251 properties
['type'] = 'number'
252 elif self
.typeref
== 'long':
253 properties
['type'] = 'integer'
254 elif self
.typeref
== 'any':
255 properties
['type'] = 'any'
256 elif self
.typeref
== 'object':
257 properties
['type'] = 'object'
258 if 'additionalProperties' not in properties
:
259 properties
['additionalProperties'] = OrderedDict()
260 properties
['additionalProperties']['type'] = 'any'
261 instance_of
= self
.parent
.GetProperty('instanceOf')
263 properties
['isInstanceOf'] = instance_of
264 elif self
.typeref
== 'ArrayBuffer':
265 properties
['type'] = 'binary'
266 properties
['isInstanceOf'] = 'ArrayBuffer'
267 elif self
.typeref
== 'FileEntry':
268 properties
['type'] = 'object'
269 properties
['isInstanceOf'] = 'FileEntry'
270 if 'additionalProperties' not in properties
:
271 properties
['additionalProperties'] = OrderedDict()
272 properties
['additionalProperties']['type'] = 'any'
273 elif self
.parent
.GetPropertyLocal('Union'):
274 properties
['choices'] = [Typeref(node
.GetProperty('TYPEREF'),
276 OrderedDict()).process(callbacks
)
277 for node
in self
.parent
.GetChildren()
278 if node
.cls
== 'Option']
279 elif self
.typeref
is None:
280 properties
['type'] = 'function'
282 if self
.typeref
in callbacks
:
283 # Do not override name and description if they are already specified.
284 name
= properties
.get('name', None)
285 description
= properties
.get('description', None)
286 properties
.update(callbacks
[self
.typeref
])
287 if description
is not None:
288 properties
['description'] = description
290 properties
['name'] = name
292 properties
['$ref'] = self
.typeref
298 Given an IDL Enum node, converts into a Python dictionary that the JSON
299 schema compiler expects to see.
301 def __init__(self
, enum_node
):
302 self
.node
= enum_node
303 self
.description
= ''
307 for node
in self
.node
.GetChildren():
308 if node
.cls
== 'EnumItem':
309 enum_value
= {'name': node
.GetName()}
310 for child
in node
.GetChildren():
311 if child
.cls
== 'Comment':
312 enum_value
['description'] = ProcessComment(child
.GetName())[0]
314 raise ValueError('Did not process %s %s' % (child
.cls
, child
))
315 enum
.append(enum_value
)
316 elif node
.cls
== 'Comment':
317 self
.description
= ProcessComment(node
.GetName())[0]
319 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
320 result
= {'id' : self
.node
.GetName(),
321 'description': self
.description
,
324 for property_name
in (
325 'inline_doc', 'noinline_doc', 'nodoc', 'cpp_enum_prefix_override',):
326 if self
.node
.GetProperty(property_name
):
327 result
[property_name
] = self
.node
.GetProperty(property_name
)
328 if self
.node
.GetProperty('deprecated'):
329 result
['deprecated'] = self
.node
.GetProperty('deprecated')
333 class Namespace(object):
335 Given an IDLNode representing an IDL namespace, converts into a Python
336 dictionary that the JSON schema compiler expects to see.
345 compiler_options
=None,
347 self
.namespace
= namespace_node
349 self
.internal
= internal
350 self
.platforms
= platforms
351 self
.compiler_options
= compiler_options
355 self
.callbacks
= OrderedDict()
356 self
.description
= description
357 self
.deprecated
= deprecated
360 for node
in self
.namespace
.GetChildren():
361 if node
.cls
== 'Dictionary':
362 self
.types
.append(Dictionary(node
).process(self
.callbacks
))
363 elif node
.cls
== 'Callback':
364 k
, v
= Member(node
).process(self
.callbacks
)
365 self
.callbacks
[k
] = v
366 elif node
.cls
== 'Interface' and node
.GetName() == 'Functions':
367 self
.functions
= self
.process_interface(node
)
368 elif node
.cls
== 'Interface' and node
.GetName() == 'Events':
369 self
.events
= self
.process_interface(node
)
370 elif node
.cls
== 'Enum':
371 self
.types
.append(Enum(node
).process())
373 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
374 if self
.compiler_options
is not None:
375 compiler_options
= self
.compiler_options
377 compiler_options
= {}
378 return {'namespace': self
.namespace
.GetName(),
379 'description': self
.description
,
382 'functions': self
.functions
,
383 'internal': self
.internal
,
384 'events': self
.events
,
385 'platforms': self
.platforms
,
386 'compiler_options': compiler_options
,
387 'deprecated': self
.deprecated
}
389 def process_interface(self
, node
):
391 for member
in node
.GetChildren():
392 if member
.cls
== 'Member':
393 _
, properties
= Member(member
).process(self
.callbacks
)
394 members
.append(properties
)
398 class IDLSchema(object):
400 Given a list of IDLNodes and IDLAttributes, converts into a Python list
401 of api_defs that the JSON schema compiler expects to see.
404 def __init__(self
, idl
):
413 compiler_options
= {}
415 for node
in self
.idl
:
416 if node
.cls
== 'Namespace':
418 # TODO(kalman): Go back to throwing an error here.
419 print('%s must have a namespace-level comment. This will '
420 'appear on the API summary page.' % node
.GetName())
422 namespace
= Namespace(node
, description
, nodoc
, internal
,
424 compiler_options
=compiler_options
or None,
425 deprecated
=deprecated
)
426 namespaces
.append(namespace
.process())
430 compiler_options
= None
431 elif node
.cls
== 'Copyright':
433 elif node
.cls
== 'Comment':
434 description
= node
.GetName()
435 elif node
.cls
== 'ExtAttribute':
436 if node
.name
== 'nodoc':
437 nodoc
= bool(node
.value
)
438 elif node
.name
== 'internal':
439 internal
= bool(node
.value
)
440 elif node
.name
== 'platforms':
441 platforms
= list(node
.value
)
442 elif node
.name
== 'implemented_in':
443 compiler_options
['implemented_in'] = node
.value
444 elif node
.name
== 'camel_case_enum_to_string':
445 compiler_options
['camel_case_enum_to_string'] = node
.value
446 elif node
.name
== 'deprecated':
447 deprecated
= str(node
.value
)
451 sys
.exit('Did not process %s %s' % (node
.cls
, node
))
457 Given the filename of an IDL file, parses it and returns an equivalent
458 Python dictionary in a format that the JSON schema compiler expects to see.
461 f
= open(filename
, 'r')
465 idl
= idl_parser
.IDLParser().ParseData(contents
, filename
)
466 idl_schema
= IDLSchema(idl
)
467 return idl_schema
.process()
472 Dump a json serialization of parse result for the IDL files whose names
473 were passed in on the command line.
475 if len(sys
.argv
) > 1:
476 for filename
in sys
.argv
[1:]:
477 schema
= Load(filename
)
478 print json
.dumps(schema
, indent
=2)
480 contents
= sys
.stdin
.read()
481 idl
= idl_parser
.IDLParser().ParseData(contents
, '<stdin>')
482 schema
= IDLSchema(idl
).process()
483 print json
.dumps(schema
, indent
=2)
486 if __name__
== '__main__':