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

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: Created 7 years, 11 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 from collections import defaultdict
2
3 from code import Code
4 from model import *
5
6 import copy
7
8 LICENSE = """
9 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
benwells 2013/01/25 03:15:32 // lose the (c), Dart -> Chromium. Put at top of f
sashab 2013/01/25 05:58:12 The dart files need this header. This license goes
10 // for details. All rights reserved. Use of this source code is governed by a
11 // BSD-style license that can be found in the LICENSE file."""
12
13 class DartGenerator(object):
14 """A .dart generator for a namespace.
15 """
16
17 def __init__(self, namespace, custom_hooks_file):
18 self._namespace = namespace
19 self._types = namespace.types
20 self._hooks = self._parseCustomHooksFile(custom_hooks_file)
21
22 global x
23 x = namespace
24
25 def _parseCustomHooksFile(self, filename):
calamity 2013/01/25 04:16:01 Style in other files seems to be _CamelCase.
sashab 2013/01/25 05:58:12 Done.
26 """Parses the custom hooks file at filename into a dictionary:
27 (type_name, method_name) -> [code_string, list of lines]
28 If the method is a getter or setter, it will be space-separated and appended
29 to the end of the method_name.
30
31 e.g.
32 ('app.window.AppWindow', 'contentWindow'): ['void contentWindow ()']
33 ('app.window.AppWindow', 'contentWindow get'): ['int get contentWindow()']
34 """
35 if filename == None:
36 return {}
37
38 hooks = {}
39 with open(filename) as f:
40 current_key = None
41 for line in f:
42 if line.startswith('// START'):
43 # TODO(sashab): Account for any whitespace, not just spaces
44 current_key = tuple(line[len('// START'):].strip().split(' ', 1))
45 hooks[current_key] = []
46 elif line.startswith('// END'):
47 current_key = None
48 elif current_key:
49 hooks[current_key].append(line)
50 return hooks
51
52 def Generate(self):
53 """Generates a Code object with the .dart for the entire namespace.
54 """
55 c = Code()
56 (c.Append(LICENSE)
57 .Append()
58 .Append("part of chrome;"))
59
60 # Add all types.
61 if self._types:
62 (c.Append()
63 .Append('/**')
64 .Append(' * Types')
65 .Append(' */')
66 )
67 for type_name in self._types:
68 c.Concat(self._GenerateType(self._types[type_name]))
69
70 # Add all events.
71 if self._namespace.events:
72 (c.Append()
73 .Append('/**')
74 .Append(' * Events')
75 .Append(' */')
76 )
77 for event_name in self._namespace.events:
78 c.Concat(self._GenerateEvent(self._namespace.events[event_name]))
79
80 # Add main class for this file.
81 (c.Append()
82 .Append('/**')
83 .Append(' * Functions')
84 .Append(' */')
85 )
86 c.Concat(self._GenerateMainClass())
87
88 return c
89
90 def _GenerateType(self, type_):
91 """Given a Type object, returns the Code with the .dart for this
92 type's definition.
93
94 Assumes this type is a Parameter Type (creatable by user), and creates an
95 object that extends ChromeObject. All parameters are specifiable as named
96 arguments in the constructor, and all methods are wrapped with getters and
97 setters that hide the JS() implementation.
98 """
99 c = Code()
100 (c.Append()
101 .Concat(self._GenerateDocumentation(type_))
102 .Sblock("class %(type_name)s extends ChromeObject {")
103 )
104
105 # Check whether this type has function members. If it does, don't allow
106 # public construction.
107 add_public_constructor = True
108 for prop_name in type_.properties:
109 if self._IsFunction(type_.properties[prop_name]):
110 add_public_constructor = False
111 break
112
113 constructor_fields = []
114 for prop_name in type_.properties:
115 constructor_fields.append(
116 self._GeneratePropertySignature(type_.properties[prop_name],
117 prependThis = False,
118 omitBasicTypes = False))
119
120 # Add the public constructor.
121 if add_public_constructor:
122 (c.Append('/*')
123 .Append(' * Public constructor')
124 .Append(' */')
125 .Sblock('%(type_name)s({%(constructor_fields)s}) {')
126 )
127
128 for prop_name in type_.properties:
129 c.Append("this.%s = %s;" % (prop_name, prop_name))
130 (c.Eblock("}")
131 .Append()
132 )
133
134 # Add the private constructor.
135 (c.Append('/*')
136 .Append(' * Private constructor')
137 .Append(' */')
138 .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
139 )
140
141 # Add an accessor (getter & setter) for each property.
142 properties = [t for t in type_.properties.values()
143 if not self._IsFunction(t))]
calamity 2013/01/25 04:16:01 Extra ) here.
sashab 2013/01/25 05:58:12 Done, I'm surprised this worked at all :S Thanks
144 if properties:
145 (c.Append()
146 .Append('/*')
147 .Append(' * Public accessors')
148 .Append(' */')
149 )
150 for prop in properties:
151 type_name = self._GetPropertyType(prop)
152 prop_is_base_type = self._IsBaseType(prop)
153
154 # Add the documentation for this property
155 (c.Append()
156 .Concat(self._GenerateDocumentation(prop))
calamity 2013/01/25 04:16:01 Align periods. Same for rest of file.
sashab 2013/01/25 05:58:12 Done. Sorry, was meant to be like this. =)
157 )
158
159 # Check for custom hooks.
160 add_getter = True
161 add_setter = True
162
163 hook_tuple = (type_.name, prop.name)
164 if hook_tuple in self._hooks:
165 for line in self._hooks[hook_tuple]:
166 c.Append(line)
167 add_getter = False
168 add_setter = False
169
170 # Add the getter.
171 if add_getter:
172 hook_tuple_get = (type_.name, prop.name + " get")
173 if hook_tuple_get in self._hooks:
174 for line in self._hooks[hook_tuple_get]:
175 c.Append(line)
176 add_getter = False
177
178 if add_getter:
179 # TODO(sashab): Serialize generic Dart objects differently.
180 if prop_is_base_type or self._IsObjectType(prop):
181 c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
182 (type_name, prop.name, type_name, prop.name))
183 elif self._IsReferencedType(prop):
184 c.Append("%s get %s => new %s._proxy(JS('', '#.%s', this._jsObject));"
185 % (type_name, prop.name, type_name, prop.name))
186 else:
187 print prop.type_
188 c.Append("???%s get %s => JS('%s', '#.%s', this._jsObject);" %
189 (type_name, prop.name, type_name, prop.name))
190
191 # Add the setter.
192 if add_setter:
193 hook_tuple_set = (type_.name, prop.name + " set")
194 if hook_tuple_set in self._hooks:
195 for line in self._hooks[hook_tuple_set]:
196 c.Append(line)
197 add_setter = False
198
199 if add_setter:
200 wrapped_name = prop.name
201 if not prop_is_base_type:
202 wrapped_name = "convertArgument(%s)" % prop.name
203
204 (c.Append()
205 .Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name))
206 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
207 (prop.name, wrapped_name))
208 .Eblock("}")
209 )
210
211 # Now add all the function properties.
212 function_properties = [t for t in type_.properties.values()
213 if self._IsFunction(t)]
214 if function_properties:
215 (c.Append()
216 .Append('/*')
217 .Append(' * Methods')
218 .Append(' */')
219 )
220 for prop in function_properties:
221 c.Concat(self._GenerateFunction(prop))
222 return (c.Eblock("}")
calamity 2013/01/25 04:16:01 The h_generator and cc_generator seem to always us
sashab 2013/01/25 05:58:12 Done, but there's a return (Code()...) thing below
223 .Substitute({
224 'type_name': type_.simple_name,
225 'constructor_fields': ', '.join(constructor_fields)
226 })
227 )
228
229 def _GenerateDocumentation(self, prop):
230 """Given an object, generates the documentation for this object (as a
231 code string) and returns the Code object.
232
233 Returns an empty code object if the object has no documentation.
234
235 Uses triple-quotes for the string."""
calamity 2013/01/25 04:16:01 Triple quotes to next line?
sashab 2013/01/25 05:58:12 Good spotting, thanks
236 c = Code()
237 if not hasattr(prop, 'description'):
238 return c
239
240 if prop.description:
241 for line in prop.description.split('\n'):
242 c.Append('/// %s' % line)
243 return c
244
245
246 def _GenerateFunction(self, f):
247 """Returns the Code object for the given function.
248 """
249 return (Code()
250 .Append()
251 .Concat(self._GenerateDocumentation(f))
252 .Append("%s => %s;" % (self._GenerateFunctionSignature(f),
253 self._GenerateProxyCall(f)))
254 )
255
256 def _GenerateProxyCall(self, function, callTarget='this._jsObject'):
257 """Given a function, generates the code to call that function via JS().
258 Returns a string.
259
260 `callTarget` is the name of the object to call the function on. The default
calamity 2013/01/25 04:16:01 Use | instead of `.
sashab 2013/01/25 05:58:12 Done.
261 is this._jsObject.
262
263 e.g.
264 JS("void", "#.resizeTo(#, #)", this._jsObject, width, height)
265 JS("void", "#.setBounds(#)", this._jsObject, convertArgument(bounds))
266 """
267
268 format = "JS('%(return_type)s', " +\
269 "'#.%(name)s(%(param_hashes)s)', " +\
270 "%(target)s%(params)s)"
calamity 2013/01/25 04:16:01 Maybe use ("text" "nextline"
sashab 2013/01/25 05:58:12 Didn't know you could do that. Thanks! :)
271
272 params = ""
273 if function.params:
274 params_wrapped = []
275 for param in function.params:
276 if not self._IsBaseType(param):
277 params_wrapped.append("convertArgument(%s)" % param.name)
278 else:
279 params_wrapped.append(param.name)
280 params = ', ' + ', '.join(params_wrapped)
281
282 return format % {
283 "return_type": self._GetPropertyType(function.returns),
284 "name": function.name,
285 "param_hashes": ', '.join('#' for p in function.params),
286 "target": callTarget,
287 "params": params
calamity 2013/01/25 04:16:01 I think convention is to use single quotes where c
sashab 2013/01/25 05:58:12 Done this where possible :)
288 }
289
290 def _GenerateEvent(self, event):
291 """Given a Function object, returns the Code with the .dart for this event,
292 represented by the function.
293
294 All events extend the Event base type.
295 """
296 c = Code()
297
298 # Add documentation for this event.
299 (c.Append()
300 .Concat(self._GenerateDocumentation(event))
301 .Sblock("class Event_%(event_name)s extends Event {")
302 )
303
304 # Override Event callback type definitions.
305 for ret_type, event_func in (('void', 'addListener'),
306 ('void', 'removeListener'),
307 ('bool', 'hasListener')):
308
309 param_list = self._GenerateParameterList(event.params, event.callback,
310 allow_optional = False)
311
312 c.Append("%s %s(void callback(%s)) => super.%s(callback);" %
313 (ret_type, event_func, param_list, event_func))
314
315 # Generate the constructor.
316 (c.Append()
317 .Append('Event_%(event_name)s(jsObject) : super(jsObject, %(param_num)d);')
318 )
319
320 return (c.Eblock("}")
321 .Substitute({
322 'event_name': self._namespace.unix_name + '_' + event.name,
323 'param_num': len(event.params)
324 })
325 )
326
327 def _GenerateMainClass(self):
328 """Generates the main class for this file, which links to all functions
329 and events.
330
331 Includes a ChromeApi member variable to represent the connection.
332
333 Returns a code object.
334 """
335 c = Code()
336 (c.Append()
337 .Sblock("class API_%(namespace_name)s {")
338 .Append('/*')
339 .Append(' * API connection')
340 .Append(' */')
341 .Append("Object _jsObject;")
342 )
343
344 # Add events.
345 if self._namespace.events:
346 (c.Append()
347 .Append('/*')
348 .Append(' * Events')
349 .Append(' */')
350 )
351 for event_name in self._namespace.events:
352 c.Append("Event_%s_%s %s;" % (self._namespace.unix_name, event_name,
353 event_name))
354
355 # Add functions.
356 if self._namespace.functions:
357 (c.Append()
358 .Append('/*')
359 .Append(' * Functions')
360 .Append(' */')
361 )
362 for function in self._namespace.functions.values():
363 c.Concat(self._GenerateFunction(function))
364
365 # Add the constructor.
366 (c.Append()
367 .Sblock('API_%(namespace_name)s(this._jsObject) {')
368 )
369
370 # Add events to constructor.
371 for event_name in self._namespace.events:
372 c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
373 (event_name, self._namespace.unix_name, event_name, event_name))
374 c.Eblock("}")
375
376 return (c.Eblock("}")
377 .Substitute({
378 'namespace_name': self._namespace.unix_name
379 })
380 )
381
382 def _GeneratePropertySignature(self, prop, prependThis = False,
383 omitBasicTypes = False,
384 functionsAsObjects = False,
385 withGetKeyword = False):
386 """Given a property, returns a signature for that property.
387 Recursively generates the signature for callbacks.
388 Returns a String for the given property.
389
390 * If `prependThis` is True, prepends "this." to all variable names.
391 * If `omitBasicTypes` is True, only adds type names for function types.
392 * If `functionsAsObjects` is True, treats callbacks as basic types and
393 prepends the type 'Function'.
394 * If `withGetKeyword` is True, adds the word 'get' between the property's
395 type and name. Used for getters in dart.
396
397 e.g.
398 bool x
399 void onClosed()
400 void doSomething(bool x, void callback([String x]))
401
402 e.g. If prependThis is True:
403 bool this.x
404 void this.onClosed()
405 void this.doSomething(bool x, void callback([String x]))
406
407 e.g. If omitBasicTypes is True:
408 this.x
409 void onClosed()
410 void doSomething(bool x, void callback([String x]))
411
412 e.g. If functionsAsObjects is True:
413 bool x
414 Function onClosed
415 Function doSomething
416
417 e.g. If withGetKeyword and functionsAsObjects is True:
418 bool get x
419 Function get onClosed
420 Function get doSomething
421
422 """
423 if self._IsFunction(prop) and not functionsAsObjects:
424 return self._GenerateFunctionSignature(prop, prependThis)
425 else:
426 name_parts = [prop.simple_name]
427 if prependThis:
428 name_parts[0] = 'this.' + name_parts[0]
429 if withGetKeyword:
430 name_parts = ['get'] + name_parts
431
432 name = ' '.join(name_parts)
433 type_ = (self._GetPropertyType(prop) + ' ') if not omitBasicTypes else ''
434
435 return "%(type)s%(name)s" % {
436 'type': type_,
437 'name': name
438 }
439
440 def _GenerateFunctionSignature(self, function, prependThis = False):
441 """Given a function object, returns the signature for that function.
442 Recursively generates the signature for callbacks.
443 Returns a String for the given function.
444
445 If prependThis is True, adds "this." to the function's name.
446
447 e.g.
448 void onClosed()
449 bool isOpen([String type])
450 void doSomething(bool x, void callback([String x]))
451
452 e.g. If prependThis is True:
453 void this.onClosed()
454 bool this.isOpen([String type])
455 void this.doSomething(bool x, void callback([String x]))
456 """
457
458 sig = "%(return_type)s %(name)s(%(params)s)"
459
460 # Get function return type.
461 if function.returns:
462 return_type = self._GetPropertyType(function.returns)
463 else:
464 return_type = 'void'
465
466 # Get function name.
467 function_name = function.simple_name
468 if prependThis:
469 function_name = 'this.' + function_name
470
471 return sig % {
472 'return_type': return_type,
473 'name': function_name,
474 'params': self._GenerateParameterList(function.params,
475 getattr(function, 'callback', None))
476 }
477
478 def _GenerateParameterList(self, params, callback=None,
479 allow_optional = True):
480 """Given a list of function parameters, generates their signature (as a
481 string).
482
483 e.g.
484 [String type]
485 bool x, void callback([String x])
486
487 If allow_optional is False, ignores optional parameters. Useful for
488 callbacks, where optional parameters are not used.
489 """
490 # params lists (required & optional), to be joined with ,'s
491 # FIXME(sashab): assuming all optional params come after required ones
492 params_req = []
493 params_opt = []
494 for param in params:
495 p_sig = self._GeneratePropertySignature(param)
496 if allow_optional and param.optional:
497 params_opt.append(p_sig)
498 else:
499 params_req.append(p_sig)
500
501 # Add the callback, if it exists.
502 if callback:
503 c_sig = self._GenerateFunctionSignature(callback)
504 if callback.optional:
505 params_opt.append(c_sig)
506 else:
507 params_req.append(c_sig)
508
509 # join params
510 params = ''
511 if params_req:
512 params += ', '.join(params_req)
513 if params_opt:
514 params += ', '
515 if params_opt:
516 params += '[' + ', '.join(params_opt) + ']'
517
518 return params
519
520 def _GetNamespace(self, name):
521 """Given a name a.b.c, returns the namespace (in this case, a.b).
522 """
523 return name.rsplit('.', 1)[0]
524
525 def _GetBaseName(self, name):
526 """Given a name a.b.c, returns the base name of the path (in this case, c).
527 """
528 return name.rsplit('.', 1)[1]
529
530 def _IsReferencedType(self, prop):
531 """Given a model.Property, returns whether this type is a referenced type.
532 """
533 return prop.type_ == PropertyType.REF
534
535 def _IsFunction(self, prop):
536 """Given a model.Property, returns whether this type is a function.
537 """
538 return prop.type_ == PropertyType.FUNCTION
539
540 def _IsObjectType(self, prop):
541 """Given a model.Property, returns whether this type is an object.
542 """
543 return (prop.type_ == PropertyType.OBJECT or
544 prop.type_ == PropertyType.ANY)
545
546 def _IsBaseType(self, prop):
547 """Given a model.Property, returns whether this type is a base type
548 (string, number or boolean).
549 """
550 base_type = self._GetPropertyType(prop)
551 if base_type in ['bool', 'num', 'int', 'double', 'String']:
552 return True
553 return False
554
555 def _GetReferencedType(self, name):
556 """Given the name of a referenced type, returns the type object for that
557 reference.
558
559 Returns None if the type is not found.
560 """
561 if name in self._namespace.types:
562 return self._namespace.types[name]
563 return None
564
565 def _GetPropertyType(self, prop):
566 """Given a model.Property object, returns its type as a Dart string.
567 """
568 if prop == None:
569 return 'void'
570
571 dart_type = None
572 type_ = prop.type_
573
574 if type_ == None:
575 dart_type = 'void'
576 elif type_ == PropertyType.REF:
577 if self._GetNamespace(prop.ref_type) == self._namespace.name:
578 # This type is in this namespace; just use its base name.
579 dart_type = self._GetBaseName(prop.ref_type)
580 else:
581 # TODO(sahab): Work out how to import this foreign type.
582 dart_type = '?REF: ' + prop.ref_type
583 elif type_ == PropertyType.BOOLEAN:
584 dart_type = 'bool'
585 elif type_ == PropertyType.INTEGER:
586 dart_type = 'int'
587 elif type_ == PropertyType.INT64:
588 dart_type = 'num'
589 elif type_ == PropertyType.DOUBLE:
590 dart_type = 'double'
591 elif type_ == PropertyType.STRING:
592 dart_type = 'String'
593 elif type_ == PropertyType.ENUM:
594 dart_type = 'String'
595 elif type_ == PropertyType.ADDITIONAL_PROPERTIES:
596 dart_type = 'Map'
597 elif type_ == PropertyType.ANY:
598 dart_type = 'Object'
599 elif type_ == PropertyType.OBJECT:
600 # TODO(sashab): Work out a mapped type name?
601 dart_type = prop.instance_of
602 elif type_ == PropertyType.FUNCTION:
603 dart_type = 'Function'
604 elif type_ == PropertyType.ARRAY:
605 if hasattr(prop, 'item_type'):
606 container_type = self._GetPropertyType(prop.item_type)
607 dart_type = 'List<%s>' % container_type
608 else:
609 dart_type = 'List'
610 elif type_ == PropertyType.BINARY:
611 dart_type = 'String'
612 else:
613 raise NotImplementedError(type_)
614
615 return dart_type.strip()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698