Example #1
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="FixedSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="FixedSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="T:System.Byte[]" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is FixedSchema fixedSchema)
            {
                var readFixed = typeof(BinaryReader)
                                .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) });

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Call(
                                       context.Reader,
                                       readFixed,
                                       Expression.Constant(fixedSchema.Size)),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {fixedSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryFixedDeserializerBuilderCase)} can only be applied to {nameof(FixedSchema)}s.")));
            }
        }
        /// <summary>
        /// Creates a new <see cref="BinaryDeserializerBuilderCaseResult" /> for an unsuccessful
        /// outcome.
        /// </summary>
        /// <param name="exception">
        /// An exception describing the inapplicability of the case.
        /// </param>
        /// <returns>
        /// A <see cref="BinaryDeserializerBuilderCaseResult" /> with <see cref="Exceptions" />
        /// populated and <see cref="Expression" /> <c>null</c>.
        /// </returns>
        public static BinaryDeserializerBuilderCaseResult FromException(Exception exception)
        {
            var result = new BinaryDeserializerBuilderCaseResult();

            result.Exceptions.Add(exception);

            return(result);
        }
Example #3
0
 /// <summary>
 /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="NullSchema" />.
 /// </summary>
 /// <returns>
 /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
 /// is a <see cref="NullSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
 /// otherwise.
 /// </returns>
 /// <inheritdoc />
 public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
 {
     if (schema is NullSchema)
     {
         return(BinaryDeserializerBuilderCaseResult.FromExpression(
                    Expression.Default(type)));
     }
     else
     {
         return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryNullDeserializerBuilderCase)} can only be applied to {nameof(NullSchema)}s.")));
     }
 }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="TimestampLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="TimestampLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="LongSchema" /> or when
        /// <paramref name="schema" /> does not have a <see cref="MicrosecondTimestampLogicalType" />
        /// or a <see cref="MillisecondTimestampLogicalType" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="DateTime" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is TimestampLogicalType)
            {
                if (schema is not LongSchema)
                {
                    throw new UnsupportedSchemaException(schema, $"{nameof(TimestampLogicalType)} deserializers can only be built for {nameof(LongSchema)}s.");
                }

                var factor = schema.LogicalType switch
                {
                    MicrosecondTimestampLogicalType => TimeSpan.TicksPerMillisecond / 1000,
                    MillisecondTimestampLogicalType => TimeSpan.TicksPerMillisecond,
                    _ => throw new UnsupportedSchemaException(schema, $"{schema.LogicalType} is not a supported {nameof(TimestampLogicalType)}."),
                };

                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.Call(context.Reader, readInteger);

                var addTicks = typeof(DateTime)
                               .GetMethod(nameof(DateTime.AddTicks), new[] { typeof(long) });

                try
                {
                    // return Epoch.AddTicks(value * factor);
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Call(
                                       Expression.Constant(Epoch),
                                       addTicks,
                                       Expression.Multiply(expression, Expression.Constant(factor))),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryTimestampDeserializerBuilderCase)} can only be applied to schemas with a {nameof(TimestampLogicalType)}.")));
            }
        }
