114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
|
from typing import List, Dict, Tuple, Set, Optional, Iterable
|
||
|
|
||
|
|
||
|
class NameGenerator:
|
||
|
"""Utility for generating distinct C names from Python names.
|
||
|
|
||
|
Since C names can't use '.' (or unicode), some care is required to
|
||
|
make C names generated from Python names unique. Also, we want to
|
||
|
avoid generating overly long C names since they make the generated
|
||
|
code harder to read.
|
||
|
|
||
|
Note that we don't restrict ourselves to a 32-character distinguishing
|
||
|
prefix guaranteed by the C standard since all the compilers we care
|
||
|
about at the moment support longer names without issues.
|
||
|
|
||
|
For names that are exported in a shared library (not static) use
|
||
|
exported_name() instead.
|
||
|
|
||
|
Summary of the approach:
|
||
|
|
||
|
* Generate a unique name prefix from suffix of fully-qualified
|
||
|
module name used for static names. If only compiling a single
|
||
|
module, this can be empty. For example, if the modules are
|
||
|
'foo.bar' and 'foo.baz', the prefixes can be 'bar_' and 'baz_',
|
||
|
respectively. If the modules are 'bar.foo' and 'baz.foo', the
|
||
|
prefixes will be 'bar_foo_' and 'baz_foo_'.
|
||
|
|
||
|
* Replace '.' in the Python name with '___' in the C name. (And
|
||
|
replace the unlikely but possible '___' with '___3_'. This
|
||
|
collides '___' with '.3_', but this is OK because names
|
||
|
may not start with a digit.)
|
||
|
|
||
|
The generated should be internal to a build and thus the mapping is
|
||
|
arbitrary. Just generating names '1', '2', ... would be correct,
|
||
|
though not very usable.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, groups: Iterable[List[str]]) -> None:
|
||
|
"""Initialize with a list of modules in each compilation group.
|
||
|
|
||
|
The names of modules are used to shorten names referring to
|
||
|
modules, for convenience. Arbitrary module
|
||
|
names are supported for generated names, but uncompiled modules
|
||
|
will use long names.
|
||
|
"""
|
||
|
self.module_map: Dict[str, str] = {}
|
||
|
for names in groups:
|
||
|
self.module_map.update(make_module_translation_map(names))
|
||
|
self.translations: Dict[Tuple[str, str], str] = {}
|
||
|
self.used_names: Set[str] = set()
|
||
|
|
||
|
def private_name(self, module: str, partial_name: Optional[str] = None) -> str:
|
||
|
"""Return a C name usable for a static definition.
|
||
|
|
||
|
Return a distinct result for each (module, partial_name) pair.
|
||
|
|
||
|
The caller should add a suitable prefix to the name to avoid
|
||
|
conflicts with other C names. Only ensure that the results of
|
||
|
this function are unique, not that they aren't overlapping with
|
||
|
arbitrary names.
|
||
|
|
||
|
If a name is not specific to any module, the module argument can
|
||
|
be an empty string.
|
||
|
"""
|
||
|
# TODO: Support unicode
|
||
|
if partial_name is None:
|
||
|
return exported_name(self.module_map[module].rstrip('.'))
|
||
|
if (module, partial_name) in self.translations:
|
||
|
return self.translations[module, partial_name]
|
||
|
if module in self.module_map:
|
||
|
module_prefix = self.module_map[module]
|
||
|
elif module:
|
||
|
module_prefix = module + '.'
|
||
|
else:
|
||
|
module_prefix = ''
|
||
|
actual = exported_name(f'{module_prefix}{partial_name}')
|
||
|
self.translations[module, partial_name] = actual
|
||
|
return actual
|
||
|
|
||
|
|
||
|
def exported_name(fullname: str) -> str:
|
||
|
"""Return a C name usable for an exported definition.
|
||
|
|
||
|
This is like private_name(), but the output only depends on the
|
||
|
'fullname' argument, so the names are distinct across multiple
|
||
|
builds.
|
||
|
"""
|
||
|
# TODO: Support unicode
|
||
|
return fullname.replace('___', '___3_').replace('.', '___')
|
||
|
|
||
|
|
||
|
def make_module_translation_map(names: List[str]) -> Dict[str, str]:
|
||
|
num_instances: Dict[str, int] = {}
|
||
|
for name in names:
|
||
|
for suffix in candidate_suffixes(name):
|
||
|
num_instances[suffix] = num_instances.get(suffix, 0) + 1
|
||
|
result = {}
|
||
|
for name in names:
|
||
|
for suffix in candidate_suffixes(name):
|
||
|
if num_instances[suffix] == 1:
|
||
|
result[name] = suffix
|
||
|
break
|
||
|
else:
|
||
|
assert False, names
|
||
|
return result
|
||
|
|
||
|
|
||
|
def candidate_suffixes(fullname: str) -> List[str]:
|
||
|
components = fullname.split('.')
|
||
|
result = ['']
|
||
|
for i in range(len(components)):
|
||
|
result.append('.'.join(components[-i - 1:]) + '.')
|
||
|
return result
|