488 lines
15 KiB
Python
488 lines
15 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
# Copyright (c) 2025 Phil Thompson <phil@riverbankcomputing.com>
|
|
|
|
|
|
from .scoped_name import ScopedName
|
|
from .specification import ArgumentType, CachedName, IfaceFile, IfaceFileType
|
|
|
|
|
|
def append_iface_file(iface_file_list, iface_file):
|
|
""" Append an IfaceFile object to a list of them. """
|
|
|
|
# Make sure we don't try and add an interface file to its own list.
|
|
if iface_file.used is iface_file_list:
|
|
return
|
|
|
|
# Don't bother if it is already there.
|
|
if iface_file in iface_file_list:
|
|
return
|
|
|
|
iface_file_list.append(iface_file)
|
|
|
|
|
|
def argument_as_str(arg):
|
|
""" Convert an Argument object to a string of valid C++. """
|
|
|
|
if arg.original_typedef is None or arg.original_typedef.no_type_name:
|
|
if arg.type is ArgumentType.TEMPLATE:
|
|
s = str(arg.definition.cpp_name)
|
|
s += '<'
|
|
|
|
need_comma = False
|
|
for sub_arg in arg.definition.types.args:
|
|
if need_comma:
|
|
s += ','
|
|
else:
|
|
need_comma = True
|
|
|
|
s += argument_as_str(sub_arg)
|
|
|
|
if s.endswith('>'):
|
|
# For compilers earlier than C++11.
|
|
s += ' '
|
|
|
|
s += '>'
|
|
|
|
elif arg.type in (ArgumentType.STRUCT, ArgumentType.DEFINED):
|
|
s = str(arg.definition)
|
|
|
|
elif arg.type in (ArgumentType.UBYTE, ArgumentType.USTRING):
|
|
s = 'unsigned char'
|
|
|
|
elif arg.type in (ArgumentType.BYTE, ArgumentType.STRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
|
|
s = 'char'
|
|
|
|
elif arg.type in (ArgumentType.SBYTE, ArgumentType.SSTRING):
|
|
s = 'signed char'
|
|
|
|
elif arg.type is ArgumentType.WSTRING:
|
|
s = 'wchar_t'
|
|
|
|
elif arg.type is ArgumentType.USHORT:
|
|
s = 'unsigned short'
|
|
|
|
elif arg.type is ArgumentType.SHORT:
|
|
s = 'short'
|
|
|
|
elif arg.type is ArgumentType.UINT:
|
|
s = 'uint'
|
|
|
|
elif arg.type in (ArgumentType.INT, ArgumentType.CINT):
|
|
s = 'int'
|
|
|
|
elif arg.type is ArgumentType.ULONG:
|
|
s = 'unsigned long'
|
|
|
|
elif arg.type is ArgumentType.LONG:
|
|
s = 'long'
|
|
|
|
elif arg.type is ArgumentType.ULONGLONG:
|
|
s = 'unsigned long long'
|
|
|
|
elif arg.type is ArgumentType.LONGLONG:
|
|
s = 'long long'
|
|
|
|
elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
|
|
s = 'float'
|
|
|
|
elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
|
|
s = 'double'
|
|
|
|
elif arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
|
|
s = 'bool'
|
|
|
|
elif arg.type is ArgumentType.VOID:
|
|
s = 'void'
|
|
|
|
elif arg.type is ArgumentType.CAPSULE:
|
|
s = 'void *'
|
|
|
|
elif arg.type is ArgumentType.SSIZE:
|
|
s = 'Py_ssize_t'
|
|
|
|
elif arg.type is ArgumentType.SIZE:
|
|
s = 'size_t'
|
|
|
|
elif arg.type is ArgumentType.HASH:
|
|
s = 'Py_hash_t'
|
|
|
|
else:
|
|
# Use the original typedef.
|
|
s = str(arg.original_typedef.fq_cpp_name)
|
|
|
|
# Remove any global scope specifier (simply to make generated less
|
|
# cluttered).
|
|
if s.startswith('::'):
|
|
s = s[2:]
|
|
|
|
for _ in arg.derefs:
|
|
s += '*'
|
|
|
|
if arg.is_reference:
|
|
s += '&'
|
|
|
|
return s
|
|
|
|
|
|
def cached_name(spec, name):
|
|
""" Add a name to the cache if necessary and return the cached name. """
|
|
|
|
# Get the line of the cache for the length of this name creating it if
|
|
# necessary.
|
|
line = spec.name_cache.setdefault(len(name), [])
|
|
|
|
# See if the name has already been cached.
|
|
for nd in line:
|
|
if nd.name == name:
|
|
return nd
|
|
|
|
# Create a new entry.
|
|
nd = CachedName(name)
|
|
line.append(nd)
|
|
|
|
return nd
|
|
|
|
|
|
def find_iface_file(spec, mod, fq_cpp_name, iface_file_type, error_logger,
|
|
cpp_type=None, scope=None):
|
|
""" Return an interface file for a fully qualified C/C++ name and type
|
|
creating it if necessary.
|
|
"""
|
|
|
|
# See if the name is already used.
|
|
for iff in spec.iface_files:
|
|
if not iff.fq_cpp_name.matches(fq_cpp_name, scope=scope):
|
|
continue
|
|
|
|
# They must be the same type except that we allow a class if we want an
|
|
# exception. This is because we allow classes to be used before they
|
|
# are defined.
|
|
if iff.type is not iface_file_type:
|
|
if iface_file_type is not IfaceFileType.EXCEPTION or iff.type is not IfaceFileType.CLASS:
|
|
error_logger(
|
|
"a class, exception, namespace or mapped type has already been defined with the same name")
|
|
|
|
# Ignore an external class declared in another module.
|
|
if iface_file_type is IfaceFileType.CLASS and iff.module is not None and iff.module is not mod:
|
|
for cd in spec.classes:
|
|
if cd.iface_file is iff:
|
|
external = cd.external
|
|
break
|
|
else:
|
|
external = False
|
|
|
|
if external:
|
|
continue
|
|
|
|
# If this is a mapped type with the same name defined in a different
|
|
# module, then check that this type isn't the same as any of the mapped
|
|
# types defined in that module.
|
|
if iface_file_type == IfaceFileType.MAPPED_TYPE and iff.module is not mod:
|
|
for mtd in spec.mapped_types:
|
|
if mtd.iface_file is not iff:
|
|
continue
|
|
|
|
if cpp_type.type is not ArgumentType.TEMPLATE or \
|
|
mtd.type.type is not ArgumentType.TEMPLATE or \
|
|
same_base_type(cpp_type, mtd.type):
|
|
error_logger(
|
|
"the mapped type has already been defined in another module")
|
|
|
|
# If we got here then we have a mapped type based on an existing
|
|
# template, but with unique parameters. We don't want to use
|
|
# interface files from other modules, so skip this one.
|
|
continue
|
|
|
|
# Ignore a namespace defined in another module.
|
|
if iface_file_type is IfaceFileType.NAMESPACE and iff.module is not mod:
|
|
continue
|
|
|
|
return iff
|
|
|
|
# Create a new interface file.
|
|
fq_cpp_name = normalised_scoped_name(fq_cpp_name, scope)
|
|
|
|
iff = IfaceFile(iface_file_type,
|
|
cpp_name=cached_name(spec, str(fq_cpp_name)),
|
|
fq_cpp_name=fq_cpp_name)
|
|
|
|
# Use the same ordering as the old parser.
|
|
spec.iface_files.insert(0, iff)
|
|
|
|
return iff
|
|
|
|
|
|
def find_method(klass, name):
|
|
""" Return the Member object for a named member of a class or None if there
|
|
was none.
|
|
"""
|
|
|
|
for member in klass.members:
|
|
if member.py_name.name == name:
|
|
return member
|
|
|
|
return None
|
|
|
|
|
|
def normalised_scoped_name(scoped_name, scope):
|
|
""" Convert a scoped name to a fully qualified name. """
|
|
|
|
# Clone the name.
|
|
fq_scoped_name = ScopedName(scoped_name)
|
|
|
|
if fq_scoped_name.is_absolute:
|
|
pass
|
|
elif scope is None:
|
|
fq_scoped_name.make_absolute()
|
|
elif fq_scoped_name.is_simple:
|
|
fq_scoped_name.prepend(scope.iface_file.fq_cpp_name)
|
|
else:
|
|
# The relative name has a scope and appears within a scope so we need
|
|
# lookup the name's scope within the current scope.
|
|
names_scope = fq_scoped_name[0]
|
|
scope_fq_cpp_name = scope.iface_file.fq_cpp_name
|
|
|
|
while scope_fq_cpp_name is not None:
|
|
if scope_fq_cpp_name.base_name == names_scope:
|
|
del fq_scoped_name[0]
|
|
fq_scoped_name.prepend(scope_fq_cpp_name)
|
|
break
|
|
|
|
scope_fq_cpp_name = scope_fq_cpp_name.scope
|
|
else:
|
|
# The lookup failed so just make the name absolute.
|
|
fq_scoped_name.make_absolute()
|
|
|
|
return fq_scoped_name
|
|
|
|
|
|
# Argument type qualifiers.
|
|
_PY_STRING = (ArgumentType.USTRING, ArgumentType.SSTRING, ArgumentType.STRING,
|
|
ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING,
|
|
ArgumentType.UTF8_STRING)
|
|
_PY_FLOAT = (ArgumentType.CFLOAT, ArgumentType.FLOAT, ArgumentType.CDOUBLE,
|
|
ArgumentType.DOUBLE)
|
|
_PY_INT = (ArgumentType.BOOL, ArgumentType.HASH, ArgumentType.SSIZE,
|
|
ArgumentType.SIZE, ArgumentType.BYTE, ArgumentType.SBYTE,
|
|
ArgumentType.UBYTE, ArgumentType.SHORT, ArgumentType.USHORT,
|
|
ArgumentType.CINT, ArgumentType.INT, ArgumentType.UINT)
|
|
_PY_LONG = (ArgumentType.LONG, ArgumentType.LONGLONG)
|
|
_PY_ULONG = (ArgumentType.ULONG, ArgumentType.ULONGLONG)
|
|
_PY_AUTO = (ArgumentType.BOOL, ArgumentType.BYTE, ArgumentType.SBYTE,
|
|
ArgumentType.UBYTE, ArgumentType.SHORT, ArgumentType.USHORT,
|
|
ArgumentType.INT, ArgumentType.UINT, ArgumentType.FLOAT,
|
|
ArgumentType.DOUBLE)
|
|
_PY_CONSTRAINED = (ArgumentType.CBOOL, ArgumentType.CINT, ArgumentType.CFLOAT,
|
|
ArgumentType.CDOUBLE)
|
|
|
|
|
|
def py_as_int(type):
|
|
""" Return True if Python will interpret a type as int. """
|
|
|
|
return type.type in _PY_INT or type.type in _PY_LONG or type.type in _PY_ULONG
|
|
|
|
|
|
def same_argument_type(spec, arg1, arg2, strict=True):
|
|
""" Compare two argument types and return True if they are the same.
|
|
'strict' means as C++ would see it, rather than Python.
|
|
"""
|
|
|
|
if arg1.is_reference != arg2.is_reference:
|
|
return False
|
|
|
|
if len(arg1.derefs) != len(arg2.derefs):
|
|
return False
|
|
|
|
if strict:
|
|
# The const should be the same.
|
|
if arg1.is_const != arg2.is_const:
|
|
return False
|
|
|
|
return same_base_type(arg1, arg2)
|
|
|
|
# If both are constrained fundamental types then the types must match.
|
|
if arg1.type in _PY_CONSTRAINED and arg2.type in _PY_CONSTRAINED:
|
|
return arg1.type is arg2.type
|
|
|
|
if spec.target_abi >= (13, 0):
|
|
# Anonymous enums are ints.
|
|
if arg1.type in _PY_INT and arg2.type is ArgumentType.ENUM and arg2.definition.fq_cpp_name is None:
|
|
return True
|
|
|
|
if arg1.type is ArgumentType.ENUM and arg1.definition.fq_cpp_name is None and arg2.type in _PY_INT:
|
|
return True
|
|
else:
|
|
# An unconstrained enum also acts as a (very) constrained int.
|
|
if arg1.type in _PY_INT and arg2.type is ArgumentType.ENUM and not arg2.is_constrained:
|
|
return True
|
|
|
|
if arg1.type is ArgumentType.ENUM and not arg1.is_constrained and arg2.type in _PY_INT:
|
|
return True
|
|
|
|
# Python will see all these as strings.
|
|
if arg1.type in _PY_STRING and arg2.type in _PY_STRING:
|
|
return True
|
|
|
|
# Python will see all these as floats.
|
|
if arg1.type in _PY_FLOAT and arg2.type in _PY_FLOAT:
|
|
return True
|
|
|
|
# Python will see all these as ints.
|
|
if arg1.type in _PY_INT and arg2.type in _PY_INT:
|
|
return True
|
|
|
|
# Python will see all these as longs.
|
|
if arg1.type in _PY_LONG and arg2.type in _PY_LONG:
|
|
return True
|
|
|
|
# Python will see all these as unsigned longs.
|
|
if arg1.type in _PY_ULONG and arg2.type in _PY_ULONG:
|
|
return True
|
|
|
|
# Python will automatically convert between these.
|
|
if arg1.type in _PY_AUTO and arg2.type in _PY_AUTO:
|
|
return True
|
|
|
|
# All the special cases have been handled.
|
|
return same_base_type(arg1, arg2)
|
|
|
|
|
|
def same_base_type(type1, type2):
|
|
""" Return True if two Argument objects refer to the same base type, ie.
|
|
without taking into account const and pointers.
|
|
"""
|
|
|
|
# The types must be the same.
|
|
if type1.type is not type2.type:
|
|
# If we are comparing a template with those that have already been used
|
|
# to instantiate a class or mapped type then we need to compare with
|
|
# the class or mapped type name.
|
|
|
|
if type1.type is ArgumentType.CLASS and type2.type is ArgumentType.DEFINED:
|
|
|
|
return type1.definition.iface_file.fq_cpp_name.matches(type2.definition)
|
|
|
|
if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.CLASS:
|
|
return type2.definition.iface_file.fq_cpp_name.matches(type1.definition)
|
|
|
|
if type1.type is ArgumentType.MAPPED and type2.type is ArgumentType.DEFINED:
|
|
return type1.definition.iface_file.fq_cpp_name.matches(type2.definition)
|
|
|
|
if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.MAPPED:
|
|
return type2.definition.iface_file.fq_cpp_name.matches(type1.definition)
|
|
|
|
if type1.type is ArgumentType.ENUM and type2.type is ArgumentType.DEFINED:
|
|
return type1.definition.fq_cpp_name.matches(type2.definition)
|
|
|
|
if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.ENUM:
|
|
return type2.definition.fq_cpp_name.matches(type1.definition)
|
|
|
|
return False
|
|
|
|
if type1.type is ArgumentType.CLASS:
|
|
return type1.definition is type2.definition
|
|
|
|
if type1.type is ArgumentType.ENUM:
|
|
return type1.definition is type2.definition
|
|
|
|
if type1.type is ArgumentType.TEMPLATE:
|
|
td1 = type1.definition
|
|
td2 = type2.definition
|
|
|
|
if td1.cpp_name.absolute != td2.cpp_name.absolute:
|
|
return False
|
|
|
|
if len(td1.types.args) != len(td2.types.args):
|
|
return False
|
|
|
|
for ad1, ad2 in zip(td1.types.args, td2.types.args):
|
|
if len(ad1.derefs) != len(ad2.derefs):
|
|
return False
|
|
|
|
if not same_base_type(ad1, ad2):
|
|
return False
|
|
|
|
return True
|
|
|
|
if type1.type in (ArgumentType.STRUCT, ArgumentType.UNION):
|
|
return type1.definition == type2.definition
|
|
|
|
if type1.type is ArgumentType.DEFINED:
|
|
return type1.definition == type2.definition
|
|
|
|
if type1.type is ArgumentType.MAPPED:
|
|
return type1.definition is type2.definition
|
|
|
|
# They must be the same if we've got this far.
|
|
return True
|
|
|
|
|
|
def same_signature(spec, sig1, sig2, strict=True):
|
|
""" Compare two signatures and return True if they are the same. """
|
|
|
|
if strict:
|
|
# Count all the arguments.
|
|
na1 = len(sig1.args)
|
|
na2 = len(sig2.args)
|
|
else:
|
|
# Count only the compulsory arguments.
|
|
na1 = 0
|
|
|
|
for arg in sig1.args:
|
|
if arg.default_value is not None:
|
|
break
|
|
|
|
na1 += 1
|
|
|
|
na2 = 0
|
|
|
|
for arg in sig2.args:
|
|
if arg.default_value is not None:
|
|
break;
|
|
|
|
na2 += 1
|
|
|
|
# The number of arguments must be the same.
|
|
if na1 != na2:
|
|
return False
|
|
|
|
# The arguments must be the same.
|
|
for a in range(len(sig1.args)):
|
|
if not strict and sig1.args[a].default_value is not None:
|
|
break
|
|
|
|
if not same_argument_type(spec, sig1.args[a], sig2.args[a], strict=strict):
|
|
return False
|
|
|
|
# Must be the same if we've got this far.
|
|
return True
|
|
|
|
|
|
def search_typedefs(spec, cpp_name, type):
|
|
""" Search the typedefs and update the given type from any definition. """
|
|
|
|
# Look for the name.
|
|
fq_cpp_name = ScopedName(cpp_name)
|
|
fq_cpp_name.make_absolute()
|
|
|
|
for typedef in spec.typedefs:
|
|
if typedef.fq_cpp_name == fq_cpp_name:
|
|
break
|
|
else:
|
|
return
|
|
|
|
# Update the type.
|
|
type.type = typedef.type.type
|
|
type.allow_none = type.allow_none or typedef.type.allow_none
|
|
type.definition = typedef.type.definition
|
|
type.derefs.extend(typedef.type.derefs)
|
|
type.disallow_none = type.disallow_none or typedef.type.disallow_none
|
|
type.is_const = type.is_const or typedef.type.is_const
|
|
type.is_reference = type.is_reference or typedef.type.is_reference
|
|
type.type_hints = type.type_hints or typedef.type.type_hints
|
|
|
|
# Remember the original typedef.
|
|
if type.original_typedef is None:
|
|
type.original_typedef = typedef
|