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.
9 from model
import Function
, PropertyType
10 from schema_util
import StripNamespace
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()
29 class _Generator(object):
30 """A .dart generator for a namespace.
33 def __init__(self
, namespace
, dart_overrides_dir
=None):
34 self
._namespace
= namespace
35 # TODO(sashab): Once inline type definitions start being added to
36 # self._types, make a _FindType(self, type_) function that looks at
37 # self._namespace.types.
38 self
._types
= namespace
.types
40 # Build a dictionary of Type Name --> Custom Dart code.
41 self
._type
_overrides
= {}
42 if dart_overrides_dir
is not None:
43 for filename
in os
.listdir(dart_overrides_dir
):
44 if filename
.startswith(namespace
.unix_name
):
45 with
open(os
.path
.join(dart_overrides_dir
, filename
)) as f
:
46 # Split off the namespace and file extension, leaving just the type.
47 type_path
= '.'.join(filename
.split('.')[1:-1])
48 self
._type
_overrides
[type_path
] = f
.read()
50 # TODO(sashab): Add all inline type definitions to the global Types
51 # dictionary here, so they have proper names, and are implemented along with
52 # all other types. Also update the parameters/members with these types
53 # to reference these new types instead.
56 """Generates a Code object with the .dart for the entire namespace.
61 .Append('// Generated from namespace: %s' % self
._namespace
.name
)
63 .Append('part of chrome;'))
72 for type_
in self
._types
.values():
73 # Check for custom dart for this whole type.
74 override
= self
._GetOverride
([type_
.name
], document_with
=type_
)
75 c
.Cblock(override
if override
is not None else self
._GenerateType
(type_
))
77 if self
._namespace
.events
:
83 for event_name
in self
._namespace
.events
:
84 c
.Cblock(self
._GenerateEvent
(self
._namespace
.events
[event_name
]))
87 .Append(' * Functions')
91 c
.Cblock(self
._GenerateMainClass
())
95 def _GenerateType(self
, type_
):
96 """Given a Type object, returns the Code with the .dart for this
99 Assumes this type is a Parameter Type (creatable by user), and creates an
100 object that extends ChromeObject. All parameters are specifiable as named
101 arguments in the constructor, and all methods are wrapped with getters and
102 setters that hide the JS() implementation.
106 # Since enums are just treated as strings for now, don't generate their
108 # TODO(sashab): Find a nice way to wrap enum objects.
109 if type_
.property_type
is PropertyType
.ENUM
:
112 (c
.Concat(self
._GenerateDocumentation
(type_
))
113 .Sblock('class %(type_name)s extends ChromeObject {')
116 # Check whether this type has function members. If it does, don't allow
117 # public construction.
118 add_public_constructor
= all(not self
._IsFunction
(p
.type_
)
119 for p
in type_
.properties
.values())
120 constructor_fields
= [self
._GeneratePropertySignature
(p
)
121 for p
in type_
.properties
.values()]
123 if add_public_constructor
:
125 .Append(' * Public constructor')
127 .Sblock('%(type_name)s({%(constructor_fields)s}) {')
130 for prop_name
in type_
.properties
:
131 (c
.Sblock('if (%s != null)' % prop_name
)
132 .Append('this.%s = %s;' % (prop_name
, prop_name
))
140 .Append(' * Private constructor')
142 .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
145 # Add an accessor (getter & setter) for each property.
146 properties
= [p
for p
in type_
.properties
.values()
147 if not self
._IsFunction
(p
.type_
)]
151 .Append(' * Public accessors')
154 for prop
in properties
:
155 override
= self
._GetOverride
([type_
.name
, prop
.name
], document_with
=prop
)
156 c
.Concat(override
if override
is not None
157 else self
._GenerateGetterAndSetter
(type_
, prop
))
159 # Now add all the methods.
160 methods
= [t
for t
in type_
.properties
.values()
161 if self
._IsFunction
(t
.type_
)]
165 .Append(' * Methods')
169 # Check if there's an override for this method.
170 override
= self
._GetOverride
([type_
.name
, prop
.name
], document_with
=prop
)
171 c
.Cblock(override
if override
is not None
172 else self
._GenerateFunction
(prop
.type_
.function
))
176 'type_name': self
._AddPrefix
(type_
.simple_name
),
177 'constructor_fields': ', '.join(constructor_fields
)
183 def _GenerateGetterAndSetter(self
, type_
, prop
):
184 """Given a Type and Property, returns the Code object for the getter and
185 setter for that property.
188 override
= self
._GetOverride
([type_
.name
, prop
.name
, '.get'],
190 c
.Cblock(override
if override
is not None
191 else self
._GenerateGetter
(type_
, prop
))
192 override
= self
._GetOverride
([type_
.name
, prop
.name
, '.set'])
193 c
.Cblock(override
if override
is not None
194 else self
._GenerateSetter
(type_
, prop
))
197 def _GenerateGetter(self
, type_
, prop
):
198 """Given a Type and Property, returns the Code object for the getter for
201 Also adds the documentation for this property before the method.
204 c
.Concat(self
._GenerateDocumentation
(prop
))
206 type_name
= self
._GetDartType
(prop
.type_
)
207 if (self
._IsBaseType
(prop
.type_
)):
208 c
.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
209 (type_name
, prop
.name
, type_name
, prop
.name
))
210 elif self
._IsSerializableObjectType
(prop
.type_
):
211 c
.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
212 "this._jsObject));" %
213 (type_name
, prop
.name
, type_name
, prop
.name
))
214 elif self
._IsListOfSerializableObjects
(prop
.type_
):
215 (c
.Sblock('%s get %s {' % (type_name
, prop
.name
))
216 .Append('%s __proxy_%s = new %s();' % (type_name
, prop
.name
,
218 .Append("int count = JS('int', '#.%s.length', this._jsObject);" %
220 .Sblock("for (int i = 0; i < count; i++) {")
221 .Append("var item = JS('', '#.%s[#]', this._jsObject, i);" % prop
.name
)
222 .Append('__proxy_%s.add(new %s._proxy(item));' % (prop
.name
,
223 self
._GetDartType
(prop
.type_
.item_type
)))
225 .Append('return __proxy_%s;' % prop
.name
)
228 elif self
._IsObjectType
(prop
.type_
):
229 # TODO(sashab): Think of a way to serialize generic Dart objects.
230 if type_name
in self
._types
:
231 c
.Append("%s get %s => new %s._proxy(JS('%s', '#.%s', "
232 "this._jsObject));" %
233 (type_name
, prop
.name
, type_name
, type_name
, prop
.name
))
235 c
.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
236 (type_name
, prop
.name
, type_name
, prop
.name
))
239 "Could not generate wrapper for %s.%s: unserializable type %s" %
240 (type_
.name
, prop
.name
, type_name
)
244 def _GenerateSetter(self
, type_
, prop
):
245 """Given a Type and Property, returns the Code object for the setter for
249 type_name
= self
._GetDartType
(prop
.type_
)
250 wrapped_name
= prop
.name
251 if not self
._IsBaseType
(prop
.type_
):
252 wrapped_name
= 'convertArgument(%s)' % prop
.name
254 (c
.Sblock("void set %s(%s %s) {" % (prop
.name
, type_name
, prop
.name
))
255 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
256 (prop
.name
, wrapped_name
))
261 def _GenerateDocumentation(self
, prop
):
262 """Given an object, generates the documentation for this object (as a
263 code string) and returns the Code object.
265 Returns an empty code object if the object has no documentation.
267 Uses triple-quotes for the string.
270 if prop
.description
is not None:
271 for line
in prop
.description
.split('\n'):
272 c
.Comment(line
, comment_prefix
='/// ')
275 def _GenerateFunction(self
, f
):
276 """Returns the Code object for the given function.
279 c
.Concat(self
._GenerateDocumentation
(f
))
281 if not self
._NeedsProxiedCallback
(f
):
282 c
.Append("%s => %s;" % (self
._GenerateFunctionSignature
(f
),
283 self
._GenerateProxyCall
(f
)))
286 (c
.Sblock("%s {" % self
._GenerateFunctionSignature
(f
))
287 .Concat(self
._GenerateProxiedFunction
(f
.callback
, f
.callback
.name
))
288 .Append('%s;' % self
._GenerateProxyCall
(f
))
294 def _GenerateProxiedFunction(self
, f
, callback_name
):
295 """Given a function (assumed to be a callback), generates the proxied
296 version of this function, which calls |callback_name| if it is defined.
298 Returns a Code object.
302 # A list of Properties, containing List<*> objects that need proxying for
303 # their members (by copying out each member and proxying it).
306 if self
._IsBaseType
(p
.type_
):
307 proxied_params
.append(p
.name
)
308 elif self
._IsSerializableObjectType
(p
.type_
):
309 proxied_params
.append('new %s._proxy(%s)' % (
310 self
._GetDartType
(p
.type_
), p
.name
))
311 elif self
._IsListOfSerializableObjects
(p
.type_
):
312 proxied_params
.append('__proxy_%s' % p
.name
)
313 lists_to_proxy
.append(p
)
314 elif self
._IsObjectType
(p
.type_
):
315 # TODO(sashab): Find a way to build generic JS objects back in Dart.
316 proxied_params
.append('%s' % p
.name
)
317 elif p
.type_
.property_type
is PropertyType
.ARRAY
:
318 # TODO(sashab): This might be okay - what if this is a list of
319 # FileEntry elements? In this case, a basic list will proxy the objects
321 proxied_params
.append('%s' % p
.name
)
324 "Cannot automatically create proxy; can't wrap %s, type %s" % (
325 self
._GenerateFunctionSignature
(f
), self
._GetDartType
(p
.type_
)))
327 (c
.Sblock("void __proxy_callback(%s) {" % ', '.join(p
.name
for p
in
329 .Sblock('if (%s != null) {' % callback_name
)
332 # Add the proxied lists.
333 for list_to_proxy
in lists_to_proxy
:
334 (c
.Append("%s __proxy_%s = new %s();" % (
335 self
._GetDartType
(list_to_proxy
.type_
),
337 self
._GetDartType
(list_to_proxy
.type_
)))
338 .Sblock("for (var o in %s) {" % list_to_proxy
.name
)
339 .Append('__proxy_%s.add(new %s._proxy(o));' % (list_to_proxy
.name
,
340 self
._GetDartType
(list_to_proxy
.type_
.item_type
)))
344 (c
.Append("%s(%s);" % (callback_name
, ', '.join(proxied_params
)))
350 def _NeedsProxiedCallback(self
, f
):
351 """Given a function, returns True if this function's callback needs to be
352 proxied, False if not.
354 Function callbacks need to be proxied if they have at least one
355 non-base-type parameter.
357 return f
.callback
and self
._NeedsProxy
(f
.callback
)
359 def _NeedsProxy(self
, f
):
360 """Given a function, returns True if it needs to be proxied, False if not.
362 A function needs to be proxied if any of its members are non-base types.
363 This means that, when the function object is passed to Javascript, it
364 needs to be wrapped in a "proxied" call that converts the JS inputs to Dart
365 objects explicitly, before calling the real function with these new objects.
367 return any(not self
._IsBaseType
(p
.type_
) for p
in f
.params
)
369 def _GenerateProxyCall(self
, function
, call_target
='this._jsObject'):
370 """Given a function, generates the code to call that function via JS().
373 |call_target| is the name of the object to call the function on. The default
377 JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
378 JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
380 n_params
= len(function
.params
)
381 if function
.callback
:
384 return_type_str
= self
._GetDartType
(function
.returns
)
387 # If this object is serializable, don't convert the type from JS - pass the
388 # JS object straight into the proxy.
389 if self
._IsSerializableObjectType
(function
.returns
):
392 params
.append("'%s'" % return_type_str
)
394 params
.append("'#.%s(%s)'" % (function
.name
, ', '.join(['#'] * n_params
)))
395 params
.append(call_target
)
397 for param
in function
.params
:
398 if not self
._IsBaseType
(param
.type_
):
399 params
.append('convertArgument(%s)' % param
.name
)
401 params
.append(param
.name
)
402 if function
.callback
:
403 # If this isn't a base type, we need a proxied callback.
404 callback_name
= function
.callback
.name
405 if self
._NeedsProxiedCallback
(function
):
406 callback_name
= "__proxy_callback"
407 params
.append('convertDartClosureToJS(%s, %s)' % (callback_name
,
408 len(function
.callback
.params
)))
410 # If the object is serializable, call the proxy constructor for this type.
411 proxy_call
= 'JS(%s)' % ', '.join(params
)
412 if self
._IsSerializableObjectType
(function
.returns
):
413 proxy_call
= 'new %s._proxy(%s)' % (return_type_str
, proxy_call
)
417 def _GenerateEvent(self
, event
):
418 """Given a Function object, returns the Code with the .dart for this event,
419 represented by the function.
421 All events extend the Event base type.
425 # Add documentation for this event.
426 (c
.Concat(self
._GenerateDocumentation
(event
))
427 .Sblock('class Event_%(event_name)s extends Event {')
430 # If this event needs a proxy, all calls need to be proxied.
431 needs_proxy
= self
._NeedsProxy
(event
)
433 # Override Event callback type definitions.
434 for ret_type
, event_func
in (('void', 'addListener'),
435 ('void', 'removeListener'),
436 ('bool', 'hasListener')):
437 param_list
= self
._GenerateParameterList
(event
.params
, event
.callback
,
438 convert_optional
=True)
440 (c
.Sblock('%s %s(void callback(%s)) {' % (ret_type
, event_func
,
442 .Concat(self
._GenerateProxiedFunction
(event
, 'callback'))
443 .Append('super.%s(__proxy_callback);' % event_func
)
447 c
.Append('%s %s(void callback(%s)) => super.%s(callback);' %
448 (ret_type
, event_func
, param_list
, event_func
))
451 # Generate the constructor.
452 (c
.Append('Event_%(event_name)s(jsObject) : '
453 'super._(jsObject, %(param_num)d);')
456 'event_name': self
._namespace
.unix_name
+ '_' + event
.name
,
457 'param_num': len(event
.params
)
463 def _GenerateMainClass(self
):
464 """Generates the main class for this file, which links to all functions
467 Returns a code object.
470 (c
.Sblock('class API_%s {' % self
._namespace
.unix_name
)
472 .Append(' * API connection')
474 .Append('Object _jsObject;')
478 if self
._namespace
.events
:
484 for event_name
in self
._namespace
.events
:
485 c
.Append('Event_%s_%s %s;' % (self
._namespace
.unix_name
, event_name
,
489 if self
._namespace
.functions
:
492 .Append(' * Functions')
495 for function
in self
._namespace
.functions
.values():
496 # Check for custom dart for this whole property.
497 override
= self
._GetOverride
([function
.name
], document_with
=function
)
498 c
.Cblock(override
if override
is not None
499 else self
._GenerateFunction
(function
))
501 # Add the constructor.
502 c
.Sblock('API_%s(this._jsObject) {' % self
._namespace
.unix_name
)
504 # Add events to constructor.
505 for event_name
in self
._namespace
.events
:
506 c
.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
507 (event_name
, self
._namespace
.unix_name
, event_name
, event_name
))
514 def _GeneratePropertySignature(self
, prop
):
515 """Given a property, returns a signature for that property.
516 Recursively generates the signature for callbacks.
517 Returns a String for the given property.
522 void doSomething(bool x, void callback([String x]))
524 if self
._IsFunction
(prop
.type_
):
525 return self
._GenerateFunctionSignature
(prop
.type_
.function
)
526 return '%(type)s %(name)s' % {
527 'type': self
._GetDartType
(prop
.type_
),
528 'name': prop
.simple_name
531 def _GenerateFunctionSignature(self
, function
, convert_optional
=False):
532 """Given a function object, returns the signature for that function.
533 Recursively generates the signature for callbacks.
534 Returns a String for the given function.
536 If convert_optional is True, changes optional parameters to be required.
540 bool isOpen([String type])
541 void doSomething(bool x, void callback([String x]))
543 sig
= '%(return_type)s %(name)s(%(params)s)'
546 return_type
= self
._GetDartType
(function
.returns
)
551 'return_type': return_type
,
552 'name': function
.simple_name
,
553 'params': self
._GenerateParameterList
(function
.params
,
555 convert_optional
=convert_optional
)
558 def _GenerateParameterList(self
,
561 convert_optional
=False):
562 """Given a list of function parameters, generates their signature (as a
567 bool x, void callback([String x])
569 If convert_optional is True, changes optional parameters to be required.
570 Useful for callbacks, where optional parameters are treated as required.
572 # Params lists (required & optional), to be joined with commas.
573 # TODO(sashab): Don't assume optional params always come after required
578 p_sig
= self
._GeneratePropertySignature
(param
)
579 if param
.optional
and not convert_optional
:
580 params_opt
.append(p_sig
)
582 params_req
.append(p_sig
)
584 # Add the callback, if it exists.
586 c_sig
= self
._GenerateFunctionSignature
(callback
, convert_optional
=True)
587 if callback
.optional
:
588 params_opt
.append(c_sig
)
590 params_req
.append(c_sig
)
592 # Join the parameters with commas.
593 # Optional parameters have to be in square brackets, e.g.:
595 # required params | optional params | output
597 # [x, y] | [] | 'x, y'
598 # [] | [a, b] | '[a, b]'
599 # [x, y] | [a, b] | 'x, y, [a, b]'
601 params_opt
[0] = '[%s' % params_opt
[0]
602 params_opt
[-1] = '%s]' % params_opt
[-1]
603 param_sets
= [', '.join(params_req
), ', '.join(params_opt
)]
605 # The 'if p' part here is needed to prevent commas where there are no
606 # parameters of a certain type.
607 # If there are no optional parameters, this prevents a _trailing_ comma,
608 # e.g. '(x, y,)'. Similarly, if there are no required parameters, this
609 # prevents a leading comma, e.g. '(, [a, b])'.
610 return ', '.join(p
for p
in param_sets
if p
)
612 def _GetOverride(self
, key_chain
, document_with
=None):
613 """Given a list of keys, joins them with periods and searches for them in
614 the custom dart overrides.
615 If there is an override for that key, finds the override code and returns
616 the Code object. If not, returns None.
618 If document_with is not None, adds the documentation for this property
619 before the override code.
622 contents
= self
._type
_overrides
.get('.'.join(key_chain
))
626 if document_with
is not None:
627 c
.Concat(self
._GenerateDocumentation
(document_with
))
628 for line
in contents
.strip('\n').split('\n'):
632 def _AddPrefix(self
, name
):
633 """Given the name of a type, prefixes the namespace (as camelcase) and
634 returns the new name.
636 # TODO(sashab): Split the dart library into multiple files, avoiding the
637 # need for this prefixing.
639 ''.join(s
.capitalize() for s
in self
._namespace
.name
.split('.')),
642 def _IsFunction(self
, type_
):
643 """Given a model.Type, returns whether this type is a function.
645 return type_
.property_type
== PropertyType
.FUNCTION
647 def _IsSerializableObjectType(self
, type_
):
648 """Given a model.Type, returns whether this type is a serializable object.
649 Serializable objects are custom types defined in this namespace.
651 If this object is a reference to something not in this namespace, assumes
652 its a serializable object.
656 if type_
.property_type
is PropertyType
.CHOICES
:
657 return all(self
._IsSerializableObjectType
(c
) for c
in type_
.choices
)
658 if type_
.property_type
is PropertyType
.REF
:
659 if type_
.ref_type
in self
._types
:
660 return self
._IsObjectType
(self
._types
[type_
.ref_type
])
662 if (type_
.property_type
== PropertyType
.OBJECT
663 and type_
.instance_of
in self
._types
):
664 return self
._IsObjectType
(self
._types
[type_
.instance_of
])
667 def _IsObjectType(self
, type_
):
668 """Given a model.Type, returns whether this type is an object.
670 return (self
._IsSerializableObjectType
(type_
)
671 or type_
.property_type
in [PropertyType
.OBJECT
, PropertyType
.ANY
])
673 def _IsListOfSerializableObjects(self
, type_
):
674 """Given a model.Type, returns whether this type is a list of serializable
675 objects (or regular objects, if this list is treated as a type - in this
676 case, the item type was defined inline).
678 If this type is a reference to something not in this namespace, assumes
679 it is not a list of serializable objects.
681 if type_
.property_type
is PropertyType
.CHOICES
:
682 return all(self
._IsListOfSerializableObjects
(c
) for c
in type_
.choices
)
683 if type_
.property_type
is PropertyType
.REF
:
684 if type_
.ref_type
in self
._types
:
685 return self
._IsListOfSerializableObjects
(self
._types
[type_
.ref_type
])
687 return (type_
.property_type
is PropertyType
.ARRAY
and
688 (self
._IsSerializableObjectType
(type_
.item_type
)))
690 def _IsListOfBaseTypes(self
, type_
):
691 """Given a model.Type, returns whether this type is a list of base type
692 objects (PropertyType.REF types).
694 if type_
.property_type
is PropertyType
.CHOICES
:
695 return all(self
._IsListOfBaseTypes
(c
) for c
in type_
.choices
)
696 return (type_
.property_type
is PropertyType
.ARRAY
and
697 self
._IsBaseType
(type_
.item_type
))
699 def _IsBaseType(self
, type_
):
700 """Given a model.type_, returns whether this type is a base type
701 (string, number, boolean, or a list of these).
703 If type_ is a Choices object, returns True if all possible choices are base
706 # TODO(sashab): Remove 'Choices' as a base type once they are wrapped in
707 # native Dart classes.
708 if type_
.property_type
is PropertyType
.CHOICES
:
709 return all(self
._IsBaseType
(c
) for c
in type_
.choices
)
711 (self
._GetDartType
(type_
) in ['bool', 'num', 'int', 'double', 'String'])
712 or (type_
.property_type
is PropertyType
.ARRAY
713 and self
._IsBaseType
(type_
.item_type
))
716 def _GetDartType(self
, type_
):
717 """Given a model.Type object, returns its type as a Dart string.
722 prop_type
= type_
.property_type
723 if prop_type
is PropertyType
.REF
:
724 if type_
.ref_type
in self
._types
:
725 return self
._GetDartType
(self
._types
[type_
.ref_type
])
726 # TODO(sashab): If the type is foreign, it might have to be imported.
727 return StripNamespace(type_
.ref_type
)
728 elif prop_type
is PropertyType
.BOOLEAN
:
730 elif prop_type
is PropertyType
.INTEGER
:
732 elif prop_type
is PropertyType
.INT64
:
734 elif prop_type
is PropertyType
.DOUBLE
:
736 elif prop_type
is PropertyType
.STRING
:
738 elif prop_type
is PropertyType
.ENUM
:
740 elif prop_type
is PropertyType
.CHOICES
:
741 # TODO(sashab): Think of a nice way to generate code for Choices objects
744 elif prop_type
is PropertyType
.ANY
:
746 elif prop_type
is PropertyType
.OBJECT
:
747 # TODO(sashab): type_.name is the name of the function's parameter for
748 # inline types defined in functions. Think of a way to generate names
749 # for this, or remove all inline type definitions at the start.
750 if type_
.instance_of
is not None:
751 return type_
.instance_of
752 if not isinstance(type_
.parent
, Function
):
753 return self
._AddPrefix
(type_
.name
)
755 elif prop_type
is PropertyType
.FUNCTION
:
757 elif prop_type
is PropertyType
.ARRAY
:
758 return 'List<%s>' % self
._GetDartType
(type_
.item_type
)
759 elif prop_type
is PropertyType
.BINARY
:
762 raise NotImplementedError(prop_type
)