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

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

Powered by Google App Engine
This is Rietveld 408576698