'''
Encoding of Curry identifiers.
'''
from .. import inspect
import collections.abc, itertools, keyword, logging, re
logger = logging.getLogger(__name__)
__all__ = ['best', 'clean', 'encode', 'isaCurryIdentifier', 'symbolToFilename']
P_SYMBOL = re.compile(r'[^0-9a-zA-Z_\s]')
TR = {
'&' : '_amp_'
, '@' : '_at_'
, '!' : '_bang_'
, '`' : '_bt_'
, '^' : '_car_'
, ':' : '_col_'
, ',' : '_com_'
, '$' : '_dolr_'
, '.' : '_dot_'
, '"' : '_dq_'
, '=' : '_eq_'
, '\\ ': '_esc_'
, '>' : '_gt_'
, '{' : '_lbc_'
, '[' : '_lbk_'
, '(' : '_lp_'
, '<' : '_lt_'
, '-' : '_neg_'
, '#' : '_hsh_'
, '%' : '_pct_'
, '|' : '_pipe_'
, '+' : '_pos_'
, '?' : '_q_'
, '}' : '_rbc_'
, ']' : '_rbk_'
, ')' : '_rp_'
, ';' : '_semi_'
, '/' : '_sep_'
, '\'': '_sq_'
, '*' : '_star_'
, '~' : '_til_'
}
SPECIAL = {
'(->)' : 'Arrow'
, '[]' : 'Nil'
, ':' : 'Cons'
, '?' : 'Choice'
, '()' : 'Unit'
, '(,)' : 'Pair'
, '(,,)' : 'Triple'
, '&&' : 'and'
, '||' : 'or'
, '&' : 'bitand'
, '|' : 'bitor'
, '^' : 'bitxor'
, '.' : 'compose'
, '=:=' : 'constrEqOp'
, '=:<=' : 'nonstrictEqOp'
, '=:<<=': 'unifEqLinearOp'
, '===' : 'eq3'
, '==' : 'eq'
, '/=' : 'ne'
, '<' : 'lt'
, '>' : 'gt'
, '<=' : 'le'
, '>=' : 'ge'
, '+' : 'add'
, '-' : 'sub'
, '*' : 'mul'
, '/' : 'div'
, '%' : 'mod'
, '>>' : 'rsh'
, '<<' : 'lsh'
, '>>=' : 'irsh'
, '<<=' : 'ilsh'
, '++' : 'append'
, '**' : 'pow'
, '<*>' : 'ufo'
, '<|>' : 'alt'
}
P_TRAILING = re.compile(r'(\S+)(_CASE\d+)')
def specialName(s):
if s in SPECIAL:
return SPECIAL[s]
elif inspect.isa_tuple_name(s):
return 'Tuple%s' % (s.count(',') + 1)
elif s.startswith('Prelude.'):
return specialName(s[8:])
else:
m = re.match(P_TRAILING, s)
if m:
front, back = m.groups()
special = specialName(front)
if special:
return special + back
[docs]
def best(prefix, thing, disallow, limit=40):
'''
Choose the best name for something.
This is more art than science. Every name must be unique and must be a valid
Python identifier. Names should be as short and easy to read as possible.
Some context (i.e., the module or package name) is helpful unless the name
gets too long. Names need not be deterministic.
'''
if isinstance(thing, str):
def stems():
yield thing
yield _shortername(thing)
else:
def stems():
yield getattr(thing, 'fullname', None)
yield getattr(thing, 'name', None)
yield getattr(thing, '__name__', None)
yield _shortername(getattr(thing, 'name', None))
yield _shortrepr(thing)
for stem in stems():
if stem is not None:
best = encode(stem, prefix, disallow)
if len(best) <= limit:
return best
else:
try:
h = hash(thing)
except TypeError:
h = hash(best)
best = encode(hex(h & 0xffff), prefix, disallow)
assert len(best) <= limit
return best
[docs]
def clean(s, dot_as_us=True):
'''Clean up a string by encoding or removing illegal characters.'''
special = specialName(s)
if special:
return special
elif s.startswith('Prelude.'):
tailname = s[8:]
if isaTypeclassIdentifier(tailname):
name = '_'.join(map(clean, tailname.split('#')[1:]))
return clean(name, dot_as_us)
else:
return clean(tailname, dot_as_us)
else:
if dot_as_us:
s = s.replace('.', '_')
a = ''.join(TR.get(ch, ch) for ch in s)
return str(re.sub(P_SYMBOL, '', a))
[docs]
def encode(name, prefix='', disallow={}):
'''
Encode a Curry name into an alphanumeric identifier.
Args:
name:
The Curry identifier to encode.
disallow:
A container of disallowed names.
Returns:
A string holding the encoded identifier.
'''
a = clean(name)
k = '%s%s' % (prefix, a)
if k in disallow or keyword.iskeyword(k):
# Append a number.
k_ = k
i = itertools.count()
while k_ in disallow or keyword.iskeyword(k_):
k_ = '%s_%d' % (k, next(i))
k = k_
assert k not in disallow
assert k.startswith(prefix)
return k
def _shortername(name):
if name is not None:
parts = name.split('.')
for i in reversed(range(len(parts))):
if isaCurryIdentifier(parts[i]):
return '.'.join(parts[i:])
def _shortrepr(obj):
if not isinstance(obj, str):
if isinstance(obj, collections.abc.Sequence):
return '_'.join(map(str, obj))
else:
return repr(obj)
[docs]
def symbolToFilename(name):
'''Makes the given symbol name into a valid UNIX filename.'''
assert name not in ['.', '..']
return ''.join(TR.get('/') if ch=='/' else ch for ch in name)
# Fixme: This does not match names such as a'.
P_IDENTIFIER = re.compile(r'^[a-zA-Z_][0-9a-zA-Z_]*$|^[^0-9a-zA-Z_\s]+$')
[docs]
def isaCurryIdentifier(basename):
'''
Indicates whether the given string is a valid Curry identifier. Legal
identifiers are 1) strings not beginning with a number where each character
is alphanumeric or an underscore; and 2) strings comprising only
non-alphanumeric, non-underscore, non-whitespace characters.
'''
return bool(re.match(P_IDENTIFIER, basename))
P_TYPECLASS_KEYS = re.compile('^_(def|Dict|impl|inst|super)#')
def isaTypeclassIdentifier(basename):
return bool(re.match(P_TYPECLASS_KEYS, basename))