blob: deade36d839e2cbdf7b2d221aa10f9589f7d9cf4 [file] [log] [blame]
# 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.
from code import Code
from model import PropertyType
import any_helper
import cpp_util
import operator
import schema_util
class CppTypeGenerator(object):
"""Manages the types of properties and provides utilities for getting the
C++ type out of a model.Property
"""
def __init__(self, root_namespace, namespace=None, cpp_namespace=None):
"""Creates a cpp_type_generator. The given root_namespace should be of the
format extensions::api::sub. The generator will generate code suitable for
use in the given namespace.
"""
self._type_namespaces = {}
self._root_namespace = root_namespace.split('::')
self._cpp_namespaces = {}
if namespace and cpp_namespace:
self._namespace = namespace
self.AddNamespace(namespace, cpp_namespace)
def AddNamespace(self, namespace, cpp_namespace):
"""Maps a model.Namespace to its C++ namespace name. All mappings are
beneath the root namespace.
"""
for type_ in namespace.types:
if type_ in self._type_namespaces:
raise ValueError('Type %s is declared in both %s and %s' %
(type_, namespace.name, self._type_namespaces[type_].name))
self._type_namespaces[type_] = namespace
self._cpp_namespaces[namespace] = cpp_namespace
def ExpandParams(self, params):
"""Returns the given parameters with PropertyType.CHOICES parameters
expanded so that each choice is a separate parameter.
"""
expanded = []
for param in params:
if param.type_ == PropertyType.CHOICES:
for choice in param.choices.values():
expanded.append(choice)
else:
expanded.append(param)
return expanded
def GetAllPossibleParameterLists(self, params):
"""Returns all possible parameter lists for the given set of parameters.
Every combination of arguments passed to any of the PropertyType.CHOICES
parameters will have a corresponding parameter list returned here.
"""
if not params:
return [[]]
partial_parameter_lists = self.GetAllPossibleParameterLists(params[1:])
return [[param] + partial_list
for param in self.ExpandParams(params[:1])
for partial_list in partial_parameter_lists]
def GetCppNamespaceName(self, namespace):
"""Gets the mapped C++ namespace name for the given namespace relative to
the root namespace.
"""
return self._cpp_namespaces[namespace]
def GetRootNamespaceStart(self):
"""Get opening root namespace declarations.
"""
c = Code()
for namespace in self._root_namespace:
c.Append('namespace %s {' % namespace)
return c
def GetRootNamespaceEnd(self):
"""Get closing root namespace declarations.
"""
c = Code()
for namespace in reversed(self._root_namespace):
c.Append('} // %s' % namespace)
return c
def GetNamespaceStart(self):
"""Get opening self._namespace namespace declaration.
"""
return Code().Append('namespace %s {' %
self.GetCppNamespaceName(self._namespace))
def GetNamespaceEnd(self):
"""Get closing self._namespace namespace declaration.
"""
return Code().Append('} // %s' %
self.GetCppNamespaceName(self._namespace))
def GetEnumNoneValue(self, prop):
"""Gets the enum value in the given model.Property indicating no value has
been set.
"""
return '%s_NONE' % self.GetReferencedProperty(prop).unix_name.upper()
def GetEnumValue(self, prop, enum_value):
"""Gets the enum value of the given model.Property of the given type.
e.g VAR_STRING
"""
return '%s_%s' % (self.GetReferencedProperty(prop).unix_name.upper(),
cpp_util.Classname(enum_value.upper()))
def GetChoicesEnumType(self, prop):
"""Gets the type of the enum for the given model.Property.
e.g VarType
"""
return cpp_util.Classname(prop.name) + 'Type'
def GetType(self, prop, pad_for_generics=False, wrap_optional=False):
return self._GetTypeHelper(prop, pad_for_generics, wrap_optional)
def GetCompiledType(self, prop, pad_for_generics=False, wrap_optional=False):
return self._GetTypeHelper(prop, pad_for_generics, wrap_optional,
use_compiled_type=True)
def _GetTypeHelper(self, prop, pad_for_generics=False, wrap_optional=False,
use_compiled_type=False):
"""Translates a model.Property into its C++ type.
If REF types from different namespaces are referenced, will resolve
using self._type_namespaces.
Use pad_for_generics when using as a generic to avoid operator ambiguity.
Use wrap_optional to wrap the type in a scoped_ptr<T> if the Property is
optional.
Use use_compiled_type when converting from prop.type_ to prop.compiled_type.
"""
cpp_type = None
type_ = prop.type_ if not use_compiled_type else prop.compiled_type
if type_ == PropertyType.REF:
dependency_namespace = self._ResolveTypeNamespace(prop.ref_type)
if not dependency_namespace:
raise KeyError('Cannot find referenced type: %s' % prop.ref_type)
if self._namespace != dependency_namespace:
cpp_type = '%s::%s' % (self._cpp_namespaces[dependency_namespace],
schema_util.StripSchemaNamespace(prop.ref_type))
else:
cpp_type = schema_util.StripSchemaNamespace(prop.ref_type)
elif type_ == PropertyType.BOOLEAN:
cpp_type = 'bool'
elif type_ == PropertyType.INTEGER:
cpp_type = 'int'
elif type_ == PropertyType.INT64:
cpp_type = 'int64'
elif type_ == PropertyType.DOUBLE:
cpp_type = 'double'
elif type_ == PropertyType.STRING:
cpp_type = 'std::string'
elif type_ == PropertyType.ENUM:
cpp_type = cpp_util.Classname(prop.name)
elif type_ == PropertyType.ADDITIONAL_PROPERTIES:
cpp_type = 'base::DictionaryValue'
elif type_ == PropertyType.ANY:
cpp_type = any_helper.ANY_CLASS
elif type_ == PropertyType.OBJECT:
cpp_type = cpp_util.Classname(prop.name)
elif type_ == PropertyType.FUNCTION:
# Functions come into the json schema compiler as empty objects. We can
# record these as empty DictionaryValue's so that we know if the function
# was passed in or not.
cpp_type = 'base::DictionaryValue'
elif type_ == PropertyType.ARRAY:
item_type = prop.item_type
if item_type.type_ == PropertyType.REF:
item_type = self.GetReferencedProperty(item_type)
if item_type.type_ in (
PropertyType.REF, PropertyType.ANY, PropertyType.OBJECT):
cpp_type = 'std::vector<linked_ptr<%s> > '
else:
cpp_type = 'std::vector<%s> '
cpp_type = cpp_type % self.GetType(
prop.item_type, pad_for_generics=True)
elif type_ == PropertyType.BINARY:
cpp_type = 'std::string'
else:
raise NotImplementedError(type_)
# Enums aren't wrapped because C++ won't allow it. Optional enums have a
# NONE value generated instead.
if wrap_optional and prop.optional and not self.IsEnumOrEnumRef(prop):
cpp_type = 'scoped_ptr<%s> ' % cpp_type
if pad_for_generics:
return cpp_type
return cpp_type.strip()
def GenerateForwardDeclarations(self):
"""Returns the forward declarations for self._namespace.
Use after GetRootNamespaceStart. Assumes all namespaces are relative to
self._root_namespace.
"""
c = Code()
namespace_type_dependencies = self._NamespaceTypeDependencies()
for namespace in sorted(namespace_type_dependencies.keys(),
key=operator.attrgetter('name')):
c.Append('namespace %s {' % namespace.name)
for type_ in sorted(namespace_type_dependencies[namespace],
key=schema_util.StripSchemaNamespace):
type_name = schema_util.StripSchemaNamespace(type_)
if namespace.types[type_].type_ == PropertyType.STRING:
c.Append('typedef std::string %s;' % type_name)
elif namespace.types[type_].type_ == PropertyType.ARRAY:
c.Append('typedef std::vector<%(item_type)s> %(name)s;')
c.Substitute({
'name': type_name,
'item_type': self.GetType(namespace.types[type_].item_type,
wrap_optional=True)})
# Enums cannot be forward declared.
elif namespace.types[type_].type_ != PropertyType.ENUM:
c.Append('struct %s;' % type_name)
c.Append('}')
c.Concat(self.GetNamespaceStart())
for (name, type_) in self._namespace.types.items():
if not type_.functions and type_.type_ == PropertyType.OBJECT:
c.Append('struct %s;' % schema_util.StripSchemaNamespace(name))
c.Concat(self.GetNamespaceEnd())
return c
def GenerateIncludes(self):
"""Returns the #include lines for self._namespace.
"""
c = Code()
for header in sorted(
['%s/%s.h' % (dependency.source_file_dir,
self._cpp_namespaces[dependency])
for dependency in self._NamespaceTypeDependencies().keys()]):
c.Append('#include "%s"' % header)
c.Append('#include "base/string_number_conversions.h"')
if self._namespace.events:
c.Append('#include "base/json/json_writer.h"')
return c
def _ResolveTypeNamespace(self, ref_type):
"""Resolves a type, which must be explicitly qualified, to its enclosing
namespace.
"""
if ref_type in self._type_namespaces:
return self._type_namespaces[ref_type]
raise KeyError('Cannot resolve type: %s. Maybe it needs a namespace prefix '
'if it comes from another namespace?' % ref_type)
return None
def GetReferencedProperty(self, prop):
"""Returns the property a property of type REF is referring to.
If the property passed in is not of type PropertyType.REF, it will be
returned unchanged.
"""
if prop.type_ != PropertyType.REF:
return prop
return self._ResolveTypeNamespace(prop.ref_type).types.get(prop.ref_type,
None)
def IsEnumOrEnumRef(self, prop):
"""Returns true if the property is an ENUM or a reference to an ENUM.
"""
return self.GetReferencedProperty(prop).type_ == PropertyType.ENUM
def IsEnumRef(self, prop):
"""Returns true if the property is a reference to an ENUM.
"""
return (prop.type_ == PropertyType.REF and
self.GetReferencedProperty(prop).type_ == PropertyType.ENUM)
def _NamespaceTypeDependencies(self):
"""Returns a dict containing a mapping of model.Namespace to the C++ type
of type dependencies for self._namespace.
"""
dependencies = set()
for function in self._namespace.functions.values():
for param in function.params:
dependencies |= self._PropertyTypeDependencies(param)
if function.callback:
for param in function.callback.params:
dependencies |= self._PropertyTypeDependencies(param)
for type_ in self._namespace.types.values():
for prop in type_.properties.values():
dependencies |= self._PropertyTypeDependencies(prop)
for event in self._namespace.events.values():
for param in event.params:
dependencies |= self._PropertyTypeDependencies(param)
dependency_namespaces = dict()
for dependency in dependencies:
namespace = self._ResolveTypeNamespace(dependency)
if namespace != self._namespace:
dependency_namespaces.setdefault(namespace, [])
dependency_namespaces[namespace].append(dependency)
return dependency_namespaces
def _PropertyTypeDependencies(self, prop):
"""Recursively gets all the type dependencies of a property.
"""
deps = set()
if prop:
if prop.type_ == PropertyType.REF:
deps.add(prop.ref_type)
elif prop.type_ == PropertyType.ARRAY:
deps = self._PropertyTypeDependencies(prop.item_type)
elif prop.type_ == PropertyType.OBJECT:
for p in prop.properties.values():
deps |= self._PropertyTypeDependencies(p)
return deps
def GeneratePropertyValues(self, property, line, nodoc=False):
"""Generates the Code to display all value-containing properties.
"""
c = Code()
if not nodoc:
c.Comment(property.description)
if property.has_value:
c.Append(line % {
"type": self._GetPrimitiveType(property.type_),
"name": property.name,
"value": property.value
})
else:
has_child_code = False
c.Sblock('namespace %s {' % property.name)
for child_property in property.properties.values():
child_code = self.GeneratePropertyValues(
child_property,
line,
nodoc=nodoc)
if child_code:
has_child_code = True
c.Concat(child_code)
c.Eblock('} // namespace %s' % property.name)
if not has_child_code:
c = None
return c
def _GetPrimitiveType(self, type_):
"""Like |GetType| but only accepts and returns C++ primitive types.
"""
if type_ == PropertyType.BOOLEAN:
return 'bool'
elif type_ == PropertyType.INTEGER:
return 'int'
elif type_ == PropertyType.DOUBLE:
return 'double'
elif type_ == PropertyType.STRING:
return 'char*'
raise Exception(type_ + ' is not primitive')