Example #5
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="StringSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="StringSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="string" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is StringSchema stringSchema)
            {
                var readString = typeof(BinaryReader)
                                 .GetMethod(nameof(BinaryReader.ReadString), Type.EmptyTypes);

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(Expression.Call(context.Reader, readString), type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {stringSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryStringDeserializerBuilderCase)} can only be applied to {nameof(StringSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DurationLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DurationLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="FixedSchema" /> with size
        /// <c>12</c>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="TimeSpan" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is DurationLogicalType)
            {
                if (!(schema is FixedSchema fixedSchema && fixedSchema.Size == DurationLogicalType.DurationSize))
                {
                    throw new UnsupportedSchemaException(schema);
                }

                var readFixed = typeof(BinaryReader)
                                .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) });

                Expression read = Expression.Call(context.Reader, readFixed, Expression.Constant(4));

                if (!BitConverter.IsLittleEndian)
                {
                    var buffer  = Expression.Variable(read.Type);
                    var reverse = typeof(Array)
                                  .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) });

                    read = Expression.Block(
                        new[] { buffer },
                        Expression.Assign(buffer, read),
                        Expression.Call(null, reverse, Expression.Convert(buffer, typeof(Array))),
                        buffer);
                }

                var toUInt32 = typeof(BitConverter)
                               .GetMethod(nameof(BitConverter.ToUInt32), new[] { typeof(byte[]), typeof(int) });

                read = Expression.ConvertChecked(
                    Expression.Call(null, toUInt32, read, Expression.Constant(0)),
                    typeof(long));

                var exceptionConstructor = typeof(OverflowException)
                                           .GetConstructor(new[] { typeof(string) });

                var timeSpanConstructor = typeof(TimeSpan)
                                          .GetConstructor(new[] { typeof(long) });

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(
                                   Expression.Block(
                                       Expression.IfThen(
                                           Expression.NotEqual(read, Expression.Constant(0L)),
                                           Expression.Throw(
                                               Expression.New(
                                                   exceptionConstructor,
                                                   Expression.Constant($"Durations containing months cannot be accurately deserialized to a {nameof(TimeSpan)}.")))),
                                       Expression.New(
                                           timeSpanConstructor,
                                           Expression.AddChecked(
                                               Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerDay)),
                                               Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerMillisecond))))),
                                   type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDurationDeserializerBuilderCase)} can only be applied schemas with a {nameof(DurationLogicalType)}.")));
            }
        }
