# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
from wolframclient.serializers.wxfencoder.constants import (
VALID_PACKED_ARRAY_TYPES,
WXF_CONSTANTS,
)
from wolframclient.serializers.wxfencoder.serializer import WXFSerializerException
from wolframclient.serializers.wxfencoder.utils import (
float_to_bytes,
integer_size,
integer_to_bytes,
write_varint,
)
from wolframclient.utils import six
__all__ = [
"WXFExprFunction",
"WXFExprInteger",
"WXFExprString",
"WXFExprSymbol",
"WXFExprBinaryString",
"WXF_CONSTANTS",
]
class WXFExpr(object):
"""Represent a WXF expression.
This is an abstract class. Only its children must be used.
"""
__slots__ = "wxf_type"
def __init__(self, wxf_type):
self.wxf_type = wxf_type
def _serialize_to_wxf(self, context, stream):
""" Write the serialized form of a given WXFExpr. """
raise NotImplementedError
[docs]class WXFExprFunction(WXFExpr):
"""Functions have a length representing the number of parts (including zero).
Each function has a head which is itself a expr, usually a `WXFExprSymbol` which
is not accounted in the length.
"""
__slots__ = "length"
def __init__(self, length):
super(WXFExprFunction, self).__init__(WXF_CONSTANTS.Function)
self.length = length
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
# Function has a head which account for one part element contrary to association
context.step_into_new_function(self.length)
write_varint(self.length, stream)
[docs]class WXFExprInteger(WXFExpr):
""" Integers have various length, from one byte up to eight and are signed
values. Values above 2^63-1 are represented with `WXFExprBigInteger`.
Internally WXF uses the two's complement representation of integer values.
The endianness is system independent and is always little-endian.
"""
__slots__ = "value", "int_size"
def __init__(self, value):
if not isinstance(value, six.integer_types):
raise TypeError("WXFExprInteger must be initialize with an integer value.")
wxf_type, self.int_size = integer_size(value)
super(WXFExprInteger, self).__init__(wxf_type)
self.value = value
""" Encode the integer into bytes and return them in a `buffer`.
Note that the buffer is an bytearray in python 2.7 and an array in 3.x.
This method is only useful to hide the Python 2.7 implementation.
It is proxying int.to_bytes for version 3.4 and above.
"""
[docs] def to_bytes(self):
return integer_to_bytes(self.value, self.int_size)
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.add_part()
stream.write(self.to_bytes())
class WXFExprReal(WXFExpr):
""" Represent a floating point value. Internally WXF represents the value with
double float-point value in the IEEE 754 standard. """
__slots__ = "value"
def __init__(self, value):
if not isinstance(value, float):
raise TypeError("WXFExprReal must be initialized with a float.")
super(WXFExprReal, self).__init__(WXF_CONSTANTS.Real64)
self.value = value
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.add_part()
stream.write(float_to_bytes(self.value))
class _WXFExprStringLike(WXFExpr):
""" Parent class of all string based expressions.
Store a given string value as a utf-8 encoded binary string.
"""
__slots__ = "length", "value"
def __init__(self, wxf_type, value, allow_binary=False):
""" Initialize using a given string `value`.
If `allow_binary` is set to false, also accept binary types: `bytes` (py3) and `str` (py2)
"""
super(_WXFExprStringLike, self).__init__(wxf_type)
if allow_binary and isinstance(value, six.binary_type):
self.value = value
elif isinstance(value, six.text_type):
self.value = value.encode("utf-8")
else:
raise TypeError(
self.__class__.__name__
+ " must be initialize with a string. Was '"
+ value.__class__.__name__
+ "'."
)
self.length = len(self.value)
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.add_part()
write_varint(self.length, stream)
stream.write(self.value)
[docs]class WXFExprSymbol(_WXFExprStringLike):
""" A symbol represented by a string name. The name is always utf8 encoded."""
def __init__(self, value):
super(WXFExprSymbol, self).__init__(WXF_CONSTANTS.Symbol, value, allow_binary=True)
[docs]class WXFExprString(_WXFExprStringLike):
""" A string of unicode character. The string is always utf8 encoded.
Notabene: Python 3 does not allow utf8 encoding of the surrogate range
from `0xD800` to `0xDFFF`. Python 2 and the Wolfram Language on the other
hand handle those characters as any other unicode code points.
"""
def __init__(self, value):
super(WXFExprString, self).__init__(WXF_CONSTANTS.String, value)
class WXFExprBigInteger(_WXFExprStringLike):
""" A string of digits representing a big integer"""
def __init__(self, value):
super(WXFExprBigInteger, self).__init__(
WXF_CONSTANTS.BigInteger, value, allow_binary=True
)
class WXFExprBigReal(_WXFExprStringLike):
""" A string representation of a real value with arbitrary precision. The string format matches the one of the
:wl:`InputForm` string representation of the real in the Wolfram Language.
"""
def __init__(self, value):
super(WXFExprBigReal, self).__init__(WXF_CONSTANTS.BigReal, value, allow_binary=True)
[docs]class WXFExprBinaryString(WXFExpr):
"""A string of arbitrary bytes. Contrary to `WXFExprString` no encoding is
required."""
__slots__ = "data"
def __init__(self, data):
if isinstance(data, (six.binary_type, bytearray, six.buffer_types)):
self.data = data
else:
raise TypeError(
"WXFExprBinaryString must be initialized with binary data: bytes in Python 3, str in Python 2.7 or bytearray."
)
super(WXFExprBinaryString, self).__init__(WXF_CONSTANTS.BinaryString)
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.add_part()
write_varint(len(self.data), stream)
stream.write(self.data)
class _WXFExprArray(WXFExpr):
"""Arrays are multidimensional tables of machine-precision numeric values.
The `dimensions` is a list of strictly positive integers representing the
array shape. The data contains the flatten binary representation of the
values."""
__slots__ = "dimensions", "value_type", "data"
def __init__(self, wxf_type, dimensions, value_type, data=None):
super(_WXFExprArray, self).__init__(wxf_type)
if not isinstance(dimensions, (list, tuple)) or len(dimensions) == 0:
raise TypeError("Dimensions must be a non-empty list.")
if not all(isinstance(dim, six.integer_types) and dim > 0 for dim in dimensions):
raise ValueError("Dimensions must be positive integers.")
self.dimensions = dimensions
self.value_type = value_type
self.data = data
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.add_part()
stream.write(self.value_type)
write_varint(len(self.dimensions), stream)
for dim in self.dimensions:
write_varint(dim, stream)
if self.data is not None:
stream.write(self.data)
else:
raise WXFSerializerException("Missing array data.")
class WXFExprPackedArray(_WXFExprArray):
""" Packed array is a type of array that only supports a subset of all the
possible type of values: signed integers, reals, complexes. See `VALID_PACKED_ARRAY_TYPES`."""
def __init__(self, dimensions, value_type, data=None):
if value_type not in VALID_PACKED_ARRAY_TYPES:
raise Exception("Invalid packed array value type ({}).", value_type)
super(WXFExprPackedArray, self).__init__(
WXF_CONSTANTS.PackedArray, dimensions, value_type, data
)
class WXFExprNumericArray(_WXFExprArray):
""" Raw array is an array that supports many type of values:
signed and unsigned integers, reals, complexes. See `ARRAY_TYPES`."""
def __init__(self, dimensions, value_type, data=None):
super(WXFExprNumericArray, self).__init__(
WXF_CONSTANTS.NumericArray, dimensions, value_type, data
)
class WXFExprAssociation(WXFExpr):
""" Association is a key value store similar to `dict`. `WXFExprAssociation`
requires a length, the number of entries. Only `WXFExprRule` and `WXFExprRuleDelayed`
are valid entry types in an association.
"""
__slot__ = "length"
def __init__(self, length):
if not isinstance(length, six.integer_types) or length < 0:
raise TypeError("WXFExprAssociation must be instantiated with a length.")
super(WXFExprAssociation, self).__init__(WXF_CONSTANTS.Association)
self.length = length
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
context.step_into_new_assoc(self.length)
write_varint(self.length, stream)
class _WXFExprRule(WXFExpr):
def __init__(self, wxf_type):
super(_WXFExprRule, self).__init__(wxf_type)
def _serialize_to_wxf(self, stream, context):
stream.write(self.wxf_type)
# make sure those special tokens are correctly used inside an association.
if not context.is_rule_valid():
raise WXFSerializerException(
'WXF Rule and RuleDelayed must be part of an Association. Use a Function with head Symbol "Rule(Delayed)" outside associations.'
)
# rule always has two parts.
context.step_into_new_rule()
class WXFExprRule(_WXFExprRule):
""" Represent a rule in an association. Rule have two parts but no head.
Rules that are not part of an association (e.g list of rules) must be encoded
as a function with head `'Rule'`.
"""
def __init__(self):
super(WXFExprRule, self).__init__(WXF_CONSTANTS.Rule)
class WXFExprRuleDelayed(_WXFExprRule):
""" Represent a rule delayed in an association. See `WXFExprRule`"""
def __init__(self):
super(WXFExprRuleDelayed, self).__init__(WXF_CONSTANTS.RuleDelayed)