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   istr = IndexedString(string)
     42   istr.__index__ = index
     43   return istr
     44 
     45 def LookupKind(kinds, spec, scope):
     46   """Tries to find which Kind a spec refers to, given the scope in which its
     47   referenced. Starts checking from the narrowest scope to most general. For
     48   example, given a struct field like
     49     Foo.Bar x;
     50   Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
     51   type 'Bar' in the struct 'Foo' in the current namespace.
     52 
     53   |scope| is a tuple that looks like (namespace, struct/interface), referring
     54   to the location where the type is referenced."""
     55   if spec.startswith('x:'):
     56     name = spec[2:]
     57     for i in xrange(len(scope), -1, -1):
     58       test_spec = 'x:'
     59       if i > 0:
     60         test_spec += '.'.join(scope[:i]) + '.'
     61       test_spec += name
     62       kind = kinds.get(test_spec)
     63       if kind:
     64         return kind
     65 
     66   return kinds.get(spec)
     67 
     68 def LookupValue(values, name, scope):
     69   """Like LookupKind, but for constant values."""
     70   for i in xrange(len(scope), -1, -1):
     71     if i > 0:
     72       test_spec = '.'.join(scope[:i]) + '.'
     73     test_spec += name
     74     value = values.get(test_spec)
     75     if value:
     76       return value
     77 
     78   return values.get(name)
     79 
     80 def FixupExpression(module, value, scope):
     81   """Translates an IDENTIFIER into a structured Value object."""
     82   if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
     83     result = LookupValue(module.values, value[1], scope)
     84     if result:
     85       return result
     86   return value
     87 
     88 def KindToData(kind):
     89   return kind.spec
     90 
     91 def KindFromData(kinds, data, scope):
     92   kind = LookupKind(kinds, data, scope)
     93   if kind:
     94     return kind
     95   if data.startswith('a:'):
     96     kind = mojom.Array()
     97     kind.kind = KindFromData(kinds, data[2:], scope)
     98   elif data.startswith('r:'):
     99     kind = mojom.InterfaceRequest()
    100     kind.kind = KindFromData(kinds, data[2:], scope)
    101   else:
    102     kind = mojom.Kind()
    103   kind.spec = data
    104   kinds[data] = kind
    105   return kind
    106 
    107 def KindFromImport(original_kind, imported_from):
    108   """Used with 'import module' - clones the kind imported from the given
    109   module's namespace. Only used with Structs, Interfaces and Enums."""
    110   kind = copy.deepcopy(original_kind)
    111   kind.imported_from = imported_from
    112   return kind
    113 
    114 def ImportFromData(module, data):
    115   import_module = data['module']
    116 
    117   import_item = {}
    118   import_item['module_name'] = import_module.name
    119   import_item['namespace'] = import_module.namespace
    120   import_item['module'] = import_module
    121 
    122   # Copy the struct kinds from our imports into the current module.
    123   for kind in import_module.kinds.itervalues():
    124     if (isinstance(kind, (mojom.Struct, mojom.Enum, mojom.Interface)) and
    125         kind.imported_from is None):
    126       kind = KindFromImport(kind, import_item)
    127       module.kinds[kind.spec] = kind
    128   # Ditto for values.
    129   for value in import_module.values.itervalues():
    130     if value.imported_from is None:
    131       value = copy.deepcopy(value)
    132       value.imported_from = import_item
    133       module.values[value.GetSpec()] = value
    134 
    135   return import_item
    136 
    137 def StructToData(struct):
    138   return {
    139     istr(0, 'name'): struct.name,
    140     istr(1, 'fields'): map(FieldToData, struct.fields)
    141   }
    142 
    143 def StructFromData(module, data):
    144   struct = mojom.Struct(module=module)
    145   struct.name = data['name']
    146   struct.attributes = data['attributes']
    147   struct.spec = 'x:' + module.namespace + '.' + struct.name
    148   module.kinds[struct.spec] = struct
    149   struct.enums = map(lambda enum:
    150       EnumFromData(module, enum, struct), data['enums'])
    151   struct.constants = map(lambda constant:
    152       ConstantFromData(module, constant, struct), data['constants'])
    153   # Stash fields data here temporarily.
    154   struct.fields_data = data['fields']
    155   return struct
    156 
    157 def FieldToData(field):
    158   data = {
    159     istr(0, 'name'): field.name,
    160     istr(1, 'kind'): KindToData(field.kind)
    161   }
    162   if field.ordinal != None:
    163     data[istr(2, 'ordinal')] = field.ordinal
    164   if field.default != None:
    165     data[istr(3, 'default')] = field.default
    166   return data
    167 
    168 def FieldFromData(module, data, struct):
    169   field = mojom.Field()
    170   field.name = data['name']
    171   field.kind = KindFromData(
    172       module.kinds, data['kind'], (module.namespace, struct.name))
    173   field.ordinal = data.get('ordinal')
    174   field.default = FixupExpression(
    175       module, data.get('default'), (module.namespace, struct.name))
    176   return field
    177 
    178 def ParameterToData(parameter):
    179   data = {
    180     istr(0, 'name'): parameter.name,
    181     istr(1, 'kind'): parameter.kind.spec
    182   }
    183   if parameter.ordinal != None:
    184     data[istr(2, 'ordinal')] = parameter.ordinal
    185   if parameter.default != None:
    186     data[istr(3, 'default')] = parameter.default
    187   return data
    188 
    189 def ParameterFromData(module, data, interface):
    190   parameter = mojom.Parameter()
    191   parameter.name = data['name']
    192   parameter.kind = KindFromData(
    193       module.kinds, data['kind'], (module.namespace, interface.name))
    194   parameter.ordinal = data.get('ordinal')
    195   parameter.default = data.get('default')
    196   return parameter
    197 
    198 def MethodToData(method):
    199   data = {
    200     istr(0, 'name'):       method.name,
    201     istr(1, 'parameters'): map(ParameterToData, method.parameters)
    202   }
    203   if method.ordinal != None:
    204     data[istr(2, 'ordinal')] = method.ordinal
    205   if method.response_parameters != None:
    206     data[istr(3, 'response_parameters')] = map(
    207         ParameterToData, method.response_parameters)
    208   return data
    209 
    210 def MethodFromData(module, data, interface):
    211   method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal'))
    212   method.default = data.get('default')
    213   method.parameters = map(lambda parameter:
    214       ParameterFromData(module, parameter, interface), data['parameters'])
    215   if data.has_key('response_parameters'):
    216     method.response_parameters = map(
    217         lambda parameter: ParameterFromData(module, parameter, interface),
    218                           data['response_parameters'])
    219   return method
    220 
    221 def InterfaceToData(interface):
    222   return {
    223     istr(0, 'name'):    interface.name,
    224     istr(1, 'client'):  interface.client,
    225     istr(2, 'methods'): map(MethodToData, interface.methods)
    226   }
    227 
    228 def InterfaceFromData(module, data):
    229   interface = mojom.Interface(module=module)
    230   interface.name = data['name']
    231   interface.spec = 'x:' + module.namespace + '.' + interface.name
    232   interface.client = data['client'] if data.has_key('client') else None
    233   module.kinds[interface.spec] = interface
    234   interface.enums = map(lambda enum:
    235       EnumFromData(module, enum, interface), data['enums'])
    236   interface.constants = map(lambda constant:
    237       ConstantFromData(module, constant, interface), data['constants'])
    238   # Stash methods data here temporarily.
    239   interface.methods_data = data['methods']
    240   return interface
    241 
    242 def EnumFieldFromData(module, enum, data, parent_kind):
    243   field = mojom.EnumField()
    244   field.name = data['name']
    245   # TODO(mpcomplete): FixupExpression should be done in the second pass,
    246   # so constants and enums can refer to each other.
    247   # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
    248   # vice versa?
    249   if parent_kind:
    250     field.value = FixupExpression(
    251         module, data['value'], (module.namespace, parent_kind.name))
    252   else:
    253     field.value = FixupExpression(
    254         module, data['value'], (module.namespace, ))
    255   value = mojom.EnumValue(module, enum, field)
    256   module.values[value.GetSpec()] = value
    257   return field
    258 
    259 def EnumFromData(module, data, parent_kind):
    260   enum = mojom.Enum(module=module)
    261   enum.name = data['name']
    262   name = enum.name
    263   if parent_kind:
    264     name = parent_kind.name + '.' + name
    265   enum.spec = 'x:%s.%s' % (module.namespace, name)
    266   enum.parent_kind = parent_kind
    267 
    268   enum.fields = map(
    269       lambda field: EnumFieldFromData(module, enum, field, parent_kind),
    270       data['fields'])
    271   module.kinds[enum.spec] = enum
    272   return enum
    273 
    274 def ConstantFromData(module, data, parent_kind):
    275   constant = mojom.Constant()
    276   constant.name = data['name']
    277   if parent_kind:
    278     scope = (module.namespace, parent_kind.name)
    279   else:
    280     scope = (module.namespace, )
    281   # TODO(mpcomplete): maybe we should only support POD kinds.
    282   constant.kind = KindFromData(module.kinds, data['kind'], scope)
    283   constant.value = FixupExpression(module, data.get('value'), scope)
    284 
    285   value = mojom.NamedValue(module, parent_kind, constant.name)
    286   module.values[value.GetSpec()] = value
    287   return constant
    288 
    289 def ModuleToData(module):
    290   return {
    291     istr(0, 'name'):       module.name,
    292     istr(1, 'namespace'):  module.namespace,
    293     istr(2, 'structs'):    map(StructToData, module.structs),
    294     istr(3, 'interfaces'): map(InterfaceToData, module.interfaces)
    295   }
    296 
    297 def ModuleFromData(data):
    298   module = mojom.Module()
    299   module.kinds = {}
    300   for kind in mojom.PRIMITIVES:
    301     module.kinds[kind.spec] = kind
    302 
    303   module.values = {}
    304 
    305   module.name = data['name']
    306   module.namespace = data['namespace']
    307   module.attributes = data['attributes']
    308   # Imports must come first, because they add to module.kinds which is used
    309   # by by the others.
    310   module.imports = map(
    311       lambda import_data: ImportFromData(module, import_data),
    312       data['imports'])
    313 
    314   # First pass collects kinds.
    315   module.enums = map(
    316       lambda enum: EnumFromData(module, enum, None), data['enums'])
    317   module.structs = map(
    318       lambda struct: StructFromData(module, struct), data['structs'])
    319   module.interfaces = map(
    320       lambda interface: InterfaceFromData(module, interface),
    321       data['interfaces'])
    322   module.constants = map(
    323       lambda constant: ConstantFromData(module, constant, None),
    324       data['constants'])
    325 
    326   # Second pass expands fields and methods. This allows fields and parameters
    327   # to refer to kinds defined anywhere in the mojom.
    328   for struct in module.structs:
    329     struct.fields = map(lambda field:
    330         FieldFromData(module, field, struct), struct.fields_data)
    331     del struct.fields_data
    332   for interface in module.interfaces:
    333     interface.methods = map(lambda method:
    334         MethodFromData(module, method, interface), interface.methods_data)
    335     del interface.methods_data
    336 
    337   return module
    338 
    339 def OrderedModuleFromData(data):
    340   module = ModuleFromData(data)
    341   next_interface_ordinal = 0
    342   for interface in module.interfaces:
    343     next_ordinal = 0
    344     for method in interface.methods:
    345       if method.ordinal is None:
    346         method.ordinal = next_ordinal
    347       next_ordinal = method.ordinal + 1
    348   return module
    349