1 /** LEB128 (Little Endian Base 128). 2 3 See_Also: https://en.wikipedia.org/wiki/LEB128 4 See_Also: http://forum.dlang.org/post/ykskvwqdsxlyjispappj@forum.dlang.org 5 6 TODO: Move to Phobos at std/experimental/codings/leb128.d 7 */ 8 module nxt.leb128; 9 10 import std.range.primitives : isOutputRange; 11 import std.traits : isUnsigned, isSigned; 12 import core.internal.traits : Unqual; 13 14 /// Encode a LEB128-encoded value of signed integer type `SInt` to `os`. 15 void encodeLEB128(SInt, Output)(ref Output os, Unqual!SInt value) 16 if (isOutputRange!(Output, ubyte) && 17 isSigned!SInt) 18 { 19 bool more = false; 20 do 21 { 22 ubyte byte_ = value & 0x7f; 23 // assumes that this signed shift is an arithmetic right shift 24 value >>= 7; 25 more = !(((value == 0 ) && ((byte_ & 0x40) == 0)) || 26 ((value == -1) && ((byte_ & 0x40) != 0))); 27 if (more) 28 byte_ |= 0x80; // mark this byte to show that more bytes will follow 29 os.put(byte_); 30 } 31 while (more); 32 } 33 34 /// Decode a LEB128-encoded value of signed integer type `SInt`. 35 SInt decodeLEB128(SInt)(ubyte *p, uint *n = null) 36 { 37 const ubyte *orig_p = p; 38 SInt value = 0; 39 uint shift = 0; 40 ubyte byte_; 41 do 42 { 43 byte_ = *p++; 44 value |= ((byte_ & 0x7f) << shift); 45 shift += 7; 46 } while (byte_ >= 128); 47 // sign extend negative numbers 48 if (byte_ & 0x40) 49 /+ TODO: Unsigned!SInt +/ 50 value |= (cast(ulong)-1) << shift; // value |= (-1ULL) << shift; 51 if (n) 52 *n = cast(uint)(p - orig_p); 53 return value; 54 } 55 56 version (unittest) 57 { 58 import std.algorithm.comparison : equal; 59 60 import std.array : Appender; 61 alias Raw = Appender!(ubyte[]); 62 // import nxt.container.dynamic_array : DynamicArray; 63 // alias Raw = DynamicArray!ubyte; 64 } 65 66 pure nothrow @safe unittest { 67 alias SInt = long; 68 foreach (immutable i; 0 .. 64) 69 { 70 Raw os; 71 os.encodeLEB128!SInt(i); 72 assert(os.data.equal([i])); 73 // const value = os.data.decodeLEB128!SInt(); 74 } 75 foreach (immutable i; 64 .. 128) 76 { 77 Raw os; 78 os.encodeLEB128!SInt(i); 79 assert(os.data.equal([128 + i, 0])); 80 } 81 } 82 83 /// Encode a ULEB128 value to `os`. 84 void encodeULEB128(UInt, Output)(ref Output os, Unqual!UInt value) 85 if (isOutputRange!(Output, ubyte) && 86 isUnsigned!UInt) 87 { 88 do 89 { 90 ubyte byte_ = value & 0x7f; 91 value >>= 7; 92 if (value != 0) 93 byte_ |= 0x80; // mark this byte to show that more bytes will follow 94 os.put(char(byte_)); 95 } 96 while (value != 0); 97 } 98 99 /// Decode a ULEB128 value. 100 ulong decodeULEB128(ubyte *p, uint *n = null) 101 { 102 const ubyte *orig_p = p; 103 ulong value = 0; 104 uint shift = 0; 105 do 106 { 107 value += ulong(*p & 0x7f) << shift; 108 shift += 7; 109 } 110 while (*p++ >= 128); 111 if (n) 112 *n = cast(uint)(p - orig_p); 113 return value; 114 } 115 116 pure nothrow @safe unittest { 117 alias UInt = ulong; 118 foreach (immutable i; 0 .. 128) 119 { 120 Raw os; 121 os.encodeULEB128!UInt(i); 122 assert(os.data.equal([i])); 123 } 124 foreach (immutable i; 128 .. 256) 125 { 126 Raw os; 127 os.encodeULEB128!UInt(i); 128 assert(os.data.equal([i, 1])); 129 } 130 foreach (immutable i; 256 .. 256 + 128) 131 { 132 Raw os; 133 os.encodeULEB128!UInt(i); 134 assert(os.data.equal([i - 128, 2])); 135 } 136 foreach (immutable i; 256 + 128 .. 512) 137 { 138 Raw os; 139 os.encodeULEB128!UInt(i); 140 assert(os.data.equal([i - 256, 3])); 141 } 142 } 143 144 /** Encode a ULEB128 value to a buffer. 145 Returns: length in bytes of the encoded value. 146 */ 147 uint encodeULEB128(ulong value, ubyte *p) 148 { 149 ubyte *orig_p = p; 150 do 151 { 152 ubyte byte_ = value & 0x7f; 153 value >>= 7; 154 if (value != 0) 155 byte_ |= 0x80; // mark this byte to show that more bytes will follow 156 *p++ = byte_; 157 } 158 while (value != 0); 159 return cast(uint)(p - orig_p); 160 }