text: #!/usr/bin/env python3
  
  """
  Copyright (c) 2016-present, Facebook, Inc.
  All rights reserved.
  
  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree. An additional grant
  of patent rights can be found in the PATENTS file in the same directory.
  """
  
  import collections
  import os
  from pyparsing import alphas, alphanums, cppStyleComment, empty, nums
  from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore
  
  """
  Helper script to parse C header files and generate the corresponding python
  types of the same name. Not intended to be general purpose C language parser.
  All C defines are extracted as consts
  All C enums are converted to classes derived from Enum
  All C Structs are converted to c_types and are derived from the C compatible
  python Structure type.
  
  Headers need to be parsed in a specific order such that there are no unknown
  types. Currently doesn't support forward declaration syntax (as it is unused
  in the s1aptester headers).
  """
  
  HEADER_FILES = [
      'fw_api_int.h',
      'fw_api_int.x',
      'trfgen.x'
  ]
  
  OUT_FILE = 's1ap_types.py'
  
  # syntax we don't want to see in the final parse tree
  _lcurl = Suppress('{')
  _rcurl = Suppress('}')
  _lsq_bracket = Suppress('[')
  _rsq_bracket = Suppress(']')
  _equal = Suppress('=')
  _comma = Suppress(',')
  _semi_colon = Suppress(';')
  _enum = Suppress('enum')
  _struct = Suppress('struct')
  _define = Suppress('#define')
  _union = Suppress('union')
  
  # Conversion from s1AP Types to c_types
  py_type_map = {
      'Bool': 'c_ubyte',
      'S8': 'char',
      'U8': 'c_ubyte',
      'S16': 'c_short',
      'U16': 'c_ushort',
      'S32': 'c_int',
      'U32': 'c_uint',
      'int': 'c_int'
  }
  
  defines = {}
  enums = {}
  c_struct = {}
  anon_types = {}
  
  
  def _write_copyright(out_fname):
      """
      Write out copyright string
      """
      cc_str = '''"""
  Copyright (c) 2016-present, Facebook, Inc.
  All rights reserved
  This source code is licensed under the BSD-style license found in the"
  LICENSE file in the root directory of this source tree. An additional grant
  of patent rights can be found in the PATENTS file in the same directory.
  """
  '''
  
      import_str = '''
  from enum import Enum
  from ctypes import *
  
  '''
  
      warn_str = '''
  """
  Autogenerated code based on types parsed from s1aptester headers.
  Hand edit with care
  """
  '''
      with open(out_fname, 'w') as fd:
          fd.write(cc_str)
          fd.write(import_str)
          fd.write(warn_str)
  
  
  def _write_constants(out_fname, const_map):
      """
      Write out a map of constant values to a python file
      Args:
          out_fname: The output filename to append constants to
          const_map: The map of constant name to value
      """
      if not const_map:
          return
      const_str = '''
  """ Auto-generated list of constants """
  '''
      with open(out_fname, 'a') as fd:
          fd.write(const_str)
          for name, val in const_map.items():
              fd.write("%s = %s\n" % (name, val))
          fd.write('\n\n')
  
  
  def extract_defines(in_fname, out_fname):
      """
      Parse #defines and convert them to python constants.
      Doesn't support macros.
      Args:
          in_fname: file name to read
          out_fname: file name to append read defines to
      Returns
          map of constant name and their value
      """
      global defines
      identifier = Word(alphas, alphanums + '_')
      value = Word(alphanums + '_')
      with open(in_fname, 'r') as header:
          sample = header.read()
      define_def = _define + identifier("name") + empty + value("value")
      for item, _, _ in define_def.scanString(sample):  # item, start, stop
          defines[item.name] = item.value
  
      _write_constants(out_fname, defines)
  
      return defines
  
  
  def _write_enums(out_fname, enum_map):
      """
      Write out enums in python style
      Args:
          enum_map: A nested map of enums where each enum is represented as a map
          of values
      """
      if not enum_map:
          return
      enum_str = '''
  """ Auto-generated list of enums """
  '''
      with open(out_fname, 'a') as fd:
          fd.write(enum_str)
          for enum_name, enum_struct in enum_map.items():
              class_str = "class %s(Enum):\n" % enum_name
              fd.write(class_str)
              for enum_prop, enum_val in enum_struct.items():
                  prop_str = "    %s = %s\n" % (enum_prop, enum_val)
                  fd.write(prop_str)
              fd.write('\n\n')
  
  
  def extract_enums(in_fname, out_fname):
      """
      Extract enums from a given file
      Args:
          in_fname: file name to read
          out_fname: file name to append read enums to
      Returns
          nested map of enums and their properties
      """
      global enums
      with open(in_fname, 'r') as header:
          sample = header.read()
      identifier = Word(alphas + '_', alphanums + '_')
      integer = Word("0x" + nums)
  
      enum_value = Group(
          identifier('name') + Optional(_equal + integer('value')))
      enum_list = Group(enum_value + ZeroOrMore(_comma + enum_value))
      enum = _enum + Optional(identifier('enum_prefix')) + _lcurl + \
             enum_list('list') + _rcurl + Optional(identifier('enum_postfix'))
      enum.ignore(cppStyleComment)
  
      for item, _, _ in enum.scanString(sample):  # item, start, stop
          enum_name = item.enum_postfix
          enum_props = collections.OrderedDict()
          enum_int_val = 0
          for entry in item.list:
              if entry.value != '':
                  enum_int_val = int(entry.value, 0)
              enum_props[entry.name] = enum_int_val
              enum_int_val += 1
          enums[enum_name] = enum_props
      _write_enums(out_fname, enums)
      return enums
  
  
  def _write_class(out_fname, class_name, class_map, base_class):
      """
      Write a class with a set of C param types to names mapping deriving from a
      C compatible base_class like Structure or Union.
      """
      if not class_map:
          return
      with open(out_fname, 'a') as fd:
          class_str = "class %s(%s):\n" % (class_name, base_class)
          prop_prefix = "    _fields_ = [\n"
          fd.write(class_str)
          fd.write(prop_prefix)
          for struct_prop, struct_type in class_map.items():
              prop_str = ' ' * 8 + '(\"%s\", %s),\n' \
                                   % (struct_prop, struct_type)
              fd.write(prop_str)
          fd.write("    ]\n")
          fd.write('\n\n')
  
  
  def _extract_inner_params(parsed_struct):
      """
  
      """
      params = collections.OrderedDict()
      for param in parsed_struct.inner_param_list:
          param_name, param_type = normalize_param(param[0].param_type,
                                                   param[0].param_name, None,
                                                   param[0].length)
  
          params[param_name] = param_type
      return params
  
  
  def extract_inner_struct(parsed_struct, out_fname):
      """
      Extract the param type and name for the anonymous struct and write
      out the struct as an explicit python Structure
      Args:
          parsed_struct: The parsed anonymous struct ParseResult object
          out_fname: The filename to write the anonymous struct to.
      Returns:
          param_type, param_name and array length of the anonymous struct
      """
      params = _extract_inner_params(parsed_struct)
      _write_class(out_fname, parsed_struct.inner_struct, params, "Structure")
      return parsed_struct.inner_struct, parsed_struct.inner_param_name, None, \
             parsed_struct.length
  
  
  def extract_inner_union(parsed_union, out_fname):
      """
      Extract the param type and name for the anonymous union and write
      out the union as an explicit python Union
      Args:
          parsed_union: The parsed anonymous union ParseResult object
          out_fname: The filename to write the anonymous union to
  
      Returns:
          param_type, param_name and array length for the union
      """
      params = _extract_inner_params(parsed_union)
      union_type_name = parsed_union.inner_param_name.title()
      _write_class(out_fname, union_type_name, params, "Union")
      return union_type_name, parsed_union.inner_param_name, None, \
             parsed_union.length
  
  
  def extract_simple_param(parsed_struct):
      return parsed_struct[0].param_type, parsed_struct[0].param_name, \
             parsed_struct[0].ptr, parsed_struct[0].length
  
  
  def normalize_param(param_type, param_name, ptr, length):
      """
      Given a param_type, name, ptr field and length generate a normalized C
      representation for the same to be representated as a Python Structure
      Args:
          param_type: The type of the param
          param_name: The name of the param
          ptr: Set if the param is a pointer
          length: Set if the param is an array, and is the length of the array
  
      Returns:
          The param name and its normalized type
      """
      p_type = param_type
      if param_type in py_type_map:
          # Replace typedef with corresponding c_type
          p_type = py_type_map[param_type]
      elif param_type in enums:
          # There is no native enum type in python
          p_type = 'c_uint'
      else:
          # Assert we know about this struct or const
          assert p_type in c_struct or p_type in defines or \
                 p_type in anon_types or p_type in enums, \
              "{} not in {} or {} or {} or {}".format(p_type, c_struct, defines,
                                                      anon_types, enums)
      if ptr:
          p_type = 'POINTER(%s)' % p_type
      if length:
          p_type = '%s * %s' % (p_type, length)
      return param_name, p_type
  
  
  def extract_structs(in_fname, out_fname):
      """
      Extract a C struct and write its corresponding C compatible python object
      Args:
          in_fname: Filename to read structs from
          out_fname: Filename to write structs to.
  
      Returns:
          A dict of c_structs written.
      """
      global anon_types
      global c_struct
      with open(in_fname, 'r') as header:
          sample = header.read()
      identifier = Word(alphas + '_', alphanums + '_')
      integer = Word(alphanums + '_')
      struct_array = _lsq_bracket + integer('length') + _rsq_bracket
      pointer = Word('*', max=1)
      struct_param = Group(identifier('param_type') + Optional(pointer('ptr')) +
                           identifier('param_name') + Optional(struct_array))
  
      simple_param = Group(struct_param + _semi_colon)
  
      # Anonymous structs (nested)
      inner_param_list = Group(ZeroOrMore(simple_param('inner_list')))
      anonymous_struct_param = Group(_struct +
                                     Optional(identifier('inner_struct')) +
                                     _lcurl +
                                     inner_param_list('inner_param_list') +
                                     _rcurl + identifier('inner_param_name') +
                                     Optional(struct_array) + _semi_colon)
      anonymous_struct_param.ignore(cppStyleComment)
  
      # Anonymous unions (nested)
      anonymous_union_param = Group(_union +
                                    Optional(identifier('inner_union')) +
                                    _lcurl +
                                    inner_param_list('inner_param_list') +
                                    _rcurl + identifier('inner_param_name') +
                                    Optional(struct_array) + _semi_colon)
  
      # A struct can contain a list of
      # 1. simple param types params name;
      # 2. Anonymous nested structs.
      # 3. Anonymous nested unions.
      struct_list = Group(ZeroOrMore(simple_param('simple_param') |
                                     anonymous_struct_param('anon_struct') |
                                     anonymous_union_param('anon_union')))
  
      struct = _struct + Optional(identifier('struct_prefix')) + _lcurl + \
               struct_list('list') + _rcurl + \
               Optional(identifier('struct_postfix')) + _semi_colon
      struct.ignore(cppStyleComment)
  
      for item, _, _ in struct.scanString(sample):  # item, start, stop
          struct_name = item.struct_postfix
          params = collections.OrderedDict()
          for parsed_entry in item.list:
  
              # Classify the struct member.
              if (parsed_entry.getName() == "anon_struct"):
                  param_type, param_name, ptr, length = \
                      extract_inner_struct(parsed_entry, out_fname)
                  anon_types[param_type] = parsed_entry
              elif (parsed_entry.getName() == "anon_union"):
                  param_type, param_name, ptr, length = \
                      extract_inner_union(parsed_entry, out_fname)
                  anon_types[param_type] = parsed_entry
              elif (parsed_entry.getName() == "simple_param"):
                  param_type, param_name, ptr, length = \
                      extract_simple_param(parsed_entry)
              else:
                  assert False, "Invalid parse"
  
              # Normalize the parameter type
              param_name, p_type = normalize_param(param_type, param_name,
                                                   ptr, length)
              params[param_name] = p_type
              c_struct[struct_name] = params
  
          # Write out the struct
          _write_class(out_fname, struct_name, params, "Structure")
      return c_struct
  
  
  if __name__ == "__main__":
      s1ap_hdr_path = os.path.join(os.environ['S1AP_ROOT'], 'bin')
      output_file = os.path.join(s1ap_hdr_path, OUT_FILE)
  
      _write_copyright(output_file)
      for header_file in HEADER_FILES:
          print("Parsing file %s" % header_file)
          f_name = os.path.join(s1ap_hdr_path, header_file)
          defines.update(extract_defines(f_name, output_file))
          enums.update(extract_enums(f_name, output_file))
          extract_structs(f_name, output_file)
  