Example #7
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="UnionSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="UnionSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> has no <see cref="UnionSchema.Schemas" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be mapped to each <see cref="Schema" /> in
        /// <paramref name="schema" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is UnionSchema unionSchema)
            {
                if (unionSchema.Schemas.Count < 1)
                {
                    throw new UnsupportedSchemaException(schema, "A deserializer cannot be built for an empty union.");
                }

                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.Call(context.Reader, readInteger);

                // create a mapping for each schema in the union:
                var cases = unionSchema.Schemas.Select((child, index) =>
                {
                    var selected   = SelectType(type, child);
                    var underlying = Nullable.GetUnderlyingType(selected);

                    if (child is NullSchema && selected.IsValueType && underlying == null)
                    {
                        throw new UnsupportedTypeException(type, $"A deserializer for {unionSchema} cannot be built for {selected} because it contains {nameof(NullSchema)}.");
                    }

                    var @case = DeserializerBuilder.BuildExpression(selected, child, context);

                    return(Expression.SwitchCase(
                               BuildConversion(@case, type),
                               Expression.Constant((long)index)));
                });

                var position = typeof(BinaryReader)
                               .GetProperty(nameof(BinaryReader.Index));

                var exceptionConstructor = typeof(InvalidEncodingException)
                                           .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) });

                try
                {
                    // generate a switch on the index:
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               Expression.Switch(
                                   expression,
                                   Expression.Throw(
                                       Expression.New(
                                           exceptionConstructor,
                                           Expression.Property(context.Reader, position),
                                           Expression.Constant($"Invalid union index; expected a value in [0-{unionSchema.Schemas.Count}). This may indicate invalid encoding earlier in the stream."),
                                           Expression.Constant(null, typeof(Exception))),
                                       type),
                                   cases.ToArray())));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {unionSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryUnionDeserializerBuilderCase)} can only be applied to {nameof(UnionSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="MapSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is a dictionary type and <paramref name="schema" /> is a <see cref="MapSchema" />; an
        /// unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is not assignable from any supported dictionary
        /// <see cref="Type" /> and does not have a constructor that can be used to instantiate it.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is MapSchema mapSchema)
            {
                var dictionaryTypes = GetDictionaryTypes(type);

                if (dictionaryTypes is not null || type == typeof(object))
                {
                    // support dynamic mapping:
                    var keyType   = dictionaryTypes?.Key ?? typeof(string);
                    var valueType = dictionaryTypes?.Value ?? typeof(object);

                    var instantiateDictionary = BuildIntermediateDictionary(type, keyType, valueType);

                    var readInteger = typeof(BinaryReader)
                                      .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                    var readKey = DeserializerBuilder
                                  .BuildExpression(keyType, new StringSchema(), context);

                    var readValue = DeserializerBuilder
                                    .BuildExpression(valueType, mapSchema.Value, context);

                    var dictionary = Expression.Parameter(instantiateDictionary.Type);
                    var index      = Expression.Variable(typeof(long));
                    var size       = Expression.Variable(typeof(long));
                    var outer      = Expression.Label();
                    var inner      = Expression.Label();

                    var add = dictionary.Type
                              .GetMethod("Add", new[] { readKey.Type, readValue.Type });

                    // var dictionary = new ...;
                    //
                    // outer: while (true)
                    // {
                    //     var size = reader.ReadInteger();
                    //
                    //     // if the block is empty, the map is complete:
                    //     if (size == 0L) break outer;
                    //
                    //     // if the block size is negative, the number of bytes in the block
                    //     // follows, so read and discard:
                    //     if (size < 0L)
                    //     {
                    //          size *= -1L;
                    //          reader.ReadInteger();
                    //     }
                    //
                    //     var index = 0;
                    //
                    //     inner: while (true)
                    //     {
                    //         // primitive for:
                    //         if (index++ == size) break inner;
                    //
                    //         dictionary.Add(..., ...);
                    //     }
                    // }
                    //
                    // return dictionary;
                    Expression expression = Expression.Block(
                        new[] { dictionary, index, size },
                        Expression.Assign(dictionary, instantiateDictionary),
                        Expression.Loop(
                            Expression.Block(
                                Expression.Assign(size, Expression.Call(context.Reader, readInteger)),
                                Expression.IfThen(
                                    Expression.Equal(size, Expression.Constant(0L)),
                                    Expression.Break(outer)),
                                Expression.IfThen(
                                    Expression.LessThan(size, Expression.Constant(0L)),
                                    Expression.Block(
                                        Expression.MultiplyAssign(size, Expression.Constant(-1L)),
                                        Expression.Call(context.Reader, readInteger))),
                                Expression.Assign(index, Expression.Constant(0L)),
                                Expression.Loop(
                                    Expression.Block(
                                        Expression.IfThen(
                                            Expression.Equal(Expression.PostIncrementAssign(index), size),
                                            Expression.Break(inner)),
                                        Expression.Call(dictionary, add, readKey, readValue)),
                                    inner)),
                            outer),
                        dictionary);

                    if (!type.IsAssignableFrom(expression.Type) && GetDictionaryConstructor(type) is ConstructorInfo constructor)
                    {
                        expression = Expression.New(constructor, expression);
                    }

                    try
                    {
                        return(BinaryDeserializerBuilderCaseResult.FromExpression(
                                   BuildConversion(expression, type)));
                    }
                    catch (InvalidOperationException exception)
                    {
                        throw new UnsupportedTypeException(type, $"Failed to map {mapSchema} to {type}.", exception);
                    }
                }
                else
                {
                    return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryMapDeserializerBuilderCase)} can only be applied to dictionary types.")));
                }
            }
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="EnumSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is an <see cref="EnumSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is an enum type without a matching member for each
        /// symbol in <paramref name="schema" /> or when <see cref="string" /> cannot be converted
        /// to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is EnumSchema enumSchema)
            {
                var readInteger = typeof(BinaryReader)
                                  .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                Expression expression = Expression.ConvertChecked(
                    Expression.Call(context.Reader, readInteger),
                    typeof(int));

                var underlying = Nullable.GetUnderlyingType(type) ?? type;

                // enum fields will always be public static, so no need to expose binding flags:
                var fields = underlying.GetFields(BindingFlags.Public | BindingFlags.Static);

                var cases = underlying.IsEnum
                    ? enumSchema.Symbols
                            .Select((symbol, index) =>
                {
                    var match = fields.SingleOrDefault(field => IsMatch(symbol, field));

                    if (enumSchema.Default != null)
                    {
                        match ??= fields.SingleOrDefault(field => IsMatch(enumSchema.Default, field));
                    }

                    if (match == null)
                    {
                        throw new UnsupportedTypeException(type, $"{type} has no value that matches {symbol} and no default value is defined.");
                    }

                    return(Expression.SwitchCase(
                               BuildConversion(Expression.Constant(Enum.Parse(underlying, match.Name)), type),
                               Expression.Constant(index)));
                })
                    : enumSchema.Symbols
                            .Select((symbol, index) =>
                {
                    return(Expression.SwitchCase(
                               BuildConversion(Expression.Constant(symbol), type),
                               Expression.Constant(index)));
                });

                var position = typeof(BinaryReader)
                               .GetProperty(nameof(BinaryReader.Index))
                               .GetGetMethod();

                var exceptionConstructor = typeof(InvalidEncodingException)
                                           .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) });

                try
                {
                    // generate a switch on the index:
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               Expression.Switch(
                                   expression,
                                   Expression.Throw(
                                       Expression.New(
                                           exceptionConstructor,
                                           Expression.Property(context.Reader, position),
                                           Expression.Constant($"Invalid enum index; expected a value in [0-{enumSchema.Symbols.Count}). This may indicate invalid encoding earlier in the stream."),
                                           Expression.Constant(null, typeof(Exception))),
                                       type),
                                   cases.ToArray())));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {enumSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryEnumDeserializerBuilderCase)} can only be applied to {nameof(EnumSchema)}s.")));
            }
        }
Example #10
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="ArraySchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" />
        /// is an enumerable type and <paramref name="schema" /> is an <see cref="ArraySchema" />;
        /// an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is not assignable from any supported array or
        /// collection <see cref="Type" /> and does not have a constructor that can be used to
        /// instantiate it.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema is ArraySchema arraySchema)
            {
                var enumerableType = GetEnumerableType(type);

                if (enumerableType is not null || type == typeof(object))
                {
                    // support dynamic mapping:
                    var itemType = enumerableType ?? typeof(object);

                    var instantiateCollection = BuildIntermediateCollection(type, itemType);

                    var readInteger = typeof(BinaryReader)
                                      .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes);

                    var readItem = DeserializerBuilder
                                   .BuildExpression(itemType, arraySchema.Item, context);

                    var collection = Expression.Parameter(instantiateCollection.Type);
                    var index      = Expression.Variable(typeof(long));
                    var size       = Expression.Variable(typeof(long));
                    var outer      = Expression.Label();
                    var inner      = Expression.Label();

                    var add = collection.Type
                              .GetMethod("Add", new[] { readItem.Type });

                    // var collection = new List<T>();
                    //
                    // outer: while (true)
                    // {
                    //     var size = reader.ReadInteger();
                    //
                    //     // if the block is empty, the array is complete:
                    //     if (size == 0L) break outer;
                    //
                    //     // if the block size is negative, the number of bytes in the block
                    //     // follows, so read and discard:
                    //     if (size < 0L)
                    //     {
                    //          size *= -1L;
                    //          reader.ReadInteger();
                    //     }
                    //
                    //     var index = 0;
                    //
                    //     inner: while (true)
                    //     {
                    //         // primitive for:
                    //         if (index++ == size) break inner;
                    //
                    //         collection.Add(...);
                    //     }
                    // }
                    //
                    // return collection;
                    Expression expression = Expression.Block(
                        new[] { collection, index, size },
                        Expression.Assign(collection, instantiateCollection),
                        Expression.Loop(
                            Expression.Block(
                                Expression.Assign(size, Expression.Call(context.Reader, readInteger)),
                                Expression.IfThen(
                                    Expression.Equal(size, Expression.Constant(0L)),
                                    Expression.Break(outer)),
                                Expression.IfThen(
                                    Expression.LessThan(size, Expression.Constant(0L)),
                                    Expression.Block(
                                        Expression.MultiplyAssign(size, Expression.Constant(-1L)),
                                        Expression.Call(context.Reader, readInteger))),
                                Expression.Assign(index, Expression.Constant(0L)),
                                Expression.Loop(
                                    Expression.Block(
                                        Expression.IfThen(
                                            Expression.Equal(Expression.PostIncrementAssign(index), size),
                                            Expression.Break(inner)),
                                        Expression.Call(collection, add, readItem)),
                                    inner)),
                            outer),
                        collection);

                    if (!type.IsAssignableFrom(expression.Type) && GetCollectionConstructor(type) is ConstructorInfo constructor)
                    {
                        expression = Expression.New(constructor, expression);
                    }

                    try
                    {
                        return(BinaryDeserializerBuilderCaseResult.FromExpression(
                                   BuildConversion(expression, type)));
                    }
                    catch (InvalidOperationException exception)
                    {
                        throw new UnsupportedTypeException(type, $"Failed to map {arraySchema} to {type}.", exception);
                    }
                }
                else
                {
                    return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryArrayDeserializerBuilderCase)} can only be applied to enumerable types.")));
                }
            }
