Last active
December 14, 2021 14:05
-
-
Save kerams/eb03fdf1b918b8bee9c90c900b3e36cf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Format = | |
[<Literal>] | |
let Nil = 0xc0uy | |
[<Literal>] | |
let False = 0xc2uy | |
[<Literal>] | |
let True = 0xc3uy | |
let inline fixposnum value = byte value | |
let inline fixnegnum value = byte value ||| 0b11100000uy | |
[<Literal>] | |
let Uint8 = 0xccuy | |
[<Literal>] | |
let Uint16 = 0xcduy | |
[<Literal>] | |
let Uint32 = 0xceuy | |
[<Literal>] | |
let Uint64 = 0xcfuy | |
[<Literal>] | |
let Int8 = 0xd0uy | |
[<Literal>] | |
let Int16 = 0xd1uy | |
[<Literal>] | |
let Int32 = 0xd2uy | |
[<Literal>] | |
let Int64 = 0xd3uy | |
let inline fixstr len = 160uy + byte len | |
[<Literal>] | |
let Str8 = 0xd9uy | |
[<Literal>] | |
let Str16 = 0xdauy | |
[<Literal>] | |
let Str32 = 0xdbuy | |
[<Literal>] | |
let Float32 = 0xcauy | |
[<Literal>] | |
let Float64 = 0xcbuy | |
let inline fixarr len = 144uy + byte len | |
[<Literal>] | |
let Array16 = 0xdcuy | |
[<Literal>] | |
let Array32 = 0xdduy | |
[<Literal>] | |
let Bin8 = 0xc4uy | |
[<Literal>] | |
let Bin16 = 0xc5uy | |
[<Literal>] | |
let Bin32 = 0xc6uy | |
let inline fixmap len = 128uy + byte len | |
[<Literal>] | |
let Map16 = 0xdeuy | |
[<Literal>] | |
let Map32 = 0xdfuy | |
module Read = | |
open System | |
open System.Text | |
open System.Collections | |
open System.Collections.Concurrent | |
open System.Collections.Generic | |
open FSharp.Reflection | |
open System.Reflection | |
let interpretStringAs (typ: Type) (str: string) = | |
#if FABLE_COMPILER | |
box str | |
#else | |
if typ = typeof<string> then | |
box str | |
elif typ = typeof<char> then | |
box str.[0] | |
else | |
// todo cacheable | |
// String enum | |
let case = FSharpType.GetUnionCases (typ, true) |> Array.find (fun y -> y.Name = str) | |
FSharpValue.MakeUnion (case, [||], true) | |
#endif | |
let inline interpretIntegerAs typ n = | |
//if typ = typeof<Int32> then int32 n |> box | |
if Object.ReferenceEquals (typ, typeof<Int32>) then int32 n |> box | |
elif typ = typeof<Int64> then int64 n |> box | |
elif typ = typeof<Int16> then int16 n |> box | |
elif typ = typeof<UInt32> then uint32 n |> box | |
elif typ = typeof<UInt64> then uint64 n |> box | |
elif typ = typeof<UInt16> then uint16 n |> box | |
elif typ = typeof<TimeSpan> then TimeSpan (int64 n) |> box | |
#if NET6_0_OR_GREATER | |
elif typ = typeof<DateOnly> then DateOnly.FromDayNumber (int32 n) |> box | |
elif typ = typeof<TimeOnly> then TimeOnly (int64 n) |> box | |
#endif | |
#if FABLE_COMPILER | |
elif typ.FullName = "Microsoft.FSharp.Core.int16`1" then int16 n |> box | |
elif typ.FullName = "Microsoft.FSharp.Core.int32`1" then int32 n |> box | |
elif typ.FullName = "Microsoft.FSharp.Core.int64`1" then int64 n |> box | |
#endif | |
elif typ = typeof<byte> then byte n |> box | |
elif typ = typeof<sbyte> then sbyte n |> box | |
#if !FABLE_COMPILER | |
elif typ.IsEnum then Enum.ToObject (typ, int64 n) | |
#else | |
elif typ.IsEnum then float n |> box | |
#endif | |
else failwithf "Cannot interpret integer %A as %s." n typ.Name | |
let inline interpretFloatAs typ n = | |
box n | |
#if !FABLE_COMPILER | |
type DictionaryDeserializer<'k,'v when 'k: equality and 'k: comparison> () = | |
static let keyType = typeof<'k> | |
static let valueType = typeof<'v> | |
static member Deserialize (len: int, isDictionary, read: Type -> obj) = | |
if isDictionary then | |
let dict = Dictionary<'k, 'v> (len) | |
for _ in 0 .. len - 1 do | |
dict.Add (read keyType :?> 'k, read valueType :?> 'v) | |
box dict | |
else | |
Array.init len (fun _ -> read keyType :?> 'k, read valueType :?> 'v) | |
|> Map.ofArray | |
|> box | |
type ListDeserializer<'a> () = | |
static let argType = typeof<'a> | |
static member Deserialize (len: int, read: Type -> obj) = | |
List.init len (fun _ -> read argType :?> 'a) | |
|> box | |
type SetDeserializer<'a when 'a : comparison> () = | |
static let argType = typeof<'a> | |
static member Deserialize (len: int, read: Type -> obj) = | |
let mutable set = Set.empty | |
for _ in 0 .. len - 1 do | |
set <- set.Add (read argType :?> 'a) | |
set | |
|> box | |
#endif | |
type Reader (data: byte[]) = | |
let mutable pos = 0 | |
let intBuf = Array.zeroCreate 8 | |
#if !FABLE_COMPILER | |
static let arrayReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> () | |
static let mapReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> () | |
static let setReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> () | |
static let unionConstructorCache = ConcurrentDictionary<UnionCaseInfo, obj [] -> obj> () | |
static let unionCaseFieldCache = ConcurrentDictionary<Type * int, UnionCaseInfo * Type[]> () | |
#endif | |
let readInt len m = | |
if BitConverter.IsLittleEndian then | |
for i in 0 .. len - 1 do | |
intBuf.[i] <- data.[pos + len - 1 - i] | |
pos <- pos + len | |
m (intBuf, 0) | |
else | |
pos <- pos + len | |
m (data, pos - len) | |
member inline _.ReadByte () = | |
pos <- pos + 1 | |
data.[pos - 1] | |
member _.ReadRawBin len = | |
pos <- pos + len | |
data.[ pos - len .. pos - 1 ] | |
member _.ReadString len = | |
pos <- pos + len | |
Encoding.UTF8.GetString (data, pos - len, len) | |
member x.ReadUInt8 () = | |
x.ReadByte () | |
member x.ReadInt8 () = | |
x.ReadByte () |> sbyte | |
member _.ReadUInt16 () = | |
readInt 2 BitConverter.ToUInt16 | |
member _.ReadInt16 () = | |
readInt 2 BitConverter.ToInt16 | |
member _.ReadUInt32 () = | |
pos <- pos + 4 | |
uint32 (data.[pos - 4] <<< 24) ||| uint32 (data.[pos - 3] <<< 16) ||| uint32 (data.[pos - 2] <<< 8) ||| uint32 data.[pos - 1] | |
//readInt 4 BitConverter.ToUInt32 | |
member _.ReadInt32 () = | |
readInt 4 BitConverter.ToInt32 | |
member _.ReadUInt64 () = | |
readInt 8 BitConverter.ToUInt64 | |
//pos <- pos + 8 | |
//uint64 (data.[pos - 8] <<< 56) ||| uint64 (data.[pos - 7] <<< 48) ||| uint64 (data.[pos - 6] <<< 40) ||| uint64 (data.[pos - 5] <<< 32) ||| | |
//uint64 (data.[pos - 4] <<< 24) ||| uint64 (data.[pos - 3] <<< 16) ||| uint64 (data.[pos - 2] <<< 8) ||| uint64 data.[pos - 1] | |
member _.ReadInt64 () = | |
readInt 8 BitConverter.ToInt64 | |
member x.ReadFloat32 () = | |
//let mutable b = x.ReadInt32 () | |
readInt 4 BitConverter.ToSingle | |
(*let sign = if (b >>> 31) = 0 then 1f else -1f | |
let mutable e = (b >>> 23) &&& 0xff | |
let m = b &&& 0x7fffff | |
let m = | |
if e = 0 then | |
if m = 0 then | |
0f | |
else | |
e <- e - 126 | |
1.192093037616359652020037174224853515625f | |
else | |
e <- e - 127 | |
1f + float32 m / 8388608f | |
sign * m * float32 (Math.Pow (2., float e))*) | |
member x.ReadFloat64 () = | |
readInt 8 BitConverter.ToDouble | |
// let mutable b = x.ReadInt64 () | |
// let sign = if (b >>> 63) = 0L then 1. else -1. | |
// let mutable e = (b >>> 52) &&& 0x07ffL | |
// let m = b &&& 0x0fffffffffffffL | |
// let m = | |
// if e = 0L then | |
// if m = 0L then | |
// 0. | |
// else | |
// e <- e - 1022L | |
// 1. / float 0x0fffffffffffffL | |
// else | |
// e <- e - 1023L | |
// 1. + float m / (float 0x10000000000000L) | |
// sign * m * (Math.Pow (2., float e)) | |
member x.ReadMap (len: int, t: Type) = | |
#if !FABLE_COMPILER | |
mapReaderCache.GetOrAdd (t, Func<_, _>(fun (t: Type) -> | |
let args = t.GetGenericArguments () | |
if args.Length <> 2 then | |
failwithf "Expecting %s, but the data contains a map." t.Name | |
let mapDeserializer = typedefof<DictionaryDeserializer<_,_>>.MakeGenericType args | |
let isDictionary = t.GetGenericTypeDefinition () = typedefof<Dictionary<_, _>> | |
let d = Delegate.CreateDelegate (typeof<Func<int, bool, (Type -> obj), obj>>, mapDeserializer.GetMethod "Deserialize") :?> Func<int, bool, (Type -> obj), obj> | |
fun (len, x: Reader) -> d.Invoke (len, isDictionary, x.Read))) (len, x) | |
#else | |
let args = t.GetGenericArguments () | |
if args.Length <> 2 then | |
failwithf "Expecting %s, but the data contains a map." t.Name | |
let pairs = | |
let arr = Array.zeroCreate len | |
for i in 0 .. len - 1 do | |
arr.[i] <- x.Read args.[0] |> box :?> IStructuralComparable, x.Read args.[1] | |
arr | |
if t.GetGenericTypeDefinition () = typedefof<Dictionary<_, _>> then | |
let dict = Dictionary<_, _> len | |
pairs |> Array.iter dict.Add | |
box dict | |
else | |
Map.ofArray pairs |> box | |
#endif | |
member x.ReadSet (len: int, t: Type) = | |
#if !FABLE_COMPILER | |
setReaderCache.GetOrAdd (t, Func<_, _>(fun (t: Type) -> | |
let args = t.GetGenericArguments () | |
if args.Length <> 1 then | |
failwithf "Expecting %s, but the data contains a set." t.Name | |
let setDeserializer = typedefof<SetDeserializer<_>>.MakeGenericType args | |
let d = Delegate.CreateDelegate (typeof<Func<int, (Type -> obj), obj>>, setDeserializer.GetMethod "Deserialize") :?> Func<int, (Type -> obj), obj> | |
fun (len, x: Reader) -> d.Invoke (len, x.Read))) (len, x) | |
#else | |
let args = t.GetGenericArguments () | |
if args.Length <> 1 then | |
failwithf "Expecting %s, but the data contains a set." t.Name | |
let mutable set = Set.empty | |
for _ in 0 .. len - 1 do | |
set <- set.Add(x.Read args.[0] |> box :?> IStructuralComparable) | |
box set | |
#endif | |
member x.ReadRawArray (len: int, elementType: Type) = | |
#if !FABLE_COMPILER | |
let arr = Array.CreateInstance (elementType, len) | |
for i in 0 .. len - 1 do | |
arr.SetValue (x.Read elementType, i) | |
arr | |
#else | |
let arr = Array.zeroCreate len | |
for i in 0 .. len - 1 do | |
arr.[i] <- x.Read elementType | |
arr | |
#endif | |
member x.ReadArray (len, t) = | |
#if !FABLE_COMPILER | |
match arrayReaderCache.TryGetValue t with | |
| true, reader -> | |
reader (len, x) | |
| _ -> | |
#endif | |
if FSharpType.IsRecord t then | |
#if !FABLE_COMPILER | |
let fieldTypes = FSharpType.GetRecordFields t |> Array.map (fun prop -> prop.PropertyType) | |
let ctor = FSharpValue.PreComputeRecordConstructor (t, true) | |
arrayReaderCache.GetOrAdd (t, fun (_, x: Reader) -> | |
ctor (fieldTypes |> Array.map x.Read)) (len, x) | |
#else | |
let props = FSharpType.GetRecordFields t | |
FSharpValue.MakeRecord (t, props |> Array.map (fun prop -> x.Read prop.PropertyType)) | |
#endif | |
elif FSharpType.IsUnion (t, true) then | |
#if !FABLE_COMPILER | |
if t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<_ list> then | |
let argType = t.GetGenericArguments () |> Array.head | |
let listDeserializer = typedefof<ListDeserializer<_>>.MakeGenericType argType | |
let d = Delegate.CreateDelegate (typeof<Func<int, (Type -> obj), obj>>, listDeserializer.GetMethod "Deserialize") :?> Func<int, (Type -> obj), obj> | |
arrayReaderCache.GetOrAdd (t, fun (len, (x: Reader)) -> d.Invoke (len, x.Read)) (len, x) | |
else | |
arrayReaderCache.GetOrAdd (t, fun (_, x: Reader) -> | |
let tag = x.Read typeof<int> :?> int | |
let case, fieldTypes = | |
unionCaseFieldCache.GetOrAdd ((t, tag), fun (t, tag) -> | |
let case = FSharpType.GetUnionCases (t, true) |> Array.find (fun x -> x.Tag = tag) | |
let fields = case.GetFields () | |
case, fields |> Array.map (fun x -> x.PropertyType)) | |
let fields = | |
// single parameter is serialized directly, not in an array, saving 1 byte on the array format | |
if fieldTypes.Length = 1 then | |
[| x.Read fieldTypes.[0] |] | |
else | |
// don't care about this byte, it's going to be a fixarr of length fieldTypes.Length | |
x.ReadByte () |> ignore | |
fieldTypes |> Array.map x.Read | |
unionConstructorCache.GetOrAdd (case, Func<_, _>(fun case -> FSharpValue.PreComputeUnionConstructor (case, true))) fields) (len, x) | |
#else | |
let tag = x.Read typeof<int> :?> int | |
let case = FSharpType.GetUnionCases (t, true) |> Array.find (fun x -> x.Tag = tag) | |
let fieldTypes = case.GetFields () |> Array.map (fun x -> x.PropertyType) | |
let fields = | |
// single parameter is serialized directly, not in an array, saving 1 byte on the array format | |
if fieldTypes.Length = 1 then | |
[| x.Read fieldTypes.[0] |] | |
else | |
// don't care about this byte, it's going to be a fixarr of length fieldTypes.Length | |
x.ReadByte () |> ignore | |
fieldTypes |> Array.map x.Read | |
FSharpValue.MakeUnion (case, fields, true) | |
#endif | |
#if FABLE_COMPILER // Fable does not recognize Option as a union | |
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<Option<_>> then | |
let tag = x.ReadByte () | |
// none case | |
if tag = 0uy then | |
x.ReadByte () |> ignore | |
box null | |
else | |
x.Read (t.GetGenericArguments () |> Array.head) |> Some |> box | |
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<_ list> then | |
let elementType = t.GetGenericArguments () |> Array.head | |
[ | |
for _ in 0 .. len - 1 -> | |
x.Read elementType | |
] |> box | |
#endif | |
elif t.IsArray then | |
x.ReadRawArray (len, t.GetElementType ()) |> box | |
elif FSharpType.IsTuple t then | |
#if !FABLE_COMPILER | |
let elementTypes = FSharpType.GetTupleElements t | |
let tupleCtor = FSharpValue.PreComputeTupleConstructor t | |
arrayReaderCache.GetOrAdd (t, fun (_, (x: Reader)) -> elementTypes |> Array.map x.Read |> tupleCtor) (len, x) | |
#else | |
FSharpValue.MakeTuple (FSharpType.GetTupleElements t |> Array.map x.Read, t) | |
#endif | |
elif t = typeof<DateTime> then | |
let dateTimeTicks = x.Read typeof<int64> :?> int64 | |
let kindAsInt = x.Read typeof<int64> :?> int64 | |
let kind = | |
match kindAsInt with | |
| 1L -> DateTimeKind.Utc | |
| 2L -> DateTimeKind.Local | |
| _ -> DateTimeKind.Unspecified | |
DateTime(ticks=dateTimeTicks, kind=kind) |> box | |
elif t = typeof<DateTimeOffset> then | |
let dateTimeTicks = x.Read typeof<int64> :?> int64 | |
let timeSpanMinutes = x.Read typeof<int16> :?> int16 | |
DateTimeOffset (dateTimeTicks, TimeSpan.FromMinutes (float timeSpanMinutes)) |> box | |
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<Set<_>> then | |
x.ReadSet(len, t) | |
#if !FABLE_COMPILER | |
elif t = typeof<System.Data.DataTable> then | |
match x.ReadRawArray(2, typeof<string>) :?> string array with | |
| [|schema;data|] -> | |
let t = new System.Data.DataTable() | |
t.ReadXmlSchema(new System.IO.StringReader(schema)) | |
t.ReadXml(new System.IO.StringReader(data)) |> ignore | |
box t | |
| otherwise -> failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos | |
elif t = typeof<System.Data.DataSet> then | |
match x.ReadRawArray(2, typeof<string>) :?> string array with | |
| [|schema;data|] -> | |
let t = new System.Data.DataSet() | |
t.ReadXmlSchema(new System.IO.StringReader(schema)) | |
t.ReadXml(new System.IO.StringReader(data)) |> ignore | |
box t | |
| otherwise -> failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos | |
#endif | |
elif t = typeof<decimal> || t.FullName = "Microsoft.FSharp.Core.decimal`1" then | |
#if !FABLE_COMPILER | |
arrayReaderCache.GetOrAdd (t, fun (_, (x: Reader)) -> x.ReadRawArray (4, typeof<int>) :?> int[] |> Decimal |> box) (len, x) | |
#else | |
x.ReadRawArray (4, typeof<int>) |> box :?> int[] |> Decimal |> box | |
#endif | |
else | |
failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos | |
member x.ReadBin (len, t) = | |
if t = typeof<Guid> then | |
x.ReadRawBin len |> Guid |> box | |
elif t = typeof<byte[]> then | |
x.ReadRawBin len |> box | |
elif t = typeof<bigint> then | |
x.ReadRawBin len |> bigint |> box | |
else | |
failwithf "Expecting %s at position %d, but the data contains bin." t.Name pos | |
member x.Read t = | |
match x.ReadByte () with | |
// fixstr | |
| b when b ||| 0b00011111uy = 0b10111111uy -> b &&& 0b00011111uy |> int |> x.ReadString |> interpretStringAs t | |
| Format.Str8 -> x.ReadByte () |> int |> x.ReadString |> interpretStringAs t | |
| Format.Str16 -> x.ReadUInt16 () |> int |> x.ReadString |> interpretStringAs t | |
| Format.Str32 -> x.ReadUInt32 () |> int |> x.ReadString |> interpretStringAs t | |
// fixposnum | |
| b when b ||| 0b01111111uy = 0b01111111uy -> interpretIntegerAs t b | |
// fixnegnum | |
| b when b ||| 0b00011111uy = 0b11111111uy -> sbyte b |> interpretIntegerAs t | |
| Format.Int64 -> x.ReadInt64 () |> interpretIntegerAs t | |
| Format.Int32 -> x.ReadInt32 () |> interpretIntegerAs t | |
| Format.Int16 -> x.ReadInt16 () |> interpretIntegerAs t | |
| Format.Int8 -> x.ReadInt8 () |> interpretIntegerAs t | |
| Format.Uint8 -> x.ReadUInt8 () |> interpretIntegerAs t | |
| Format.Uint16 -> x.ReadUInt16 () |> interpretIntegerAs t | |
| Format.Uint32 -> x.ReadUInt32 () |> interpretIntegerAs t | |
| Format.Uint64 -> x.ReadUInt64 () |> interpretIntegerAs t | |
| Format.Float32 -> x.ReadFloat32 () |> interpretFloatAs t | |
| Format.Float64 -> x.ReadFloat64 () |> interpretFloatAs t | |
| Format.Nil -> box null | |
| Format.True -> box true | |
| Format.False -> box false | |
// fixarr | |
| b when b ||| 0b00001111uy = 0b10011111uy -> x.ReadArray (b &&& 0b00001111uy |> int, t) | |
| Format.Array16 -> | |
let len = x.ReadUInt16 () |> int | |
x.ReadArray (len, t) | |
| Format.Array32 -> | |
let len = x.ReadUInt32 () |> int | |
x.ReadArray (len, t) | |
// fixmap | |
| b when b ||| 0b00001111uy = 0b10001111uy -> x.ReadMap (b &&& 0b00001111uy |> int, t) | |
| Format.Map16 -> | |
let len = x.ReadUInt16 () |> int | |
x.ReadMap (len, t) | |
| Format.Map32 -> | |
let len = x.ReadUInt32 () |> int | |
x.ReadMap (len, t) | |
| Format.Bin8 -> | |
let len = x.ReadByte () |> int | |
x.ReadBin (len, t) | |
| Format.Bin16 -> | |
let len = x.ReadUInt16 () |> int | |
x.ReadBin (len, t) | |
| Format.Bin32 -> | |
let len = x.ReadUInt32 () |> int | |
x.ReadBin (len, t) | |
| b -> | |
failwithf "Position %d, byte %d, expected type %s." pos b t.Name | |
module Write = | |
open System.IO | |
open System | |
open System.Collections.Generic | |
open System.Text | |
open FSharp.Reflection | |
open FSharp.NativeInterop | |
open System.Reflection | |
open System.Collections.Concurrent | |
module Fable = | |
let private serializerCache = Dictionary<string, obj -> ResizeArray<byte> -> unit> () | |
let private cacheGetOrAdd (typ: Type, f) = | |
match serializerCache.TryGetValue typ.FullName with | |
| true, f -> f | |
| _ -> | |
serializerCache.Add (typ.FullName, f) | |
f | |
let inline private write32bitNumber b1 b2 b3 b4 (out: ResizeArray<byte>) writeFormat = | |
if b2 > 0uy || b1 > 0uy then | |
if writeFormat then out.Add Format.Uint32 | |
out.Add b1 | |
out.Add b2 | |
out.Add b3 | |
out.Add b4 | |
elif (b3 > 0uy) then | |
if writeFormat then out.Add Format.Uint16 | |
out.Add b3 | |
out.Add b4 | |
else | |
if writeFormat then out.Add Format.Uint8 | |
out.Add b4 | |
let private write64bitNumber b1 b2 b3 b4 b5 b6 b7 b8 (out: ResizeArray<byte>) = | |
if b4 > 0uy || b3 > 0uy || b2 > 0uy || b1 > 0uy then | |
out.Add Format.Uint64 | |
out.Add b1 | |
out.Add b2 | |
out.Add b3 | |
out.Add b4 | |
out.Add b5 | |
out.Add b6 | |
out.Add b7 | |
out.Add b8 | |
else | |
write32bitNumber b5 b6 b7 b8 out true | |
let inline private writeUnsigned32bitNumber (n: UInt32) (out: ResizeArray<byte>) = | |
write32bitNumber (n >>> 24 |> byte) (n >>> 16 |> byte) (n >>> 8 |> byte) (byte n) out | |
let inline private writeUnsigned64bitNumber (n: UInt64) (out: ResizeArray<byte>) = | |
write64bitNumber (n >>> 56 |> byte) (n >>> 48 |> byte) (n >>> 40 |> byte) (n >>> 32 |> byte) (n >>> 24 |> byte) (n >>> 16 |> byte) (n >>> 8 |> byte) (byte n) out | |
let inline private writeNil (out: ResizeArray<byte>) = out.Add Format.Nil | |
let inline private writeBool x (out: ResizeArray<byte>) = out.Add (if x then Format.True else Format.False) | |
let private writeSignedNumber bytes (out: ResizeArray<byte>) = | |
if BitConverter.IsLittleEndian then | |
Array.rev bytes |> out.AddRange | |
else | |
out.AddRange bytes | |
let private writeUInt64 (n: UInt64) (out: ResizeArray<byte>) = | |
if n < 128UL then | |
out.Add (Format.fixposnum n) | |
else | |
writeUnsigned64bitNumber n out | |
let private writeInt64 (n: int64) (out: ResizeArray<byte>) = | |
if n >= 0L then | |
writeUInt64 (uint64 n) out | |
else | |
if n > -32L then | |
out.Add (Format.fixnegnum n) | |
else | |
//todo length optimization | |
out.Add Format.Int64 | |
writeSignedNumber (BitConverter.GetBytes n) out | |
let private writeByte b (out: ResizeArray<byte>) = | |
if b < 128uy then | |
out.Add (Format.fixposnum b) | |
else | |
out.Add Format.Uint8 | |
out.Add b | |
let inline private writeString (str: string) (out: ResizeArray<byte>) = | |
let str = Encoding.UTF8.GetBytes str | |
if str.Length < 32 then | |
out.Add (Format.fixstr str.Length) | |
else | |
if str.Length < 256 then | |
out.Add Format.Str8 | |
elif str.Length < 65536 then | |
out.Add Format.Str16 | |
else | |
out.Add Format.Str32 | |
writeUnsigned32bitNumber (uint32 str.Length) out false | |
out.AddRange str | |
let private writeSingle (n: float32) (out: ResizeArray<byte>) = | |
out.Add Format.Float32 | |
writeSignedNumber (BitConverter.GetBytes n) out | |
let private writeDouble (n: float) (out: ResizeArray<byte>) = | |
out.Add Format.Float64 | |
writeSignedNumber (BitConverter.GetBytes n) out | |
let private writeBin (data: byte[]) (out: ResizeArray<byte>) = | |
if data.Length < 256 then | |
out.Add Format.Bin8 | |
elif data.Length < 65536 then | |
out.Add Format.Bin16 | |
else | |
out.Add Format.Bin32 | |
writeUnsigned32bitNumber (uint32 data.Length) out false | |
out.AddRange data | |
let inline private writeDateTime (out: ResizeArray<byte>) (dto: DateTime) = | |
out.Add (Format.fixarr 2uy) | |
writeInt64 dto.Ticks out | |
writeInt64 (int64 dto.Kind) out | |
let inline private writeDateTimeOffset (out: ResizeArray<byte>) (dto: DateTimeOffset) = | |
out.Add (Format.fixarr 2uy) | |
writeInt64 dto.Ticks out | |
writeInt64 (int64 dto.Offset.TotalMinutes) out | |
#if NET6_0_OR_GREATER | |
let inline private writeDateOnly (out: ResizeArray<byte>) (date: DateOnly) = | |
writeUnsigned32bitNumber (uint32 date.DayNumber) out true | |
let inline private writeTimeOnly (out: ResizeArray<byte>) (time: TimeOnly) = | |
writeUInt64 (uint64 time.Ticks) out | |
#endif | |
let private writeArrayHeader len (out: ResizeArray<byte>) = | |
if len < 16 then | |
out.Add (Format.fixarr len) | |
elif len < 65536 then | |
out.Add Format.Array16 | |
out.Add (len >>> 8 |> FSharp.Core.Operators.byte) | |
out.Add (FSharp.Core.Operators.byte len) | |
else | |
out.Add Format.Array32 | |
writeUnsigned32bitNumber (uint32 len) out false | |
let private writeDecimal (n: decimal) (out: ResizeArray<byte>) = | |
let bits = Decimal.GetBits n | |
writeArrayHeader bits.Length out | |
for b in bits do | |
writeUnsigned32bitNumber (uint32 b) out true | |
let rec private writeArray (out: ResizeArray<byte>) t (arr: System.Collections.ICollection) = | |
writeArrayHeader arr.Count out | |
for x in arr do | |
writeObject x t out | |
and private writeMap (out: ResizeArray<byte>) keyType valueType (dict: IDictionary<obj, obj>) = | |
let length = dict.Count | |
if length < 16 then | |
out.Add (Format.fixmap length) | |
elif length < 65536 then | |
out.Add Format.Map16 | |
out.Add (length >>> 8 |> FSharp.Core.Operators.byte) | |
out.Add (FSharp.Core.Operators.byte length) | |
else | |
out.Add Format.Map32 | |
writeUnsigned32bitNumber (uint32 length) out false | |
for kvp in dict do | |
writeObject kvp.Key keyType out | |
writeObject kvp.Value valueType out | |
and private writeSet (out: ResizeArray<byte>) t (set: System.Collections.ICollection) = | |
writeArrayHeader set.Count out | |
for x in set do | |
writeObject x t out | |
and inline private writeRecord (out: ResizeArray<byte>) (types: Type[]) (vals: obj[]) = | |
writeArrayHeader vals.Length out | |
for i in 0 .. vals.Length - 1 do | |
writeObject vals.[i] types.[i] out | |
and inline private writeTuple (out: ResizeArray<byte>) (types: Type[]) (vals: obj[]) = | |
writeRecord out types vals | |
and private writeUnion (out: ResizeArray<byte>) tag (types: Type[]) (vals: obj[]) = | |
out.Add (Format.fixarr 2uy) | |
out.Add (Format.fixposnum tag) | |
// save 1 byte if the union case has a single parameter | |
if vals.Length <> 1 then | |
writeArrayHeader vals.Length out | |
for i in 0 .. vals.Length - 1 do | |
writeObject vals.[i] types.[i] out | |
else | |
writeObject vals.[0] types.[0] out | |
and writeObject (x: obj) (t: Type) (out: ResizeArray<byte>) = | |
#if !FABLE_COMPILER | |
raise (NotSupportedException "This function is meant to be used in Fable, please use serializeObj or makeSerializer.") | |
#else | |
if isNull x then writeNil out else | |
match serializerCache.TryGetValue (t.FullName) with | |
| true, writer -> | |
writer x out | |
| _ -> | |
if FSharpType.IsRecord (t, true) then | |
let fieldTypes = FSharpType.GetRecordFields (t, true) |> Array.map (fun x -> x.PropertyType) | |
cacheGetOrAdd (t, fun x out -> writeRecord out fieldTypes (FSharpValue.GetRecordFields (x, true))) x out | |
elif t.IsArray then | |
let elementType = t.GetElementType () | |
cacheGetOrAdd (t, fun x out -> writeArray out elementType (x :?> System.Collections.ICollection)) x out | |
elif FSharpType.IsUnion (t, true) then | |
cacheGetOrAdd (t, fun x out -> | |
let case, fields = FSharpValue.GetUnionFields (x, t, true) | |
let fieldTypes = case.GetFields () |> Array.map (fun x -> x.PropertyType) | |
writeUnion out case.Tag fieldTypes fields) x out | |
elif FSharpType.IsTuple t then | |
let fieldTypes = FSharpType.GetTupleElements t | |
cacheGetOrAdd (t, fun x out -> writeTuple out fieldTypes (FSharpValue.GetTupleFields x)) x out | |
elif t.IsEnum then | |
cacheGetOrAdd (t, fun x -> writeInt64 (box x :?> int64)) x out | |
elif t.IsGenericType then | |
let tDef = t.GetGenericTypeDefinition() | |
let genArgs = t.GetGenericArguments () | |
if tDef = typedefof<_ list> then | |
let elementType = genArgs |> Array.head | |
cacheGetOrAdd (t, fun x out -> writeArray out elementType (x :?> System.Collections.ICollection)) x out | |
elif tDef = typedefof<_ option> then | |
cacheGetOrAdd (t, fun x out -> | |
let opt = x :?> _ option | |
let tag, value = if Option.isSome opt then 1, opt.Value else 0, null | |
writeUnion out tag genArgs [| value |]) x out | |
elif tDef = typedefof<Dictionary<_, _>> || tDef = typedefof<Map<_, _>> then | |
let keyType = genArgs.[0] | |
let valueType = genArgs.[1] | |
cacheGetOrAdd (t, fun x out -> writeMap out keyType valueType (box x :?> IDictionary<obj, obj>)) x out | |
elif tDef = typedefof<Set<_>> then | |
let elementType = genArgs |> Array.head | |
cacheGetOrAdd (t, fun x out -> writeSet out elementType (x :?> System.Collections.ICollection)) x out | |
else | |
failwithf "Cannot serialize %s." t.Name | |
elif t.FullName = "Microsoft.FSharp.Core.int16`1" || t.FullName = "Microsoft.FSharp.Core.int32`1" || t.FullName = "Microsoft.FSharp.Core.int64`1" then | |
cacheGetOrAdd (t, fun x out -> writeInt64 (x :?> int64) out) x out | |
elif t.FullName = "Microsoft.FSharp.Core.decimal`1" then | |
cacheGetOrAdd (t, fun x out -> writeDecimal (x :?> decimal) out) x out | |
elif t.FullName = "Microsoft.FSharp.Core.float`1" then | |
cacheGetOrAdd (t, fun x out -> writeDouble (x :?> float) out) x out | |
elif t.FullName = "Microsoft.FSharp.Core.float32`1" then | |
cacheGetOrAdd (t, fun x out -> writeSingle (x :?> float32) out) x out | |
else | |
failwithf "Cannot serialize %s." t.Name | |
#endif | |
let inline writeType<'T> (x: 'T) (out: ResizeArray<byte>) = | |
#if !FABLE_COMPILER | |
raise (NotSupportedException "This function is meant to be used in Fable, please use serializeObj or makeSerializer.") | |
#else | |
writeObject x typeof<'T> out | |
#endif | |
#if FABLE_COMPILER | |
serializerCache.Add (typeof<byte>.FullName, fun x out -> writeByte (x :?> byte) out) | |
serializerCache.Add (typeof<sbyte>.FullName, fun x out -> writeByte (x :?> sbyte |> byte) out) | |
serializerCache.Add (typeof<unit>.FullName, fun _ out -> writeNil out) | |
serializerCache.Add (typeof<bool>.FullName, fun x out -> writeBool (x :?> bool) out) | |
serializerCache.Add (typeof<char>.FullName, fun x out -> writeString (x :?> string) out) // There are only strings in JS | |
serializerCache.Add (typeof<string>.FullName, fun x out -> writeString (x :?> string) out) | |
serializerCache.Add (typeof<int>.FullName, fun x out -> writeInt64 (x :?> int |> int64) out) | |
serializerCache.Add (typeof<int16>.FullName, fun x out -> writeInt64 (x :?> int16 |> int64) out) | |
serializerCache.Add (typeof<int64>.FullName, fun x out -> writeInt64 (x :?> int64) out) | |
serializerCache.Add (typeof<UInt32>.FullName, fun x out -> writeUInt64 (x :?> UInt32 |> uint64) out) | |
serializerCache.Add (typeof<UInt16>.FullName, fun x out -> writeUInt64 (x :?> UInt16 |> uint64) out) | |
serializerCache.Add (typeof<UInt64>.FullName, fun x out -> writeUInt64 (x :?> UInt64) out) | |
serializerCache.Add (typeof<float32>.FullName, fun x out -> writeSingle (x :?> float32) out) | |
serializerCache.Add (typeof<float>.FullName, fun x out -> writeDouble (x :?> float) out) | |
serializerCache.Add (typeof<decimal>.FullName, fun x out -> writeDecimal (x :?> decimal) out) | |
serializerCache.Add (typeof<byte[]>.FullName, fun x out -> writeBin (x :?> byte[]) out) | |
serializerCache.Add (typeof<bigint>.FullName, fun x out -> writeBin ((x :?> bigint).ToByteArray ()) out) | |
serializerCache.Add (typeof<Guid>.FullName, fun x out -> writeBin ((x :?> Guid).ToByteArray ()) out) | |
serializerCache.Add (typeof<DateTime>.FullName, fun x out -> writeDateTime out (x :?> DateTime)) | |
serializerCache.Add (typeof<DateTimeOffset>.FullName, fun x out -> writeDateTimeOffset out (x :?> DateTimeOffset)) | |
#if NET6_0_OR_GREATER | |
serializerCache.Add (typeof<DateOnly>.FullName, fun x out -> writeDateOnly out (x :?> DateOnly)) | |
serializerCache.Add (typeof<TimeOnly>.FullName, fun x out -> writeTimeOnly out (x :?> TimeOnly)) | |
#endif | |
serializerCache.Add (typeof<TimeSpan>.FullName, fun x out -> writeInt64 (x :?> TimeSpan).Ticks out) | |
#endif | |
open System | |
let time f x = | |
let s = DateTime.Now | |
f x |> ignore | |
printfn "%A" (DateTime.Now - s).TotalMilliseconds | |
let a = ResizeArray () | |
let x = [| for i in 0 .. 1_000_000 -> 500000 |], [| for i in 0 .. 0 -> float i |] | |
time (fun () -> Write.Fable.writeObject x typeof<int32[] * float[]> a) () | |
let b = a |> Seq.toArray | |
time (fun () -> | |
let r = Read.Reader(b) | |
r.Read(typeof<int32[] * float[]>) |> ignore) () |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment