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 }