make @safe, improve coverage and refresh the style

This commit is contained in:
Basile Burg 2018-06-10 10:25:34 +02:00
parent b77a3fb709
commit edf4979207

View file

@ -6,10 +6,6 @@
/// A minimal library providing functionality for changing the endianness of data. /// A minimal library providing functionality for changing the endianness of data.
module tinyendian; module tinyendian;
import core.stdc.string;
import std.algorithm;
import std.system; import std.system;
import std.utf; import std.utf;
@ -23,8 +19,8 @@ enum UTFEncoding : ubyte
UTF_16, UTF_16,
UTF_32 UTF_32
} }
///
unittest @safe unittest
{ {
const ints = [314, -101]; const ints = [314, -101];
int[2] intsSwapBuffer = ints; int[2] intsSwapBuffer = ints;
@ -39,8 +35,6 @@ unittest
assert(floats == floatsSwapBuffer, "Lost information when swapping byte order"); assert(floats == floatsSwapBuffer, "Lost information when swapping byte order");
} }
@nogc @system pure nothrow:
/** Swap byte order of items in an array in place. /** Swap byte order of items in an array in place.
* *
* Params: * Params:
@ -48,19 +42,20 @@ unittest
* T = Item type. Must be either 2 or 4 bytes long. * T = Item type. Must be either 2 or 4 bytes long.
* array = Buffer with values to fix byte order of. * array = Buffer with values to fix byte order of.
*/ */
void swapByteOrder(T)(T[] array) void swapByteOrder(T)(T[] array) @trusted @nogc pure nothrow
if([2, 4].canFind(T.sizeof)) if (T.sizeof == 2 || T.sizeof == 4)
{ {
import core.bitop;
// Swap the byte order of all read characters. // Swap the byte order of all read characters.
foreach (ref item; array) foreach (ref item; array)
{ {
static if (T.sizeof == 2) static if (T.sizeof == 2)
{ {
import std.algorithm.mutation : swap;
swap(*cast(ubyte*)&item, *(cast(ubyte*)&item + 1)); swap(*cast(ubyte*)&item, *(cast(ubyte*)&item + 1));
} }
else static if (T.sizeof == 4) else static if (T.sizeof == 4)
{ {
import core.bitop : bswap;
const swapped = bswap(*cast(uint*)&item); const swapped = bswap(*cast(uint*)&item);
item = *cast(const(T)*)&swapped; item = *cast(const(T)*)&swapped;
} }
@ -68,6 +63,15 @@ void swapByteOrder(T)(T[] array)
} }
} }
/// See fixUTFByteOrder.
struct FixUTFByteOrderResult
{
ubyte[] array;
UTFEncoding encoding;
Endian endian;
uint bytesStripped = 0;
}
/** Convert byte order of an array encoded in UTF(8/16/32) to system endianness in place. /** Convert byte order of an array encoded in UTF(8/16/32) to system endianness in place.
* *
* Uses the UTF byte-order-mark (BOM) to determine UTF encoding. If there is no BOM * Uses the UTF byte-order-mark (BOM) to determine UTF encoding. If there is no BOM
@ -100,7 +104,7 @@ void swapByteOrder(T)(T[] array)
* *
* Complexity: (BIGOH array.length) * Complexity: (BIGOH array.length)
*/ */
auto fixUTFByteOrder(ubyte[] array) auto fixUTFByteOrder(ubyte[] array) @safe @nogc pure nothrow
{ {
// Enumerates UTF BOMs, matching indices to byteOrderMarks/bomEndian. // Enumerates UTF BOMs, matching indices to byteOrderMarks/bomEndian.
enum BOM: ubyte enum BOM: ubyte
@ -126,23 +130,17 @@ auto fixUTFByteOrder(ubyte[] array)
Endian.bigEndian ]; Endian.bigEndian ];
// Documented in function ddoc. // Documented in function ddoc.
struct Result
{ FixUTFByteOrderResult result;
ubyte[] array;
UTFEncoding encoding;
Endian endian;
uint bytesStripped = 0;
}
Result result;
// Detect BOM, if any, in the bytes we've read. -1 means no BOM. // Detect BOM, if any, in the bytes we've read. -1 means no BOM.
// Need the last match: First 2 bytes of UTF-32LE BOM match the UTF-16LE BOM. If we // Need the last match: First 2 bytes of UTF-32LE BOM match the UTF-16LE BOM. If we
// used the first match, UTF-16LE would be detected when we have a UTF-32LE BOM. // used the first match, UTF-16LE would be detected when we have a UTF-32LE BOM.
import std.algorithm.searching : startsWith;
BOM bomId = BOM.None; BOM bomId = BOM.None;
foreach(i, bom; byteOrderMarks) if(array.startsWith(bom)) foreach (i, bom; byteOrderMarks)
{ if (array.startsWith(bom))
bomId = cast(BOM)i; bomId = cast(BOM)i;
}
result.endian = (bomId != BOM.None) ? bomEndian[bomId] : Endian.init; result.endian = (bomId != BOM.None) ? bomEndian[bomId] : Endian.init;
@ -168,21 +166,52 @@ auto fixUTFByteOrder(ubyte[] array)
break; break;
} }
array = array[0 .. $ - result.bytesStripped];
// If there's a BOM, we need to move data back to ensure it starts at array[0] // If there's a BOM, we need to move data back to ensure it starts at array[0]
if (start != 0) if (start != 0)
{ {
core.stdc.string.memmove(array.ptr, array.ptr + start, array.length - start); array = array[start .. $ - result.bytesStripped];
array = array[0 .. $ - start];
} }
// We enforce above that array.length is divisible by 2/4 for UTF-16/32 // We enforce above that array.length is divisible by 2/4 for UTF-16/32
if (std.system.endian != result.endian) if (std.system.endian != result.endian)
{ {
if(result.encoding == UTFEncoding.UTF_16) { swapByteOrder(cast(wchar[])array); } if (result.encoding == UTFEncoding.UTF_16)
else if(result.encoding == UTFEncoding.UTF_32) { swapByteOrder(cast(dchar[])array); } swapByteOrder(cast(wchar[])array);
else if (result.encoding == UTFEncoding.UTF_32)
swapByteOrder(cast(dchar[])array);
} }
result.array = array; result.array = array;
return result; return result;
} }
///
@safe unittest
{
{
ubyte[] s = [0xEF, 0xBB, 0xBF, 'a'];
FixUTFByteOrderResult r = fixUTFByteOrder(s);
assert(r.encoding == UTFEncoding.UTF_8);
assert(r.array.length == 1);
assert(r.array == ['a']);
assert(r.endian == Endian.littleEndian);
}
{
ubyte[] s = ['a'];
FixUTFByteOrderResult r = fixUTFByteOrder(s);
assert(r.encoding == UTFEncoding.UTF_8);
assert(r.array.length == 1);
assert(r.array == ['a']);
assert(r.endian == Endian.bigEndian);
}
{
// strip 'a' b/c not complete unit
ubyte[] s = [0xFE, 0xFF, 'a'];
FixUTFByteOrderResult r = fixUTFByteOrder(s);
assert(r.encoding == UTFEncoding.UTF_16);
assert(r.array.length == 0);
assert(r.endian == Endian.bigEndian);
}
}