1 /++ 2 $(H4 High level Ion serialization API) 3 4 Macros: 5 IONREF = $(REF_ALTTEXT $(TT $2), $2, mir, ion, $1)$(NBSP) 6 +/ 7 module mir.ser.ion; 8 9 import mir.primitives: isOutputRange; 10 public import mir.serde; 11 12 import mir.ion.symbol_table: IonSymbolTable; 13 14 @trusted pure nothrow @nogc 15 IonSerializer!(bufferStackSize, compiletimeSymbolTable, tableGC) 16 ionSerializer 17 (uint bufferStackSize, string[] compiletimeSymbolTable = null, bool tableGC) 18 () 19 { 20 import core.lifetime: move; 21 typeof(return) ret = void; 22 23 ret.buffer.allData = null; 24 ret.buffer.currentTapePosition = 0; 25 version(assert) ret.buffer.ctrlStack = 0; 26 27 // ret.initialize(runtimeTable, serdeTarget); 28 return ret; 29 } 30 31 struct IonSerializer(uint bufferStackSize, string[] compiletimeSymbolTable, bool tableGC = true) 32 { 33 import mir.appender: ScopedBuffer; 34 import mir.bignum.decimal: Decimal; 35 import mir.bignum.integer: BigInt; 36 import mir.bignum.low_level_view: BigIntView; 37 import mir.ion.symbol_table: IonSymbolTable, IonSystemSymbolTable_v1; 38 import mir.ion.tape; 39 import mir.ion.type_code; 40 import mir.lob; 41 import mir.string_table: createTable, minimalIndexType; 42 import mir.timestamp; 43 import mir.utility: _expect; 44 import std.traits: isNumeric; 45 46 private alias createTableChar = createTable!char; 47 private alias U = minimalIndexType!(IonSystemSymbolTable_v1.length + compiletimeSymbolTable.length); 48 49 private static immutable compiletimeTable = createTableChar!(IonSystemSymbolTable_v1 ~ compiletimeSymbolTable, false); 50 static immutable U[IonSystemSymbolTable_v1.length + compiletimeTable.sortedKeys.length] compiletimeIndex = () { 51 U[IonSystemSymbolTable_v1.length + compiletimeTable.sortedKeys.length] index; 52 foreach (i, key; IonSystemSymbolTable_v1 ~ compiletimeSymbolTable) 53 index[compiletimeTable[key]] = cast(U) i; 54 return index; 55 } (); 56 static immutable ubyte[] compiletimeTableTape = () { 57 IonSymbolTable!true table; 58 table.initialize; 59 foreach (key; compiletimeSymbolTable) 60 table.insert(key); 61 table.finalize; 62 return table.data; 63 } (); 64 65 /// 66 import mir.ion.internal.data_holder; 67 IonTapeHolder!(bufferStackSize) buffer = void; 68 69 /// 70 IonSymbolTable!tableGC* runtimeTable; 71 72 /// Mutable value used to choose format specidied or user-defined serialization specializations 73 int serdeTarget = SerdeTarget.ion; 74 75 nothrow pure @trusted: 76 77 void initialize(ref IonSymbolTable!tableGC runtimeTable, int serdeTarget = SerdeTarget.ion) scope @trusted 78 { 79 buffer.initialize; 80 this.runtimeTable = &runtimeTable; 81 this.serdeTarget = serdeTarget; 82 } 83 84 void initializeNoTable(int serdeTarget = SerdeTarget.ion) @trusted 85 { 86 pragma(inline, true); 87 buffer.initialize; 88 this.runtimeTable = null; 89 this.serdeTarget = serdeTarget; 90 } 91 92 void finalize() @trusted 93 { 94 } 95 96 inout(ubyte)[] data() inout scope return 97 { 98 return buffer.data; 99 } 100 101 scope: 102 103 private void putEnd(size_t beginPosition, IonTypeCode typeCode) 104 { 105 buffer.reserve(128 + 3); 106 auto length = buffer._currentLength - beginPosition - ionPutStartLength; 107 buffer._currentLength = beginPosition + ionPutEnd(buffer.data.ptr + beginPosition, typeCode, length); 108 } 109 110 /// 111 size_t structBegin(size_t length = size_t.max) 112 { 113 auto ret = buffer._currentLength; 114 buffer.reserve(ionPutStartLength); 115 buffer._currentLength += ionPutStartLength; 116 return ret; 117 } 118 119 /// 120 void structEnd(size_t state) 121 { 122 putEnd(state, IonTypeCode.struct_); 123 } 124 125 /// 126 alias listBegin = structBegin; 127 128 /// 129 void listEnd(size_t state) 130 { 131 putEnd(state, IonTypeCode.list); 132 } 133 134 /// 135 alias sexpBegin = listBegin; 136 137 /// 138 void sexpEnd(size_t state) 139 { 140 putEnd(state, IonTypeCode.sexp); 141 } 142 143 /// 144 alias stringBegin = structBegin; 145 146 /++ 147 Puts string part. The implementation allows to split string unicode points. 148 +/ 149 void putStringPart(scope const(char)[] str) 150 { 151 buffer.put(cast(const ubyte[]) str); 152 } 153 154 /// 155 void stringEnd(size_t state) 156 { 157 putEnd(state, IonTypeCode..string); 158 } 159 160 /// 161 auto annotationsEnd(size_t state) 162 { 163 size_t length = buffer._currentLength - (state + ionPutStartLength + ionPutAnnotationsListStartLength); 164 if (_expect(length >= 0x80, false)) 165 buffer.reserve(9); 166 return buffer._currentLength = state + ionPutStartLength + ionPutAnnotationsListEnd(buffer.data.ptr + state + ionPutStartLength, length); 167 } 168 169 /// 170 size_t annotationWrapperBegin(size_t length = size_t.max) 171 { 172 auto ret = buffer._currentLength; 173 buffer.reserve(ionPutStartLength + ionPutAnnotationsListStartLength); 174 buffer._currentLength += ionPutStartLength + ionPutAnnotationsListStartLength; 175 return ret; 176 } 177 178 /// 179 void annotationWrapperEnd(size_t annotationsState, size_t state) 180 { 181 putEnd(state, IonTypeCode.annotations); 182 } 183 184 /// 185 void putCompiletimeKey(string key)() 186 { 187 enum id = compiletimeTable[key]; 188 putKeyId(compiletimeIndex[id]); 189 } 190 191 /// 192 alias putCompiletimeAnnotation = putCompiletimeKey; 193 194 uint _getId(scope const char[] key) 195 { 196 import mir.utility: _expect; 197 uint id; 198 if (_expect(compiletimeTable.get(key, id), true)) 199 { 200 return id = compiletimeIndex[id]; 201 } 202 else // use GC CTFE symbol table because likely `putKey` is used either for Associative array of for similar types. 203 { 204 if (_expect(!runtimeTable.initialized, false)) 205 { 206 runtimeTable.initialize; 207 foreach (ctKey; compiletimeSymbolTable) 208 { 209 runtimeTable.insert(ctKey); 210 } 211 } 212 return runtimeTable.insert(cast(const(char)[])key); 213 } 214 } 215 216 static if (tableGC) 217 /// 218 void putKey(scope const char[] key) 219 { 220 putKeyId(_getId(key)); 221 } 222 else 223 /// 224 void putKey(scope const char[] key) @nogc 225 { 226 putKeyId(_getId(key)); 227 } 228 229 static if (tableGC) 230 /// 231 void putAnnotation(scope const char[] key) 232 { 233 putAnnotationId(_getId(key)); 234 } 235 else 236 /// 237 void putAnnotation(scope const char[] key) @nogc 238 { 239 putAnnotationId(_getId(key)); 240 } 241 242 /// 243 void putKeyId(T)(const T id) 244 if (__traits(isUnsigned, T)) 245 { 246 buffer._currentLength += ionPutVarUInt(buffer.reserve(11).ptr, id); 247 } 248 249 /// 250 /// 251 void putAnnotationId(T)(const T id) 252 if (__traits(isUnsigned, T)) 253 { 254 buffer._currentLength += ionPutVarUInt(buffer.reserve(11).ptr, id); 255 } 256 257 /// 258 void putSymbolId(size_t id) 259 { 260 buffer._currentLength += ionPutSymbolId(buffer.reserve(9).ptr, id); 261 } 262 263 /// 264 void putSymbol(scope const char[] key) 265 { 266 import mir.utility: _expect; 267 putSymbolId(_getId(key)); 268 } 269 270 /// 271 void putValue(Num)(const Num num) 272 if (isNumeric!Num && !is(Num == enum)) 273 { 274 buffer._currentLength += ionPut(buffer.reserve(Num.sizeof + 1).ptr, num); 275 } 276 277 /// 278 void putValue(W)(BigIntView!W view) 279 { 280 buffer._currentLength += ionPut(buffer.reserve(view.unsigned.coefficients.length * W.sizeof + 12).ptr, view); 281 } 282 283 /// 284 void putValue(size_t size)(auto ref const BigInt!size num) 285 { 286 putValue(num.view); 287 } 288 289 /// 290 void putValue(size_t size)(auto ref const Decimal!size num) 291 { 292 buffer._currentLength += ionPut(buffer.reserve(num.coefficient.coefficients.length * size_t.sizeof + 23).ptr, num.view); 293 } 294 295 /// 296 void putValue(typeof(null)) 297 { 298 putNull(IonTypeCode.null_); 299 } 300 301 /// 302 void putNull(IonTypeCode code) 303 { 304 buffer._currentLength += ionPut(buffer.reserve(1).ptr, null, code); 305 } 306 307 /// 308 void putValue(bool b) 309 { 310 buffer._currentLength += ionPut(buffer.reserve(1).ptr, b); 311 } 312 313 /// 314 void putValue(scope const char[] value) 315 { 316 buffer._currentLength += ionPut(buffer.reserve(value.length + size_t.sizeof + 1).ptr, value); 317 } 318 319 /// 320 void putValue(scope Clob value) 321 { 322 buffer._currentLength += ionPut(buffer.reserve(value.data.length + size_t.sizeof + 1).ptr, value); 323 } 324 325 /// 326 void putValue(scope Blob value) 327 { 328 buffer._currentLength += ionPut(buffer.reserve(value.data.length + size_t.sizeof + 1).ptr, value); 329 } 330 331 /// 332 void putValue(Timestamp value) 333 { 334 buffer._currentLength += ionPut(buffer.reserve(20).ptr, value); 335 } 336 337 /// 338 void elemBegin() 339 { 340 } 341 342 /// 343 alias sexpElemBegin = elemBegin; 344 345 /// 346 void nextTopLevelValue() 347 { 348 } 349 } 350 351 /++ 352 Ion serialization function. 353 +/ 354 immutable(ubyte)[] serializeIon(T)(auto ref T value, int serdeTarget = SerdeTarget.ion) 355 { 356 import mir.utility: _expect; 357 import mir.ion.internal.data_holder: ionPrefix; 358 import mir.ser: serializeValue; 359 import mir.ion.symbol_table: IonSymbolTable, removeSystemSymbols; 360 361 enum nMax = 4096u; 362 enum keys = serdeGetSerializationKeysRecurse!T.removeSystemSymbols; 363 364 IonSymbolTable!true table; 365 auto serializer = ionSerializer!(nMax * 8, keys, true); 366 serializer.initialize(table, serdeTarget); 367 368 serializeValue(serializer, value); 369 serializer.finalize; 370 371 static immutable ubyte[] compiletimePrefixAndTableTapeData = ionPrefix ~ serializer.compiletimeTableTape; 372 373 // use runtime table 374 if (_expect(table.initialized, false)) 375 { 376 table.finalize; 377 return () @trusted { return cast(immutable) (ionPrefix ~ table.data ~ serializer.data); } (); 378 } 379 // compile time table 380 else 381 { 382 return () @trusted { return cast(immutable) (compiletimePrefixAndTableTapeData ~ serializer.data); } (); 383 } 384 } 385 386 /// 387 version(mir_ion_ser_test) 388 unittest 389 { 390 static struct S 391 { 392 string s; 393 double aaaa; 394 int bbbb; 395 } 396 397 enum s = S("str", 1.23, 123); 398 399 static immutable ubyte[] data = [ 400 0xe0, 0x01, 0x00, 0xea, 0xee, 0x92, 0x81, 0x83, 401 0xde, 0x8e, 0x87, 0xbc, 0x81, 0x73, 0x84, 0x61, 402 0x61, 0x61, 0x61, 0x84, 0x62, 0x62, 0x62, 0x62, 403 0xde, 0x92, 0x8a, 0x83, 0x73, 0x74, 0x72, 0x8b, 404 0x48, 0x3f, 0xf3, 0xae, 0x14, 0x7a, 0xe1, 0x47, 405 0xae, 0x8c, 0x21, 0x7b, 406 ]; 407 408 import mir.test; 409 s.serializeIon.should == data; 410 enum staticData = s.serializeIon; 411 static assert (staticData == data); 412 } 413 414 /// 415 version(mir_ion_ser_test) 416 unittest 417 { 418 import mir.serde: SerdeTarget; 419 static immutable ubyte[] binaryDataAB = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x61, 0x81, 0x62, 0xd6, 0x8a, 0x21, 0x01, 0x8b, 0x21, 0x02]; 420 static immutable ubyte[] binaryDataBA = [0xe0, 0x01, 0x00, 0xea, 0xe9, 0x81, 0x83, 0xd6, 0x87, 0xb4, 0x81, 0x62, 0x81, 0x61, 0xd6, 0x8a, 0x21, 0x02, 0x8b, 0x21, 0x01]; 421 int[string] table = ["a" : 1, "b" : 2]; 422 auto data = table.serializeIon(SerdeTarget.ion); 423 assert(data == binaryDataAB || data == binaryDataBA); 424 } 425 426 /++ 427 Ion serialization for custom outputt range. 428 +/ 429 void serializeIon(Appender, T)(scope ref Appender appender, auto ref T value, int serdeTarget = SerdeTarget.ion) 430 if (isOutputRange!(Appender, const(ubyte)[]) && !is(T == SerdeTarget)) 431 { 432 import mir.utility: _expect; 433 import mir.ion.internal.data_holder: ionPrefix; 434 import mir.ser: serializeValue; 435 import mir.ion.symbol_table: IonSymbolTable, removeSystemSymbols; 436 437 enum nMax = 4096u; 438 enum keys = serdeGetSerializationKeysRecurse!T.removeSystemSymbols; 439 440 auto table = () @trusted { IonSymbolTable!false ret = void; ret.initializeNull; return ret; }(); 441 scope serializer = ionSerializer!(nMax * 8, keys, false); 442 serializer.initialize(table, serdeTarget); 443 444 serializeValue(serializer, value); 445 serializer.finalize; 446 447 appender.put(ionPrefix); 448 449 // use runtime table 450 if (_expect(table.initialized, false)) 451 { 452 table.finalize; 453 appender.put(table.data); 454 } 455 // compile time table 456 else 457 { 458 appender.put(serializer.compiletimeTableTape); 459 } 460 appender.put(serializer.data); 461 } 462 463 /// 464 version(mir_ion_ser_test) 465 @safe pure nothrow @nogc 466 unittest 467 { 468 import mir.test; 469 import mir.appender: scopedBuffer; 470 471 static struct S 472 { 473 string s; 474 double aaaa; 475 int bbbb; 476 } 477 478 auto s = S("str", 1.23, 123); 479 480 static immutable ubyte[] data = [ 481 0xe0, 0x01, 0x00, 0xea, 0xee, 0x92, 0x81, 0x83, 482 0xde, 0x8e, 0x87, 0xbc, 0x81, 0x73, 0x84, 0x61, 483 0x61, 0x61, 0x61, 0x84, 0x62, 0x62, 0x62, 0x62, 484 0xde, 0x92, 0x8a, 0x83, 0x73, 0x74, 0x72, 0x8b, 485 0x48, 0x3f, 0xf3, 0xae, 0x14, 0x7a, 0xe1, 0x47, 486 0xae, 0x8c, 0x21, 0x7b, 487 ]; 488 489 auto buffer = scopedBuffer!ubyte; 490 serializeIon(buffer, s); 491 buffer.data.should == data; 492 }