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