API: Add support for type aliases 06/15906/9
authorOle Troan <ot@cisco.com>
Tue, 13 Nov 2018 11:36:56 +0000 (12:36 +0100)
committerFlorin Coras <florin.coras@gmail.com>
Thu, 29 Nov 2018 07:39:22 +0000 (07:39 +0000)
Previously all types are compound. This adds support for aliases,
so one can do things like:

typedef u32 interface_index;

or
typedef u8 ip4_address[4];

Change-Id: I0455cad0123fc88acb491d2a3ea2725426bdb246
Signed-off-by: Ole Troan <ot@cisco.com>
Signed-off-by: Klement Sekera <ksekera@cisco.com>
13 files changed:
extras/japi/java/jvpp/gen/jvppgen/jvpp_model.py
src/tools/vppapigen/vppapigen.py
src/tools/vppapigen/vppapigen_c.py
src/tools/vppapigen/vppapigen_json.py
src/vnet/interface.api
src/vnet/interface_types.api [new file with mode: 0644]
src/vnet/ipip/ipip.api
src/vpp-api/python/vpp_papi/vpp_papi.py
src/vpp-api/python/vpp_papi/vpp_serializer.py
src/vpp-api/vapi/vapi_c_gen.py
src/vpp-api/vapi/vapi_cpp_gen.py
src/vpp-api/vapi/vapi_json_parser.py
test/ext/fake.api.json

index 299796b..3c2db15 100755 (executable)
@@ -320,6 +320,7 @@ class JVppModel(object):
         types = {}
         self._messages = []
         self._services = {}
+        self._aliases = {}
         for file_name in json_api_files:
             with open(file_name) as f:
                 j = json.load(f)
@@ -328,6 +329,8 @@ class JVppModel(object):
                 types.update({d[0]: {'type': 'union', 'data': d} for d in j['unions']})
                 self._messages.extend(j['messages'])
                 self._services.update(j['services'])
+                self._aliases.update(j['aliases'])
+
         self._parse_types(types)
 
     def _parse_types(self, types):
@@ -481,6 +484,10 @@ class JVppModel(object):
 
     def _parse_field(self, field, fields):
         type_name = _extract_type_name(field[0])
+        if type_name in self._aliases and type_name not in self._types_by_name:
+            aliased_type = self._types_by_name.get(self._aliases.get(type_name).get("type"))
+            self._types_by_name[type_name] = aliased_type
+
         if type_name in self._types_by_name:
             if len(field) > 2:
                 # Array field
index 9d04ec2..3f882c4 100755 (executable)
@@ -151,6 +151,25 @@ class Typedef():
         return self.name + str(self.flags) + str(self.block)
 
 
+class Using():
+    def __init__(self, name, alias):
+        global global_crc
+        self.name = name
+
+        if isinstance(alias, Array):
+            a = { 'type': alias.fieldtype,
+                  'length': alias.length }
+        else:
+            a = { 'type': alias.fieldtype }
+        self.alias = a
+        self.crc = binascii.crc32(str(alias)) & 0xffffffff
+        global_crc = binascii.crc32(str(alias), global_crc)
+        global_type_add(name)
+
+    def __repr__(self):
+        return self.name + str(self.alias)
+
+
 class Union():
     def __init__(self, name, block):
         self.type = 'Union'
@@ -457,6 +476,10 @@ class VPPAPIParser(object):
         '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
         p[0] = Typedef(p[2], [], p[4])
 
+    def p_typedef_alias(self, p):
+        '''typedef : TYPEDEF declaration '''
+        p[0] = Using(p[2].fieldname, p[2])
+
     def p_block_statements_opt(self, p):
         '''block_statements_opt : block_statements '''
         p[0] = p[1]
@@ -594,6 +617,7 @@ class VPPAPI(object):
         s['Service'] = []
         s['types'] = []
         s['Import'] = []
+        s['Alias'] = {}
         for o in objs:
             tname = o.__class__.__name__
             if isinstance(o, Define):
@@ -608,6 +632,8 @@ class VPPAPI(object):
                         s['Service'].append(o2)
             elif isinstance(o, Enum) or isinstance(o, Typedef) or isinstance(o, Union):
                 s['types'].append(o)
+            elif isinstance(o, Using):
+                s['Alias'][o.name] = o.alias
             else:
                 if tname not in s:
                     raise ValueError('Unknown class type: {} {}'.format(tname, o))
@@ -686,7 +712,8 @@ class VPPAPI(object):
             if in_import and not (isinstance(o, Enum) or
                                   isinstance(o, Union) or
                                   isinstance(o, Typedef) or
-                                  isinstance(o, Import)):
+                                  isinstance(o, Import) or
+                                  isinstance(o, Using)):
                 continue
             if isinstance(o, Import):
                 self.process_imports(o.result, True, result)
