Skip to content

Code Autogeneration

brice edited this page Jan 10, 2021 · 3 revisions

Code Autogeneration

The code is autogenerated from the fitgenparser.py utility

It parses the Profiles.xlsx into

  • Type Objects for each type on the Types tab,
  • Message Objects for each message from the Messages tab linking each fields as Field object with the correct Type
  • a Context Object to access types, and messages

The parser file naming convention is such that

  • function start with either swift or objc depending on the language they generate

  • function then indicate what they generate:

    • fname a function name
    • func a function
    • stmt a statement
    • expr an expression

Types

Note that the parser assign for each type in the excel file a number which will be used internally to identify given types with an int value.

Main Swift File rzfit_swift_map.swift

This file will be autogenerate and contains

Extension for FitMessageType

This extension will define a constant for each known message num as well as a name() function to return the String for the name of the message. This is generated by swift_stmt_extension

Building of a FitMessage Object for .fast parsing rzfit_swift_build_mesg

This function is a switch on each mesg_num that in return calls the swift function generated to create the Date, Double or string dictionary from the c-structure the parser populated. As an example the code to generate the file_id message from a uptr from a FIT_MESG buffer pointer is

    case 0: // file_id
      uptr.withMemoryRebound(to: FIT_FILE_ID_MESG.self, capacity: 1) {
      rv = FitMessage( mesg_num:    0,
                       mesg_values: rzfit_swift_value_dict_for_file_id(ptr: $0),
                       mesg_enums:  rzfit_swift_string_dict_for_file_id(ptr: $0),
                       mesg_dates:  rzfit_swift_date_dict_for_file_id(ptr: $0))
      }

Each function creating the dictionaries will deference a value from the structure and convert it to the proper swift type either by a type cast or in the case of a Fit Type by calling the appropriate function. Note that in the case of fields whose value or type depend on another field it will handle it properly, as in the following example for file_id handling the field product

func rzfit_swift_string_dict_for_file_id( ptr : UnsafePointer<FIT_FILE_ID_MESG>) -> [String:String] {
  var rv : [String:String] = [:]
  var x : FIT_FILE_ID_MESG = ptr.pointee
  let product_name = withUnsafeBytes(of: &x.product_name) { (rawPtr) -> String in
    let ptr = rawPtr.baseAddress!.assumingMemoryBound(to: CChar.self)
    return String(cString: ptr)
  }
  if !product_name.isEmpty {
    rv[ "product_name" ] = product_name
  }
  if( x.manufacturer != FIT_UINT16_INVALID ) {
    rv[ "manufacturer" ] = rzfit_swift_string_from_manufacturer(x.manufacturer)
  }
  if( x.product != FIT_UINT16_INVALID ) {
      if x.manufacturer == 263 { // favero_electronics
        rv[ "favero_product" ] = rzfit_swift_string_from_favero_product(FIT_UINT16(x.product))
      }else if x.manufacturer == 1 { // garmin
        rv[ "garmin_product" ] = rzfit_swift_string_from_garmin_product(FIT_UINT16(x.product))
      }else if x.manufacturer == 15 { // dynastream
        rv[ "garmin_product" ] = rzfit_swift_string_from_garmin_product(FIT_UINT16(x.product))
      }else if x.manufacturer == 13 { // dynastream_oem
        rv[ "garmin_product" ] = rzfit_swift_string_from_garmin_product(FIT_UINT16(x.product))
      }else if x.manufacturer == 89 { // tacx
        rv[ "garmin_product" ] = rzfit_swift_string_from_garmin_product(FIT_UINT16(x.product))
    }
  }
}

Objective c file with mappings rzfit_objc_map.m

The .generic parsing is mostly written in objective-c because it is easier to do generic typecasting of addresses in a buffer in c than swift, some of the swift conversion function of message number and field number are also generated in objective-c.

Information about fields for generic parsing

In addition, it generates functions that provide information about fields in a structure FIT_FIELD_INFO are also generated. These will return the scale, offset, fit_type, unit and some flag whether a field is a date or has dependencies to another field. This information is auto generated from the Profile.xlsx file and accounts for field that depend on the value of other fields.

For fields with dependence on reference fields, it will use the current value of FIT_INTERP_FIELD to try to extract the reference field value. The auto generated function look like the following:

static FIT_FIELD_INFO rzfit_objc_field_info_for_file_id(FIT_UINT16 field, FIT_INTERP_FIELD * interp){
  switch( field ){
    case 0: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 1, .fit_unit = 0, .fit_flag = 0 }; // type
    case 1: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 58, .fit_unit = 0, .fit_flag = 0 }; // manufacturer
    case 2: // product
    {
      FIT_UINT32 manufacturer = fit_interp_string_value(interp, 1);
      if( manufacturer == 263 ){ // favero_electronics 
         return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 161, .fit_unit = 0, .fit_flag = 0 };
      }else if( manufacturer == 1 ){ // garmin 
         return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 59, .fit_unit = 0, .fit_flag = 0 };
      }else if( manufacturer == 15 ){ // dynastream 
         return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 59, .fit_unit = 0, .fit_flag = 0 };
      }else if( manufacturer == 13 ){ // dynastream_oem 
         return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 59, .fit_unit = 0, .fit_flag = 0 };
      }else if( manufacturer == 89 ){ // tacx 
         return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 59, .fit_unit = 0, .fit_flag = 0 };
      }
      return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = FIT_TYPE_PENDING, .fit_unit = 0, .fit_flag = 0 };
    }
    case 3: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 0, .fit_unit = 0, .fit_flag = 0 }; // serial_number
    case 4: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 6, .fit_unit = 0, .fit_flag = 1 }; // time_created
    case 5: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 0, .fit_unit = 0, .fit_flag = 0 }; // number
    case 8: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 0, .fit_unit = 0, .fit_flag = 0 }; // product_name
    default: return (FIT_FIELD_INFO){.scale = 0, .offset = 0, .fit_type = 0, .fit_unit = 0, .fit_flag = 0 };
  }
}