"""
Functions for working with constant values.
"""
import json
from enum import Enum
from typing import Union
from penman.exceptions import ConstantError
from penman.types import Constant
pytype = type # store because type() is redefined below
class Type(Enum):
"""
An enumeration of constant value types.
"""
SYMBOL = 'Symbol'
STRING = 'String'
INTEGER = 'Integer'
FLOAT = 'Float'
NULL = 'Null'
# Make them available at the module level
SYMBOL = Type.SYMBOL #: Symbol constants (e.g., :code:`(... :polarity -)`)
STRING = Type.STRING #: String constants (e.g., :code:`(... :op1 "Kim")`)
INTEGER = Type.INTEGER #: Integer constants (e.g., :code:`(... :value 12)`)
FLOAT = Type.FLOAT #: Float constants (e.g., :code:`(... :value 1.2)`)
NULL = Type.NULL #: Empty values (e.g., :code:`(... :ARG1 )`)
_typemap = {
str: SYMBOL, # needs further checking
int: INTEGER,
float: FLOAT,
type(None): NULL,
}
[docs]
def type(constant_string: Union[str, None]) -> Type:
"""
Return the type of constant encoded by *constant_string*.
Examples:
>>> from penman import constant
>>> constant.type('-')
<Type.SYMBOL: 'Symbol'>
>>> constant.type('"foo"')
<Type.STRING: 'String'>
>>> constant.type('1')
<Type.INTEGER: 'Integer'>
>>> constant.type('1.2')
<Type.FLOAT: 'Float'>
>>> constant.type('')
<Type.NULL: 'Null'>
"""
if constant_string is None:
typ = NULL
else:
assert isinstance(constant_string, str)
value = evaluate(constant_string)
typ = _typemap[pytype(value)]
if (
typ == Type.SYMBOL
and constant_string.startswith('"')
and constant_string.endswith('"')
):
typ = Type.STRING
return typ
[docs]
def evaluate(constant_string: Union[str, None]) -> Constant:
"""
Evaluate and return *constant_string*.
If *constant_string* is ``None`` or an empty symbol (``''``), this
function returns ``None``, while an empty string constant
(``'""'``) returns an empty :py:class:`str` object
(``''``). Otherwise, symbols are returned unchanged while strings
get quotes removed and escape sequences are unescaped. Note that
this means it is impossible to recover the original type of
strings and symbols once they have been evaluated. For integer and
float constants, this function returns the equivalent Python
:py:class:`int` or :py:class:`float` objects.
Examples:
>>> from penman import constant
>>> constant.evaluate('-')
'-'
>>> constant.evaluate('"foo"')
'foo'
>>> constant.evaluate('1')
1
>>> constant.evaluate('1.2')
1.2
>>> constant.evaluate('') is None
True
"""
value: Constant = constant_string
if value is None or value == '':
value = None
else:
assert isinstance(constant_string, str)
if constant_string.startswith('"') ^ constant_string.endswith('"'):
raise ConstantError(f'unbalanced quotes: {constant_string}')
if constant_string not in ('true', 'false', 'null'):
try:
value = json.loads(constant_string, parse_constant=str)
except json.JSONDecodeError:
value = constant_string
if not (value is None or isinstance(value, (str, int, float))):
raise ConstantError(f'invalid constant: {value!r}')
return value
[docs]
def quote(constant: Constant) -> str:
"""
Return *constant* as a quoted string.
If *constant* is ``None``, this function returns an empty string
constant (``'""'``). All other types are cast to a string and
quoted.
Examples:
>>> from penman import constant
>>> constant.quote(None)
'""'
>>> constant.quote('')
'""'
>>> constant.quote('foo')
'"foo"'
>>> constant.quote('"foo"')
'"\\\\"foo\\\\""'
>>> constant.quote(1)
'"1"'
>>> constant.quote(1.5)
'"1.5"'
"""
if constant is None:
return '""'
else:
return json.dumps(str(constant))