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

Unified 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: Cleaned up dart_generator, and added wrappers for callbacks 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 side-by-side diff with in-line comments
Download patch
Index: tools/json_schema_compiler/dart_generator.py
diff --git a/tools/json_schema_compiler/dart_generator.py b/tools/json_schema_compiler/dart_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3b1135d1ae2f9fa97a2ff27afcf0cbf437c6cf4
--- /dev/null
+++ b/tools/json_schema_compiler/dart_generator.py
@@ -0,0 +1,569 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""
+Generator language component for compiler.py that adds Dart language support.
+
+Pass 'dart' with the -l flag to compiler.py to activate the use of this library.
+"""
+
+from code import Code
+from model import *
+from schema_util import *
+
+import os
+from datetime import datetime
+
+LICENSE = ("""
+// Copyright (c) %s, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.""" %
+ datetime.now().year)
+
+class DartGenerator(object):
+ """A .dart generator for a namespace.
+ """
+
+ def __init__(self, namespace, dart_overrides_dir=None):
+ self._namespace = namespace
+ self._types = namespace.types
+
+ # Build a dictionary of Type Name --> Custom Dart code.
+ self._custom_dart = {}
not at google - send to devlin 2013/01/31 02:08:48 _type_overrides?
sashab 2013/01/31 04:41:40 Done.
+ if dart_overrides_dir is not None:
+ for filename in os.listdir(dart_overrides_dir):
+ if filename.startswith(namespace.unix_name):
+ with open(os.path.join(dart_overrides_dir, filename)) as f:
+ # Split off the namespace and file extension, leaving just the type.
+ type_path = '.'.join(filename.split('.')[1:-1])
+ self._custom_dart[type_path] = f.read()
+
+ def Generate(self):
+ """Generates a Code object with the .dart for the entire namespace.
+ """
+ c = Code()
+ (c.Append(LICENSE)
+ .Append()
+ .Append('part of chrome;'))
+
+ if self._types:
+ (c.Append()
+ .Append('/**')
+ .Append(' * Types')
+ .Append(' */')
+ )
+ for type_name in self._types:
+ c.Concat(self._GenerateType(self._types[type_name]))
+
+ if self._namespace.events:
+ (c.Append()
+ .Append('/**')
+ .Append(' * Events')
+ .Append(' */')
+ )
+ for event_name in self._namespace.events:
+ c.Concat(self._GenerateEvent(self._namespace.events[event_name]))
+
+ (c.Append()
+ .Append('/**')
+ .Append(' * Functions')
+ .Append(' */')
+ )
+ c.Concat(self._GenerateMainClass())
+
+ return c
+
+ def _GenerateType(self, type_):
+ """Given a Type object, returns the Code with the .dart for this
+ type's definition.
+
+ Assumes this type is a Parameter Type (creatable by user), and creates an
+ object that extends ChromeObject. All parameters are specifiable as named
+ arguments in the constructor, and all methods are wrapped with getters and
+ setters that hide the JS() implementation.
+ """
+ c = Code()
+ (c.Append()
+ .Concat(self._GenerateDocumentation(type_))
+ .Sblock('class %(type_name)s extends ChromeObject {')
+ )
+
+ # Check whether this type has function members. If it does, don't allow
+ # public construction.
+ for p in type_.properties.values():
+ print p.name, self._IsFunction(p)
+ add_public_constructor = all(not self._IsFunction(p)
+ for p in type_.properties.values())
+ constructor_fields = [self._GeneratePropertySignature(p)
+ for p in type_.properties.values()]
+
+ if add_public_constructor:
+ (c.Append('/*')
+ .Append(' * Public constructor')
+ .Append(' */')
+ .Sblock('%(type_name)s({%(constructor_fields)s}) {')
+ )
+
+ for prop_name in type_.properties:
+ c.Append('this.%s = %s;' % (prop_name, prop_name))
+ (c.Eblock('}')
+ .Append()
+ )
+
+ (c.Append('/*')
+ .Append(' * Private constructor')
+ .Append(' */')
+ .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
+ )
+
+ # Add an accessor (getter & setter) for each property.
+ properties = [p for p in type_.properties.values()
+ if not self._IsFunction(p)]
+ if properties:
+ (c.Append()
+ .Append('/*')
+ .Append(' * Public accessors')
+ .Append(' */')
+ )
+ for prop in properties:
+ type_name = self._GetPropertyType(prop)
+ prop_is_base_type = self._IsBaseType(prop)
+
+ # Check for custom dart for this whole property.
+ if not self._TryAppendOverride(c, type_, prop, add_doc=True):
+ # Add the getter.
+ if not self._TryAppendOverride(c, type_, prop, '.get', add_doc=True):
not at google - send to devlin 2013/01/31 02:08:48 key_suffix='.get'
sashab 2013/01/31 04:41:40 Done.
+ # Add the documentation for this property.
+ (c.Append()
+ .Concat(self._GenerateDocumentation(prop))
+ )
+
+ # TODO(sashab): Serialize generic Dart objects differently.
+ if prop_is_base_type or self._IsObjectType(prop):
+ c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
+ (type_name, prop.name, type_name, prop.name))
+ elif prop.type_.property_type == PropertyType.REF:
+ c.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
+ "this._jsObject));"
+ % (type_name, prop.name, type_name, prop.name))
+ else:
+ raise Exception(
+ "Could not generate wrapper for %s.%s: unserializable type %s" %
+ (type_.name, prop.name, type_name)
+ )
+
+ # Add the setter.
+ if not self._TryAppendOverride(c, type_, prop, '.set'):
not at google - send to devlin 2013/01/31 02:08:48 key_suffix='.set'
sashab 2013/01/31 04:41:40 Done.
+ wrapped_name = prop.name
+ if not prop_is_base_type:
+ wrapped_name = 'convertArgument(%s)' % prop.name
+
+ (c.Append()
+ .Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name))
+ .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
+ (prop.name, wrapped_name))
+ .Eblock("}")
+ )
+
+ # Now add all the methods.
+ methods = [t for t in type_.properties.values()
+ if self._IsFunction(t)]
not at google - send to devlin 2013/01/31 02:08:48 might fit on 1 line?
sashab 2013/01/31 04:41:40 Done.
+ if methods:
+ (c.Append()
+ .Append('/*')
+ .Append(' * Methods')
+ .Append(' */')
+ )
+ for prop in methods:
+ c.Concat(self._GenerateFunction(prop.type_.function))
+
+ (c.Eblock('}')
+ .Substitute({
+ 'type_name': type_.simple_name,
+ 'constructor_fields': ', '.join(constructor_fields)
+ })
+ )
+
+ return c
+
+ def _GenerateDocumentation(self, prop):
+ """Given an object, generates the documentation for this object (as a
+ code string) and returns the Code object.
+
+ Returns an empty code object if the object has no documentation.
+
+ Uses triple-quotes for the string.
+ """
+ c = Code()
+ if prop.description is not None:
+ for line in prop.description.split('\n'):
+ c.Comment(line, comment_prefix='/// ')
+ return c
+
+
+ def _GenerateFunction(self, f):
+ """Returns the Code object for the given function.
+ """
+ c = Code()
+ (c.Append()
+ .Concat(self._GenerateDocumentation(f))
+ )
+
+ if not self._NeedsProxiedCallback(f):
+ c.Append("%s => %s;" % (self._GenerateFunctionSignature(f),
+ self._GenerateProxyCall(f)))
+ return c
+
+ c.Sblock("%s {" % self._GenerateFunctionSignature(f))
+
+ # Define the proxied callback.
+ proxied_params = []
+ for p in f.callback.params:
+ if self._IsBaseType(p):
+ proxied_params.append(p.name)
+ elif self._IsObjectType(p):
+ proxied_params.append("new %s._proxy(%s)" % (
+ self._GetPropertyType(p), p.name))
+ else:
+ raise Exception(
+ "Cannot automatically create proxy; can't wrap %s.%s, type %s" % (
+ f.name, p.name, p.type_.name))
+
+ (c.Sblock("void __proxy_callback(%s) {" % ', '.join(p.name for p in
+ f.callback.params))
+ .Sblock("if (?%s)" % f.callback.name)
+ .Append("%s(%s);" % (f.callback.name, ', '.join(proxied_params)))
+ .Eblock(None)
not at google - send to devlin 2013/01/31 02:08:48 like I said earlier, just make None the default? T
sashab 2013/01/31 04:41:40 Done.
+ .Eblock("}")
+ .Append("%s;" % self._GenerateProxyCall(f))
+ .Eblock("}")
+ )
+ return c
+
+ def _NeedsProxiedCallback(self, f):
+ """Given a function, returns True if this function's callback needs to be
+ proxied, False if not.
+
+ Function callbacks need to be proxied if they have at least one
+ non-base-type parameter.
+ """
+ return f.callback and any(
+ not self._IsBaseType(p) for p in f.callback.params)
+
+ def _GenerateProxyCall(self, function, call_target='this._jsObject'):
+ """Given a function, generates the code to call that function via JS().
+ Returns a string.
+
+ |call_target| is the name of the object to call the function on. The default
+ is this._jsObject.
+
+ e.g.
+ JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
+ JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
+ """
+ n_params = len(function.params)
+ if function.callback:
+ n_params += 1
+
+ params = ["'%s'" % self._GetPropertyType(function.returns),
+ "'#.%s(%s)'" % (function.name,
+ ', '.join(['#'] * n_params)),
+ call_target]
+
+ for param in function.params:
+ if not self._IsBaseType(param):
+ params.append('convertArgument(%s)' % param.name)
+ else:
+ params.append(param.name)
+ if function.callback:
+ # If this isn't a base type, we need a proxied callback.
+ callback_name = function.callback.name
+ if self._NeedsProxiedCallback(function):
+ callback_name = "__proxy_callback"
+ params.append('convertDartClosureToJS(%s, %s)' % (callback_name,
+ len(function.callback.params)))
+
+ return 'JS(%s)' % ', '.join(params)
+
+ def _GenerateEvent(self, event):
+ """Given a Function object, returns the Code with the .dart for this event,
+ represented by the function.
+
+ All events extend the Event base type.
+ """
+ c = Code()
+
+ # Add documentation for this event.
+ (c.Append()
+ .Concat(self._GenerateDocumentation(event))
+ .Sblock('class Event_%(event_name)s extends Event {')
+ )
+
+ # Override Event callback type definitions.
+ for ret_type, event_func in (('void', 'addListener'),
+ ('void', 'removeListener'),
+ ('bool', 'hasListener')):
+
+ param_list = self._GenerateParameterList(event.params, event.callback,
+ allow_optional = False)
+
+ c.Append('%s %s(void callback(%s)) => super.%s(callback);' %
+ (ret_type, event_func, param_list, event_func))
+
+ # Generate the constructor.
+ (c.Append()
+ .Append('Event_%(event_name)s(jsObject) : '
+ 'super(jsObject, %(param_num)d);')
+ )
+
+ (c.Eblock('}')
+ .Substitute({
+ 'event_name': self._namespace.unix_name + '_' + event.name,
+ 'param_num': len(event.params)
+ })
+ )
+
+ return c
+
+ def _GenerateMainClass(self):
+ """Generates the main class for this file, which links to all functions
+ and events.
+
+ Returns a code object.
+ """
+ c = Code()
+ (c.Append()
+ .Sblock('class API_%s {' % self._namespace.unix_name)
+ .Append('/*')
+ .Append(' * API connection')
+ .Append(' */')
+ .Append('Object _jsObject;')
+ )
+
+ # Add events.
+ if self._namespace.events:
+ (c.Append()
+ .Append('/*')
+ .Append(' * Events')
+ .Append(' */')
+ )
+ for event_name in self._namespace.events:
+ c.Append('Event_%s_%s %s;' % (self._namespace.unix_name, event_name,
+ event_name))
+
+ # Add functions.
+ if self._namespace.functions:
+ (c.Append()
+ .Append('/*')
+ .Append(' * Functions')
+ .Append(' */')
+ )
+ for function in self._namespace.functions.values():
+ c.Concat(self._GenerateFunction(function))
+
+ # Add the constructor.
+ (c.Append()
+ .Sblock('API_%s(this._jsObject) {' % self._namespace.unix_name)
+ )
+
+ # Add events to constructor.
+ for event_name in self._namespace.events:
+ c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
+ (event_name, self._namespace.unix_name, event_name, event_name))
+
+ (c.Eblock('}')
+ .Eblock('}')
+ )
+ return c
+
+ def _GeneratePropertySignature(self, prop):
+ """Given a property, returns a signature for that property.
+ Recursively generates the signature for callbacks.
+ Returns a String for the given property.
+
+ e.g.
+ bool x
+ void onClosed()
+ void doSomething(bool x, void callback([String x]))
+ """
+ if self._IsFunction(prop):
+ return self._GenerateFunctionSignature(prop.type_.function)
+ return '%(type)s %(name)s' % {
+ 'type': self._GetPropertyType(prop),
+ 'name': prop.simple_name
+ }
+
+ def _GenerateFunctionSignature(self, function):
+ """Given a function object, returns the signature for that function.
+ Recursively generates the signature for callbacks.
+ Returns a String for the given function.
+
+ If prepend_this is True, adds "this." to the function's name.
+
+ e.g.
+ void onClosed()
+ bool isOpen([String type])
+ void doSomething(bool x, void callback([String x]))
+
+ e.g. If prepend_this is True:
+ void this.onClosed()
+ bool this.isOpen([String type])
+ void this.doSomething(bool x, void callback([String x]))
+ """
+ sig = '%(return_type)s %(name)s(%(params)s)'
+
+ if function.returns:
+ return_type = self._GetPropertyType(function.returns)
+ else:
+ return_type = 'void'
+
+ return sig % {
+ 'return_type': return_type,
+ 'name': function.simple_name,
+ 'params': self._GenerateParameterList(function.params,
+ function.callback)
+ }
+
+ def _GenerateParameterList(self, params, callback=None,
+ allow_optional=True):
not at google - send to devlin 2013/01/31 02:08:48 if it can't fit on 1 line then align parameters ve
sashab 2013/01/31 04:41:40 Done.
+ """Given a list of function parameters, generates their signature (as a
+ string).
+
+ e.g.
+ [String type]
+ bool x, void callback([String x])
+
+ If allow_optional is False, ignores optional parameters. Useful for
+ callbacks, where optional parameters are not used.
+ """
+ # Params lists (required & optional), to be joined with ,'s.
not at google - send to devlin 2013/01/31 02:08:48 commas?
sashab 2013/01/31 04:41:40 Done.
+ # FIXME(sashab): assuming all optional params come after required ones
not at google - send to devlin 2013/01/31 02:08:48 I can't parse this sentence. Should it be like "Do
sashab 2013/01/31 04:41:40 Haha, yes, you're right. Fixed
+ params_req = []
+ params_opt = []
+ for param in params:
+ p_sig = self._GeneratePropertySignature(param)
+ if allow_optional and param.optional:
+ params_opt.append(p_sig)
+ else:
+ params_req.append(p_sig)
not at google - send to devlin 2013/01/31 02:08:48 this isn't ignoring optional parameters, it's maki
sashab 2013/01/31 04:41:40 Sorry, comment changed. And I also changed the nam
+
+ # Add the callback, if it exists.
+ if callback:
+ c_sig = self._GenerateFunctionSignature(callback)
+ if callback.optional:
+ params_opt.append(c_sig)
+ else:
+ params_req.append(c_sig)
+
+ # Join the parameters, wrapping the optional params in square brackets.
+ if params_opt:
+ params_opt[0] = '[%s' % params_opt[0]
+ params_opt[-1] = '%s]' % params_opt[-1]
not at google - send to devlin 2013/01/31 02:08:48 a bit bizarre but much better, and I can't think o
sashab 2013/01/31 04:41:40 Done. Or should this rather be in the function sig
not at google - send to devlin 2013/02/02 00:45:47 What you have is good.
+ param_sets = [', '.join(params_req),
+ ', '.join(params_opt)]
not at google - send to devlin 2013/01/31 02:08:48 single line
sashab 2013/01/31 04:41:40 Done.
+
+ return ', '.join(p for p in param_sets if p)
not at google - send to devlin 2013/01/31 02:08:48 why the "if p"? I.e. why can't it be ', '.join(par
sashab 2013/01/31 04:41:40 This is part of the bizarre logic. If there are no
+
+ def _TryAppendOverride(self, c, type_, prop, key_suffix='', add_doc=False):
not at google - send to devlin 2013/01/31 02:08:48 TryAppendOverride is a bit of an awkward name. Per
sashab 2013/01/31 04:41:40 It returns True if it appended the override, False
+ """Given a particular type and property to find in the custom dart
+ overrides, checks whether there is an override for that key.
+ If there is, adds a newline, appends the override code, and returns True.
not at google - send to devlin 2013/01/31 02:08:48 "adds a newline" -> doesn't seem like this should
sashab 2013/01/31 04:41:40 Ok, the reason this is needed is in case the overr
+ If not, returns False.
+
+ |key_suffix| will be added to the end of the key before searching, e.g.
+ '.set' or '.get' can be used for setters and getters respectively.
+
+ If add_doc is given, adds the documentation for this property before the
+ override code.
+ If the override code is empty, does nothing (and does not add
+ documentation), but returns True (treats it as a valid override).
+ """
+ contents = self._custom_dart.get('%s.%s%s' % (type_.name, prop.name,
+ key_suffix))
+ if contents is None:
+ return False
+
not at google - send to devlin 2013/01/31 02:08:48 maybe early-exit here too, if it's empty: if cont
sashab 2013/01/31 04:41:40 Good idea! :D
+ if contents.strip():
+ if prop is not None:
not at google - send to devlin 2013/01/31 02:08:48 although TBH I find this idiom a bit strange. Peop
sashab 2013/01/31 04:41:40 It's a little tough, because its unclear: does put
+ (c.Append()
+ .Concat(self._GenerateDocumentation(prop))
+ )
+ else:
+ c.Append()
not at google - send to devlin 2013/01/31 02:08:48 seem to be always appending here, so pull out abov
sashab 2013/01/31 04:41:40 Thanks, fixed this one; see comment above about ca
+ for line in contents.split('\n'):
+ c.Append(line)
+ return True
+
+ def _IsFunction(self, prop):
+ """Given a model.Property, returns whether this type is a function.
+ """
+ return prop.type_.property_type == PropertyType.FUNCTION
+
+ def _IsObjectType(self, prop):
+ """Given a model.Property, returns whether this type is an object.
+ """
+ return prop.type_.property_type in [PropertyType.OBJECT, PropertyType.ANY]
+
+ def _IsBaseType(self, prop):
+ """Given a model.Property, returns whether this type is a base type
+ (string, number or boolean).
+ """
+ return (self._GetPropertyType(prop) in
+ ['bool', 'num', 'int', 'double', 'String'])
+
+ def _GetPropertyType(self, prop, container_type=False):
not at google - send to devlin 2013/01/31 02:08:48 call this _GetDartType and pass in the type rather
sashab 2013/01/31 04:41:40 Yup, was going to do this before. Made a wrapper _
+ """Given a model.Property object, returns its type as a Dart string.
+
+ If container_type is True, returns the type of the objects prop is
+ containing, rather than the type of prop itself.
+ """
+ if prop is None:
+ return 'void'
+
+ if container_type:
+ type_ = prop.type_.item_type
+ else:
+ type_ = prop.type_
+ prop_type = type_.property_type
+
+ if prop_type is None:
+ return 'void'
+ elif prop_type is PropertyType.REF:
+ if GetNamespace(type_.ref_type) == self._namespace.name:
+ # This type is in this namespace; just use its base name.
+ return GetBaseNamespace(type_.ref_type)
+ else:
+ # TODO(sashab): Work out how to import this foreign type.
+ return type_.ref_type
+ elif prop_type is PropertyType.BOOLEAN:
+ return 'bool'
+ elif prop_type is PropertyType.INTEGER:
+ return 'int'
+ elif prop_type is PropertyType.INT64:
+ return 'num'
+ elif prop_type is PropertyType.DOUBLE:
+ return 'double'
+ elif prop_type is PropertyType.STRING:
+ return 'String'
+ elif prop_type is PropertyType.ENUM:
+ return 'String'
+ elif prop_type is PropertyType.CHOICES:
+ # TODO: What is a Choices type? Is it closer to a Map Dart object?
+ return 'Object'
not at google - send to devlin 2013/01/31 02:08:48 I wrote a long thing in response to what you said,
sashab 2013/01/31 04:41:40 'Object' is the equivalent of untyped. See other c
+ elif prop_type is PropertyType.ANY:
+ return 'Object'
+ elif prop_type is PropertyType.OBJECT:
+ # TODO(sashab): Work out a mapped type name.
+ return type_.instance_of or 'Object'
+ elif prop_type is PropertyType.FUNCTION:
+ return 'Function'
+ elif prop_type is PropertyType.ARRAY:
+ if type_.item_type:
not at google - send to devlin 2013/01/31 02:08:48 there should always be a list type, this check sho
sashab 2013/01/31 04:41:40 After investigating, you're right - its invalid ID
+ return 'List<%s>' % self._GetPropertyType(prop, container_type=True)
+ else:
+ return 'List'
+ elif prop_type is PropertyType.BINARY:
+ return 'String'
+ else:
+ raise NotImplementedError(prop_type)
+

Powered by Google App Engine
This is Rietveld 408576698