Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: tools/json_schema_compiler/dart_generator.py

Issue 12041098: Initial commit of the Dart Chrome Extension APIs generators (Closed) Base URL: http://git.chromium.org/chromium/src.git@file_path_bugfix
Patch Set: Added wrappers for more types of functions (including events, callbacks and lists) Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4 """
5 Generator language component for compiler.py that adds Dart language support.
6 """
7
8 from code import Code
9 from model import *
10 from schema_util import *
11
12 import os
13 from datetime import datetime
14
15 LICENSE = ("""
16 // Copyright (c) %s, the Dart project authors. Please see the AUTHORS file
17 // for details. All rights reserved. Use of this source code is governed by a
18 // BSD-style license that can be found in the LICENSE file.""" %
19 datetime.now().year)
20
21 class DartGenerator(object):
22 def __init__(self, dart_overrides_dir=None):
23 self._dart_overrides_dir = dart_overrides_dir
24
25 def Generate(self, namespace):
26 return _Generator(namespace, self._dart_overrides_dir).Generate()
27
28 class _Generator(object):
29 """A .dart generator for a namespace.
30 """
31
32 def __init__(self, namespace, dart_overrides_dir=None):
33 self._namespace = namespace
34 self._types = namespace.types
35
36 # Build a dictionary of Type Name --> Custom Dart code.
37 self._type_overrides = {}
38 if dart_overrides_dir is not None:
39 for filename in os.listdir(dart_overrides_dir):
40 if filename.startswith(namespace.unix_name):
41 with open(os.path.join(dart_overrides_dir, filename)) as f:
42 # Split off the namespace and file extension, leaving just the type.
43 type_path = '.'.join(filename.split('.')[1:-1])
44 self._type_overrides[type_path] = f.read()
45
46 def Generate(self):
47 """Generates a Code object with the .dart for the entire namespace.
48 """
49 c = Code()
50 (c.Append(LICENSE)
51 .Append()
52 .Append('part of chrome;'))
53
54 if self._types:
55 (c.Append()
56 .Append('/**')
57 .Append(' * Types')
58 .Append(' */')
59 )
60 for type_name in self._types:
61 c.Concat(self._GenerateType(self._types[type_name]))
62
63 if self._namespace.events:
64 (c.Append()
65 .Append('/**')
66 .Append(' * Events')
67 .Append(' */')
68 )
69 for event_name in self._namespace.events:
70 c.Concat(self._GenerateEvent(self._namespace.events[event_name]))
71
72 (c.Append()
73 .Append('/**')
74 .Append(' * Functions')
75 .Append(' */')
76 )
77 c.Concat(self._GenerateMainClass())
78
79 return c
80
81 def _GenerateType(self, type_):
82 """Given a Type object, returns the Code with the .dart for this
83 type's definition.
84
85 Assumes this type is a Parameter Type (creatable by user), and creates an
86 object that extends ChromeObject. All parameters are specifiable as named
87 arguments in the constructor, and all methods are wrapped with getters and
88 setters that hide the JS() implementation.
89 """
90 c = Code()
91 (c.Append()
92 .Concat(self._GenerateDocumentation(type_))
93 .Sblock('class %(type_name)s extends ChromeObject {')
94 )
95
96 # Check whether this type has function members. If it does, don't allow
97 # public construction.
98 add_public_constructor = all(not self._IsFunction(p.type_)
99 for p in type_.properties.values())
100 constructor_fields = [self._GeneratePropertySignature(p)
101 for p in type_.properties.values()]
102
103 if add_public_constructor:
104 (c.Append('/*')
105 .Append(' * Public constructor')
106 .Append(' */')
107 .Sblock('%(type_name)s({%(constructor_fields)s}) {')
108 )
109
110 for prop_name in type_.properties:
111 c.Append('this.%s = %s;' % (prop_name, prop_name))
112 (c.Eblock('}')
113 .Append()
114 )
115
116 (c.Append('/*')
117 .Append(' * Private constructor')
118 .Append(' */')
119 .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
120 )
121
122 # Add an accessor (getter & setter) for each property.
123 properties = [p for p in type_.properties.values()
124 if not self._IsFunction(p.type_)]
125 if properties:
126 (c.Append()
127 .Append('/*')
128 .Append(' * Public accessors')
129 .Append(' */')
130 )
131 for prop in properties:
132 type_name = self._GetDartType(prop.type_)
133
134 # Check for custom dart for this whole property.
135 c.Append()
136 if not self._ConcatOverride(c, type_, prop, add_doc=True):
137 # Add the getter.
138 if not self._ConcatOverride(c, type_, prop, key_suffix='.get',
139 add_doc=True):
140 # Add the documentation for this property.
141 c.Concat(self._GenerateDocumentation(prop))
142
143 if (self._IsBaseType(prop.type_)
144 or self._IsListOfBaseTypes(prop.type_)):
145 c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
146 (type_name, prop.name, type_name, prop.name))
147 elif self._IsSerializableObjectType(prop.type_):
148 c.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
149 "this._jsObject));"
150 % (type_name, prop.name, type_name, prop.name))
151 elif self._IsListOfSerializableObjects(prop.type_):
152 (c.Sblock('%s get %s {' % (type_name, prop.name))
153 .Append('%s __proxy_%s = new %s();' % (type_name, prop.name,
154 type_name))
155 .Sblock("for (var o in JS('List', '#.%s', this._jsObject)) {" %
156 prop.name)
157 .Append('__proxy_%s.add(new %s._proxy(o));' % (prop.name,
158 self._GetDartType(prop.type_.item_type)))
159 .Eblock('}')
160 .Append('return __proxy_%s;' % prop.name)
161 .Eblock('}')
162 )
163 elif self._IsObjectType(prop.type_):
164 # TODO(sashab): Think of a way to serialize generic Dart objects.
165 c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
166 (type_name, prop.name, type_name, prop.name))
167 else:
168 raise Exception(
169 "Could not generate wrapper for %s.%s: unserializable type %s" %
170 (type_.name, prop.name, type_name)
171 )
172
173 # Add the setter.
174 c.Append()
175 if not self._ConcatOverride(c, type_, prop, key_suffix='.set'):
176 wrapped_name = prop.name
177 if not self._IsBaseType(prop.type_):
178 wrapped_name = 'convertArgument(%s)' % prop.name
179
180 (c.Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name))
181 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
182 (prop.name, wrapped_name))
183 .Eblock("}")
184 )
185
186 # Now add all the methods.
187 methods = [t for t in type_.properties.values()
188 if self._IsFunction(t.type_)]
189 if methods:
190 (c.Append()
191 .Append('/*')
192 .Append(' * Methods')
193 .Append(' */')
194 )
195 for prop in methods:
196 c.Concat(self._GenerateFunction(prop.type_.function))
197
198 (c.Eblock('}')
199 .Substitute({
200 'type_name': type_.simple_name,
201 'constructor_fields': ', '.join(constructor_fields)
202 })
203 )
204
205 return c
206
207 def _GenerateDocumentation(self, prop):
208 """Given an object, generates the documentation for this object (as a
209 code string) and returns the Code object.
210
211 Returns an empty code object if the object has no documentation.
212
213 Uses triple-quotes for the string.
214 """
215 c = Code()
216 if prop.description is not None:
217 for line in prop.description.split('\n'):
218 c.Comment(line, comment_prefix='/// ')
219 return c
220
221 def _GenerateFunction(self, f):
222 """Returns the Code object for the given function.
223 """
224 c = Code()
225 (c.Append()
226 .Concat(self._GenerateDocumentation(f))
227 )
228
229 if not self._NeedsProxiedCallback(f):
230 c.Append("%s => %s;" % (self._GenerateFunctionSignature(f),
231 self._GenerateProxyCall(f)))
232 return c
233
234 (c.Sblock("%s {" % self._GenerateFunctionSignature(f))
235 .Concat(self._GenerateProxiedFunction(f.callback, f.callback.name))
236 .Append('%s;' % self._GenerateProxyCall(f))
237 .Eblock('}')
238 )
239
240 return c
241
242 def _GenerateProxiedFunction(self, f, callback_name):
243 """Given a function (assumed to be a callback), generates the proxied
244 version of this function, which calls |callback_name| if it is defined.
245
246 Returns a Code object.
247 """
248 c = Code()
249 proxied_params = []
250 lists_to_proxy = []
251 for p in f.params:
252 if self._IsBaseType(p.type_) or self._IsListOfBaseTypes(p.type_):
253 proxied_params.append(p.name)
254 elif self._IsSerializableObjectType(p.type_):
255 proxied_params.append('new %s._proxy(%s)' % (
256 self._GetDartType(p.type_), p.name))
257 elif self._IsListOfSerializableObjects(p.type_):
258 proxied_params.append('__proxy_%s' % p.name)
259 lists_to_proxy.append(p)
260 elif self._IsObjectType(p.type_):
261 # TODO(sashab): Find a way to build generic JS objects back in Dart.
262 proxied_params.append('%s' % p.name)
263 else:
264 raise Exception(
265 "Cannot automatically create proxy; can't wrap %s, type %s" % (
266 self._GenerateFunctionSignature(f), self._GetDartType(p.type_)))
267
268 (c.Sblock("void __proxy_callback(%s) {" % ', '.join(p.name for p in
269 f.params))
270 .Sblock('if (?%s) {' % callback_name)
271 )
272
273 # Add the proxied lists.
274 for l in lists_to_proxy:
not at google - send to devlin 2013/02/04 17:21:06 l is always a bad idea for a variable name
sashab 2013/02/04 23:12:45 Done.
275 (c.Append("%s __proxy_%s = new %s();" % (self._GetDartType(l.type_),
276 l.name,
277 self._GetDartType(l.type_)))
278 .Sblock("for (var o in %s) {" % l.name)
279 .Append('__proxy_%s.add(new %s._proxy(o));' % (l.name,
280 self._GetDartType(l.type_.item_type)))
281 .Eblock("}")
282 )
283
284 (c.Append("%s(%s);" % (callback_name, ', '.join(proxied_params)))
285 .Eblock('}')
286 .Eblock('}')
287 )
288 return c
289
290 def _NeedsProxiedCallback(self, f):
291 """Given a function, returns True if this function's callback needs to be
292 proxied, False if not.
293
294 Function callbacks need to be proxied if they have at least one
295 non-base-type parameter.
296 """
297 return f.callback and self._NeedsProxy(f.callback)
298
299 def _NeedsProxy(self, f):
300 """Given a function, returns True if this function's needs to be proxied,
not at google - send to devlin 2013/02/04 17:21:06 s/this function's/it/ P.S. please briefly explain
sashab 2013/02/04 23:12:45 Done.
301 False if not.
302 """
303 return any(not self._IsBaseType(p.type_) for p in f.params)
304
305 def _GenerateProxyCall(self, function, call_target='this._jsObject'):
306 """Given a function, generates the code to call that function via JS().
307 Returns a string.
308
309 |call_target| is the name of the object to call the function on. The default
310 is this._jsObject.
311
312 e.g.
313 JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
314 JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
315 """
316 n_params = len(function.params)
317 if function.callback:
318 n_params += 1
319
320 params = ["'%s'" % self._GetDartType(function.returns),
321 "'#.%s(%s)'" % (function.name, ', '.join(['#'] * n_params)),
322 call_target]
323
324 for param in function.params:
325 if not self._IsBaseType(param.type_):
326 params.append('convertArgument(%s)' % param.name)
327 else:
328 params.append(param.name)
329 if function.callback:
330 # If this isn't a base type, we need a proxied callback.
331 callback_name = function.callback.name
332 if self._NeedsProxiedCallback(function):
333 callback_name = "__proxy_callback"
334 params.append('convertDartClosureToJS(%s, %s)' % (callback_name,
335 len(function.callback.params)))
336
337 return 'JS(%s)' % ', '.join(params)
338
339 def _GenerateEvent(self, event):
340 """Given a Function object, returns the Code with the .dart for this event,
341 represented by the function.
342
343 All events extend the Event base type.
344 """
345 c = Code()
346
347 # Add documentation for this event.
348 (c.Append()
349 .Concat(self._GenerateDocumentation(event))
350 .Sblock('class Event_%(event_name)s extends Event {')
351 )
352
353 # If this event needs a proxy, all calls need to be proxied.
354 needs_proxy = self._NeedsProxy(event)
355
356 # Override Event callback type definitions.
357 for ret_type, event_func in (('void', 'addListener'),
358 ('void', 'removeListener'),
359 ('bool', 'hasListener')):
360 param_list = self._GenerateParameterList(event.params, event.callback,
361 convert_optional=True)
362 if needs_proxy:
363 (c.Sblock('%s %s(void callback(%s)) {' % (ret_type, event_func,
364 param_list))
365 .Concat(self._GenerateProxiedFunction(event, 'callback'))
366 .Append('super.%s(callback);' % event_func)
367 .Eblock('}')
368 )
369 else:
370 c.Append('%s %s(void callback(%s)) => super.%s(callback);' %
371 (ret_type, event_func, param_list, event_func))
372 c.Append()
373
374 # Generate the constructor.
375 (c.Append('Event_%(event_name)s(jsObject) : '
376 'super(jsObject, %(param_num)d);')
377 .Eblock('}')
378 .Substitute({
379 'event_name': self._namespace.unix_name + '_' + event.name,
380 'param_num': len(event.params)
381 })
382 )
383
384 return c
385
386 def _GenerateMainClass(self):
387 """Generates the main class for this file, which links to all functions
388 and events.
389
390 Returns a code object.
391 """
392 c = Code()
393 (c.Append()
394 .Sblock('class API_%s {' % self._namespace.unix_name)
395 .Append('/*')
396 .Append(' * API connection')
397 .Append(' */')
398 .Append('Object _jsObject;')
399 )
400
401 # Add events.
402 if self._namespace.events:
403 (c.Append()
404 .Append('/*')
405 .Append(' * Events')
406 .Append(' */')
407 )
408 for event_name in self._namespace.events:
409 c.Append('Event_%s_%s %s;' % (self._namespace.unix_name, event_name,
410 event_name))
411
412 # Add functions.
413 if self._namespace.functions:
414 (c.Append()
415 .Append('/*')
416 .Append(' * Functions')
417 .Append(' */')
418 )
419 for function in self._namespace.functions.values():
420 c.Concat(self._GenerateFunction(function))
421
422 # Add the constructor.
423 (c.Append()
424 .Sblock('API_%s(this._jsObject) {' % self._namespace.unix_name)
425 )
426
427 # Add events to constructor.
428 for event_name in self._namespace.events:
429 c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
430 (event_name, self._namespace.unix_name, event_name, event_name))
431
432 (c.Eblock('}')
433 .Eblock('}')
434 )
435 return c
436
437 def _GeneratePropertySignature(self, prop):
438 """Given a property, returns a signature for that property.
439 Recursively generates the signature for callbacks.
440 Returns a String for the given property.
441
442 e.g.
443 bool x
444 void onClosed()
445 void doSomething(bool x, void callback([String x]))
446 """
447 if self._IsFunction(prop.type_):
448 return self._GenerateFunctionSignature(prop.type_.function)
449 return '%(type)s %(name)s' % {
450 'type': self._GetDartType(prop.type_),
451 'name': prop.simple_name
452 }
453
454 def _GenerateFunctionSignature(self, function):
455 """Given a function object, returns the signature for that function.
456 Recursively generates the signature for callbacks.
457 Returns a String for the given function.
458
459 If prepend_this is True, adds "this." to the function's name.
460
461 e.g.
462 void onClosed()
463 bool isOpen([String type])
464 void doSomething(bool x, void callback([String x]))
465
466 e.g. If prepend_this is True:
467 void this.onClosed()
468 bool this.isOpen([String type])
469 void this.doSomething(bool x, void callback([String x]))
470 """
471 sig = '%(return_type)s %(name)s(%(params)s)'
472
473 if function.returns:
474 return_type = self._GetDartType(function.returns)
475 else:
476 return_type = 'void'
477
478 return sig % {
479 'return_type': return_type,
480 'name': function.simple_name,
481 'params': self._GenerateParameterList(function.params,
482 function.callback)
483 }
484
485 def _GenerateParameterList(self,
486 params,
487 callback=None,
488 convert_optional=False):
489 """Given a list of function parameters, generates their signature (as a
490 string).
491
492 e.g.
493 [String type]
494 bool x, void callback([String x])
495
496 If convert_optional is True, changes optional parameters to be required.
497 Useful for callbacks, where optional parameters are treated as required.
498 """
499 # Params lists (required & optional), to be joined with commas.
500 # TODO(sashab): Don't assume optional params always come after required
501 # ones.
502 params_req = []
503 params_opt = []
504 for param in params:
505 p_sig = self._GeneratePropertySignature(param)
506 if param.optional and not convert_optional:
507 params_opt.append(p_sig)
508 else:
509 params_req.append(p_sig)
510
511 # Add the callback, if it exists.
512 if callback:
513 c_sig = self._GenerateFunctionSignature(callback)
514 if callback.optional:
515 params_opt.append(c_sig)
516 else:
517 params_req.append(c_sig)
518
519 # Join the parameters with commas.
520 # Optional parameters have to be in square brackets, e.g.:
521 #
522 # required params | optional params | output
523 # [] | [] | ''
524 # [x, y] | [] | 'x, y'
525 # [] | [a, b] | '[a, b]'
526 # [x, y] | [a, b] | 'x, y, [a, b]'
527 if params_opt:
528 params_opt[0] = '[%s' % params_opt[0]
529 params_opt[-1] = '%s]' % params_opt[-1]
530 param_sets = [', '.join(params_req), ', '.join(params_opt)]
531
532 # The 'if p' part here is needed to prevent commas where there are no
533 # parameters of a certain type.
534 # If there are no optional parameters, this prevents a _trailing_ comma,
535 # e.g. '(x, y,)'. Similarly, if there are no required parameters, this
536 # prevents a leading comma, e.g. '(, [a, b])'.
537 return ', '.join(p for p in param_sets if p)
538
539 def _ConcatOverride(self, c, type_, prop, key_suffix='', add_doc=False):
540 """Given a particular type and property to find in the custom dart
541 overrides, checks whether there is an override for that key.
542 If there is, appends the override code, and returns True.
543 If not, returns False.
544
545 |key_suffix| will be added to the end of the key before searching, e.g.
546 '.set' or '.get' can be used for setters and getters respectively.
547
548 If add_doc is given, adds the documentation for this property before the
549 override code.
550 """
551 contents = self._type_overrides.get('%s.%s%s' % (type_.name, prop.name,
552 key_suffix))
553 if contents is None:
554 return False
555
556 if prop is not None:
557 c.Concat(self._GenerateDocumentation(prop))
558 for line in contents.split('\n'):
559 c.Append(line)
560 return True
561
562 def _IsFunction(self, type_):
563 """Given a model.Type, returns whether this type is a function.
564 """
565 return type_.property_type == PropertyType.FUNCTION
566
567 def _IsSerializableObjectType(self, type_):
568 """Given a model.Type, returns whether this type is a serializable object.
569 Serializable objects are custom types defined in this namespace.
570 """
571 if (type_.property_type == PropertyType.REF
572 and type_.ref_type in self._types):
573 return self._IsObjectType(self._types[type_.ref_type])
574 if (type_.property_type == PropertyType.OBJECT
575 and type_.instance_of in self._types):
576 return self._IsObjectType(self._types[type_.instance_of])
577 return False
578
579 def _IsObjectType(self, type_):
580 """Given a model.Type, returns whether this type is an object.
581 """
582 return (self._IsSerializableObjectType(type_)
583 or type_.property_type in [PropertyType.OBJECT, PropertyType.ANY])
584
585 def _IsListOfSerializableObjects(self, type_):
586 """Given a model.Type, returns whether this type is a list of serializable
587 objects (PropertyType.REF types).
588 """
589 return (type_.property_type is PropertyType.ARRAY and
590 type_.item_type.property_type is PropertyType.REF)
591
592 def _IsListOfBaseTypes(self, type_):
593 """Given a model.Type, returns whether this type is a list of base type
594 objects (PropertyType.REF types).
595 """
596 return (type_.property_type is PropertyType.ARRAY and
597 self._IsBaseType(type_.item_type))
598
599 def _IsBaseType(self, type_):
600 """Given a model.type_, returns whether this type is a base type
601 (string, number or boolean).
602 """
603 return (self._GetDartType(type_) in
604 ['bool', 'num', 'int', 'double', 'String'])
605
606 def _GetDartType(self, type_):
607 """Given a model.Type object, returns its type as a Dart string.
608 """
609 if type_ is None:
610 return 'void'
611
612 prop_type = type_.property_type
613
614 if prop_type is None:
not at google - send to devlin 2013/02/04 17:21:06 this condition is not possible
sashab 2013/02/04 23:12:45 Done.
615 return 'void'
616 elif prop_type is PropertyType.REF:
617 # TODO(sashab): If the type is foreign, it might have to be imported.
618 return StripNamespace(type_.ref_type)
not at google - send to devlin 2013/02/04 17:21:06 Yeah, cpp_type_generator contains a lot of that lo
619 elif prop_type is PropertyType.BOOLEAN:
620 return 'bool'
621 elif prop_type is PropertyType.INTEGER:
622 return 'int'
623 elif prop_type is PropertyType.INT64:
624 return 'num'
625 elif prop_type is PropertyType.DOUBLE:
626 return 'double'
627 elif prop_type is PropertyType.STRING:
628 return 'String'
629 elif prop_type is PropertyType.ENUM:
630 return 'String'
631 elif prop_type is PropertyType.CHOICES:
632 # TODO: What is a Choices type? Is it closer to a Map Dart object?
633 return 'Object'
634 elif prop_type is PropertyType.ANY:
635 return 'Object'
636 elif prop_type is PropertyType.OBJECT:
637 # TODO(sashab): Work out a mapped type name.
638 return type_.instance_of or 'Object'
639 elif prop_type is PropertyType.FUNCTION:
640 return 'Function'
641 elif prop_type is PropertyType.ARRAY:
642 return 'List<%s>' % self._GetDartType(type_.item_type)
643 elif prop_type is PropertyType.BINARY:
644 return 'String'
645 else:
646 raise NotImplementedError(prop_type)
647
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698