index b56e072..2a66ff3 100644 (file)
@@ -94,7 +94,7 @@ def duplicate_wrapper_tail():
     return '#endif\n\n'
 
 
-def typedefs(objs, filename):
+def typedefs(objs, aliases, filename):
     name = filename.replace('.', '_')
     output = '''\
 
@@ -106,6 +106,15 @@ def typedefs(objs, filename):
 #define included_{module}
 '''
     output = output.format(module=name)
+
+    for k, v in aliases.items():
+        output += duplicate_wrapper_head(k)
+        if 'length' in v:
+            output +=  'typedef %s vl_api_%s_t[%s];\n' % (v['type'], k, v['length'])
+        else:
+            output += 'typedef %s vl_api_%s_t;\n' % (v['type'], k)
+        output += duplicate_wrapper_tail()
+
     for o in objs:
         tname = o.__class__.__name__
         output += duplicate_wrapper_head(o.name)
@@ -276,7 +285,7 @@ def run(input_filename, s, file_crc):
     output += msg_ids(s)
     output += msg_names(s)
     output += msg_name_crc_list(s, filename)
-    output += typedefs(s['types'] + s['Define'], filename + file_extension)
+    output += typedefs(s['types'] + s['Define'], s['Alias'], filename + file_extension)
     output += printfun(s['types'] + s['Define'])
     output += endianfun(s['types'] + s['Define'])
     output += version_tuple(s, basename)
index 2991bec..b57e16c 100644 (file)
@@ -65,5 +65,6 @@ def run(filename, s, file_crc):
     j['unions'] = walk_defs([o for o in s['types'] if o.__class__.__name__ == 'Union'])
     j['enums'] = walk_enums([o for o in s['types'] if o.__class__.__name__ == 'Enum'])
     j['services'] = walk_services(s['Service'])
+    j['aliases'] = s['Alias']
     j['vl_api_version'] = hex(file_crc)
     return json.dumps(j, indent=4, separators=(',', ': '))
index 2010d8b..84e0483 100644 (file)
@@ -1,5 +1,23 @@
+/* Hey Emacs use -*- mode: C -*- */
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 option version = "2.2.0";
 
