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.
5 Generator language component for compiler.py that adds Dart language support.
10 from schema_util
import *
13 from datetime
import datetime
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.""" %
21 class DartGenerator(object):
22 def __init__(self
, dart_overrides_dir
=None):
23 self
._dart
_overrides
_dir
= dart_overrides_dir
25 def Generate(self
, namespace
):
26 return _Generator(namespace
, self
._dart
_overrides
_dir
).Generate()
28 class _Generator(object):
29 """A .dart generator for a namespace.
32 def __init__(self
, namespace
, dart_overrides_dir
=None):
33 self
._namespace
= namespace
34 # TODO(sashab): Once inline type definitions start being added to
35 # self._types, make a _FindType(self, type_) function that looks at
36 # self._namespace.types.
37 self
._types
= namespace
.types
39 # Build a dictionary of Type Name --> Custom Dart code.
40 self
._type
_overrides
= {}
41 if dart_overrides_dir
is not None:
42 for filename
in os
.listdir(dart_overrides_dir
):
43 if filename
.startswith(namespace
.unix_name
):
44 with
open(os
.path
.join(dart_overrides_dir
, filename
)) as f
:
45 # Split off the namespace and file extension, leaving just the type.
46 type_path
= '.'.join(filename
.split('.')[1:-1])
47 self
._type
_overrides
[type_path
] = f
.read()
49 # TODO(sashab): Add all inline type definitions to the global Types
50 # dictionary here, so they have proper names, and are implemented along with
51 # all other types. Also update the parameters/members with these types
52 # to reference these new types instead.
55 """Generates a Code object with the .dart for the entire namespace.
60 .Append('// Generated from namespace: %s' % self
._namespace
.name
)
62 .Append('part of chrome;'))
71 for type_
in self
._types
.values():
72 # Check for custom dart for this whole type.
73 override
= self
._GetOverride
([type_
.name
], document_with
=type_
)
74 c
.Cblock(override
if override
is not None else self
._GenerateType
(type_
))
76 if self
._namespace
.events
:
82 for event_name
in self
._namespace
.events
:
83 c
.Cblock(self
._GenerateEvent
(self
._namespace
.events
[event_name
]))
86 .Append(' * Functions')
90 c
.Cblock(self
._GenerateMainClass
())
94 def _GenerateType(self
, type_
):
95 """Given a Type object, returns the Code with the .dart for this
98 Assumes this type is a Parameter Type (creatable by user), and creates an
99 object that extends ChromeObject. All parameters are specifiable as named
100 arguments in the constructor, and all methods are wrapped with getters and
101 setters that hide the JS() implementation.
105 # Since enums are just treated as strings for now, don't generate their
107 # TODO(sashab): Find a nice way to wrap enum objects.
108 if type_
.property_type
is PropertyType
.ENUM
:
111 (c
.Concat(self
._GenerateDocumentation
(type_
))
112 .Sblock('class %(type_name)s extends ChromeObject {')
115 # Check whether this type has function members. If it does, don't allow
116 # public construction.
117 add_public_constructor
= all(not self
._IsFunction
(p
.type_
)
118 for p
in type_
.properties
.values())
119 constructor_fields
= [self
._GeneratePropertySignature
(p
)
120 for p
in type_
.properties
.values()]
122 if add_public_constructor
:
124 .Append(' * Public constructor')
126 .Sblock('%(type_name)s({%(constructor_fields)s}) {')
129 for prop_name
in type_
.properties
:
130 (c
.Sblock('if (?%s)' % prop_name
)
131 .Append('this.%s = %s;' % (prop_name
, prop_name
))
139 .Append(' * Private constructor')
141 .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
144 # Add an accessor (getter & setter) for each property.
145 properties
= [p
for p
in type_
.properties
.values()
146 if not self
._IsFunction
(p
.type_
)]
150 .Append(' * Public accessors')
153 for prop
in properties
:
154 override
= self
._GetOverride
([type_
.name
, prop
.name
], document_with
=prop
)
155 c
.Concat(override
if override
is not None
156 else self
._GenerateGetterAndSetter
(type_
, prop
))
158 # Now add all the methods.
159 methods
= [t
for t
in type_
.properties
.values()
160 if self
._IsFunction
(t
.type_
)]
164 .Append(' * Methods')
168 # Check if there's an override for this method.
169 override
= self
._GetOverride
([type_
.name
, prop
.name
], document_with
=prop
)
170 c
.Cblock(override
if override
is not None
171 else self
._GenerateFunction
(prop
.type_
.function
))
175 'type_name': self
._AddPrefix
(type_
.simple_name
),
176 'constructor_fields': ', '.join(constructor_fields
)
182 def _GenerateGetterAndSetter(self
, type_
, prop
):
183 """Given a Type and Property, returns the Code object for the getter and
184 setter for that property.
187 override
= self
._GetOverride
([type_
.name
, prop
.name
, '.get'],
189 c
.Cblock(override
if override
is not None
190 else self
._GenerateGetter
(type_
, prop
))
191 override
= self
._GetOverride
([type_
.name
, prop
.name
, '.set'])
192 c
.Cblock(override
if override
is not None
193 else self
._GenerateSetter
(type_
, prop
))
196 def _GenerateGetter(self
, type_
, prop
):
197 """Given a Type and Property, returns the Code object for the getter for
200 Also adds the documentation for this property before the method.
203 c
.Concat(self
._GenerateDocumentation
(prop
))
205 type_name
= self
._GetDartType
(prop
.type_
)
206 if (self
._IsBaseType
(prop
.type_
)):
207 c
.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
208 (type_name
, prop
.name
, type_name
, prop
.name
))
209 elif self
._IsSerializableObjectType
(prop
.type_
):
210 c
.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
211 "this._jsObject));" %
212 (type_name
, prop
.name
, type_name
, prop
.name
))
213 elif self
._IsListOfSerializableObjects
(prop
.type_
):
214 (c
.Sblock('%s get %s {' % (type_name
, prop
.name
))
215 .Append('%s __proxy_%s = new %s();' % (type_name
, prop
.name
,
217 .Append("int count = JS('int', '#.%s.length', this._jsObject);" %
219 .Sblock("for (int i = 0; i < count; i++) {")
220 .Append("var item = JS('', '#.%s[#]', this._jsObject, i);" % prop
.name
)
221 .Append('__proxy_%s.add(new %s._proxy(item));' % (prop
.name
,
222 self
._GetDartType
(prop
.type_
.item_type
)))
224 .Append('return __proxy_%s;' % prop
.name
)
227 elif self
._IsObjectType
(prop
.type_
):
228 # TODO(sashab): Think of a way to serialize generic Dart objects.
229 if type_name
in self
._types
:
230 c
.Append("%s get %s => new %s._proxy(JS('%s', '#.%s', "
231 "this._jsObject));" %
232 (type_name
, prop
.name
, type_name
, type_name
, prop
.name
))
234 c
.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
235 (type_name
, prop
.name
, type_name
, prop
.name
))
238 "Could not generate wrapper for %s.%s: unserializable type %s" %
239 (type_
.name
, prop
.name
, type_name
)
243 def _GenerateSetter(self
, type_
, prop
):
244 """Given a Type and Property, returns the Code object for the setter for
248 type_name
= self
._GetDartType
(prop
.type_
)
249 wrapped_name
= prop
.name
250 if not self
._IsBaseType
(prop
.type_
):
251 wrapped_name
= 'convertArgument(%s)' % prop
.name
253 (c
.Sblock("void set %s(%s %s) {" % (prop
.name
, type_name
, prop
.name
))
254 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
255 (prop
.name
, wrapped_name
))
260 def _GenerateDocumentation(self
, prop
):
261 """Given an object, generates the documentation for this object (as a
262 code string) and returns the Code object.
264 Returns an empty code object if the object has no documentation.
266 Uses triple-quotes for the string.
269 if prop
.description
is not None:
270 for line
in prop
.description
.split('\n'):
271 c
.Comment(line
, comment_prefix
='/// ')
274 def _GenerateFunction(self
, f
):
275 """Returns the Code object for the given function.
278 c
.Concat(self
._GenerateDocumentation
(f
))
280 if not self
._NeedsProxiedCallback
(f
):
281 c
.Append("%s => %s;" % (self
._GenerateFunctionSignature
(f
),
282 self
._GenerateProxyCall
(f
)))
285 (c
.Sblock("%s {" % self
._GenerateFunctionSignature
(f
))
286 .Concat(self
._GenerateProxiedFunction
(f
.callback
, f
.callback
.name
))
287 .Append('%s;' % self
._GenerateProxyCall
(f
))
293 def _GenerateProxiedFunction(self
, f
, callback_name
):
294 """Given a function (assumed to be a callback), generates the proxied
295 version of this function, which calls |callback_name| if it is defined.
297 Returns a Code object.
301 # A list of Properties, containing List<*> objects that need proxying for
302 # their members (by copying out each member and proxying it).
305 if self
._IsBaseType
(p
.type_
):
306 proxied_params
.append(p
.name
)
307 elif self
._IsSerializableObjectType
(p
.type_
):
308 proxied_params
.append('new %s._proxy(%s)' % (
309 self
._GetDartType
(p
.type_
), p
.name
))
310 elif self
._IsListOfSerializableObjects
(p
.type_
):
311 proxied_params
.append('__proxy_%s' % p
.name
)
312 lists_to_proxy
.append(p
)
313 elif self
._IsObjectType
(p
.type_
):
314 # TODO(sashab): Find a way to build generic JS objects back in Dart.
315 proxied_params
.append('%s' % p
.name
)
316 elif p
.type_
.property_type
is PropertyType
.ARRAY
:
317 # TODO(sashab): This might be okay - what if this is a list of
318 # FileEntry elements? In this case, a basic list will proxy the objects
320 proxied_params
.append('%s' % p
.name
)
323 "Cannot automatically create proxy; can't wrap %s, type %s" % (
324 self
._GenerateFunctionSignature
(f
), self
._GetDartType
(p
.type_
)))
326 (c
.Sblock("void __proxy_callback(%s) {" % ', '.join(p
.name
for p
in
328 .Sblock('if (?%s) {' % callback_name
)
331 # Add the proxied lists.
332 for list_to_proxy
in lists_to_proxy
:
333 (c
.Append("%s __proxy_%s = new %s();" % (
334 self
._GetDartType
(list_to_proxy
.type_
),
336 self
._GetDartType
(list_to_proxy
.type_
)))
337 .Sblock("for (var o in %s) {" % list_to_proxy
.name
)
338 .Append('__proxy_%s.add(new %s._proxy(o));' % (list_to_proxy
.name
,
339 self
._GetDartType
(list_to_proxy
.type_
.item_type
)))
343 (c
.Append("%s(%s);" % (callback_name
, ', '.join(proxied_params
)))
349 def _NeedsProxiedCallback(self
, f
):
350 """Given a function, returns True if this function's callback needs to be
351 proxied, False if not.
353 Function callbacks need to be proxied if they have at least one
354 non-base-type parameter.
356 return f
.callback
and self
._NeedsProxy
(f
.callback
)
358 def _NeedsProxy(self
, f
):
359 """Given a function, returns True if it needs to be proxied, False if not.
361 A function needs to be proxied if any of its members are non-base types.
362 This means that, when the function object is passed to Javascript, it
363 needs to be wrapped in a "proxied" call that converts the JS inputs to Dart
364 objects explicitly, before calling the real function with these new objects.
366 return any(not self
._IsBaseType
(p
.type_
) for p
in f
.params
)
368 def _GenerateProxyCall(self
, function
, call_target
='this._jsObject'):
369 """Given a function, generates the code to call that function via JS().
372 |call_target| is the name of the object to call the function on. The default
376 JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
377 JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
379 n_params
= len(function
.params
)
380 if function
.callback
:
383 return_type_str
= self
._GetDartType
(function
.returns
)
386 # If this object is serializable, don't convert the type from JS - pass the
387 # JS object straight into the proxy.
388 if self
._IsSerializableObjectType
(function
.returns
):
391 params
.append("'%s'" % return_type_str
)
393 params
.append("'#.%s(%s)'" % (function
.name
, ', '.join(['#'] * n_params
)))
394 params
.append(call_target
)
396 for param
in function
.params
:
397 if not self
._IsBaseType
(param
.type_
):
398 params
.append('convertArgument(%s)' % param
.name
)
400 params
.append(param
.name
)
401 if function
.callback
:
402 # If this isn't a base type, we need a proxied callback.
403 callback_name
= function
.callback
.name
404 if self
._NeedsProxiedCallback
(function
):
405 callback_name
= "__proxy_callback"
406 params
.append('convertDartClosureToJS(%s, %s)' % (callback_name
,
407 len(function
.callback
.params
)))
409 # If the object is serializable, call the proxy constructor for this type.
410 proxy_call
= 'JS(%s)' % ', '.join(params
)
411 if self
._IsSerializableObjectType
(function
.returns
):
412 proxy_call
= 'new %s._proxy(%s)' % (return_type_str
, proxy_call
)
416 def _GenerateEvent(self
, event
):
417 """Given a Function object, returns the Code with the .dart for this event,
418 represented by the function.
420 All events extend the Event base type.
424 # Add documentation for this event.
425 (c
.Concat(self
._GenerateDocumentation
(event
))
426 .Sblock('class Event_%(event_name)s extends Event {')
429 # If this event needs a proxy, all calls need to be proxied.
430 needs_proxy
= self
._NeedsProxy
(event
)
432 # Override Event callback type definitions.
433 for ret_type
, event_func
in (('void', 'addListener'),
434 ('void', 'removeListener'),
435 ('bool', 'hasListener')):
436 param_list
= self
._GenerateParameterList
(event
.params
, event
.callback
,
437 convert_optional
=True)
439 (c
.Sblock('%s %s(void callback(%s)) {' % (ret_type
, event_func
,
441 .Concat(self
._GenerateProxiedFunction
(event
, 'callback'))
442 .Append('super.%s(__proxy_callback);' % event_func
)
446 c
.Append('%s %s(void callback(%s)) => super.%s(callback);' %
447 (ret_type
, event_func
, param_list
, event_func
))
450 # Generate the constructor.
451 (c
.Append('Event_%(event_name)s(jsObject) : '
452 'super._(jsObject, %(param_num)d);')
455 'event_name': self
._namespace
.unix_name
+ '_' + event
.name
,
456 'param_num': len(event
.params
)
462 def _GenerateMainClass(self
):
463 """Generates the main class for this file, which links to all functions
466 Returns a code object.
469 (c
.Sblock('class API_%s {' % self
._namespace
.unix_name
)
471 .Append(' * API connection')
473 .Append('Object _jsObject;')
477 if self
._namespace
.events
:
483 for event_name
in self
._namespace
.events
:
484 c
.Append('Event_%s_%s %s;' % (self
._namespace
.unix_name
, event_name
,
488 if self
._namespace
.functions
:
491 .Append(' * Functions')
494 for function
in self
._namespace
.functions
.values():
495 # Check for custom dart for this whole property.
496 override
= self
._GetOverride
([function
.name
], document_with
=function
)
497 c
.Cblock(override
if override
is not None
498 else self
._GenerateFunction
(function
))
500 # Add the constructor.
501 c
.Sblock('API_%s(this._jsObject) {' % self
._namespace
.unix_name
)
503 # Add events to constructor.
504 for event_name
in self
._namespace
.events
:
505 c
.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
506 (event_name
, self
._namespace
.unix_name
, event_name
, event_name
))
513 def _GeneratePropertySignature(self
, prop
):
514 """Given a property, returns a signature for that property.
515 Recursively generates the signature for callbacks.
516 Returns a String for the given property.
521 void doSomething(bool x, void callback([String x]))
523 if self
._IsFunction
(prop
.type_
):
524 return self
._GenerateFunctionSignature
(prop
.type_
.function
)
525 return '%(type)s %(name)s' % {
526 'type': self
._GetDartType
(prop
.type_
),
527 'name': prop
.simple_name
530 def _GenerateFunctionSignature(self
, function
, convert_optional
=False):
531 """Given a function object, returns the signature for that function.
532 Recursively generates the signature for callbacks.
533 Returns a String for the given function.
535 If convert_optional is True, changes optional parameters to be required.
539 bool isOpen([String type])
540 void doSomething(bool x, void callback([String x]))
542 sig
= '%(return_type)s %(name)s(%(params)s)'
545 return_type
= self
._GetDartType
(function
.returns
)
550 'return_type': return_type
,
551 'name': function
.simple_name
,
552 'params': self
._GenerateParameterList
(function
.params
,
554 convert_optional
=convert_optional
)
557 def _GenerateParameterList(self
,
560 convert_optional
=False):
561 """Given a list of function parameters, generates their signature (as a
566 bool x, void callback([String x])
568 If convert_optional is True, changes optional parameters to be required.
569 Useful for callbacks, where optional parameters are treated as required.
571 # Params lists (required & optional), to be joined with commas.
572 # TODO(sashab): Don't assume optional params always come after required
577 p_sig
= self
._GeneratePropertySignature
(param
)
578 if param
.optional
and not convert_optional
:
579 params_opt
.append(p_sig
)
581 params_req
.append(p_sig
)
583 # Add the callback, if it exists.
585 c_sig
= self
._GenerateFunctionSignature
(callback
, convert_optional
=True)
586 if callback
.optional
:
587 params_opt
.append(c_sig
)
589 params_req
.append(c_sig
)
591 # Join the parameters with commas.
592 # Optional parameters have to be in square brackets, e.g.:
594 # required params | optional params | output
596 # [x, y] | [] | 'x, y'
597 # [] | [a, b] | '[a, b]'
598 # [x, y] | [a, b] | 'x, y, [a, b]'
600 params_opt
[0] = '[%s' % params_opt
[0]
601 params_opt
[-1] = '%s]' % params_opt
[-1]
602 param_sets
= [', '.join(params_req
), ', '.join(params_opt
)]
604 # The 'if p' part here is needed to prevent commas where there are no
605 # parameters of a certain type.
606 # If there are no optional parameters, this prevents a _trailing_ comma,
607 # e.g. '(x, y,)'. Similarly, if there are no required parameters, this
608 # prevents a leading comma, e.g. '(, [a, b])'.
609 return ', '.join(p
for p
in param_sets
if p
)
611 def _GetOverride(self
, key_chain
, document_with
=None):
612 """Given a list of keys, joins them with periods and searches for them in
613 the custom dart overrides.
614 If there is an override for that key, finds the override code and returns
615 the Code object. If not, returns None.
617 If document_with is not None, adds the documentation for this property
618 before the override code.
621 contents
= self
._type
_overrides
.get('.'.join(key_chain
))
625 if document_with
is not None:
626 c
.Concat(self
._GenerateDocumentation
(document_with
))
627 for line
in contents
.strip('\n').split('\n'):
631 def _AddPrefix(self
, name
):
632 """Given the name of a type, prefixes the namespace (as camelcase) and
633 returns the new name.
635 # TODO(sashab): Split the dart library into multiple files, avoiding the
636 # need for this prefixing.
638 ''.join(s
.capitalize() for s
in self
._namespace
.name
.split('.')),
641 def _IsFunction(self
, type_
):
642 """Given a model.Type, returns whether this type is a function.
644 return type_
.property_type
== PropertyType
.FUNCTION
646 def _IsSerializableObjectType(self
, type_
):
647 """Given a model.Type, returns whether this type is a serializable object.
648 Serializable objects are custom types defined in this namespace.
650 If this object is a reference to something not in this namespace, assumes
651 its a serializable object.
655 if type_
.property_type
is PropertyType
.CHOICES
:
656 return all(self
._IsSerializableObjectType
(c
) for c
in type_
.choices
)
657 if type_
.property_type
is PropertyType
.REF
:
658 if type_
.ref_type
in self
._types
:
659 return self
._IsObjectType
(self
._types
[type_
.ref_type
])
661 if (type_
.property_type
== PropertyType
.OBJECT
662 and type_
.instance_of
in self
._types
):
663 return self
._IsObjectType
(self
._types
[type_
.instance_of
])
666 def _IsObjectType(self
, type_
):
667 """Given a model.Type, returns whether this type is an object.
669 return (self
._IsSerializableObjectType
(type_
)
670 or type_
.property_type
in [PropertyType
.OBJECT
, PropertyType
.ANY
])
672 def _IsListOfSerializableObjects(self
, type_
):
673 """Given a model.Type, returns whether this type is a list of serializable
674 objects (or regular objects, if this list is treated as a type - in this
675 case, the item type was defined inline).
677 If this type is a reference to something not in this namespace, assumes
678 it is not a list of serializable objects.
680 if type_
.property_type
is PropertyType
.CHOICES
:
681 return all(self
._IsListOfSerializableObjects
(c
) for c
in type_
.choices
)
682 if type_
.property_type
is PropertyType
.REF
:
683 if type_
.ref_type
in self
._types
:
684 return self
._IsListOfSerializableObjects
(self
._types
[type_
.ref_type
])
686 return (type_
.property_type
is PropertyType
.ARRAY
and
687 (self
._IsSerializableObjectType
(type_
.item_type
)))
689 def _IsListOfBaseTypes(self
, type_
):
690 """Given a model.Type, returns whether this type is a list of base type
691 objects (PropertyType.REF types).
693 if type_
.property_type
is PropertyType
.CHOICES
:
694 return all(self
._IsListOfBaseTypes
(c
) for c
in type_
.choices
)
695 return (type_
.property_type
is PropertyType
.ARRAY
and
696 self
._IsBaseType
(type_
.item_type
))
698 def _IsBaseType(self
, type_
):
699 """Given a model.type_, returns whether this type is a base type
700 (string, number, boolean, or a list of these).
702 If type_ is a Choices object, returns True if all possible choices are base
705 # TODO(sashab): Remove 'Choices' as a base type once they are wrapped in
706 # native Dart classes.
707 if type_
.property_type
is PropertyType
.CHOICES
:
708 return all(self
._IsBaseType
(c
) for c
in type_
.choices
)
710 (self
._GetDartType
(type_
) in ['bool', 'num', 'int', 'double', 'String'])
711 or (type_
.property_type
is PropertyType
.ARRAY
712 and self
._IsBaseType
(type_
.item_type
))
715 def _GetDartType(self
, type_
):
716 """Given a model.Type object, returns its type as a Dart string.
721 prop_type
= type_
.property_type
722 if prop_type
is PropertyType
.REF
:
723 if type_
.ref_type
in self
._types
:
724 return self
._GetDartType
(self
._types
[type_
.ref_type
])
725 # TODO(sashab): If the type is foreign, it might have to be imported.
726 return StripNamespace(type_
.ref_type
)
727 elif prop_type
is PropertyType
.BOOLEAN
:
729 elif prop_type
is PropertyType
.INTEGER
:
731 elif prop_type
is PropertyType
.INT64
:
733 elif prop_type
is PropertyType
.DOUBLE
:
735 elif prop_type
is PropertyType
.STRING
:
737 elif prop_type
is PropertyType
.ENUM
:
739 elif prop_type
is PropertyType
.CHOICES
:
740 # TODO(sashab): Think of a nice way to generate code for Choices objects
743 elif prop_type
is PropertyType
.ANY
:
745 elif prop_type
is PropertyType
.OBJECT
:
746 # TODO(sashab): type_.name is the name of the function's parameter for
747 # inline types defined in functions. Think of a way to generate names
748 # for this, or remove all inline type definitions at the start.
749 if type_
.instance_of
is not None:
750 return type_
.instance_of
751 if not isinstance(type_
.parent
, Function
):
752 return self
._AddPrefix
(type_
.name
)
754 elif prop_type
is PropertyType
.FUNCTION
:
756 elif prop_type
is PropertyType
.ARRAY
:
757 return 'List<%s>' % self
._GetDartType
(type_
.item_type
)
758 elif prop_type
is PropertyType
.BINARY
:
761 raise NotImplementedError(prop_type
)