1 /// Barebones memory stream implementation using `IStream` without any bounds checking. 2 module tern.stream.memory_stream; 3 4 public import tern.stream.impl; 5 import tern.serialization; 6 import tern.traits; 7 8 /// Memory stream implementation backed by a pointer, has no bounds checking. 9 public class MemoryStream : IStream 10 { 11 public: 12 final: 13 ubyte* data; 14 size_t position; 15 16 this(T : U*, U)(T ptr) 17 { 18 data = cast(ubyte*)ptr; 19 } 20 21 /** 22 * Always returns true. 23 */ 24 bool mayRead(T)() 25 { 26 return true; 27 } 28 29 /** 30 * Always returns true. 31 */ 32 bool mayRead(size_t size = 1) 33 { 34 return true; 35 } 36 37 /** 38 * Moves the position in the stream by the size of type T. 39 * 40 * Params: 41 * T = The size of type to move the position by. 42 */ 43 void step(T)(size_t count = 1) 44 { 45 position += T.sizeof * count; 46 } 47 48 /** 49 * Moves the position in the stream forward by one until `val` is peeked. 50 * 51 * Params: 52 * val = The value to be peeked. 53 */ 54 void stepUntil(T)(T val) 55 { 56 static if (isSomeString!T) 57 { 58 while (peekString!(ElementType!T) != val) 59 position++; 60 } 61 else 62 { 63 while (peek!T != val) 64 position++; 65 } 66 } 67 68 /** 69 * Seeks to a new position in the stream based on the provided offset and seek direction. 70 * 71 * Params: 72 * SEEK = The direction of the seek operation (Start, Current, or End). 73 * offset = The offset from the seek direction to be set. 74 */ 75 void seek(Seek SEEK)(size_t offset) 76 { 77 static if (SEEK = Seek.Current) 78 position += offset; 79 else static if (SEEK = Seek.Start) 80 position = offset; 81 else 82 position = data.length - offset; 83 } 84 85 /** 86 * Reads the next value from the stream of type T. 87 * 88 * Params: 89 * T = The type of data to be read. 90 * 91 * Returns: 92 * The value read from the stream. 93 */ 94 T read(T)() 95 if (!isDynamicArray!T) 96 { 97 return *cast(T*)data[position..(position += T.sizeof)].ptr; 98 } 99 100 /** 101 * Peeks at the next value from the stream of type T without advancing the stream position. 102 * 103 * Params: 104 * T = The type of data to peek. 105 * 106 * Returns: 107 * The value peeked from the stream. 108 */ 109 T peek(T)() 110 if (!isDynamicArray!T) 111 { 112 return *cast(T*)data[position..(position + T.sizeof)].ptr; 113 } 114 115 /** 116 * Reads multiple values of type T from the stream. 117 * 118 * Params: 119 * T = The type of data to be read. 120 * count = The number of values to read from the stream. 121 * 122 * Returns: 123 * An array of values read from the stream. 124 */ 125 T[] read(T)(size_t count) 126 if (!isDynamicArray!T) 127 { 128 T[] arr; 129 foreach (i; 0..count) 130 arr ~= read!T; 131 return arr; 132 } 133 134 /** 135 * Peeks at multiple values of type T from the stream without advancing the stream position. 136 * 137 * Params: 138 * T = The type of data to peek. 139 * count = The number of values to peek from the stream. 140 * 141 * Returns: 142 * An array of values peeked from the stream. 143 */ 144 T[] peek(T)(size_t count) 145 if (!isDynamicArray!T) 146 { 147 auto _position = position; 148 scope (exit) position = _position; 149 T[] arr; 150 foreach (i; 0..count) 151 arr ~= read!T; 152 return arr; 153 } 154 155 /** 156 * Reads an array of type T from the stream. 157 * 158 * Params: 159 * T = The type of data to be read. 160 * 161 * Returns: 162 * An array read from the stream. 163 */ 164 T read(T : U[], U)() 165 if (isDynamicArray!T) 166 { 167 return read!(ElementType!T)(cast(size_t)read7EncodedInt()); 168 } 169 170 /** 171 * Peeks an array of type T from the stream without advancing the stream position. 172 * 173 * Params: 174 * T = The type of data to peek. 175 * 176 * Returns: 177 * An array peeked from the stream. 178 */ 179 T peek(T : U[], U)() 180 if (isDynamicArray!T) 181 { 182 return peek!(ElementType!T)(cast(size_t)read7EncodedInt()); 183 } 184 185 /** 186 * Writes the provided value to the stream. 187 * 188 * Params: 189 * T = The type of data to be written. 190 * val = The value to be written to the stream. 191 */ 192 void write(T)(T val) 193 { 194 data[position..(position += T.sizeof)] = (cast(ubyte*)&val)[0..T.sizeof]; 195 } 196 197 /** 198 * Writes the provided value to the stream without advancing the stream position. 199 * 200 * Params: 201 * T = The type of data to be written. 202 * val = The value to be written to the stream. 203 */ 204 void put(T)(T val) 205 { 206 data[position..(position + T.sizeof)] = (cast(ubyte*)&val)[0..T.sizeof]; 207 } 208 209 /** 210 * Writes multiple values of type T to the stream. 211 * 212 * Params: 213 * T = The type of data to be written. 214 * items = An array of values to be written to the stream. 215 */ 216 void write(T, bool PREFIXED = true)(T val) 217 if (isArray!T) 218 { 219 static if (PREFIXED) 220 write7EncodedInt(cast(uint)(val.length)); 221 222 foreach (u; val) 223 write(u); 224 } 225 226 /** 227 * Writes multiple values of type T to the stream without advancing the stream position. 228 * 229 * Params: 230 * T = The type of data to be written. 231 * items = An array of values to be written to the stream. 232 */ 233 void put(T, bool PREFIXED = true)(T val) 234 if (isArray!T) 235 { 236 auto _position = position; 237 scope (exit) position = _position; 238 static if (PREFIXED) 239 write7EncodedInt(cast(uint)(val.length)); 240 241 foreach (u; val) 242 write(u); 243 } 244 245 /** 246 * Reads a string from the stream considering the character width and prefixing. 247 * 248 * Params: 249 * CHAR = The character type used for reading the string (char, wchar, or dchar). 250 * PREFIXED = Indicates whether the string is prefixed. Default is false. 251 * 252 * Returns: 253 * The read string from the stream. 254 */ 255 immutable(CHAR)[] readString(CHAR, bool PREFIXED = false)() 256 { 257 static if (PREFIXED) 258 return cast(immutable(CHAR)[])read!(immutable(CHAR)[]); 259 else 260 { 261 immutable(CHAR)[] ret; 262 while (peek!CHAR != '\0') 263 ret ~= read!CHAR; 264 return ret; 265 } 266 } 267 268 /** 269 * Reads a string from the stream considering the character width and prefixing without advancing the stream position. 270 * 271 * Params: 272 * CHAR = The character type used for reading the string (char, wchar, or dchar). 273 * PREFIXED = Indicates whether the string is prefixed. Default is false. 274 * 275 * Returns: 276 * The read string from the stream. 277 */ 278 immutable(CHAR)[] peekString(CHAR, bool PREFIXED = false)() 279 { 280 auto _position = position; 281 scope (exit) position = _position; 282 static if (PREFIXED) 283 return cast(immutable(CHAR)[])read!(immutable(CHAR)[]); 284 else 285 { 286 immutable(CHAR)[] ret; 287 while (peek!CHAR != '\0') 288 ret ~= read!CHAR; 289 return ret; 290 } 291 } 292 293 /** 294 * Writes a string to the stream considering the character width and prefixing. 295 * 296 * Params: 297 * CHAR = The character type used for writing the string (char, wchar, or dchar). 298 * PREFIXED = Indicates whether the string is prefixed. Default is false. 299 * val = The string to be written to the stream. 300 */ 301 void writeString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val) 302 { 303 static if (!PREFIXED) 304 val ~= '\0'; 305 306 write!(immutable(CHAR)[], PREFIXED)(val); 307 } 308 309 /** 310 * Writes a string into the stream considering the character width and prefixing without advancing the stream position. 311 * 312 * Params: 313 * CHAR = The character type used for writing the string (char, wchar, or dchar). 314 * PREFIXED = Indicates whether the string is prefixed. Default is false. 315 * val = The string to be put into the stream. 316 */ 317 void putString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val) 318 { 319 static if (!PREFIXED) 320 val ~= '\0'; 321 322 put!(immutable(CHAR)[], PREFIXED)(val); 323 } 324 325 /** 326 * Reads an integer value encoded in 7 bits from the stream. 327 * 328 * Returns: 329 * The integer value read from the stream. 330 */ 331 uint read7EncodedInt() 332 { 333 uint result = 0; 334 uint shift = 0; 335 336 foreach (i; 0..5) 337 { 338 ubyte b = read!ubyte; 339 result |= cast(uint)(b & 0x7F) << shift; 340 if ((b & 0x80) == 0) 341 return result; 342 shift += 7; 343 } 344 345 return result; 346 } 347 348 /** 349 * Writes an integer value encoded in 7 bits to the stream. 350 * 351 * Params: 352 * val = The integer value to be written to the stream. 353 */ 354 void write7EncodedInt(uint val) 355 { 356 foreach (i; 0..5) 357 { 358 byte b = cast(byte)(val & 0x7F); 359 val >>= 7; 360 if (val != 0) 361 b |= 0x80; 362 write(b); 363 if (val == 0) 364 return; 365 } 366 } 367 368 /** 369 * Reads data from a byte stream into a structured type based on specified field names and read kinds. 370 * Designed specifically for better control reading string and array fields. 371 * 372 * Params: 373 * T = The type representing the structure to read into. 374 * ARGS = Variadic template parameter representing field names and read kinds. 375 * 376 * Returns: 377 * Returns an instance of type T with fields populated based on the specified read operations. 378 */ 379 T read(T, ARGS...)() 380 { 381 T val; 382 foreach (field; Fields!T) 383 { 384 alias M = TypeOf!(val, field); 385 bool cread; 386 static foreach (i, ARG; ARGS) 387 { 388 static if (i % 3 == 0) 389 { 390 static assert(is(typeof(ARG) == string), 391 "Field name expected, found " ~ ARG.stringof); 392 } 393 else static if (i % 3 == 1) 394 { 395 static assert(is(typeof(ARG) == ReadKind), 396 "Read kind expected, found " ~ ARG.stringof); 397 } 398 else 399 { 400 static if (field == ARGS[i - 2] || ARGS[i - 2] == "") 401 { 402 static if (!isStaticArray!M && is(M == string)) 403 { 404 cread = true; 405 static if (ARGS[i - 1] == ReadKind.Field) 406 { 407 __traits(getMember, val, field) = read!char(__traits(getMember, val, ARG)).to!string; 408 } 409 else static if (ARGS[i - 1] == ReadKind.Fixed) 410 { 411 __traits(getMember, val, field) = read!char(ARG).to!string; 412 } 413 else 414 { 415 __traits(getMember, val, field) = readString!(char, ARG); 416 } 417 } 418 else static if (!isStaticArray!M && is(M == wstring)) 419 { 420 cread = true; 421 static if (ARGS[i - 1] == ReadKind.Field) 422 { 423 __traits(getMember, val, field) = read!wchar(__traits(getMember, val, ARG)).to!string; 424 } 425 else static if (ARGS[i - 1] == ReadKind.Fixed) 426 { 427 __traits(getMember, val, field) = read!wchar(ARG).to!string; 428 } 429 else 430 { 431 __traits(getMember, val, field) = readString!(wchar, ARG); 432 } 433 } 434 static if (!isStaticArray!M && is(M == dstring)) 435 { 436 cread = true; 437 static if (ARGS[i - 1] == ReadKind.Field) 438 { 439 __traits(getMember, val, field) = read!dchar(__traits(getMember, val, ARG)).to!string; 440 } 441 else static if (ARGS[i - 1] == ReadKind.Fixed) 442 { 443 __traits(getMember, val, field) = read!dchar(ARG).to!string; 444 } 445 else 446 { 447 __traits(getMember, val, field) = readString!(dchar, ARG); 448 } 449 } 450 else static if (isDynamicArray!M) 451 { 452 cread = true; 453 static if (ARGS[i - 1] == ReadKind.Field) 454 { 455 __traits(getMember, val, field) = read!(ElementType!M)(__traits(getMember, val, ARG)); 456 } 457 else static if (ARGS[i - 1] == ReadKind.Fixed) 458 { 459 __traits(getMember, val, field) = read!(ElementType!M)(ARG); 460 } 461 else 462 { 463 __traits(getMember, val, field) = read!M; 464 } 465 } 466 } 467 } 468 } 469 if (!cread) 470 __traits(getMember, val, field) = read!M; 471 } 472 return val; 473 } 474 475 /// ditto 476 T[] read(T, ARGS...)(size_t count) 477 { 478 T[] items; 479 foreach (i; 0..count) 480 items ~= read!(T, ARGS); 481 return items; 482 } 483 484 /** 485 * Reads a type from the stream using optional fields. 486 * 487 * Params: 488 * T = The type to be read from the stream. 489 * ARGS... = The arguments for optional fields. 490 * 491 * Returns: 492 * The read type read from the stream. 493 */ 494 T readPlasticized(T, ARGS...)() 495 if (ARGS.length % 3 == 0) 496 { 497 T val; 498 foreach (field; Fields!T) 499 { 500 bool cread = true; 501 static foreach (i, ARG; ARGS) 502 { 503 static if (i % 3 == 0) 504 { 505 static assert(is(typeof(ARG) == string), 506 "Field name expected, found " ~ ARG.stringof); 507 } 508 else static if (i % 3 == 1) 509 { 510 static assert(is(typeof(ARG) == string), 511 "Conditional field name expected, found " ~ ARG.stringof); 512 } 513 else 514 { 515 if (field == ARGS[i - 2] && __traits(getMember, val, ARGS[i - 1]) != ARG) 516 cread = false; 517 } 518 } 519 if (cread) 520 __traits(getMember, val, field) = read!(TypeOf!(val, field)); 521 } 522 return val; 523 } 524 }