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

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: Cleaned up dart_generator, and added wrappers for callbacks 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 Pass 'dart' with the -l flag to compiler.py to activate the use of this library.
8 """
9
10 from code import Code
11 from model import *
12 from schema_util import *
13
14 import os
15 from datetime import datetime
16
17 LICENSE = ("""
18 // Copyright (c) %s, the Dart project authors. Please see the AUTHORS file
19 // for details. All rights reserved. Use of this source code is governed by a
20 // BSD-style license that can be found in the LICENSE file.""" %
21 datetime.now().year)
22
23 class DartGenerator(object):
24 """A .dart generator for a namespace.
25 """
26
27 def __init__(self, namespace, dart_overrides_dir=None):
28 self._namespace = namespace
29 self._types = namespace.types
30
31 # Build a dictionary of Type Name --> Custom Dart code.
32 self._custom_dart = {}
not at google - send to devlin 2013/01/31 02:08:48 _type_overrides?
sashab 2013/01/31 04:41:40 Done.
33 if dart_overrides_dir is not None:
34 for filename in os.listdir(dart_overrides_dir):
35 if filename.startswith(namespace.unix_name):
36 with open(os.path.join(dart_overrides_dir, filename)) as f:
37 # Split off the namespace and file extension, leaving just the type.
38 type_path = '.'.join(filename.split('.')[1:-1])
39 self._custom_dart[type_path] = f.read()
40
41 def Generate(self):
42 """Generates a Code object with the .dart for the entire namespace.
43 """
44 c = Code()
45 (c.Append(LICENSE)
46 .Append()
47 .Append('part of chrome;'))
48
49 if self._types:
50 (c.Append()
51 .Append('/**')
52 .Append(' * Types')
53 .Append(' */')
54 )
55 for type_name in self._types:
56 c.Concat(self._GenerateType(self._types[type_name]))
57
58 if self._namespace.events:
59 (c.Append()
60 .Append('/**')
61 .Append(' * Events')
62 .Append(' */')
63 )
64 for event_name in self._namespace.events:
65 c.Concat(self._GenerateEvent(self._namespace.events[event_name]))
66
67 (c.Append()
68 .Append('/**')
69 .Append(' * Functions')
70 .Append(' */')
71 )
72 c.Concat(self._GenerateMainClass())
73
74 return c
75
76 def _GenerateType(self, type_):
77 """Given a Type object, returns the Code with the .dart for this
78 type's definition.
79
80 Assumes this type is a Parameter Type (creatable by user), and creates an
81 object that extends ChromeObject. All parameters are specifiable as named
82 arguments in the constructor, and all methods are wrapped with getters and
83 setters that hide the JS() implementation.
84 """
85 c = Code()
86 (c.Append()
87 .Concat(self._GenerateDocumentation(type_))
88 .Sblock('class %(type_name)s extends ChromeObject {')
89 )
90
91 # Check whether this type has function members. If it does, don't allow
92 # public construction.
93 for p in type_.properties.values():
94 print p.name, self._IsFunction(p)
95 add_public_constructor = all(not self._IsFunction(p)
96 for p in type_.properties.values())
97 constructor_fields = [self._GeneratePropertySignature(p)
98 for p in type_.properties.values()]
99
100 if add_public_constructor:
101 (c.Append('/*')
102 .Append(' * Public constructor')
103 .Append(' */')
104 .Sblock('%(type_name)s({%(constructor_fields)s}) {')
105 )
106
107 for prop_name in type_.properties:
108 c.Append('this.%s = %s;' % (prop_name, prop_name))
109 (c.Eblock('}')
110 .Append()
111 )
112
113 (c.Append('/*')
114 .Append(' * Private constructor')
115 .Append(' */')
116 .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
117 )
118
119 # Add an accessor (getter & setter) for each property.
120 properties = [p for p in type_.properties.values()
121 if not self._IsFunction(p)]
122 if properties:
123 (c.Append()
124 .Append('/*')
125 .Append(' * Public accessors')
126 .Append(' */')
127 )
128 for prop in properties:
129 type_name = self._GetPropertyType(prop)
130 prop_is_base_type = self._IsBaseType(prop)
131
132 # Check for custom dart for this whole property.
133 if not self._TryAppendOverride(c, type_, prop, add_doc=True):
134 # Add the getter.
135 if not self._TryAppendOverride(c, type_, prop, '.get', add_doc=True):
not at google - send to devlin 2013/01/31 02:08:48 key_suffix='.get'
sashab 2013/01/31 04:41:40 Done.
136 # Add the documentation for this property.
137 (c.Append()
138 .Concat(self._GenerateDocumentation(prop))
139 )
140
141 # TODO(sashab): Serialize generic Dart objects differently.
142 if prop_is_base_type or self._IsObjectType(prop):
143 c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
144 (type_name, prop.name, type_name, prop.name))
145 elif prop.type_.property_type == PropertyType.REF:
146 c.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
147 "this._jsObject));"
148 % (type_name, prop.name, type_name, prop.name))
149 else:
150 raise Exception(
151 "Could not generate wrapper for %s.%s: unserializable type %s" %
152 (type_.name, prop.name, type_name)
153 )
154
155 # Add the setter.
156 if not self._TryAppendOverride(c, type_, prop, '.set'):
not at google - send to devlin 2013/01/31 02:08:48 key_suffix='.set'
sashab 2013/01/31 04:41:40 Done.
157 wrapped_name = prop.name
158 if not prop_is_base_type:
159 wrapped_name = 'convertArgument(%s)' % prop.name
160
161 (c.Append()
162 .Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name))
163 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
164 (prop.name, wrapped_name))
165 .Eblock("}")
166 )
167
168 # Now add all the methods.
169 methods = [t for t in type_.properties.values()
170 if self._IsFunction(t)]
not at google - send to devlin 2013/01/31 02:08:48 might fit on 1 line?
sashab 2013/01/31 04:41:40 Done.
171 if methods:
172 (c.Append()
173 .Append('/*')
174 .Append(' * Methods')
175 .Append(' */')
176 )
177 for prop in methods:
178 c.Concat(self._GenerateFunction(prop.type_.function))
179
180 (c.Eblock('}')
181 .Substitute({
182 'type_name': type_.simple_name,
183 'constructor_fields': ', '.join(constructor_fields)
184 })
185 )
186
187 return c
188
189 def _GenerateDocumentation(self, prop):
190 """Given an object, generates the documentation for this object (as a
191 code string) and returns the Code object.
192
193 Returns an empty code object if the object has no documentation.
194
195 Uses triple-quotes for the string.
196 """
197 c = Code()
198 if prop.description is not None:
199 for line in prop.description.split('\n'):
200 c.Comment(line, comment_prefix='/// ')
201 return c
202
203
204 def _GenerateFunction(self, f):
205 """Returns the Code object for the given function.
206 """
207 c = Code()
208 (c.Append()
209 .Concat(self._GenerateDocumentation(f))
210 )
211
212 if not self._NeedsProxiedCallback(f):
213 c.Append("%s => %s;" % (self._GenerateFunctionSignature(f),
214 self._GenerateProxyCall(f)))
215 return c
216
217 c.Sblock("%s {" % self._GenerateFunctionSignature(f))
218
219 # Define the proxied callback.
220 proxied_params = []
221 for p in f.callback.params:
222 if self._IsBaseType(p):
223 proxied_params.append(p.name)
224 elif self._IsObjectType(p):
225 proxied_params.append("new %s._proxy(%s)" % (
226 self._GetPropertyType(p), p.name))
227 else:
228 raise Exception(
229 "Cannot automatically create proxy; can't wrap %s.%s, type %s" % (
230 f.name, p.name, p.type_.name))
231
232 (c.Sblock("void __proxy_callback(%s) {" % ', '.join(p.name for p in
233 f.callback.params))
234 .Sblock("if (?%s)" % f.callback.name)
235 .Append("%s(%s);" % (f.callback.name, ', '.join(proxied_params)))
236 .Eblock(None)
not at google - send to devlin 2013/01/31 02:08:48 like I said earlier, just make None the default? T
sashab 2013/01/31 04:41:40 Done.
237 .Eblock("}")
238 .Append("%s;" % self._GenerateProxyCall(f))
239 .Eblock("}")
240 )
241 return c
242
243 def _NeedsProxiedCallback(self, f):
244 """Given a function, returns True if this function's callback needs to be
245 proxied, False if not.
246
247 Function callbacks need to be proxied if they have at least one
248 non-base-type parameter.
249 """
250 return f.callback and any(
251 not self._IsBaseType(p) for p in f.callback.params)
252
253 def _GenerateProxyCall(self, function, call_target='this._jsObject'):
254 """Given a function, generates the code to call that function via JS().
255 Returns a string.
256
257 |call_target| is the name of the object to call the function on. The default
258 is this._jsObject.
259
260 e.g.
261 JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
262 JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
263 """
264 n_params = len(function.params)
265 if function.callback:
266 n_params += 1
267
268 params = ["'%s'" % self._GetPropertyType(function.returns),
269 "'#.%s(%s)'" % (function.name,
270 ', '.join(['#'] * n_params)),
271 call_target]
272
273 for param in function.params:
274 if not self._IsBaseType(param):
275 params.append('convertArgument(%s)' % param.name)
276 else:
277 params.append(param.name)
278 if function.callback:
279 # If this isn't a base type, we need a proxied callback.
280 callback_name = function.callback.name
281 if self._NeedsProxiedCallback(function):
282 callback_name = "__proxy_callback"
283 params.append('convertDartClosureToJS(%s, %s)' % (callback_name,
284 len(function.callback.params)))
285
286 return 'JS(%s)' % ', '.join(params)
287
288 def _GenerateEvent(self, event):
289 """Given a Function object, returns the Code with the .dart for this event,
290 represented by the function.
291
292 All events extend the Event base type.
293 """
294 c = Code()
295
296 # Add documentation for this event.
297 (c.Append()
298 .Concat(self._GenerateDocumentation(event))
299 .Sblock('class Event_%(event_name)s extends Event {')
300 )
301
302 # Override Event callback type definitions.
303 for ret_type, event_func in (('void', 'addListener'),
304 ('void', 'removeListener'),
305 ('bool', 'hasListener')):
306
307 param_list = self._GenerateParameterList(event.params, event.callback,
308 allow_optional = False)
309
310 c.Append('%s %s(void callback(%s)) => super.%s(callback);' %
311 (ret_type, event_func, param_list, event_func))
312
313 # Generate the constructor.
314 (c.Append()
315 .Append('Event_%(event_name)s(jsObject) : '
316 'super(jsObject, %(param_num)d);')
317 )
318
319 (c.Eblock('}')
320 .Substitute({
321 'event_name': self._namespace.unix_name + '_' + event.name,
322 'param_num': len(event.params)
323 })
324 )
325
326 return c
327
328 def _GenerateMainClass(self):
329 """Generates the main class for this file, which links to all functions
330 and events.
331
332 Returns a code object.
333 """
334 c = Code()
335 (c.Append()
336 .Sblock('class API_%s {' % self._namespace.unix_name)
337 .Append('/*')
338 .Append(' * API connection')
339 .Append(' */')
340 .Append('Object _jsObject;')
341 )
342
343 # Add events.
344 if self._namespace.events:
345 (c.Append()
346 .Append('/*')
347 .Append(' * Events')
348 .Append(' */')
349 )
350 for event_name in self._namespace.events:
351 c.Append('Event_%s_%s %s;' % (self._namespace.unix_name, event_name,
352 event_name))
353
354 # Add functions.
355 if self._namespace.functions:
356 (c.Append()
357 .Append('/*')
358 .Append(' * Functions')
359 .Append(' */')
360 )
361 for function in self._namespace.functions.values():
362 c.Concat(self._GenerateFunction(function))
363
364 # Add the constructor.
365 (c.Append()
366 .Sblock('API_%s(this._jsObject) {' % self._namespace.unix_name)
367 )
368
369 # Add events to constructor.
370 for event_name in self._namespace.events:
371 c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
372 (event_name, self._namespace.unix_name, event_name, event_name))
373
374 (c.Eblock('}')
375 .Eblock('}')
376 )
377 return c
378
379 def _GeneratePropertySignature(self, prop):
380 """Given a property, returns a signature for that property.
381 Recursively generates the signature for callbacks.
382 Returns a String for the given property.
383
384 e.g.
385 bool x
386 void onClosed()
387 void doSomething(bool x, void callback([String x]))
388 """
389 if self._IsFunction(prop):
390 return self._GenerateFunctionSignature(prop.type_.function)
391 return '%(type)s %(name)s' % {
392 'type': self._GetPropertyType(prop),
393 'name': prop.simple_name
394 }
395
396 def _GenerateFunctionSignature(self, function):
397 """Given a function object, returns the signature for that function.
398 Recursively generates the signature for callbacks.
399 Returns a String for the given function.
400
401 If prepend_this is True, adds "this." to the function's name.
402
403 e.g.
404 void onClosed()
405 bool isOpen([String type])
406 void doSomething(bool x, void callback([String x]))
407
408 e.g. If prepend_this is True:
409 void this.onClosed()
410 bool this.isOpen([String type])
411 void this.doSomething(bool x, void callback([String x]))
412 """
413 sig = '%(return_type)s %(name)s(%(params)s)'
414
415 if function.returns:
416 return_type = self._GetPropertyType(function.returns)
417 else:
418 return_type = 'void'
419
420 return sig % {
421 'return_type': return_type,
422 'name': function.simple_name,
423 'params': self._GenerateParameterList(function.params,
424 function.callback)
425 }
426
427 def _GenerateParameterList(self, params, callback=None,
428 allow_optional=True):
not at google - send to devlin 2013/01/31 02:08:48 if it can't fit on 1 line then align parameters ve
sashab 2013/01/31 04:41:40 Done.
429 """Given a list of function parameters, generates their signature (as a
430 string).
431
432 e.g.
433 [String type]
434 bool x, void callback([String x])
435
436 If allow_optional is False, ignores optional parameters. Useful for
437 callbacks, where optional parameters are not used.
438 """
439 # Params lists (required & optional), to be joined with ,'s.
not at google - send to devlin 2013/01/31 02:08:48 commas?
sashab 2013/01/31 04:41:40 Done.
440 # FIXME(sashab): assuming all optional params come after required ones
not at google - send to devlin 2013/01/31 02:08:48 I can't parse this sentence. Should it be like "Do
sashab 2013/01/31 04:41:40 Haha, yes, you're right. Fixed
441 params_req = []
442 params_opt = []
443 for param in params:
444 p_sig = self._GeneratePropertySignature(param)
445 if allow_optional and param.optional:
446 params_opt.append(p_sig)
447 else:
448 params_req.append(p_sig)
not at google - send to devlin 2013/01/31 02:08:48 this isn't ignoring optional parameters, it's maki
sashab 2013/01/31 04:41:40 Sorry, comment changed. And I also changed the nam
449
450 # Add the callback, if it exists.
451 if callback:
452 c_sig = self._GenerateFunctionSignature(callback)
453 if callback.optional:
454 params_opt.append(c_sig)
455 else:
456 params_req.append(c_sig)
457
458 # Join the parameters, wrapping the optional params in square brackets.
459 if params_opt:
460 params_opt[0] = '[%s' % params_opt[0]
461 params_opt[-1] = '%s]' % params_opt[-1]
not at google - send to devlin 2013/01/31 02:08:48 a bit bizarre but much better, and I can't think o
sashab 2013/01/31 04:41:40 Done. Or should this rather be in the function sig
not at google - send to devlin 2013/02/02 00:45:47 What you have is good.
462 param_sets = [', '.join(params_req),
463 ', '.join(params_opt)]
not at google - send to devlin 2013/01/31 02:08:48 single line
sashab 2013/01/31 04:41:40 Done.
464
465 return ', '.join(p for p in param_sets if p)
not at google - send to devlin 2013/01/31 02:08:48 why the "if p"? I.e. why can't it be ', '.join(par
sashab 2013/01/31 04:41:40 This is part of the bizarre logic. If there are no
466
467 def _TryAppendOverride(self, c, type_, prop, key_suffix='', add_doc=False):
not at google - send to devlin 2013/01/31 02:08:48 TryAppendOverride is a bit of an awkward name. Per
sashab 2013/01/31 04:41:40 It returns True if it appended the override, False
468 """Given a particular type and property to find in the custom dart
469 overrides, checks whether there is an override for that key.
470 If there is, adds a newline, appends the override code, and returns True.
not at google - send to devlin 2013/01/31 02:08:48 "adds a newline" -> doesn't seem like this should
sashab 2013/01/31 04:41:40 Ok, the reason this is needed is in case the overr
471 If not, returns False.
472
473 |key_suffix| will be added to the end of the key before searching, e.g.
474 '.set' or '.get' can be used for setters and getters respectively.
475
476 If add_doc is given, adds the documentation for this property before the
477 override code.
478 If the override code is empty, does nothing (and does not add
479 documentation), but returns True (treats it as a valid override).
480 """
481 contents = self._custom_dart.get('%s.%s%s' % (type_.name, prop.name,
482 key_suffix))
483 if contents is None:
484 return False
485
not at google - send to devlin 2013/01/31 02:08:48 maybe early-exit here too, if it's empty: if cont
sashab 2013/01/31 04:41:40 Good idea! :D
486 if contents.strip():
487 if prop is not None:
not at google - send to devlin 2013/01/31 02:08:48 although TBH I find this idiom a bit strange. Peop
sashab 2013/01/31 04:41:40 It's a little tough, because its unclear: does put
488 (c.Append()
489 .Concat(self._GenerateDocumentation(prop))
490 )
491 else:
492 c.Append()
not at google - send to devlin 2013/01/31 02:08:48 seem to be always appending here, so pull out abov
sashab 2013/01/31 04:41:40 Thanks, fixed this one; see comment above about ca
493 for line in contents.split('\n'):
494 c.Append(line)
495 return True
496
497 def _IsFunction(self, prop):
498 """Given a model.Property, returns whether this type is a function.
499 """
500 return prop.type_.property_type == PropertyType.FUNCTION
501
502 def _IsObjectType(self, prop):
503 """Given a model.Property, returns whether this type is an object.
504 """
505 return prop.type_.property_type in [PropertyType.OBJECT, PropertyType.ANY]
506
507 def _IsBaseType(self, prop):
508 """Given a model.Property, returns whether this type is a base type
509 (string, number or boolean).
510 """
511 return (self._GetPropertyType(prop) in
512 ['bool', 'num', 'int', 'double', 'String'])
513
514 def _GetPropertyType(self, prop, container_type=False):
not at google - send to devlin 2013/01/31 02:08:48 call this _GetDartType and pass in the type rather
sashab 2013/01/31 04:41:40 Yup, was going to do this before. Made a wrapper _
515 """Given a model.Property object, returns its type as a Dart string.
516
517 If container_type is True, returns the type of the objects prop is
518 containing, rather than the type of prop itself.
519 """
520 if prop is None:
521 return 'void'
522
523 if container_type:
524 type_ = prop.type_.item_type
525 else:
526 type_ = prop.type_
527 prop_type = type_.property_type
528
529 if prop_type is None:
530 return 'void'
531 elif prop_type is PropertyType.REF:
532 if GetNamespace(type_.ref_type) == self._namespace.name:
533 # This type is in this namespace; just use its base name.
534 return GetBaseNamespace(type_.ref_type)
535 else:
536 # TODO(sashab): Work out how to import this foreign type.
537 return type_.ref_type
538 elif prop_type is PropertyType.BOOLEAN:
539 return 'bool'
540 elif prop_type is PropertyType.INTEGER:
541 return 'int'
542 elif prop_type is PropertyType.INT64:
543 return 'num'
544 elif prop_type is PropertyType.DOUBLE:
545 return 'double'
546 elif prop_type is PropertyType.STRING:
547 return 'String'
548 elif prop_type is PropertyType.ENUM:
549 return 'String'
550 elif prop_type is PropertyType.CHOICES:
551 # TODO: What is a Choices type? Is it closer to a Map Dart object?
552 return 'Object'
not at google - send to devlin 2013/01/31 02:08:48 I wrote a long thing in response to what you said,
sashab 2013/01/31 04:41:40 'Object' is the equivalent of untyped. See other c
553 elif prop_type is PropertyType.ANY:
554 return 'Object'
555 elif prop_type is PropertyType.OBJECT:
556 # TODO(sashab): Work out a mapped type name.
557 return type_.instance_of or 'Object'
558 elif prop_type is PropertyType.FUNCTION:
559 return 'Function'
560 elif prop_type is PropertyType.ARRAY:
561 if type_.item_type:
not at google - send to devlin 2013/01/31 02:08:48 there should always be a list type, this check sho
sashab 2013/01/31 04:41:40 After investigating, you're right - its invalid ID
562 return 'List<%s>' % self._GetPropertyType(prop, container_type=True)
563 else:
564 return 'List'
565 elif prop_type is PropertyType.BINARY:
566 return 'String'
567 else:
568 raise NotImplementedError(prop_type)
569
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698