blob: 3d307a9f0be77d775046ed226c764bf824da0b6b [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 cpp_util
from json_parse import OrderedDict
import schema_util
class _TypeDependency(object):
"""Contains information about a dependency a namespace has on a type: the
type's model, and whether that dependency is "hard" meaning that it cannot be
forward declared.
"""
def __init__(self, type_, hard=False):
self.type_ = type_
self.hard = hard
def GetSortKey(self):
return '%s.%s' % (self.type_.namespace.name, self.type_.name)
class CppTypeGenerator(object):
"""Manages the types of properties and provides utilities for getting the
C++ type out of a model.Property
"""
def __init__(self, model, schema_loader, default_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 model's namespace.
"""
self._default_namespace = default_namespace
if self._default_namespace is None:
self._default_namespace = model.namespaces.values()[0]
self._schema_loader = schema_loader
def GetEnumNoneValue(self, type_):
"""Gets the enum value in the given model.Property indicating no value has
been set.
"""
return '%s_NONE' % self.FollowRef(type_).unix_name.upper()
def GetEnumLastValue(self, type_):
"""Gets the enum value in the given model.Property indicating the last value
for the type.
"""
return '%s_LAST' % self.FollowRef(type_).unix_name.upper()
def GetEnumValue(self, type_, enum_value):
"""Gets the enum value of the given model.Property of the given type.
e.g VAR_STRING
"""
value = cpp_util.Classname(enum_value.name.upper())
prefix = (type_.cpp_enum_prefix_override or
self.FollowRef(type_).unix_name)
value = '%s_%s' % (prefix.upper(), value)
# To avoid collisions with built-in OS_* preprocessor definitions, we add a
# trailing slash to enum names that start with OS_.
if value.startswith("OS_"):
value += "_"
return value
def GetCppType(self, type_, is_ptr=False, is_in_container=False):
"""Translates a model.Property or model.Type into its C++ type.
If REF types from different namespaces are referenced, will resolve
using self._schema_loader.
Use |is_ptr| if the type is optional. This will wrap the type in a
scoped_ptr if possible (it is not possible to wrap an enum).
Use |is_in_container| if the type is appearing in a collection, e.g. a
std::vector or std::map. This will wrap it in the correct type with spacing.
"""
cpp_type = None
if type_.property_type == PropertyType.REF:
ref_type = self._FindType(type_.ref_type)
if ref_type is None:
raise KeyError('Cannot find referenced type: %s' % type_.ref_type)
cpp_type = self.GetCppType(ref_type)
elif type_.property_type == PropertyType.BOOLEAN:
cpp_type = 'bool'
elif type_.property_type == PropertyType.INTEGER:
cpp_type = 'int'
elif type_.property_type == PropertyType.INT64:
cpp_type = 'int64_t'
elif type_.property_type == PropertyType.DOUBLE:
cpp_type = 'double'
elif type_.property_type == PropertyType.STRING:
cpp_type = 'std::string'
elif type_.property_type in (PropertyType.ENUM,
PropertyType.OBJECT,
PropertyType.CHOICES):
if self._default_namespace is type_.namespace:
cpp_type = cpp_util.Classname(type_.name)
else:
cpp_namespace = cpp_util.GetCppNamespace(
type_.namespace.environment.namespace_pattern,
type_.namespace.unix_name)
cpp_type = '%s::%s' % (cpp_namespace,
cpp_util.Classname(type_.name))
elif type_.property_type == PropertyType.ANY:
cpp_type = 'base::Value'
elif type_.property_type == PropertyType.FUNCTION:
# Functions come into the json schema compiler as empty objects. We can
# record these as empty DictionaryValues so that we know if the function
# was passed in or not.
cpp_type = 'base::DictionaryValue'
elif type_.property_type == PropertyType.ARRAY:
item_cpp_type = self.GetCppType(type_.item_type, is_in_container=True)
cpp_type = 'std::vector<%s>' % cpp_util.PadForGenerics(item_cpp_type)
elif type_.property_type == PropertyType.BINARY:
cpp_type = 'std::vector<char>'
else:
raise NotImplementedError('Cannot get type of %s' % type_.property_type)
# HACK: optional ENUM is represented elsewhere with a _NONE value, so it
# never needs to be wrapped in pointer shenanigans.
# TODO(kalman): change this - but it's an exceedingly far-reaching change.
if not self.FollowRef(type_).property_type == PropertyType.ENUM:
if is_in_container and (is_ptr or not self.IsCopyable(type_)):
cpp_type = 'linked_ptr<%s>' % cpp_util.PadForGenerics(cpp_type)
elif is_ptr:
cpp_type = 'scoped_ptr<%s>' % cpp_util.PadForGenerics(cpp_type)
return cpp_type
def IsCopyable(self, type_):
return not (self.FollowRef(type_).property_type in (PropertyType.ANY,
PropertyType.ARRAY,
PropertyType.OBJECT,
PropertyType.CHOICES))
def GenerateForwardDeclarations(self):
"""Returns the forward declarations for self._default_namespace.
"""
c = Code()
for namespace, deps in self._NamespaceTypeDependencies().iteritems():
filtered_deps = [
dep for dep in deps
# Add more ways to forward declare things as necessary.
if (not dep.hard and
dep.type_.property_type in (PropertyType.CHOICES,
PropertyType.OBJECT))]
if not filtered_deps:
continue
cpp_namespace = cpp_util.GetCppNamespace(
namespace.environment.namespace_pattern,
namespace.unix_name)
c.Concat(cpp_util.OpenNamespace(cpp_namespace))
for dep in filtered_deps:
c.Append('struct %s;' % dep.type_.name)
c.Concat(cpp_util.CloseNamespace(cpp_namespace))
return c
def GenerateIncludes(self, include_soft=False):
"""Returns the #include lines for self._default_namespace.
"""
c = Code()
for namespace, dependencies in self._NamespaceTypeDependencies().items():
for dependency in dependencies:
if dependency.hard or include_soft:
c.Append('#include "%s/%s.h"' % (namespace.source_file_dir,
namespace.unix_name))
return c
def _FindType(self, full_name):
"""Finds the model.Type with name |qualified_name|. If it's not from
|self._default_namespace| then it needs to be qualified.
"""
namespace = self._schema_loader.ResolveType(full_name,
self._default_namespace)
if namespace is None:
raise KeyError('Cannot resolve type %s. Maybe it needs a prefix '
'if it comes from another namespace?' % full_name)
return namespace.types[schema_util.StripNamespace(full_name)]
def FollowRef(self, type_):
"""Follows $ref link of types to resolve the concrete type a ref refers to.
If the property passed in is not of type PropertyType.REF, it will be
returned unchanged.
"""
if type_.property_type != PropertyType.REF:
return type_
return self.FollowRef(self._FindType(type_.ref_type))
def _NamespaceTypeDependencies(self):
"""Returns a dict ordered by namespace name containing a mapping of
model.Namespace to every _TypeDependency for |self._default_namespace|,
sorted by the type's name.
"""
dependencies = set()
for function in self._default_namespace.functions.values():
for param in function.params:
dependencies |= self._TypeDependencies(param.type_,
hard=not param.optional)
if function.callback:
for param in function.callback.params:
dependencies |= self._TypeDependencies(param.type_,
hard=not param.optional)
for type_ in self._default_namespace.types.values():
for prop in type_.properties.values():
dependencies |= self._TypeDependencies(prop.type_,
hard=not prop.optional)
for event in self._default_namespace.events.values():
for param in event.params:
dependencies |= self._TypeDependencies(param.type_,
hard=not param.optional)
# Make sure that the dependencies are returned in alphabetical order.
dependency_namespaces = OrderedDict()
for dependency in sorted(dependencies, key=_TypeDependency.GetSortKey):
namespace = dependency.type_.namespace
if namespace is self._default_namespace:
continue
if namespace not in dependency_namespaces:
dependency_namespaces[namespace] = []
dependency_namespaces[namespace].append(dependency)
return dependency_namespaces
def _TypeDependencies(self, type_, hard=False):
"""Gets all the type dependencies of a property.
"""
deps = set()
if type_.property_type == PropertyType.REF:
deps.add(_TypeDependency(self._FindType(type_.ref_type), hard=hard))
elif type_.property_type == PropertyType.ARRAY:
# Non-copyable types are not hard because they are wrapped in linked_ptrs
# when generated. Otherwise they're typedefs, so they're hard (though we
# could generate those typedefs in every dependent namespace, but that
# seems weird).
deps = self._TypeDependencies(type_.item_type,
hard=self.IsCopyable(type_.item_type))
elif type_.property_type == PropertyType.CHOICES:
for type_ in type_.choices:
deps |= self._TypeDependencies(type_, hard=self.IsCopyable(type_))
elif type_.property_type == PropertyType.OBJECT:
for p in type_.properties.values():
deps |= self._TypeDependencies(p.type_, hard=not p.optional)
return deps
def GeneratePropertyValues(self, prop, line, nodoc=False):
"""Generates the Code to display all value-containing properties.
"""
c = Code()
if not nodoc:
c.Comment(prop.description)
if prop.value is not None:
cpp_type = self.GetCppType(prop.type_)
cpp_value = prop.value
if cpp_type == 'std::string':
cpp_value = '"%s"' % cpp_type
c.Append(line % {
"type": cpp_type,
"name": prop.name,
"value": cpp_value
})
else:
has_child_code = False
c.Sblock('namespace %s {' % prop.name)
for child_property in prop.type_.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' % prop.name)
if not has_child_code:
c = None
return c