267 lines
9.0 KiB
Python
267 lines
9.0 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
# Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>
|
|
|
|
|
|
from copy import copy
|
|
|
|
from .scoped_name import ScopedName
|
|
from .specification import ArgumentType, IfaceFileType
|
|
from .utils import append_iface_file, argument_as_str, same_base_type
|
|
|
|
|
|
def encoded_template_name(template):
|
|
""" Return the encoded name of a template. """
|
|
|
|
snd = ScopedName(template.cpp_name)
|
|
|
|
for ad in template.types.args:
|
|
flags = 0
|
|
|
|
if ad.is_const:
|
|
flags |= 1
|
|
|
|
if ad.is_reference:
|
|
flags |= 2
|
|
|
|
# We use numbers so they don't conflict with names.
|
|
encoding = '{:02d}{}{}'.format(ad.type.value, flags, len(ad.derefs))
|
|
|
|
if ad.type is ArgumentType.DEFINED:
|
|
arg_snd = ScopedName(ad.definition)
|
|
elif ad.type is ArgumentType.TEMPLATE:
|
|
arg_snd = encoded_template_name(ad.definition)
|
|
elif ad.type is ArgumentType.STRUCT:
|
|
arg_snd = ScopedName(ad.definition)
|
|
else:
|
|
arg_snd = None
|
|
|
|
if arg_snd is None:
|
|
snd.append(encoding)
|
|
else:
|
|
# Replace the first element of the argument name with a copy with
|
|
# the encoding prepended.
|
|
arg_snd[0] = encoding + arg_snd[0]
|
|
|
|
for name in arg_snd:
|
|
snd.append(name)
|
|
|
|
return snd
|
|
|
|
|
|
def same_template_signature(sig1, sig2, deep=False):
|
|
""" Return True if the template signatures are the same. A deep comparison
|
|
is used for mapped type templates where we want to recurse into any nested
|
|
templates.
|
|
"""
|
|
|
|
if len(sig1.args) != len(sig2.args):
|
|
return False
|
|
|
|
for type1, type2 in zip(sig1.args, sig2.args):
|
|
# If we are doing a shallow comparision (ie. for class templates) then
|
|
# a type name in the first signature matches anything in the second
|
|
# signature.
|
|
if type1.type is ArgumentType.DEFINED and not deep:
|
|
continue
|
|
|
|
# For type names only compare the references and pointers, and do the
|
|
# same for any nested templates.
|
|
if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.DEFINED:
|
|
if type1.is_reference != type2.is_reference or len(type1.derefs) != len(type2.derefs):
|
|
return False
|
|
|
|
elif type1.type is ArgumentType.TEMPLATE and type2.type is ArgumentType.TEMPLATE:
|
|
if not same_template_signature(type1.definition.types, type2.definition.types, deep=deep):
|
|
return False
|
|
|
|
elif not same_base_type(type1, type2):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def template_code(spec, used, proto_code, expansions):
|
|
""" Return a copy of an optional CodeBlock object with sub-strings replaced
|
|
by corresponding values.
|
|
"""
|
|
|
|
# Handle the trivial case.
|
|
if proto_code is None:
|
|
return None
|
|
|
|
return _template_code_block(spec, used, proto_code, expansions)
|
|
|
|
|
|
def template_code_blocks(spec, used, proto_code_blocks, expansions):
|
|
""" Return a copy of a list of CodeBlock objects with sub-strings replaced
|
|
by corresponding values.
|
|
"""
|
|
|
|
return [_template_code_block(spec, used, pc, expansions)
|
|
for pc in proto_code_blocks]
|
|
|
|
|
|
def template_expansions(template_names, instantiation_values,
|
|
declared_names=None):
|
|
""" Return a dict of expansions to be applied when instantiating mapped
|
|
type of class templates (including handwritten code). The key is the
|
|
symbolic name of a template argument and the value is the replacement to be
|
|
used in a particular instantiation.
|
|
"""
|
|
|
|
# TODO: This is broken (or possibly just over-complicated). The
|
|
# declaration of template parameters is used for two purposes: firstly to
|
|
# allow real types to be used to distinguish between overloaded templates;
|
|
# secondly to define the names of the parameters that will be replaced by
|
|
# values provided at instantiation. It's possible that only the latter
|
|
# will ever be expanded.
|
|
|
|
expansions = {}
|
|
|
|
for arg_nr, name_arg in enumerate(template_names.args):
|
|
if name_arg.type is ArgumentType.DEFINED:
|
|
# If the type names have been declared (as they are with a mapped
|
|
# type template) check that this is one of them.
|
|
if declared_names is not None:
|
|
# Only consider unscoped names.
|
|
if not name_arg.definition.is_simple:
|
|
continue
|
|
|
|
for declared in declared_names.args:
|
|
# Skip anything but simple names.
|
|
if declared.type is not ArgumentType.DEFINED:
|
|
continue
|
|
|
|
if name_arg.definition.base_name == declared.definition.base_name:
|
|
name = name_arg.definition.base_name
|
|
break
|
|
else:
|
|
continue
|
|
else:
|
|
name = name_arg.definition.base_name
|
|
|
|
# Get the corresponding value. For defined types we don't want any
|
|
# indirection or references.
|
|
value_arg = instantiation_values.args[arg_nr]
|
|
|
|
if value_arg.type is ArgumentType.DEFINED:
|
|
value = str(value_arg.definition)
|
|
else:
|
|
value = argument_as_str(value_arg)
|
|
|
|
# We do want const.
|
|
if value_arg.is_const:
|
|
value = 'const ' + value;
|
|
|
|
expansions[name] = value
|
|
|
|
elif name_arg.type is ArgumentType.TEMPLATE:
|
|
value_arg = instantiation_values.args[arg_nr]
|
|
|
|
# These checks shouldn't be necessary, but...
|
|
if value_arg.type is ArgumentType.TEMPLATE and len(name_arg.definition.types.args) == len(value_arg.definition.types.args):
|
|
expansions.update(
|
|
template_expansions(name_arg.definition.types,
|
|
value_arg.definition.types, declared_names))
|
|
|
|
return expansions
|
|
|
|
|
|
def template_string(proto_str, expansions, scope_replacement=None):
|
|
""" Return a copy of a string with sub-strings replaced by corresponding
|
|
values.
|
|
"""
|
|
|
|
for name, value in expansions.items():
|
|
value = _strip_const(value)
|
|
|
|
# Translate any C++ scoping.
|
|
if scope_replacement is not None:
|
|
value = value.replace('::', scope_replacement)
|
|
|
|
# Perform any replacement.
|
|
proto_str = proto_str.replace(name, value)
|
|
|
|
return proto_str
|
|
|
|
|
|
def _strip_const(s):
|
|
""" Strip any leading 'const' from a string. """
|
|
|
|
if s.startswith('const '):
|
|
s = s[6:]
|
|
|
|
return s
|
|
|
|
|
|
def _template_code_block(spec, used, proto_code, expansions):
|
|
""" Return a copy of a CodeBlock object with sub-strings replaced by
|
|
corresponding values or the original if there were no substitutions.
|
|
"""
|
|
|
|
i_code = copy(proto_code)
|
|
|
|
i_lines = []
|
|
for proto_line in proto_code.text.split('\n'):
|
|
i_line = proto_line
|
|
|
|
# Don't do any substitution in lines that appear to be preprocessor
|
|
# directives. This prevents #include'd file names being broken.
|
|
if not proto_line.lstrip().startswith('#'):
|
|
# Go through each expansion.
|
|
for name, value in expansions.items():
|
|
# Look for the name at the current position in the current
|
|
# line.
|
|
pos = i_line.find(name)
|
|
while pos >= 0:
|
|
# See if the name is referring to a generated type
|
|
# structure.
|
|
for gen_type in ('sipType_', 'sipException_'):
|
|
if i_line[:pos].endswith(gen_type):
|
|
value = _strip_const(value)
|
|
|
|
_add_used_from_code(spec, used, value)
|
|
|
|
# Convert the value to the rest of the name of the
|
|
# generated type structure.
|
|
if value.startswith('::'):
|
|
value = value[2:]
|
|
|
|
value = value.replace('::', '_')
|
|
|
|
break
|
|
|
|
# Perform the substitution and update the current position.
|
|
i_line = i_line[0:pos] + value + i_line[pos + len(name):]
|
|
pos = i_line.find(name, pos + len(value))
|
|
|
|
i_lines.append(i_line)
|
|
|
|
i_code.text = '\n'.join(i_lines)
|
|
|
|
# Return the prototype itself if nothing changed.
|
|
if proto_code.text == i_code.text:
|
|
return proto_code
|
|
|
|
return i_code
|
|
|
|
|
|
def _add_used_from_code(spec, used, name):
|
|
""" Add any interface files to a used list that are defined for a name. """
|
|
|
|
name = ScopedName.parse(name)
|
|
name.make_absolute()
|
|
|
|
for iface_file in spec.iface_files:
|
|
if iface_file.type in (IfaceFileType.CLASS, IfaceFileType.EXCEPTION):
|
|
if iface_file.fq_cpp_name == name:
|
|
append_iface_file(used, iface_file)
|
|
return
|
|
|
|
for enum in spec.enums:
|
|
if enum.scope is not None:
|
|
if enum.fq_cpp_name == name:
|
|
append_iface_file(used, enum.scope.iface_file)
|
|
return
|