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', "
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 .Sblock("for (var o in JS('List', '#.%s', this._jsObject)) {" %
219 .Append('__proxy_%s.add(new %s._proxy(o));' % (prop
.name
,
220 self
._GetDartType
(prop
.type_
.item_type
)))
222 .Append('return __proxy_%s;' % prop
.name
)
225 elif self
._IsObjectType
(prop
.type_
):
226 # TODO(sashab): Think of a way to serialize generic Dart objects.
227 if type_name
in self
._types
:
228 c
.Append("%s get %s => new %s._proxy(JS('%s', '#.%s', "
229 "this._jsObject));" %
230 (type_name
, prop
.name
, type_name
, type_name
, prop
.name
))
232 c
.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
233 (type_name
, prop
.name
, type_name
, prop
.name
))
236 "Could not generate wrapper for %s.%s: unserializable type %s" %
237 (type_
.name
, prop
.name
, type_name
)
241 def _GenerateSetter(self
, type_
, prop
):
242 """Given a Type and Property, returns the Code object for the setter for
246 type_name
= self
._GetDartType
(prop
.type_
)
247 wrapped_name
= prop
.name
248 if not self
._IsBaseType
(prop
.type_
):
249 wrapped_name
= 'convertArgument(%s)' % prop
.name
251 (c
.Sblock("void set %s(%s %s) {" % (prop
.name
, type_name
, prop
.name
))
252 .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
253 (prop
.name
, wrapped_name
))
258 def _GenerateDocumentation(self
, prop
):
259 """Given an object, generates the documentation for this object (as a
260 code string) and returns the Code object.
262 Returns an empty code object if the object has no documentation.
264 Uses triple-quotes for the string.
267 if prop
.description
is not None:
268 for line
in prop
.description
.split('\n'):
269 c
.Comment(line
, comment_prefix
='/// ')
272 def _GenerateFunction(self
, f
):
273 """Returns the Code object for the given function.
276 c
.Concat(self
._GenerateDocumentation
(f
))
278 if not self
._NeedsProxiedCallback
(f
):
279 c
.Append("%s => %s;" % (self
._GenerateFunctionSignature
(f
),
280 self
._GenerateProxyCall
(f
)))
283 (c
.Sblock("%s {" % self
._GenerateFunctionSignature
(f
))
284 .Concat(self
._GenerateProxiedFunction
(f
.callback
, f
.callback
.name
))
285 .Append('%s;' % self
._GenerateProxyCall
(f
))
291 def _GenerateProxiedFunction(self
, f
, callback_name
):
292 """Given a function (assumed to be a callback), generates the proxied
293 version of this function, which calls |callback_name| if it is defined.
295 Returns a Code object.
299 # A list of Properties, containing List<*> objects that need proxying for
300 # their members (by copying out each member and proxying it).
303 if self
._IsBaseType
(p
.type_
):
304 proxied_params
.append(p
.name
)
305 elif self
._IsSerializableObjectType
(p
.type_
):
306 proxied_params
.append('new %s._proxy(%s)' % (
307 self
._GetDartType
(p
.type_
), p
.name
))
308 elif self
._IsListOfSerializableObjects
(p
.type_
):
309 proxied_params
.append('__proxy_%s' % p
.name
)
310 lists_to_proxy
.append(p
)
311 elif self
._IsObjectType
(p
.type_
):
312 # TODO(sashab): Find a way to build generic JS objects back in Dart.
313 proxied_params
.append('%s' % p
.name
)
314 elif p
.type_
.property_type
is PropertyType
.ARRAY
:
315 # TODO(sashab): This might be okay - what if this is a list of
316 # FileEntry elements? In this case, a basic list will proxy the objects
318 proxied_params
.append('%s' % p
.name
)
321 "Cannot automatically create proxy; can't wrap %s, type %s" % (
322 self
._GenerateFunctionSignature
(f
), self
._GetDartType
(p
.type_
)))
324 (c
.Sblock("void __proxy_callback(%s) {" % ', '.join(p
.name
for p
in
326 .Sblock('if (?%s) {' % callback_name
)
329 # Add the proxied lists.
330 for list_to_proxy
in lists_to_proxy
:
331 (c
.Append("%s __proxy_%s = new %s();" % (
332 self
._GetDartType
(list_to_proxy
.type_
),
334 self
._GetDartType
(list_to_proxy
.type_
)))
335 .Sblock("for (var o in %s) {" % list_to_proxy
.name
)
336 .Append('__proxy_%s.add(new %s._proxy(o));' % (list_to_proxy
.name
,
337 self
._GetDartType
(list_to_proxy
.type_
.item_type
)))
341 (c
.Append("%s(%s);" % (callback_name
, ', '.join(proxied_params
)))
347 def _NeedsProxiedCallback(self
, f
):
348 """Given a function, returns True if this function's callback needs to be
349 proxied, False if not.
351 Function callbacks need to be proxied if they have at least one
352 non-base-type parameter.
354 return f
.callback
and self
._NeedsProxy
(f
.callback
)
356 def _NeedsProxy(self
, f
):
357 """Given a function, returns True if it needs to be proxied, False if not.
359 A function needs to be proxied if any of its members are non-base types.
360 This means that, when the function object is passed to Javascript, it
361 needs to be wrapped in a "proxied" call that converts the JS inputs to Dart
362 objects explicitly, before calling the real function with these new objects.
364 return any(not self
._IsBaseType
(p
.type_
) for p
in f
.params
)
366 def _GenerateProxyCall(self
, function
, call_target
='this._jsObject'):
367 """Given a function, generates the code to call that function via JS().
370 |call_target| is the name of the object to call the function on. The default
374 JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
375 JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
377 n_params
= len(function
.params
)
378 if function
.callback
:
381 return_type_str
= self
._GetDartType
(function
.returns
)
384 # If this object is serializable, don't convert the type from JS - pass the
385 # JS object straight into the proxy.
386 if self
._IsSerializableObjectType
(function
.returns
):
389 params
.append("'%s'" % return_type_str
)
391 params
.append("'#.%s(%s)'" % (function
.name
, ', '.join(['#'] * n_params
)))
392 params
.append(call_target
)
394 for param
in function
.params
:
395 if not self
._IsBaseType
(param
.type_
):
396 params
.append('convertArgument(%s)' % param
.name
)
398 params
.append(param
.name
)
399 if function
.callback
:
400 # If this isn't a base type, we need a proxied callback.
401 callback_name
= function
.callback
.name
402 if self
._NeedsProxiedCallback
(function
):
403 callback_name
= "__proxy_callback"
404 params
.append('convertDartClosureToJS(%s, %s)' % (callback_name
,
405 len(function
.callback
.params
)))
407 # If the object is serializable, call the proxy constructor for this type.
408 proxy_call
= 'JS(%s)' % ', '.join(params
)
409 if self
._IsSerializableObjectType
(function
.returns
):
410 proxy_call
= 'new %s._proxy(%s)' % (return_type_str
, proxy_call
)
414 def _GenerateEvent(self
, event
):
415 """Given a Function object, returns the Code with the .dart for this event,
416 represented by the function.
418 All events extend the Event base type.
422 # Add documentation for this event.
423 (c
.Concat(self
._GenerateDocumentation
(event
))
424 .Sblock('class Event_%(event_name)s extends Event {')
427 # If this event needs a proxy, all calls need to be proxied.
428 needs_proxy
= self
._NeedsProxy
(event
)
430 # Override Event callback type definitions.
431 for ret_type
, event_func
in (('void', 'addListener'),
432 ('void', 'removeListener'),
433 ('bool', 'hasListener')):
434 param_list
= self
._GenerateParameterList
(event
.params
, event
.callback
,
435 convert_optional
=True)
437 (c
.Sblock('%s %s(void callback(%s)) {' % (ret_type
, event_func
,
439 .Concat(self
._GenerateProxiedFunction
(event
, 'callback'))
440 .Append('super.%s(callback);' % event_func
)
444 c
.Append('%s %s(void callback(%s)) => super.%s(callback);' %
445 (ret_type
, event_func
, param_list
, event_func
))
448 # Generate the constructor.
449 (c
.Append('Event_%(event_name)s(jsObject) : '
450 'super._(jsObject, %(param_num)d);')
453 'event_name': self
._namespace
.unix_name
+ '_' + event
.name
,
454 'param_num': len(event
.params
)
460 def _GenerateMainClass(self
):
461 """Generates the main class for this file, which links to all functions
464 Returns a code object.
467 (c
.Sblock('class API_%s {' % self
._namespace
.unix_name
)
469 .Append(' * API connection')
471 .Append('Object _jsObject;')
475 if self
._namespace
.events
:
481 for event_name
in self
._namespace
.events
:
482 c
.Append('Event_%s_%s %s;' % (self
._namespace
.unix_name
, event_name
,
486 if self
._namespace
.functions
:
489 .Append(' * Functions')
492 for function
in self
._namespace
.functions
.values():
493 # Check for custom dart for this whole property.
494 override
= self
._GetOverride
([function
.name
], document_with
=function
)
495 c
.Cblock(override
if override
is not None
496 else self
._GenerateFunction
(function
))
498 # Add the constructor.
499 c
.Sblock('API_%s(this._jsObject) {' % self
._namespace
.unix_name
)
501 # Add events to constructor.
502 for event_name
in self
._namespace
.events
:
503 c
.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
504 (event_name
, self
._namespace
.unix_name
, event_name
, event_name
))
511 def _GeneratePropertySignature(self
, prop
):
512 """Given a property, returns a signature for that property.
513 Recursively generates the signature for callbacks.
514 Returns a String for the given property.
519 void doSomething(bool x, void callback([String x]))
521 if self
._IsFunction
(prop
.type_
):
522 return self
._GenerateFunctionSignature
(prop
.type_
.function
)
523 return '%(type)s %(name)s' % {
524 'type': self
._GetDartType
(prop
.type_
),
525 'name': prop
.simple_name
528 def _GenerateFunctionSignature(self
, function
, convert_optional
=False):
529 """Given a function object, returns the signature for that function.
530 Recursively generates the signature for callbacks.
531 Returns a String for the given function.
533 If convert_optional is True, changes optional parameters to be required.
537 bool isOpen([String type])
538 void doSomething(bool x, void callback([String x]))
540 sig
= '%(return_type)s %(name)s(%(params)s)'
543 return_type
= self
._GetDartType
(function
.returns
)
548 'return_type': return_type
,
549 'name': function
.simple_name
,
550 'params': self
._GenerateParameterList
(function
.params
,
552 convert_optional
=convert_optional
)
555 def _GenerateParameterList(self
,
558 convert_optional
=False):
559 """Given a list of function parameters, generates their signature (as a
564 bool x, void callback([String x])
566 If convert_optional is True, changes optional parameters to be required.
567 Useful for callbacks, where optional parameters are treated as required.
569 # Params lists (required & optional), to be joined with commas.
570 # TODO(sashab): Don't assume optional params always come after required
575 p_sig
= self
._GeneratePropertySignature
(param
)
576 if param
.optional
and not convert_optional
:
577 params_opt
.append(p_sig
)
579 params_req
.append(p_sig
)
581 # Add the callback, if it exists.
583 c_sig
= self
._GenerateFunctionSignature
(callback
, convert_optional
=True)
584 if callback
.optional
:
585 params_opt
.append(c_sig
)
587 params_req
.append(c_sig
)
589 # Join the parameters with commas.
590 # Optional parameters have to be in square brackets, e.g.:
592 # required params | optional params | output
594 # [x, y] | [] | 'x, y'
595 # [] | [a, b] | '[a, b]'
596 # [x, y] | [a, b] | 'x, y, [a, b]'
598 params_opt
[0] = '[%s' % params_opt
[0]
599 params_opt
[-1] = '%s]' % params_opt
[-1]
600 param_sets
= [', '.join(params_req
), ', '.join(params_opt
)]
602 # The 'if p' part here is needed to prevent commas where there are no
603 # parameters of a certain type.
604 # If there are no optional parameters, this prevents a _trailing_ comma,
605 # e.g. '(x, y,)'. Similarly, if there are no required parameters, this
606 # prevents a leading comma, e.g. '(, [a, b])'.
607 return ', '.join(p
for p
in param_sets
if p
)
609 def _GetOverride(self
, key_chain
, document_with
=None):
610 """Given a list of keys, joins them with periods and searches for them in
611 the custom dart overrides.
612 If there is an override for that key, finds the override code and returns
613 the Code object. If not, returns None.
615 If document_with is not None, adds the documentation for this property
616 before the override code.
619 contents
= self
._type
_overrides
.get('.'.join(key_chain
))
623 if document_with
is not None:
624 c
.Concat(self
._GenerateDocumentation
(document_with
))
625 for line
in contents
.strip('\n').split('\n'):
629 def _AddPrefix(self
, name
):
630 """Given the name of a type, prefixes the namespace (as camelcase) and
631 returns the new name.
633 # TODO(sashab): Split the dart library into multiple files, avoiding the
634 # need for this prefixing.
636 ''.join(s
.capitalize() for s
in self
._namespace
.name
.split('.')),
639 def _IsFunction(self
, type_
):
640 """Given a model.Type, returns whether this type is a function.
642 return type_
.property_type
== PropertyType
.FUNCTION
644 def _IsSerializableObjectType(self
, type_
):
645 """Given a model.Type, returns whether this type is a serializable object.
646 Serializable objects are custom types defined in this namespace.
648 If this object is a reference to something not in this namespace, assumes
649 its a serializable object.
653 if type_
.property_type
is PropertyType
.CHOICES
:
654 return all(self
._IsSerializableObjectType
(c
) for c
in type_
.choices
)
655 if type_
.property_type
is PropertyType
.REF
:
656 if type_
.ref_type
in self
._types
:
657 return self
._IsObjectType
(self
._types
[type_
.ref_type
])
659 if (type_
.property_type
== PropertyType
.OBJECT
660 and type_
.instance_of
in self
._types
):
661 return self
._IsObjectType
(self
._types
[type_
.instance_of
])
664 def _IsObjectType(self
, type_
):
665 """Given a model.Type, returns whether this type is an object.
667 return (self
._IsSerializableObjectType
(type_
)
668 or type_
.property_type
in [PropertyType
.OBJECT
, PropertyType
.ANY
])
670 def _IsListOfSerializableObjects(self
, type_
):
671 """Given a model.Type, returns whether this type is a list of serializable
672 objects (or regular objects, if this list is treated as a type - in this
673 case, the item type was defined inline).
675 If this type is a reference to something not in this namespace, assumes
676 it is not a list of serializable objects.
678 if type_
.property_type
is PropertyType
.CHOICES
:
679 return all(self
._IsListOfSerializableObjects
(c
) for c
in type_
.choices
)
680 if type_
.property_type
is PropertyType
.REF
:
681 if type_
.ref_type
in self
._types
:
682 return self
._IsListOfSerializableObjects
(self
._types
[type_
.ref_type
])
684 return (type_
.property_type
is PropertyType
.ARRAY
and
685 (self
._IsSerializableObjectType
(type_
.item_type
)))
687 def _IsListOfBaseTypes(self
, type_
):
688 """Given a model.Type, returns whether this type is a list of base type
689 objects (PropertyType.REF types).
691 if type_
.property_type
is PropertyType
.CHOICES
:
692 return all(self
._IsListOfBaseTypes
(c
) for c
in type_
.choices
)
693 return (type_
.property_type
is PropertyType
.ARRAY
and
694 self
._IsBaseType
(type_
.item_type
))
696 def _IsBaseType(self
, type_
):
697 """Given a model.type_, returns whether this type is a base type
698 (string, number, boolean, or a list of these).
700 If type_ is a Choices object, returns True if all possible choices are base
703 # TODO(sashab): Remove 'Choices' as a base type once they are wrapped in
704 # native Dart classes.
705 if type_
.property_type
is PropertyType
.CHOICES
:
706 return all(self
._IsBaseType
(c
) for c
in type_
.choices
)
708 (self
._GetDartType
(type_
) in ['bool', 'num', 'int', 'double', 'String'])
709 or (type_
.property_type
is PropertyType
.ARRAY
710 and self
._IsBaseType
(type_
.item_type
))
713 def _GetDartType(self
, type_
):
714 """Given a model.Type object, returns its type as a Dart string.
719 prop_type
= type_
.property_type
720 if prop_type
is PropertyType
.REF
:
721 if type_
.ref_type
in self
._types
:
722 return self
._GetDartType
(self
._types
[type_
.ref_type
])
723 # TODO(sashab): If the type is foreign, it might have to be imported.
724 return StripNamespace(type_
.ref_type
)
725 elif prop_type
is PropertyType
.BOOLEAN
:
727 elif prop_type
is PropertyType
.INTEGER
:
729 elif prop_type
is PropertyType
.INT64
:
731 elif prop_type
is PropertyType
.DOUBLE
:
733 elif prop_type
is PropertyType
.STRING
:
735 elif prop_type
is PropertyType
.ENUM
:
737 elif prop_type
is PropertyType
.CHOICES
:
738 # TODO(sashab): Think of a nice way to generate code for Choices objects
741 elif prop_type
is PropertyType
.ANY
:
743 elif prop_type
is PropertyType
.OBJECT
:
744 # TODO(sashab): type_.name is the name of the function's parameter for
745 # inline types defined in functions. Think of a way to generate names
746 # for this, or remove all inline type definitions at the start.
747 if type_
.instance_of
is not None:
748 return type_
.instance_of
749 if not isinstance(type_
.parent
, Function
):
750 return self
._AddPrefix
(type_
.name
)
752 elif prop_type
is PropertyType
.FUNCTION
:
754 elif prop_type
is PropertyType
.ARRAY
:
755 return 'List<%s>' % self
._GetDartType
(type_
.item_type
)
756 elif prop_type
is PropertyType
.BINARY
:
759 raise NotImplementedError(prop_type
)