Home | History | Annotate | Download | only in json_schema_compiler
      1 # Copyright 2013 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.
      4 
      5 import collections
      6 import datetime
      7 import os.path
      8 import sys
      9 
     10 import code
     11 import cpp_util
     12 import model
     13 
     14 try:
     15   import jinja2
     16 except ImportError:
     17   sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',
     18                                'third_party'))
     19   import jinja2
     20 
     21 
     22 class _PpapiGeneratorBase(object):
     23   """A base class for ppapi generators.
     24 
     25   Implementations should set TEMPLATE_NAME to a string containing the name of
     26   the template file without its extension. The template will be rendered with
     27   the following symbols available:
     28     name: A string containing the name of the namespace.
     29     enums: A list of enums within the namespace.
     30     types: A list of types within the namespace, sorted such that no element
     31         depends on an earlier element.
     32     events: A dict of events within the namespace.
     33     functions: A dict of functions within the namespace.
     34     year: An int containing the current year.
     35     source_file: The name of the input file.
     36   """
     37 
     38   def __init__(self, namespace):
     39     self._namespace = namespace
     40     self._required_types = {}
     41     self._array_types = set()
     42     self._optional_types = set()
     43     self._optional_array_types = set()
     44     self._dependencies = collections.OrderedDict()
     45     self._types = []
     46     self._enums = []
     47 
     48     self.jinja_environment = jinja2.Environment(
     49         loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__),
     50                                                     'templates', 'ppapi')))
     51     self._SetupFilters()
     52     self._ResolveTypeDependencies()
     53 
     54   def _SetupFilters(self):
     55     self.jinja_environment.filters.update({
     56       'ppapi_type': self.ToPpapiType,
     57       'classname': cpp_util.Classname,
     58       'enum_value': self.EnumValueName,
     59       'return_type': self.GetFunctionReturnType,
     60       'format_param_type': self.FormatParamType,
     61       'needs_optional': self.NeedsOptional,
     62       'needs_array': self.NeedsArray,
     63       'needs_optional_array': self.NeedsOptionalArray,
     64       'has_array_outs': self.HasArrayOuts,
     65     })
     66 
     67   def Render(self, template_name, values):
     68     generated_code = code.Code()
     69     template = self.jinja_environment.get_template(
     70         '%s.template' % template_name)
     71     generated_code.Append(template.render(values))
     72     return generated_code
     73 
     74   def Generate(self):
     75     """Generates a Code object for a single namespace."""
     76     return self.Render(self.TEMPLATE_NAME, {
     77       'name': self._namespace.name,
     78       'enums': self._enums,
     79       'types': self._types,
     80       'events': self._namespace.events,
     81       'functions': self._namespace.functions,
     82       # TODO(sammc): Don't change years when regenerating existing output files.
     83       'year': datetime.date.today().year,
     84       'source_file': self._namespace.source_file,
     85     })
     86 
     87   def _ResolveTypeDependencies(self):
     88     """Calculates the transitive closure of the types in _required_types.
     89 
     90     Returns a tuple containing the list of struct types and the list of enum
     91     types. The list of struct types is ordered such that no type depends on a
     92     type later in the list.
     93 
     94     """
     95     if self._namespace.functions:
     96       for function in self._namespace.functions.itervalues():
     97         self._FindFunctionDependencies(function)
     98 
     99     if self._namespace.events:
    100       for event in self._namespace.events.itervalues():
    101         self._FindFunctionDependencies(event)
    102     resolved_types = set()
    103     while resolved_types < set(self._required_types):
    104       for typename in sorted(set(self._required_types) - resolved_types):
    105         type_ = self._required_types[typename]
    106         self._dependencies.setdefault(typename, set())
    107         for member in type_.properties.itervalues():
    108           self._RegisterDependency(member, self._NameComponents(type_))
    109         resolved_types.add(typename)
    110     while self._dependencies:
    111       for name, deps in self._dependencies.items():
    112         if not deps:
    113           if (self._required_types[name].property_type ==
    114               model.PropertyType.ENUM):
    115             self._enums.append(self._required_types[name])
    116           else:
    117             self._types.append(self._required_types[name])
    118           for deps in self._dependencies.itervalues():
    119             deps.discard(name)
    120           del self._dependencies[name]
    121           break
    122       else:
    123         raise ValueError('Circular dependency %s' % self._dependencies)
    124 
    125   def _FindFunctionDependencies(self, function):
    126     for param in function.params:
    127       self._RegisterDependency(param, None)
    128     if function.callback:
    129       for param in function.callback.params:
    130         self._RegisterDependency(param, None)
    131     if function.returns:
    132       self._RegisterTypeDependency(function.returns, None, False, False)
    133 
    134   def _RegisterDependency(self, member, depender):
    135     self._RegisterTypeDependency(member.type_, depender, member.optional, False)
    136 
    137   def _RegisterTypeDependency(self, type_, depender, optional, array):
    138     if type_.property_type == model.PropertyType.ARRAY:
    139       self._RegisterTypeDependency(type_.item_type, depender, optional, True)
    140     elif type_.property_type == model.PropertyType.REF:
    141       self._RegisterTypeDependency(self._namespace.types[type_.ref_type],
    142                                    depender, optional, array)
    143     elif type_.property_type in (model.PropertyType.OBJECT,
    144                                  model.PropertyType.ENUM):
    145       name_components = self._NameComponents(type_)
    146       self._required_types[name_components] = type_
    147       if depender:
    148         self._dependencies.setdefault(depender, set()).add(
    149             name_components)
    150       if array:
    151         self._array_types.add(name_components)
    152         if optional:
    153           self._optional_array_types.add(name_components)
    154       elif optional:
    155         self._optional_types.add(name_components)
    156 
    157   @staticmethod
    158   def _NameComponents(entity):
    159     """Returns a tuple of the fully-qualified name of an entity."""
    160     names = []
    161     while entity:
    162       if (not isinstance(entity, model.Type) or
    163           entity.property_type != model.PropertyType.ARRAY):
    164         names.append(entity.name)
    165       entity = entity.parent
    166     return tuple(reversed(names[:-1]))
    167 
    168   def ToPpapiType(self, type_, array=False, optional=False):
    169     """Returns a string containing the name of the Pepper C type for |type_|.
    170 
    171     If array is True, returns the name of an array of |type_|. If optional is
    172     True, returns the name of an optional |type_|. If both array and optional
    173     are True, returns the name of an optional array of |type_|.
    174     """
    175     if isinstance(type_, model.Function) or type_.property_type in (
    176         model.PropertyType.OBJECT, model.PropertyType.ENUM):
    177       return self._FormatPpapiTypeName(
    178           array, optional, '_'.join(
    179               cpp_util.Classname(s) for s in self._NameComponents(type_)),
    180               namespace=cpp_util.Classname(self._namespace.name))
    181     elif type_.property_type == model.PropertyType.REF:
    182       return self.ToPpapiType(self._namespace.types[type_.ref_type],
    183                               optional=optional, array=array)
    184     elif type_.property_type == model.PropertyType.ARRAY:
    185       return self.ToPpapiType(type_.item_type, array=True,
    186                               optional=optional)
    187     elif type_.property_type == model.PropertyType.STRING and not array:
    188       return 'PP_Var'
    189     elif array or optional:
    190       if type_.property_type in self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP:
    191         return self._FormatPpapiTypeName(
    192             array, optional,
    193             self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP[type_.property_type], '')
    194     return self._PPAPI_PRIMITIVE_TYPE_MAP.get(type_.property_type, 'PP_Var')
    195 
    196   _PPAPI_PRIMITIVE_TYPE_MAP = {
    197     model.PropertyType.BOOLEAN: 'PP_Bool',
    198     model.PropertyType.DOUBLE: 'double_t',
    199     model.PropertyType.INT64: 'int64_t',
    200     model.PropertyType.INTEGER: 'int32_t',
    201   }
    202   _PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP = {
    203     model.PropertyType.BOOLEAN: 'Bool',
    204     model.PropertyType.DOUBLE: 'Double',
    205     model.PropertyType.INT64: 'Int64',
    206     model.PropertyType.INTEGER: 'Int32',
    207     model.PropertyType.STRING: 'String',
    208   }
    209 
    210   @staticmethod
    211   def _FormatPpapiTypeName(array, optional, name, namespace=''):
    212     if namespace:
    213       namespace = '%s_' % namespace
    214     if array:
    215       if optional:
    216         return 'PP_%sOptional_%s_Array' % (namespace, name)
    217       return 'PP_%s%s_Array' % (namespace, name)
    218     if optional:
    219       return 'PP_%sOptional_%s' % (namespace, name)
    220     return 'PP_%s%s' % (namespace, name)
    221 
    222   def NeedsOptional(self, type_):
    223     """Returns True if an optional |type_| is required."""
    224     return self._NameComponents(type_) in self._optional_types
    225 
    226   def NeedsArray(self, type_):
    227     """Returns True if an array of |type_| is required."""
    228     return self._NameComponents(type_) in self._array_types
    229 
    230   def NeedsOptionalArray(self, type_):
    231     """Returns True if an optional array of |type_| is required."""
    232     return self._NameComponents(type_) in self._optional_array_types
    233 
    234   def FormatParamType(self, param):
    235     """Formats the type of a parameter or property."""
    236     return self.ToPpapiType(param.type_, optional=param.optional)
    237 
    238   @staticmethod
    239   def GetFunctionReturnType(function):
    240     return 'int32_t' if function.callback or function.returns else 'void'
    241 
    242   def EnumValueName(self, enum_value, enum_type):
    243     """Returns a string containing the name for an enum value."""
    244     return '%s_%s' % (self.ToPpapiType(enum_type).upper(),
    245                       enum_value.name.upper())
    246 
    247   def _ResolveType(self, type_):
    248     if type_.property_type == model.PropertyType.REF:
    249       return self._ResolveType(self._namespace.types[type_.ref_type])
    250     if type_.property_type == model.PropertyType.ARRAY:
    251       return self._ResolveType(type_.item_type)
    252     return type_
    253 
    254   def _IsOrContainsArray(self, type_):
    255     if type_.property_type == model.PropertyType.ARRAY:
    256       return True
    257     type_ = self._ResolveType(type_)
    258     if type_.property_type == model.PropertyType.OBJECT:
    259       return any(self._IsOrContainsArray(param.type_)
    260                  for param in type_.properties.itervalues())
    261     return False
    262 
    263   def HasArrayOuts(self, function):
    264     """Returns True if the function produces any arrays as outputs.
    265 
    266     This includes arrays that are properties of other objects.
    267     """
    268     if function.callback:
    269       for param in function.callback.params:
    270         if self._IsOrContainsArray(param.type_):
    271           return True
    272     return function.returns and self._IsOrContainsArray(function.returns)
    273 
    274 
    275 class _IdlGenerator(_PpapiGeneratorBase):
    276   TEMPLATE_NAME = 'idl'
    277 
    278 
    279 class _GeneratorWrapper(object):
    280   def __init__(self, generator_factory):
    281     self._generator_factory = generator_factory
    282 
    283   def Generate(self, namespace):
    284     return self._generator_factory(namespace).Generate()
    285 
    286 
    287 class PpapiGenerator(object):
    288   def __init__(self):
    289     self.idl_generator = _GeneratorWrapper(_IdlGenerator)
    290