Example #11
0
        /// <summary>
        /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DecimalLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DecimalLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="BytesSchema" /> or a
        /// <see cref="FixedSchema "/>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <see cref="decimal" /> cannot be converted to <paramref name="type" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context)
        {
            if (schema.LogicalType is DecimalLogicalType decimalLogicalType)
            {
                var precision = decimalLogicalType.Precision;
                var scale     = decimalLogicalType.Scale;

                Expression expression;

                // figure out the size:
                if (schema is BytesSchema)
                {
                    var readBytes = typeof(BinaryReader)
                                    .GetMethod(nameof(BinaryReader.ReadBytes), Type.EmptyTypes);

                    expression = Expression.Call(context.Reader, readBytes);
                }
                else if (schema is FixedSchema fixedSchema)
                {
                    var readFixed = typeof(BinaryReader)
                                    .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(long) });

                    expression = Expression.Call(context.Reader, readFixed, Expression.Constant((long)fixedSchema.Size));
                }
                else
                {
                    throw new UnsupportedSchemaException(schema);
                }

                // declare variables for in-place transformation:
                var bytes     = Expression.Variable(typeof(byte[]));
                var remainder = Expression.Variable(typeof(BigInteger));

                var divide = typeof(BigInteger)
                             .GetMethod(nameof(BigInteger.DivRem), new[] { typeof(BigInteger), typeof(BigInteger), typeof(BigInteger).MakeByRefType() });

                var integerConstructor = typeof(BigInteger)
                                         .GetConstructor(new[] { typeof(byte[]) });

                var reverse = typeof(Array)
                              .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) });

                // var bytes = ...;
                //
                // // BigInteger is little-endian, so reverse:
                // Array.Reverse(bytes);
                //
                // var whole = BigInteger.DivRem(new BigInteger(bytes), BigInteger.Pow(10, scale), out var remainder);
                // var fraction = (decimal)remainder / (decimal)Math.Pow(10, scale);
                //
                // return whole + fraction;
                expression = Expression.Block(
                    new[] { bytes, remainder },
                    Expression.Assign(bytes, expression),
                    Expression.Call(null, reverse, bytes),
                    Expression.Add(
                        Expression.ConvertChecked(
                            Expression.Call(
                                null,
                                divide,
                                Expression.New(integerConstructor, bytes),
                                Expression.Constant(BigInteger.Pow(10, scale)),
                                remainder),
                            typeof(decimal)),
                        Expression.Divide(
                            Expression.ConvertChecked(remainder, typeof(decimal)),
                            Expression.Constant((decimal)Math.Pow(10, scale)))));

                try
                {
                    return(BinaryDeserializerBuilderCaseResult.FromExpression(
                               BuildConversion(expression, type)));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }
            }
            else
            {
                return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDecimalDeserializerBuilderCase)} can only be applied schemas with a {nameof(DecimalLogicalType)}.")));
            }
        }