data/method/mavlink/pymavlink/generator/mavgen_swift.py

381 lines
15 KiB
Python

#!/usr/bin/env python
"""
Parse a MAVLink protocol XML file and generate Swift implementation
Copyright Max Odnovolyk 2015
Released under GNU GPL version 3 or later
"""
from __future__ import print_function
import os
from . import mavparse, mavtemplate
abbreviations = ["MAV", "PX4", "UDB", "PPZ", "PIXHAWK", "SLUGS", "FP", "ASLUAV", "VTOL", "ROI", "UART", "UDP", "IMU", "IMU2", "3D", "RC", "GPS", "GPS1", "GPS2", "NED", "RTK", "ADSB"]
swift_keywords = ["associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol",
"public", "static", "struct", "subscript", "typealias", "var", "break" "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch",
"where", "while", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try"]
swift_types = {'char' : ("String", '"\\0"', "string(at: %u, length: %u)", "set(%s, at: %u, length: %u)"),
'uint8_t' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)"),
'int8_t' : ("Int8", 0, "number(at: %u)", "set(%s, at: %u)"),
'uint16_t' : ("UInt16", 0, "number(at: %u)", "set(%s, at: %u)"),
'int16_t' : ("Int16", 0, "number(at: %u)", "set(%s, at: %u)"),
'uint32_t' : ("UInt32", 0, "number(at: %u)", "set(%s, at: %u)"),
'int32_t' : ("Int32", 0, "number(at: %u)", "set(%s, at: %u)"),
'uint64_t' : ("UInt64", 0, "number(at: %u)", "set(%s, at: %u)"),
'int64_t' : ("Int64", 0, "number(at: %u)", "set(%s, at: %u)"),
'float' : ("Float", 0, "number(at: %u)", "set(%s, at: %u)"),
'double' : ("Double", 0, "number(at: %u)", "set(%s, at: %u)"),
'uint8_t_mavlink_version' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)")}
t = mavtemplate.MAVTemplate()
def generate_mavlink(directory, filelist, xml_list, msgs):
print("Generating MAVLink.swift file")
mavparse.mkdir_p(directory)
filename = 'MAVLink.swift'
filepath = os.path.join(directory, filename)
outf = open(filepath, "w")
generate_header(outf, filelist, xml_list, filename)
append_static_code('MAVLink.swift', outf)
generate_message_mappings_array(outf, msgs)
generate_message_lengths_array(outf, msgs)
generate_message_crc_extra_array(outf, msgs)
outf.close()
def generate_header(outf, filelist, xml_list, filename):
"""Generate Swift file header with source files list and creation date"""
t.write(outf, """
//
// ${FILENAME}
// MAVLink Protocol Swift Library
//
// Generated from ${FILELIST} on ${PARSE_TIME} by mavgen_swift.py
// https://github.com/modnovolyk/MAVLinkSwift
//
""", {'FILENAME' : filename,
'FILELIST' : ", ".join(filelist),
'PARSE_TIME' : xml_list[0].parse_time})
def generate_enums(directory, filelist, xml_list, enums):
"""Iterate through all enums and create Swift equivalents"""
print("Generating Enumerations")
for enum in enums:
if enum.is_a_bitmask:
continue
filename = "%s%sEnum.swift" % (enum.swift_name, enum.basename)
filepath = os.path.join(directory, filename)
outf = open(filepath, "w")
generate_header(outf, filelist, xml_list, filename)
t.write(outf, """
${formatted_description}public enum ${swift_name}: ${raw_value_type} {
${{entry:${formatted_description}\tcase ${swift_name} = ${value}\n}}
}
extension ${swift_name}: Enumeration {
public static var typeName = "${name}"
public static var typeDescription = "${entity_description}"
public static var allMembers = [${all_entities}]
public static var membersDescriptions = [${entities_info}]
public static var enumEnd = UInt(${enum_end})
}
""", enum)
outf.close()
def generate_optionsets(directory, filelist, xml_list, enums):
"""Iterate through all enums and create Swift equivalents"""
print("Generating Enumerations")
for enum in enums:
if not enum.is_a_bitmask:
continue
for entry in enum.entry:
entry.parent_swift_name = enum.swift_name
filename = "%s%sOptionSet.swift" % (enum.swift_name, enum.basename)
filepath = os.path.join(directory, filename)
outf = open(filepath, "w")
generate_header(outf, filelist, xml_list, filename)
t.write(outf, """
${formatted_description}public struct ${swift_name}: OptionSet {
\tpublic let rawValue: ${raw_value_type}
\tpublic init(rawValue: ${raw_value_type}) {
\t\tself.rawValue = rawValue
\t}
${{entry:${formatted_description}\tpublic static let ${swift_name} = ${parent_swift_name}(rawValue: ${value})\n}}
}
extension ${swift_name}: MAVLinkBitmask {
public static var typeName = "${name}"
public static var typeDescription = "${entity_description}"
public static var allMembers = [${all_entities}]
public static var membersDescriptions = [${entities_info}]
public static var enumEnd = UInt(${enum_end})
}
""", enum)
outf.close()
def get_enum_raw_type(enum, msgs):
"""Search appropirate raw type for enums in messages fields"""
for msg in msgs:
for field in msg.fields:
if field.enum == enum.name:
return swift_types[field.type][0]
return "Int"
def generate_messages(directory, filelist, xml_list, msgs):
"""Generate Swift structs to represent all MAVLink messages"""
print("Generating Messages")
for msg in msgs:
filename = "%s%sMsg.swift" % (msg.swift_name, msg.basename)
filepath = os.path.join(directory, filename)
outf = open(filepath, "w")
generate_header(outf, filelist, xml_list, filename)
t.write(outf, """
import Foundation
${formatted_description}public struct ${swift_name} {
${{fields:${formatted_description}\tpublic let ${swift_name}: ${return_type}\n}}
}
extension ${swift_name}: Message {
public static let id = UInt8(${id})
public static var typeName = "${name}"
public static var typeDescription = "${message_description}"
public static var fieldDefinitions: [FieldDefinition] = [${fields_info}]
public init(data: Data) throws {
${{ordered_fields:\t\t${init_accessor} = ${initial_value}\n}}
}
public func pack() throws -> Data {
var payload = Data(count: ${wire_length})
${{ordered_fields:\t\ttry payload.${payload_setter}\n}}
return payload
}
}
""", msg)
outf.close()
def append_static_code(filename, outf):
"""Open and copy static code from specified file"""
basepath = os.path.dirname(os.path.realpath(__file__))
filepath = os.path.join(basepath, 'swift/%s' % filename)
print("Appending content of %s" % filename)
with open(filepath) as inf:
for line in inf:
outf.write(line)
def generate_message_mappings_array(outf, msgs):
"""Create array for mapping message Ids to proper structs"""
classes = []
for msg in msgs:
classes.append("%u: %s.self" % (msg.id, msg.swift_name))
t.write(outf, """
/// Array for mapping message id to proper struct
private let messageIdToClass: [UInt8: Message.Type] = [${ARRAY_CONTENT}]
""", {'ARRAY_CONTENT' : ", ".join(classes)})
def generate_message_lengths_array(outf, msgs):
"""Create array with message lengths to validate known message lengths"""
# form message lengths array
lengths = []
for msg in msgs:
lengths.append("%u: %u" % (msg.id, msg.wire_length))
t.write(outf, """
/// Message lengths array for known messages length validation
private let messageLengths: [UInt8: UInt8] = [${ARRAY_CONTENT}]
""", {'ARRAY_CONTENT' : ", ".join(lengths)})
def generate_message_crc_extra_array(outf, msgs):
"""Add array with CRC extra values to detect incompatible XML changes"""
crcs = []
for msg in msgs:
crcs.append("%u: %u" % (msg.id, msg.crc_extra))
t.write(outf, """
/// Message CRSs extra for detection incompatible XML changes
private let messageCRCsExtra: [UInt8: UInt8] = [${ARRAY_CONTENT}]
""", {'ARRAY_CONTENT' : ", ".join(crcs)})
def camel_case_from_underscores(string):
"""Generate a CamelCase string from an underscore_string"""
components = string.split('_')
string = ''
for component in components:
if component in abbreviations:
string += component
else:
string += component[0].upper() + component[1:].lower()
return string
def lower_camel_case_from_underscores(string):
"""Generate a lower-cased camelCase string from an underscore_string"""
components = string.split('_')
string = components[0].lower()
for component in components[1:]:
string += component[0].upper() + component[1:].lower()
return string
def contains_a_bitmask(enums, enumName):
for enum in enums:
if enum.name == enumName:
return enum.is_a_bitmask
return False
def enum_is_a_bitmask(enum):
values = []
for entry in enum.entry:
values.append(entry.value)
values.sort()
for i, value in enumerate(values):
if 2 ** i != value:
return False
return True
def generate_enums_type_info(enums, msgs):
"""Add camel case swift names for enums an entries, descriptions and sort enums alphabetically"""
for enum in enums:
enum.swift_name = camel_case_from_underscores(enum.name)
enum.raw_value_type = get_enum_raw_type(enum, msgs)
enum.formatted_description = ""
if enum.description:
enum.description = " ".join(enum.description.split())
enum.formatted_description = "/// %s\n" % enum.description
for index, entry in enumerate(enum.entry):
if entry.name.endswith("_ENUM_END"):
enum.enum_end = entry.value
del enum.entry[index]
all_entities = []
entities_info = []
for entry in enum.entry:
name = entry.name.replace(enum.name + '_', '')
"""Ensure that enums entry name does not start from digit"""
if name[0].isdigit():
name = "MAV_" + name
entry.swift_name = lower_camel_case_from_underscores(name)
"""Ensure that enums entry name does not match any swift keyword"""
if entry.swift_name in swift_keywords:
entry.swift_name = lower_camel_case_from_underscores("MAV_" + name)
entry.formatted_description = ""
if entry.description:
entry.description = " ".join(entry.description.split())
entry.formatted_description = "\n\t/// " + entry.description + "\n"
all_entities.append(entry.swift_name)
entities_info.append('("%s", "%s")' % (entry.name, entry.description.replace('"','\\"')))
enum.all_entities = ", ".join(all_entities)
enum.entities_info = ", ".join(entities_info)
enum.entity_description = enum.description.replace('"','\\"')
enum.is_a_bitmask = enum_is_a_bitmask(enum)
enums.sort(key = lambda enum : enum.swift_name)
def generate_messages_type_info(msgs, enums):
"""Add proper formatted variable names, initializers and type names to use in templates"""
for msg in msgs:
msg.swift_name = camel_case_from_underscores(msg.name)
msg.formatted_description = ""
if msg.description:
msg.description = " ".join(msg.description.split())
msg.formatted_description = "/// %s\n" % " ".join(msg.description.split())
msg.message_description = msg.description.replace('"','\\"')
for field in msg.ordered_fields:
field.swift_name = lower_camel_case_from_underscores(field.name)
field.init_accessor = field.swift_name if field.swift_name != "data" else "self.%s" % field.swift_name
field.pack_accessor = field.swift_name if field.swift_name != "payload" else "self.%s" % field.swift_name
field.return_type = swift_types[field.type][0]
# configure fields initializers
if field.enum:
# handle enums
field.return_type = camel_case_from_underscores(field.enum)
if contains_a_bitmask(enums, field.enum):
field.initial_value = "try data.bitmask(at: %u)" % field.wire_offset
else:
field.initial_value = "try data.enumeration(at: %u)" % field.wire_offset
field.payload_setter = "set(%s, at: %u)" % (field.pack_accessor, field.wire_offset)
elif field.array_length > 0:
if field.return_type == "String":
# handle strings
field.initial_value = "try data." + swift_types[field.type][2] % (field.wire_offset, field.array_length)
field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset, field.array_length)
else:
# other array types
field.return_type = "[%s]" % field.return_type
field.initial_value = "try data.array(at: %u, capacity: %u)" % (field.wire_offset, field.array_length)
field.payload_setter = "set(%s, at: %u, capacity: %u)" % (field.pack_accessor, field.wire_offset, field.array_length)
else:
# simple type field
field.initial_value = "try data." + swift_types[field.type][2] % field.wire_offset
field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset)
field.formatted_description = ""
if field.description:
field.description = " ".join(field.description.split())
field.formatted_description = "\n\t/// " + field.description + "\n"
fields_info = ['("%s", %u, "%s", %u, "%s")' % (field.swift_name, field.wire_offset, field.return_type, field.array_length, field.description.replace('"','\\"')) for field in msg.fields]
msg.fields_info = ", ".join(fields_info)
msgs.sort(key = lambda msg : msg.id)
def generate(basename, xml_list):
"""Generate complete MAVLink Swift implementation"""
msgs = []
enums = []
filelist = []
for xml in xml_list:
for msg in xml.message: msg.basename = xml.basename.title()
for enum in xml.enum: enum.basename = xml.basename.title()
msgs.extend(xml.message)
enums.extend(xml.enum)
filelist.append(os.path.basename(xml.filename))
generate_enums_type_info(enums, msgs)
generate_messages_type_info(msgs, enums)
generate_mavlink(basename, filelist, xml_list, msgs)
generate_enums(basename, filelist, xml_list, enums)
generate_optionsets(basename, filelist, xml_list, enums)
generate_messages(basename, filelist, xml_list, msgs)