blob: e47b9540e34654e1dceb617e8a4d2d3e7f571d42 [file] [log] [blame]
# Copyright 2013 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.
import collections
import datetime
import os.path
import sys
import code
import cpp_util
import model
try:
import jinja2
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',
'third_party'))
import jinja2
class _PpapiGeneratorBase(object):
"""A base class for ppapi generators.
Implementations should set TEMPLATE_NAME to a string containing the name of
the template file without its extension. The template will be rendered with
the following symbols available:
name: A string containing the name of the namespace.
enums: A list of enums within the namespace.
types: A list of types within the namespace, sorted such that no element
depends on an earlier element.
events: A dict of events within the namespace.
functions: A dict of functions within the namespace.
year: An int containing the current year.
source_file: The name of the input file.
"""
def __init__(self, namespace):
self._namespace = namespace
self._required_types = {}
self._array_types = set()
self._optional_types = set()
self._optional_array_types = set()
self._dependencies = collections.OrderedDict()
self._types = []
self._enums = []
self.jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__),
'templates', 'ppapi')))
self._SetupFilters()
self._ResolveTypeDependencies()
def _SetupFilters(self):
self.jinja_environment.filters.update({
'ppapi_type': self.ToPpapiType,
'classname': cpp_util.Classname,
'enum_value': self.EnumValueName,
'return_type': self.GetFunctionReturnType,
'format_param_type': self.FormatParamType,
'needs_optional': self.NeedsOptional,
'needs_array': self.NeedsArray,
'needs_optional_array': self.NeedsOptionalArray,
'has_array_outs': self.HasArrayOuts,
})
def Render(self, template_name, values):
generated_code = code.Code()
template = self.jinja_environment.get_template(
'%s.template' % template_name)
generated_code.Append(template.render(values))
return generated_code
def Generate(self):
"""Generates a Code object for a single namespace."""
return self.Render(self.TEMPLATE_NAME, {
'name': self._namespace.name,
'enums': self._enums,
'types': self._types,
'events': self._namespace.events,
'functions': self._namespace.functions,
# TODO(sammc): Don't change years when regenerating existing output files.
'year': datetime.date.today().year,
'source_file': self._namespace.source_file,
})
def _ResolveTypeDependencies(self):
"""Calculates the transitive closure of the types in _required_types.
Returns a tuple containing the list of struct types and the list of enum
types. The list of struct types is ordered such that no type depends on a
type later in the list.
"""
if self._namespace.functions:
for function in self._namespace.functions.itervalues():
self._FindFunctionDependencies(function)
if self._namespace.events:
for event in self._namespace.events.itervalues():
self._FindFunctionDependencies(event)
resolved_types = set()
while resolved_types < set(self._required_types):
for typename in sorted(set(self._required_types) - resolved_types):
type_ = self._required_types[typename]
self._dependencies.setdefault(typename, set())
for member in type_.properties.itervalues():
self._RegisterDependency(member, self._NameComponents(type_))
resolved_types.add(typename)
while self._dependencies:
for name, deps in self._dependencies.items():
if not deps:
if (self._required_types[name].property_type ==
model.PropertyType.ENUM):
self._enums.append(self._required_types[name])
else:
self._types.append(self._required_types[name])
for deps in self._dependencies.itervalues():
deps.discard(name)
del self._dependencies[name]
break
else:
raise ValueError('Circular dependency %s' % self._dependencies)
def _FindFunctionDependencies(self, function):
for param in function.params:
self._RegisterDependency(param, None)
if function.callback:
for param in function.callback.params:
self._RegisterDependency(param, None)
if function.returns:
self._RegisterTypeDependency(function.returns, None, False, False)
def _RegisterDependency(self, member, depender):
self._RegisterTypeDependency(member.type_, depender, member.optional, False)
def _RegisterTypeDependency(self, type_, depender, optional, array):
if type_.property_type == model.PropertyType.ARRAY:
self._RegisterTypeDependency(type_.item_type, depender, optional, True)
elif type_.property_type == model.PropertyType.REF:
self._RegisterTypeDependency(self._namespace.types[type_.ref_type],
depender, optional, array)
elif type_.property_type in (model.PropertyType.OBJECT,
model.PropertyType.ENUM):
name_components = self._NameComponents(type_)
self._required_types[name_components] = type_
if depender:
self._dependencies.setdefault(depender, set()).add(
name_components)
if array:
self._array_types.add(name_components)
if optional:
self._optional_array_types.add(name_components)
elif optional:
self._optional_types.add(name_components)
@staticmethod
def _NameComponents(entity):
"""Returns a tuple of the fully-qualified name of an entity."""
names = []
while entity:
if (not isinstance(entity, model.Type) or
entity.property_type != model.PropertyType.ARRAY):
names.append(entity.name)
entity = entity.parent
return tuple(reversed(names[:-1]))
def ToPpapiType(self, type_, array=False, optional=False):
"""Returns a string containing the name of the Pepper C type for |type_|.
If array is True, returns the name of an array of |type_|. If optional is
True, returns the name of an optional |type_|. If both array and optional
are True, returns the name of an optional array of |type_|.
"""
if isinstance(type_, model.Function) or type_.property_type in (
model.PropertyType.OBJECT, model.PropertyType.ENUM):
return self._FormatPpapiTypeName(
array, optional, '_'.join(
cpp_util.Classname(s) for s in self._NameComponents(type_)),
namespace=cpp_util.Classname(self._namespace.name))
elif type_.property_type == model.PropertyType.REF:
return self.ToPpapiType(self._namespace.types[type_.ref_type],
optional=optional, array=array)
elif type_.property_type == model.PropertyType.ARRAY:
return self.ToPpapiType(type_.item_type, array=True,
optional=optional)
elif type_.property_type == model.PropertyType.STRING and not array:
return 'PP_Var'
elif array or optional:
if type_.property_type in self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP:
return self._FormatPpapiTypeName(
array, optional,
self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP[type_.property_type], '')
return self._PPAPI_PRIMITIVE_TYPE_MAP.get(type_.property_type, 'PP_Var')
_PPAPI_PRIMITIVE_TYPE_MAP = {
model.PropertyType.BOOLEAN: 'PP_Bool',
model.PropertyType.DOUBLE: 'double_t',
model.PropertyType.INT64: 'int64_t',
model.PropertyType.INTEGER: 'int32_t',
}
_PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP = {
model.PropertyType.BOOLEAN: 'Bool',
model.PropertyType.DOUBLE: 'Double',
model.PropertyType.INT64: 'Int64',
model.PropertyType.INTEGER: 'Int32',
model.PropertyType.STRING: 'String',
}
@staticmethod
def _FormatPpapiTypeName(array, optional, name, namespace=''):
if namespace:
namespace = '%s_' % namespace
if array:
if optional:
return 'PP_%sOptional_%s_Array' % (namespace, name)
return 'PP_%s%s_Array' % (namespace, name)
if optional:
return 'PP_%sOptional_%s' % (namespace, name)
return 'PP_%s%s' % (namespace, name)
def NeedsOptional(self, type_):
"""Returns True if an optional |type_| is required."""
return self._NameComponents(type_) in self._optional_types
def NeedsArray(self, type_):
"""Returns True if an array of |type_| is required."""
return self._NameComponents(type_) in self._array_types
def NeedsOptionalArray(self, type_):
"""Returns True if an optional array of |type_| is required."""
return self._NameComponents(type_) in self._optional_array_types
def FormatParamType(self, param):
"""Formats the type of a parameter or property."""
return self.ToPpapiType(param.type_, optional=param.optional)
@staticmethod
def GetFunctionReturnType(function):
return 'int32_t' if function.callback or function.returns else 'void'
def EnumValueName(self, enum_value, enum_type):
"""Returns a string containing the name for an enum value."""
return '%s_%s' % (self.ToPpapiType(enum_type).upper(),
enum_value.name.upper())
def _ResolveType(self, type_):
if type_.property_type == model.PropertyType.REF:
return self._ResolveType(self._namespace.types[type_.ref_type])
if type_.property_type == model.PropertyType.ARRAY:
return self._ResolveType(type_.item_type)
return type_
def _IsOrContainsArray(self, type_):
if type_.property_type == model.PropertyType.ARRAY:
return True
type_ = self._ResolveType(type_)
if type_.property_type == model.PropertyType.OBJECT:
return any(self._IsOrContainsArray(param.type_)
for param in type_.properties.itervalues())
return False
def HasArrayOuts(self, function):
"""Returns True if the function produces any arrays as outputs.
This includes arrays that are properties of other objects.
"""
if function.callback:
for param in function.callback.params:
if self._IsOrContainsArray(param.type_):
return True
return function.returns and self._IsOrContainsArray(function.returns)
class _IdlGenerator(_PpapiGeneratorBase):
TEMPLATE_NAME = 'idl'
class _GeneratorWrapper(object):
def __init__(self, generator_factory):
self._generator_factory = generator_factory
def Generate(self, namespace):
return self._generator_factory(namespace).Generate()
class PpapiGenerator(object):
def __init__(self):
self.idl_generator = _GeneratorWrapper(_IdlGenerator)