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

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

Powered by Google App Engine
This is Rietveld 408576698