313 lines
11 KiB
Python
313 lines
11 KiB
Python
|
#!/usr/bin/env python3
|
||
|
# coding=utf-8
|
||
|
|
||
|
#===============================================================================
|
||
|
# Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd.
|
||
|
# Description: Parse C headers and extract message definitions as ctypes objects.
|
||
|
#===============================================================================
|
||
|
import pycparser
|
||
|
import ctypes
|
||
|
|
||
|
binary_operators = {
|
||
|
"&": lambda x, y: x & y,
|
||
|
"|": lambda x, y: x | y,
|
||
|
"*": lambda x, y: x * y,
|
||
|
"+": lambda x, y: x + y,
|
||
|
"-": lambda x, y: x - y,
|
||
|
"/": lambda x, y: x // y,
|
||
|
"<<": lambda x, y: x << y,
|
||
|
">>": lambda x, y: x >> y,
|
||
|
"<=": lambda x, y: int(x <= y),
|
||
|
">=": lambda x, y: int(x >= y),
|
||
|
"==": lambda x, y: int(x == y),
|
||
|
"&&": lambda x, y: int(bool(x and y)),
|
||
|
"||": lambda x, y: int(bool(x or y)),
|
||
|
}
|
||
|
|
||
|
unary_operators = {
|
||
|
"-": lambda x: -x,
|
||
|
"!": lambda x: 1 if x else 0,
|
||
|
"sizeof": lambda x: ctypes.sizeof(x),
|
||
|
}
|
||
|
|
||
|
fundamental_types = {
|
||
|
"": ctypes.c_int,
|
||
|
"_Bool": ctypes.c_bool,
|
||
|
"char unsigned": ctypes.c_ubyte,
|
||
|
"char": ctypes.c_byte,
|
||
|
"double": ctypes.c_double,
|
||
|
#
|
||
|
# 32bit arm and riscv compilers define "long double" type as 8 and 16 byte long respectively.
|
||
|
# ctypes.c_longdouble under 32bit windows, 32bit linux, 64bit windows, 64bit linux OS has length 8, 12, 8, 16 respectively.
|
||
|
# We cannot find a ctype mapping to generalize.
|
||
|
# Especially that 16 byte riscv "long double" type cannot be interpreted by any 32/64bit windows ctype.
|
||
|
# Fortunately we don't have any long doubles in messages and etypes. So disable this mapping for now.
|
||
|
#
|
||
|
# "double long": ctypes.c_longdouble,
|
||
|
"float": ctypes.c_float,
|
||
|
"long long unsigned": ctypes.c_ulonglong,
|
||
|
"long long": ctypes.c_longlong,
|
||
|
#
|
||
|
# 32bit arm and riscv compilers define "long" type as 4 byte long.
|
||
|
# Messages xml generation calls this script with 32bit python while kv generation uses 64bit python.
|
||
|
# We need to map "long" type to ctypes.c_int which is 4 byte across 32/64bit OSs
|
||
|
# instead of ctypes.c_long which is 8 byte on 64bit linux and 4 byte otherwise.
|
||
|
# This also applies to "unsigned long" type.
|
||
|
#
|
||
|
"long unsigned": ctypes.c_uint,
|
||
|
"long": ctypes.c_int,
|
||
|
"short unsigned": ctypes.c_ushort,
|
||
|
"short": ctypes.c_short,
|
||
|
"unsigned": ctypes.c_uint,
|
||
|
"void": None,
|
||
|
}
|
||
|
|
||
|
# These types will have a typedef but map to alternative ctypes values.
|
||
|
override_typedefs = {
|
||
|
"osal_char": ctypes.c_char,
|
||
|
}
|
||
|
|
||
|
def get_typename(names):
|
||
|
specifiers = []
|
||
|
for name in sorted(names):
|
||
|
if name in ["signed", "int"]:
|
||
|
continue
|
||
|
specifiers.append(name)
|
||
|
return " ".join(specifiers)
|
||
|
|
||
|
def create_enum_type(enumname, enums):
|
||
|
smallest = min(enums.values())
|
||
|
largest = max(enums.values())
|
||
|
if smallest < 0:
|
||
|
if -128 <= smallest and largest <= 127:
|
||
|
base = ctypes.c_byte
|
||
|
elif -32768 <= smallest and largest <= 32767:
|
||
|
base = ctypes.c_short
|
||
|
else:
|
||
|
base = ctypes.c_int
|
||
|
else:
|
||
|
if largest < 256:
|
||
|
base = ctypes.c_ubyte
|
||
|
elif largest < 65536:
|
||
|
base = ctypes.c_ushort
|
||
|
else:
|
||
|
base = ctypes.c_uint
|
||
|
return type(enumname, (base,), {"members": enums})
|
||
|
|
||
|
|
||
|
class Visitor(pycparser.c_ast.NodeVisitor):
|
||
|
# Parse all typedefs and expand identifiers.
|
||
|
def __init__(self):
|
||
|
self.enums = {}
|
||
|
self.typedefs = dict(fundamental_types)
|
||
|
|
||
|
# The context in which the ID names exist.
|
||
|
self.id_context = self.enums
|
||
|
self.anon_struct_count = 1
|
||
|
self.anon_enum_count = 1
|
||
|
self.anon_union_count = 1
|
||
|
|
||
|
def generic_visit(self, node):
|
||
|
# Dump ast to see what is not implemented.
|
||
|
raise SystemExit("Unhandled ast element at %s: %s" % (node.coord, node))
|
||
|
|
||
|
def visit_Decl(self, node):
|
||
|
return
|
||
|
|
||
|
def visit_FuncDef(self, node):
|
||
|
return
|
||
|
|
||
|
def visit_FuncDecl(self, node):
|
||
|
return
|
||
|
|
||
|
def visit_FileAST(self, node):
|
||
|
for c in node:
|
||
|
self.visit(c)
|
||
|
|
||
|
def visit_ID(self, node):
|
||
|
try:
|
||
|
return self.id_context[node.name]
|
||
|
except KeyError:
|
||
|
raise SystemExit("Failed to resolve identifier '%s' at %s" % (node.name, node.coord))
|
||
|
|
||
|
def visit_Typename(self, node):
|
||
|
return self.visit(node.type)
|
||
|
|
||
|
def visit_TypeDecl(self, node):
|
||
|
return self.visit(node.type)
|
||
|
|
||
|
def visit_CompoundLiteral(self, node):
|
||
|
return self.visit(node.type)
|
||
|
|
||
|
def visit_PtrDecl(self, node):
|
||
|
return ctypes.POINTER(self.visit(node.type))
|
||
|
|
||
|
def visit_Typedef(self, node):
|
||
|
if node.name in self.typedefs:
|
||
|
raise SystemExit("Duplicate typedef '%s' at %s" % (node.name, node.coord))
|
||
|
if node.name in override_typedefs:
|
||
|
value = override_typedefs[node.name]
|
||
|
else:
|
||
|
value = self.visit(node.type)
|
||
|
self.typedefs[node.name] = value
|
||
|
|
||
|
def visit_ArrayRef(self, node):
|
||
|
# For accessing type of an array element
|
||
|
array = self.visit(node.name)
|
||
|
#index = self.visit(node.subscript)
|
||
|
return array._type_
|
||
|
|
||
|
def visit_StructRef(self, node):
|
||
|
# This is needed to get access to types inside a struct.
|
||
|
struct = self.visit(node.name)
|
||
|
self.id_context = dict(struct._fields_)
|
||
|
try:
|
||
|
return self.visit(node.field)
|
||
|
finally:
|
||
|
self.id_context = self.enums
|
||
|
|
||
|
def visit_BinaryOp(self, node):
|
||
|
try:
|
||
|
op = binary_operators[node.op]
|
||
|
except KeyError:
|
||
|
raise SystemExit("Unhandled binary operator '%s' at %s" % (node.op, node.coord))
|
||
|
leftval = self.visit(node.left)
|
||
|
rightval = self.visit(node.right)
|
||
|
return op(leftval, rightval)
|
||
|
|
||
|
def visit_UnaryOp(self, node):
|
||
|
value = self.visit(node.expr)
|
||
|
try:
|
||
|
op = unary_operators[node.op]
|
||
|
except KeyError:
|
||
|
raise SystemExit("Unhandled unary operator '%s' at %s" % (node.op, node.coord))
|
||
|
return op(value)
|
||
|
|
||
|
def visit_Enum(self, node):
|
||
|
# Mapping of enum names to enum values from all parsed enums.
|
||
|
value = -1
|
||
|
enums = {}
|
||
|
for enum in node.values:
|
||
|
if enum.value is None:
|
||
|
value += 1
|
||
|
else:
|
||
|
value = self.visit(enum.value)
|
||
|
self.enums[enum.name] = enums[enum.name] = value
|
||
|
|
||
|
if node.name is None:
|
||
|
enumname = "enum_anon_%d" % self.anon_enum_count
|
||
|
self.anon_enum_count += 1
|
||
|
else:
|
||
|
enumname = "enum_%s" % str(node.name)
|
||
|
return create_enum_type(enumname, enums)
|
||
|
|
||
|
def visit_Constant(self, node):
|
||
|
if node.type not in ["int", "unsigned int"]:
|
||
|
raise SystemExit("Unhandled Constant node type '%s' at %s" % (node.type, node.coord))
|
||
|
value = node.value.rstrip("LlUu")
|
||
|
if value.startswith(("0x", "0X")):
|
||
|
return int(value, 16)
|
||
|
return int(value)
|
||
|
|
||
|
def visit_IdentifierType(self, node):
|
||
|
name = get_typename(node.names)
|
||
|
try:
|
||
|
return self.typedefs[name]
|
||
|
except KeyError:
|
||
|
raise SystemExit("Invalid type specifier '%s' at %s" % (name, node.coord))
|
||
|
|
||
|
def _handle_fields(self, decls):
|
||
|
fields = []
|
||
|
# node.decls can be None when the struct declaration is outside the typedef.
|
||
|
if decls is not None:
|
||
|
field_count = 1
|
||
|
for decl in decls:
|
||
|
value = self.visit(decl.type)
|
||
|
if value is None:
|
||
|
# This is the void type - indicates an invalid message definition type.
|
||
|
return None
|
||
|
if decl.name is None:
|
||
|
fieldname = "unnamed_%d" % field_count
|
||
|
field_count += 1
|
||
|
else:
|
||
|
fieldname = decl.name
|
||
|
fields.append((fieldname, value))
|
||
|
return fields
|
||
|
|
||
|
def visit_Struct(self, node):
|
||
|
fields = self._handle_fields(node.decls)
|
||
|
if fields is None:
|
||
|
return None
|
||
|
|
||
|
if node.name is None:
|
||
|
structname = "struct_anon_%d" % self.anon_struct_count
|
||
|
self.anon_struct_count += 1
|
||
|
else:
|
||
|
structname = "struct_%s" % str(node.name)
|
||
|
return type(structname, (ctypes.Structure,), {"_fields_": fields})
|
||
|
|
||
|
def visit_Union(self, node):
|
||
|
fields = self._handle_fields(node.decls)
|
||
|
if fields is None:
|
||
|
return None
|
||
|
|
||
|
if node.name is None:
|
||
|
unionname = "union_anon_%d" % self.anon_union_count
|
||
|
self.anon_union_count += 1
|
||
|
else:
|
||
|
unionname = "union_%s" % str(node.name)
|
||
|
return type(unionname, (ctypes.Union,), {"_fields_": fields})
|
||
|
|
||
|
def visit_ArrayDecl(self, node):
|
||
|
type = self.visit(node.type)
|
||
|
if node.dim is None:
|
||
|
# Flexible array members do not have a size.
|
||
|
# Return a pointer for compatibility with old ctypesgen based implementation.
|
||
|
return ctypes.POINTER(type)
|
||
|
length = self.visit(node.dim)
|
||
|
if length is None or length < 0:
|
||
|
raise SystemExit("Invalid array len %s at %s" % (length, node.dim.coord))
|
||
|
if type is None:
|
||
|
raise SystemExit("Invalid array type %s at %s" % (type, node.type.coord))
|
||
|
return type * length
|
||
|
|
||
|
def message_enum_name(elemName):
|
||
|
if elemName.endswith("_t"):
|
||
|
return elemName[:-2].upper()
|
||
|
if elemName.endswith("_s"):
|
||
|
# NAS messages, already uppercase
|
||
|
return elemName[:-2]
|
||
|
if elemName.endswith("_STRUCT"):
|
||
|
return elemName[:-7]
|
||
|
if elemName.upper().find("LOG_MESSAGE") != -1:
|
||
|
return elemName.upper() +'_ID'
|
||
|
|
||
|
def parse_preprocessed_headers(source, core):
|
||
|
# Public function that returns the required data for producing messagexml
|
||
|
try:
|
||
|
node = pycparser.parse_file(source)
|
||
|
except pycparser.plyparser.ParseError as e:
|
||
|
raise SystemExit("ERROR parsing msgdefs %s: %s" % (source, e))
|
||
|
v = Visitor()
|
||
|
if core == 'acore':
|
||
|
v.anon_struct_count = 0x8001
|
||
|
v.anon_enum_count = 0x8001
|
||
|
v.anon_union_count = 0x8001
|
||
|
v.visit(node)
|
||
|
|
||
|
messages = []
|
||
|
for structname, fields in sorted(v.typedefs.items()):
|
||
|
messageEnumName = message_enum_name(structname)
|
||
|
if messageEnumName is None:
|
||
|
# Not a valid message definition name.
|
||
|
continue
|
||
|
try:
|
||
|
messageId = v.enums[messageEnumName]
|
||
|
except KeyError:
|
||
|
# No associated message id, so not a message definition.
|
||
|
continue
|
||
|
if fields is None:
|
||
|
raise SystemExit("Message definition contains undefined type: %s" % structname)
|
||
|
messages.append((messageEnumName, structname, messageId, fields))
|
||
|
return messages
|