Source code for wolframclient.serializers.wxfencoder.wxfexpr

# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function, unicode_literals

from wolframclient.serializers.wxfencoder.constants import (
    VALID_PACKED_ARRAY_TYPES, WXF_CONSTANTS, StructDouble, StructInt8LE,
    StructInt16LE, StructInt32LE, StructInt64LE)
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)