+import "vnet/interface_types.api";
+
 service {
   rpc want_interface_events returns want_interface_events_reply
     events sw_interface_event;
diff --git a/src/vnet/interface_types.api b/src/vnet/interface_types.api
new file mode 100644 (file)
index 0000000..f6fb421
--- /dev/null
@@ -0,0 +1,18 @@
+/* Hey Emacs use -*- mode: C -*- */
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+typedef u32 interface_index;
+
index 5cad28f..3dc087c 100644 (file)
@@ -49,6 +49,7 @@
  */
 
 option version = "1.1.0";
+import "vnet/interface_types.api";
 
 /**
  * Create an IP{v4,v6} over IP{v4,v6} tunnel.
@@ -70,7 +71,7 @@ define ipip_add_tunnel_reply
 {
   u32 context;
   i32 retval;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
 };
 
 /**
@@ -80,7 +81,7 @@ autoreply define ipip_del_tunnel
 {
   u32 client_index;
   u32 context;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
 };
 
 /**
@@ -106,7 +107,7 @@ define ipip_6rd_add_tunnel_reply
 {
   u32 context;
   i32 retval;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
 };
 
 /**
@@ -116,7 +117,7 @@ autoreply define ipip_6rd_del_tunnel
 {
   u32 client_index;
   u32 context;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
 };
 
 /**
@@ -126,13 +127,13 @@ define ipip_tunnel_dump
 {
   u32 client_index;
   u32 context;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
 };
 
 define ipip_tunnel_details
 {
   u32 context;
-  u32 sw_if_index;
+  vl_api_interface_index_t sw_if_index;
   u32 instance;
   u8 is_ipv6;
   u8 src_address[16];
index ca4b955..bd2682f 100644 (file)
@@ -27,7 +27,7 @@ import fnmatch
 import weakref
 import atexit
 from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType, BaseTypes
-from . vpp_serializer import VPPMessage, vpp_get_type
+from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
 from . vpp_format import VPPFormat
 
 if sys.version[0] == '2':
@@ -102,13 +102,15 @@ class VPP(object):
         for t in api['types']:
             t[0] = 'vl_api_' + t[0] + '_t'
             types[t[0]] = {'type': 'type', 'data': t}
+        for t, v in api['aliases'].items():
+            types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
 
         i = 0
         while True:
             unresolved = {}
             for k, v in types.items():
                 t = v['data']
-                if not vpp_get_type(t[0]):
+                if not vpp_get_type(k):
                     if v['type'] == 'enum':
                         try:
                             VPPEnumType(t[0], t[1:])
@@ -124,6 +126,11 @@ class VPP(object):
                             VPPType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
+                    elif v['type'] == 'alias':
+                        try:
+                            VPPTypeAlias(k, t)
+                        except ValueError:
+                            unresolved[k] = v
             if len(unresolved) == 0:
                 break
             if i > 3:
index bd0f738..a001cca 100644 (file)
@@ -79,7 +79,7 @@ class FixedList_u8(object):
         self.packer = BaseTypes(field_type, num)
         self.size = self.packer.size
 
-    def pack(self, list, kwargs):
+    def pack(self, list, kwargs = None):
         """Packs a fixed length bytestring. Left-pads with zeros
         if input data is too short."""
         if not list:
@@ -277,6 +277,18 @@ class VPPUnionType(object):
         return self.tuple._make(r), maxsize
 
 
+def VPPTypeAlias(name, msgdef):
+    t = vpp_get_type(msgdef['type'])
+    if not t:
+        raise ValueError()
+    if 'length' in msgdef:
+        if msgdef['length'] == 0:
+            raise ValueError()
+        types[name] = FixedList(name, msgdef['type'], msgdef['length'])
+    else:
+        types[name] = t
+
+
 class VPPType(object):
     # Set everything up to be able to pack / unpack
     def __init__(self, name, msgdef):
index eb1006d..9939bc0 100755 (executable)
@@ -5,15 +5,18 @@ import os
 import sys
 import logging
 from vapi_json_parser import Field, Struct, Enum, Union, Message, JsonParser,\
-    SimpleType, StructType
+    SimpleType, StructType, Alias
 
 
 class CField(Field):
+    def get_c_name(self):
+        return self.name
+
     def get_c_def(self):
         if self.len is not None:
-            return "%s %s[%d]" % (self.type.get_c_name(), self.name, self.len)
+            return "%s %s[%d];" % (self.type.get_c_name(), self.name, self.len)
         else:
-            return "%s %s" % (self.type.get_c_name(), self.name)
+            return "%s %s;" % (self.type.get_c_name(), self.name)
 
     def get_swap_to_be_code(self, struct, var):
         if self.len is not None:
@@ -95,12 +98,26 @@ class CField(Field):
         return result
 
 
+class CAlias(CField):
+    def get_c_name(self):
+        return self.name
+
+    def get_c_def(self):
+        return "typedef %s" % super(CAlias, self).get_c_def()
+        # if self.len is not None:
+        #     return "typedef %s %s[%d];" % (self.type.get_c_name(), self.name, self.len)
+        # else:
+        #     return "typedef %s %s;" % (self.type.get_c_name(), self.name)
+
+    # def needs_byte_swap
+
+
 class CStruct(Struct):
     def get_c_def(self):
         return "\n".join([
-            "typedef struct __attribute__((__packed__)) {\n%s;" % (
-                ";\n".join(["  %s" % x.get_c_def()
-                            for x in self.fields])),
+            "typedef struct __attribute__((__packed__)) {\n%s" % (
+                "\n".join(["  %s" % x.get_c_def()
+                           for x in self.fields])),
             "} %s;" % self.get_c_name()])
 
     def get_vla_assign_code(self, prefix, path):
@@ -156,7 +173,7 @@ class CSimpleType (SimpleType):
         try:
             self.get_swap_to_host_func_name()
             return True
-        except:
+        except KeyError:
             pass
         return False
 
@@ -335,8 +352,8 @@ class CMessage (Message):
         if self.has_payload():
             return "\n".join([
                 "typedef struct __attribute__ ((__packed__)) {",
-                "%s; " %
-                ";\n".join(self.payload_members),
+                "%s " %
+                "\n".join(self.payload_members),
                 "} %s;" % self.get_payload_struct_name(),
                 "",
                 "typedef struct __attribute__ ((__packed__)) {",
@@ -609,7 +626,8 @@ def emit_definition(parser, json_file, emitted, o):
         if (o not in parser.enums_by_json[json_file] and
                 o not in parser.types_by_json[json_file] and
                 o not in parser.unions_by_json[json_file] and
-                o.name not in parser.messages_by_json[json_file]):
+                o.name not in parser.messages_by_json[json_file] and
+                o not in parser.aliases_by_json[json_file]):
             return
         guard = "defined_%s" % o.get_c_name()
         print("#ifndef %s" % guard)
@@ -690,6 +708,8 @@ def gen_json_unified_header(parser, logger, j, io, name):
     emitted = []
     for e in parser.enums_by_json[j]:
         emit_definition(parser, j, emitted, e)
+    for a in parser.aliases_by_json[j]:
+        emit_definition(parser, j, emitted, a)
     for u in parser.unions_by_json[j]:
         emit_definition(parser, j, emitted, u)
     for t in parser.types_by_json[j]:
@@ -765,7 +785,8 @@ if __name__ == '__main__':
                             union_class=CUnion,
                             struct_type_class=CStructType,
                             field_class=CField,
-                            message_class=CMessage)
+                            message_class=CMessage,
+                            alias_class=CAlias)
 
     # not using the model of having separate generated header and code files
     # with generated symbols present in shared library (per discussion with
index 6b62bc4..c08993d 100755 (executable)
@@ -5,7 +5,7 @@ import os
 import sys
 import logging
 from vapi_c_gen import CField, CEnum, CStruct, CSimpleType, CStructType,\
-    CMessage, json_to_c_header_name
+    CMessage, json_to_c_header_name, CAlias
 from vapi_json_parser import JsonParser
 
 
@@ -21,6 +21,10 @@ class CppEnum(CEnum):
     pass
 
 
+class CppAlias(CAlias):
+    pass
+
+
 class CppSimpleType (CSimpleType):
     pass
 
@@ -251,7 +255,8 @@ if __name__ == '__main__':
                             struct_type_class=CppStructType,
                             field_class=CppField,
                             enum_class=CppEnum,
-                            message_class=CppMessage)
+                            message_class=CppMessage,
+                            alias_class=CppAlias)
 
     gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix,
                     args.remove_path)
index 39acca0..3eb5d36 100644 (file)
@@ -45,6 +45,10 @@ class Field(object):
         return self.is_vla() or self.type.has_vla()
 
 
+class Alias(Field):
+    pass
+
+
 class Type(object):
     def __init__(self, name):
         self.name = name
@@ -55,12 +59,6 @@ class Type(object):
 
 class SimpleType (Type):
 
-    def __init__(self, name):
-        super(SimpleType, self).__init__(name)
-
-    def __str__(self):
-        return self.name
-
     def has_vla(self):
         return False
 
@@ -290,11 +288,12 @@ class JsonParser(object):
     def __init__(self, logger, files, simple_type_class=SimpleType,
                  enum_class=Enum, union_class=Union,
                  struct_type_class=StructType, field_class=Field,
-                 message_class=Message):
+                 message_class=Message, alias_class=Alias):
         self.services = {}
         self.messages = {}
         self.enums = {}
         self.unions = {}
+        self.aliases = {}
         self.types = {
             x: simple_type_class(x) for x in [
                 'i8', 'i16', 'i32', 'i64',
@@ -310,6 +309,7 @@ class JsonParser(object):
         self.union_class = union_class
         self.struct_type_class = struct_type_class
         self.field_class = field_class
+        self.alias_class = alias_class
         self.message_class = message_class
 
         self.exceptions = []
@@ -317,6 +317,7 @@ class JsonParser(object):
         self.types_by_json = {}
         self.enums_by_json = {}
         self.unions_by_json = {}
+        self.aliases_by_json = {}
         self.messages_by_json = {}
         self.logger = logger
         for f in files:
@@ -329,6 +330,7 @@ class JsonParser(object):
         self.types_by_json[path] = []
         self.enums_by_json[path] = []
         self.unions_by_json[path] = []
+        self.aliases_by_json[path] = []
         self.messages_by_json[path] = {}
         with open(path) as f:
             j = json.load(f)
@@ -369,6 +371,19 @@ class JsonParser(object):
                     self.unions[union.name] = union
                     self.logger.debug("Parsed union: %s" % union)
                     self.unions_by_json[path].append(union)
+                for name, body in j['aliases'].iteritems():
+                    if name in self.aliases:
+                        progress = progress + 1
+                        continue
+                    if 'length' in body:
+                        array_len = body['length']
+                    else:
+                        array_len = None
+                    t = self.types[body['type']]
+                    alias = self.alias_class(name, t, array_len)
+                    self.aliases[name] = alias
+                    self.logger.debug("Parsed alias: %s" % alias)
+                    self.aliases_by_json[path].append(alias)
                 for t in j['types']:
                     if t[0] in self.types:
                         progress = progress + 1
@@ -429,12 +444,16 @@ class JsonParser(object):
             return self.enums[name]
         elif name in self.unions:
             return self.unions[name]
+        elif name in self.aliases:
+            return self.aliases[name]
         elif mundane_name in self.types:
             return self.types[mundane_name]
         elif mundane_name in self.enums:
             return self.enums[mundane_name]
         elif mundane_name in self.unions:
             return self.unions[mundane_name]
+        elif mundane_name in self.aliases:
+            return self.aliases[mundane_name]
         raise ParseError(
             "Could not find type, enum or union by magic name `%s' nor by "
             "mundane name `%s'" % (name, mundane_name))
index 4a7c64e..24c9f4d 100644 (file)
@@ -43,5 +43,7 @@
             {"crc" : "0xcafebafe"}
         ]
     ],
+    "aliases" : {
+    },
 "vl_api_version" :"0x224c7aad"
 }