Home | History | Annotate | Download | only in generate
      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 # TODO(vtl): "data" is a pretty vague name. Rename it?
      6 
      7 import copy
      8 
      9 import module as mojom
     10 
     11 # This module provides a mechanism to turn mojom Modules to dictionaries and
     12 # back again. This can be used to persist a mojom Module created progromatically
     13 # or to read a dictionary from code or a file.
     14 # Example:
     15 # test_dict = {
     16 #   'name': 'test',
     17 #   'namespace': 'testspace',
     18 #   'structs': [{
     19 #     'name': 'teststruct',
     20 #     'fields': [
     21 #       {'name': 'testfield1', 'kind': 'i32'},
     22 #       {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}],
     23 #   'interfaces': [{
     24 #     'name': 'Server',
     25 #     'methods': [{
     26 #       'name': 'Foo',
     27 #       'parameters': [{
     28 #         'name': 'foo', 'kind': 'i32'},
     29 #         {'name': 'bar', 'kind': 'a:x:teststruct'}],
     30 #     'ordinal': 42}]}]
     31 # }
     32 # test_module = data.ModuleFromData(test_dict)
     33 
     34 # Used to create a subclass of str that supports sorting by index, to make
     35 # pretty printing maintain the order.
     36 def istr(index, string):
     37   class IndexedString(str):
     38     def __lt__(self, other):
     39       return self.__index__ < other.__index__
     40 
     41   rv = IndexedString(string)
     42   rv.__index__ = index
     43   return rv
     44 
     45 builtin_values = frozenset([
     46     "double.INFINITY",
     47     "double.NEGATIVE_INFINITY",
     48     "double.NAN",
     49     "float.INFINITY",
     50     "float.NEGATIVE_INFINITY",
     51     "float.NAN"])
     52 
     53 def IsBuiltinValue(value):
     54   return value in builtin_values
     55 
     56 def LookupKind(kinds, spec, scope):
     57   """Tries to find which Kind a spec refers to, given the scope in which its
     58   referenced. Starts checking from the narrowest scope to most general. For
     59   example, given a struct field like
     60     Foo.Bar x;
     61   Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
     62   type 'Bar' in the struct 'Foo' in the current namespace.
     63 
     64   |scope| is a tuple that looks like (namespace, struct/interface), referring
     65   to the location where the type is referenced."""
     66   if spec.startswith('x:'):
     67     name = spec[2:]
     68     for i in xrange(len(scope), -1, -1):
     69       test_spec = 'x:'
     70       if i > 0:
     71         test_spec += '.'.join(scope[:i]) + '.'
     72       test_spec += name
     73       kind = kinds.get(test_spec)
     74       if kind:
     75         return kind
     76 
     77   return kinds.get(spec)
     78 
     79 def LookupValue(values, name, scope, kind):
     80   """Like LookupKind, but for constant values."""
     81   # If the type is an enum, the value can be specified as a qualified name, in
     82   # which case the form EnumName.ENUM_VALUE must be used. We use the presence
     83   # of a '.' in the requested name to identify this. Otherwise, we prepend the
     84   # enum name.
     85   if isinstance(kind, mojom.Enum) and '.' not in name:
     86     name = '%s.%s' % (kind.spec.split(':', 1)[1], name)
     87   for i in reversed(xrange(len(scope) + 1)):
     88     test_spec = '.'.join(scope[:i])
     89     if test_spec:
     90       test_spec += '.'
     91     test_spec += name
     92     value = values.get(test_spec)
     93     if value:
     94       return value
     95 
     96   return values.get(name)
     97 
     98 def FixupExpression(module, value, scope, kind):
     99   """Translates an IDENTIFIER into a built-in value or structured NamedValue
    100      object."""
    101   if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
    102     # Allow user defined values to shadow builtins.
    103     result = LookupValue(module.values, value[1], scope, kind)
    104     if result:
    105       if isinstance(result, tuple):
    106         raise Exception('Unable to resolve expression: %r' % value[1])
    107       return result
    108     if IsBuiltinValue(value[1]):
    109       return mojom.BuiltinValue(value[1])
    110   return value
    111 
    112 def KindToData(kind):
    113   return kind.spec
    114 
    115 def KindFromData(kinds, data, scope):
    116   kind = LookupKind(kinds, data, scope)
    117   if kind:
    118     return kind
    119 
    120   if data.startswith('?'):
    121     kind = KindFromData(kinds, data[1:], scope).MakeNullableKind()
    122   elif data.startswith('a:'):
    123     kind = mojom.Array(KindFromData(kinds, data[2:], scope))
    124   elif data.startswith('r:'):
    125     kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope))
    126   elif data.startswith('a'):
    127     colon = data.find(':')
    128     length = int(data[1:colon])
    129     kind = mojom.FixedArray(length, KindFromData(kinds, data[colon+1:], scope))
    130   else:
    131     kind = mojom.Kind(data)
    132 
    133   kinds[data] = kind
    134   return kind
    135 
    136 def KindFromImport(original_kind, imported_from):
    137   """Used with 'import module' - clones the kind imported from the given
    138   module's namespace. Only used with Structs, Interfaces and Enums."""
    139   kind = copy.copy(original_kind)
    140   # |shared_definition| is used to store various properties (see
    141   # |AddSharedProperty()| in module.py), including |imported_from|. We don't
    142   # want the copy to share these with the original, so copy it if necessary.
    143   if hasattr(original_kind, 'shared_definition'):
    144     kind.shared_definition = copy.copy(original_kind.shared_definition)
    145   kind.imported_from = imported_from
    146   return kind
    147 
    148 def ImportFromData(module, data):
    149   import_module = data['module']
    150 
    151   import_item = {}
    152   import_item['module_name'] = import_module.name
    153   import_item['namespace'] = import_module.namespace
    154   import_item['module'] = import_module
    155 
    156   # Copy the struct kinds from our imports into the current module.
    157   for kind in import_module.kinds.itervalues():
    158     if (isinstance(kind, (mojom.Struct, mojom.Enum, mojom.Interface)) and
    159         kind.imported_from is None):
    160       kind = KindFromImport(kind, import_item)
    161       module.kinds[kind.spec] = kind
    162   # Ditto for values.
    163   for value in import_module.values.itervalues():
    164     if value.imported_from is None:
    165       # Values don't have shared definitions (since they're not nullable), so no
    166       # need to do anything special.
    167       value = copy.copy(value)
    168       value.imported_from = import_item
    169       module.values[value.GetSpec()] = value
    170 
    171   return import_item
    172 
    173 def StructToData(struct):
    174   return {
    175     istr(0, 'name'): struct.name,
    176     istr(1, 'fields'): map(FieldToData, struct.fields)
    177   }
    178 
    179 def StructFromData(module, data):
    180   struct = mojom.Struct(module=module)
    181   struct.name = data['name']
    182   struct.attributes = data['attributes']
    183   struct.spec = 'x:' + module.namespace + '.' + struct.name
    184   module.kinds[struct.spec] = struct
    185   struct.enums = map(lambda enum:
    186       EnumFromData(module, enum, struct), data['enums'])
    187   struct.constants = map(lambda constant:
    188       ConstantFromData(module, constant, struct), data['constants'])
    189   # Stash fields data here temporarily.
    190   struct.fields_data = data['fields']
    191   return struct
    192 
    193 def FieldToData(field):
    194   data = {
    195     istr(0, 'name'): field.name,
    196     istr(1, 'kind'): KindToData(field.kind)
    197   }
    198   if field.ordinal != None:
    199     data[istr(2, 'ordinal')] = field.ordinal
    200   if field.default != None:
    201     data[istr(3, 'default')] = field.default
    202   return data
    203 
    204 def FieldFromData(module, data, struct):
    205   field = mojom.Field()
    206   field.name = data['name']
    207   field.kind = KindFromData(
    208       module.kinds, data['kind'], (module.namespace, struct.name))
    209   field.ordinal = data.get('ordinal')
    210   field.default = FixupExpression(
    211       module, data.get('default'), (module.namespace, struct.name), field.kind)
    212   return field
    213 
    214 def ParameterToData(parameter):
    215   data = {
    216     istr(0, 'name'): parameter.name,
    217     istr(1, 'kind'): parameter.kind.spec
    218   }
    219   if parameter.ordinal != None:
    220     data[istr(2, 'ordinal')] = parameter.ordinal
    221   if parameter.default != None:
    222     data[istr(3, 'default')] = parameter.default
    223   return data
    224 
    225 def ParameterFromData(module, data, interface):
    226   parameter = mojom.Parameter()
    227   parameter.name = data['name']
    228   parameter.kind = KindFromData(
    229       module.kinds, data['kind'], (module.namespace, interface.name))
    230   parameter.ordinal = data.get('ordinal')
    231   parameter.default = data.get('default')
    232   return parameter
    233 
    234 def MethodToData(method):
    235   data = {
    236     istr(0, 'name'):       method.name,
    237     istr(1, 'parameters'): map(ParameterToData, method.parameters)
    238   }
    239   if method.ordinal != None:
    240     data[istr(2, 'ordinal')] = method.ordinal
    241   if method.response_parameters != None:
    242     data[istr(3, 'response_parameters')] = map(
    243         ParameterToData, method.response_parameters)
    244   return data
    245 
    246 def MethodFromData(module, data, interface):
    247   method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal'))
    248   method.default = data.get('default')
    249   method.parameters = map(lambda parameter:
    250       ParameterFromData(module, parameter, interface), data['parameters'])
    251   if data.has_key('response_parameters'):
    252     method.response_parameters = map(
    253         lambda parameter: ParameterFromData(module, parameter, interface),
    254                           data['response_parameters'])
    255   return method
    256 
    257 def InterfaceToData(interface):
    258   return {
    259     istr(0, 'name'):    interface.name,
    260     istr(1, 'client'):  interface.client,
    261     istr(2, 'methods'): map(MethodToData, interface.methods)
    262   }
    263 
    264 def InterfaceFromData(module, data):
    265   interface = mojom.Interface(module=module)
    266   interface.name = data['name']
    267   interface.spec = 'x:' + module.namespace + '.' + interface.name
    268   interface.client = data['client'] if data.has_key('client') else None
    269   module.kinds[interface.spec] = interface
    270   interface.enums = map(lambda enum:
    271       EnumFromData(module, enum, interface), data['enums'])
    272   interface.constants = map(lambda constant:
    273       ConstantFromData(module, constant, interface), data['constants'])
    274   # Stash methods data here temporarily.
    275   interface.methods_data = data['methods']
    276   return interface
    277 
    278 def EnumFieldFromData(module, enum, data, parent_kind):
    279   field = mojom.EnumField()
    280   field.name = data['name']
    281   # TODO(mpcomplete): FixupExpression should be done in the second pass,
    282   # so constants and enums can refer to each other.
    283   # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
    284   # vice versa?
    285   if parent_kind:
    286     field.value = FixupExpression(
    287         module, data['value'], (module.namespace, parent_kind.name), enum)
    288   else:
    289     field.value = FixupExpression(
    290         module, data['value'], (module.namespace, ), enum)
    291   value = mojom.EnumValue(module, enum, field)
    292   module.values[value.GetSpec()] = value
    293   return field
    294 
    295 def EnumFromData(module, data, parent_kind):
    296   enum = mojom.Enum(module=module)
    297   enum.name = data['name']
    298   name = enum.name
    299   if parent_kind:
    300     name = parent_kind.name + '.' + name
    301   enum.spec = 'x:%s.%s' % (module.namespace, name)
    302   enum.parent_kind = parent_kind
    303 
    304   enum.fields = map(
    305       lambda field: EnumFieldFromData(module, enum, field, parent_kind),
    306       data['fields'])
    307   module.kinds[enum.spec] = enum
    308   return enum
    309 
    310 def ConstantFromData(module, data, parent_kind):
    311   constant = mojom.Constant()
    312   constant.name = data['name']
    313   if parent_kind:
    314     scope = (module.namespace, parent_kind.name)
    315   else:
    316     scope = (module.namespace, )
    317   # TODO(mpcomplete): maybe we should only support POD kinds.
    318   constant.kind = KindFromData(module.kinds, data['kind'], scope)
    319   constant.value = FixupExpression(module, data.get('value'), scope, None)
    320 
    321   value = mojom.ConstantValue(module, parent_kind, constant)
    322   module.values[value.GetSpec()] = value
    323   return constant
    324 
    325 def ModuleToData(module):
    326   return {
    327     istr(0, 'name'):       module.name,
    328     istr(1, 'namespace'):  module.namespace,
    329     istr(2, 'structs'):    map(StructToData, module.structs),
    330     istr(3, 'interfaces'): map(InterfaceToData, module.interfaces)
    331   }
    332 
    333 def ModuleFromData(data):
    334   module = mojom.Module()
    335   module.kinds = {}
    336   for kind in mojom.PRIMITIVES:
    337     module.kinds[kind.spec] = kind
    338 
    339   module.values = {}
    340 
    341   module.name = data['name']
    342   module.namespace = data['namespace']
    343   module.attributes = data['attributes']
    344   # Imports must come first, because they add to module.kinds which is used
    345   # by by the others.
    346   module.imports = map(
    347       lambda import_data: ImportFromData(module, import_data),
    348       data['imports'])
    349 
    350   # First pass collects kinds.
    351   module.enums = map(
    352       lambda enum: EnumFromData(module, enum, None), data['enums'])
    353   module.structs = map(
    354       lambda struct: StructFromData(module, struct), data['structs'])
    355   module.interfaces = map(
    356       lambda interface: InterfaceFromData(module, interface),
    357       data['interfaces'])
    358   module.constants = map(
    359       lambda constant: ConstantFromData(module, constant, None),
    360       data['constants'])
    361 
    362   # Second pass expands fields and methods. This allows fields and parameters
    363   # to refer to kinds defined anywhere in the mojom.
    364   for struct in module.structs:
    365     struct.fields = map(lambda field:
    366         FieldFromData(module, field, struct), struct.fields_data)
    367     del struct.fields_data
    368   for interface in module.interfaces:
    369     interface.methods = map(lambda method:
    370         MethodFromData(module, method, interface), interface.methods_data)
    371     del interface.methods_data
    372 
    373   return module
    374 
    375 def OrderedModuleFromData(data):
    376   module = ModuleFromData(data)
    377   for interface in module.interfaces:
    378     next_ordinal = 0
    379     for method in interface.methods:
    380       if method.ordinal is None:
    381         method.ordinal = next_ordinal
    382       next_ordinal = method.ordinal + 1
    383   return module
    384