1 /**
2  * Utility functions to convert PLC words
3  */
4 module dfins.util;
5 
6 import std.bitmanip : read;
7 import std.system : Endian; // for Endian
8 import std.range;
9 
10 /**
11  * Bytes per each word.
12  *
13  * A word is a $(D ushort), so each word has 2 bytes.
14  */
15 enum BYTES_PER_WORD = 2;
16 
17 /**
18  * Converts an array of type T into an ubyte array.
19  *
20  * Omron stores data in LittleEndian format.
21  *
22  * Params:
23  *   input = array of type T to convert
24  *
25  * Returns: An array of ubyte
26  */
27 ubyte[] toBytes(T)(T[] input) {
28    import std.array : appender;
29    import std.bitmanip : append;
30 
31    auto buffer = appender!(const(ubyte)[])();
32    foreach (dm; input) {
33       buffer.append!(T, Endian.littleEndian)(dm);
34    }
35    return buffer.data.dup;
36 }
37 ///
38 unittest {
39    assert([0x8034].toBytes!ushort() == [0x34, 0x80]);
40    ushort[] buf = [0x8034, 0x2010];
41    assert(buf.toBytes!ushort() == [0x34, 0x80, 0x10, 0x20]);
42    assert(buf.length == 2);
43 
44    assert([0x8034].toBytes!uint() == [0x34, 0x80, 0, 0]);
45    assert([0x010464].toBytes!uint() == [0x64, 0x04, 1, 0]);
46 }
47 
48 /**
49  * Converts an array of bytes into wordd $(D ushort) array.
50  *
51  * Params:  bytes = array to convert
52  *
53  * Returns: An ushort array that rapresents words
54  */
55 ushort[] toWords(ubyte[] bytes) {
56    ushort[] dm;
57    while (bytes.length >= BYTES_PER_WORD) {
58       dm ~= bytes.read!(ushort, Endian.littleEndian);
59    }
60    if (bytes.length > 0) {
61       dm ~= bytes[0];
62    }
63    return dm;
64 }
65 
66 ///
67 unittest {
68    assert([0x10].toWords() == [0x10]);
69    assert([0, 0xAB].toWords() == [0xAB00]);
70    assert([0x20, 0x0].toWords() == [0x20]);
71    assert([0x10, 0x20, 0x30, 0x40, 0x50].toWords() == [0x2010, 0x4030, 0x50]);
72 }
73 
74 /**
75  * Takes an array of word $(D ushort) and converts the first $(D T.sizeof / 2)
76  * word to $(D T).
77  * The array is $(B not) consumed.
78  *
79  * Params:
80  *  T = The integral type to convert the first `T.sizeof / 2` words to.
81  *  words = The array of word to convert
82  */
83 T peek(T)(ushort[] words) {
84    return peek!T(words, 0);
85 }
86 ///
87 unittest {
88    ushort[] words = [0x645A, 0x3ffb];
89    assert(words.peek!float == 1.964F);
90    assert(words.length == 2);
91 
92    ushort[] odd = [0x645A, 0x3ffb, 0xffaa];
93    assert(odd.peek!float == 1.964F);
94    assert(odd.length == 3);
95 }
96 
97 /**
98  * Takes an array of word ($(D ushort)) and converts the first $(D T.sizeof / 2)
99  * word to $(D T) starting from index `index`.
100  *
101  * The array is $(B not) consumed.
102  *
103  * Params:
104  *  T = The integral type to convert the first `T.sizeof / 2` word to.
105  *  words = The array of word to convert
106  *  index = The index to start reading from (instead of starting at the front).
107  */
108 T peek(T)(ushort[] words, size_t index) {
109    import std.bitmanip : peek;
110 
111    ubyte[] buffer = toBytes(words);
112    return buffer.peek!(T, Endian.littleEndian)(index * BYTES_PER_WORD);
113 }
114 ///
115 unittest {
116    assert([0x645A, 0x3ffb].peek!float(0) == 1.964F);
117    assert([0, 0, 0x645A, 0x3ffb].peek!float(2) == 1.964F);
118    assert([0, 0, 0x645A, 0x3ffb].peek!float(0) == 0);
119    assert([0x80, 0, 0].peek!ushort(0) == 128);
120    assert([0xFFFF].peek!short(0) == -1);
121    assert([0xFFFF].peek!ushort(0) == 65_535);
122    assert([0xFFF7].peek!ushort(0) == 65_527);
123    assert([0xFFF7].peek!short(0) == -9);
124    assert([0xFFFB].peek!short(0) == -5);
125    assert([0xFFFB].peek!ushort(0) == 65_531);
126    assert([0x8000].peek!short(0) == -32_768);
127 }
128 
129 /**
130  * Converts ushort value into BDC format
131  *
132  * Params:
133  *  dec = ushort in decimal format
134  *
135  * Returns: BCD value
136  */
137 ushort toBCD(ushort dec) {
138    enum ushort MAX_VALUE = 9999;
139    enum ushort MIN_VALUE = 0;
140    if ((dec > MAX_VALUE) || (dec < MIN_VALUE)) {
141       throw new Exception("Decimal out of range (should be 0..9999)");
142    } else {
143       ushort bcd;
144       enum ushort NUM_BASE = 10;
145       ushort i;
146       for (; dec > 0; dec /= NUM_BASE) {
147          ushort rem = cast(ushort)(dec % NUM_BASE);
148          bcd += cast(ushort)(rem << 4 * i++);
149       }
150       return bcd;
151    }
152 }
153 ///
154 unittest {
155    assert(0.toBCD() == 0);
156    assert(10.toBCD() == 0x10);
157    assert(34.toBCD() == 52);
158    assert(127.toBCD() == 0x127);
159    assert(110.toBCD() == 0x110);
160    assert(9999.toBCD() == 0x9999);
161    assert(9999.toBCD() == 39_321);
162 }
163 
164 /**
165  * Converts BCD value into decimal format
166  *
167  * Params:
168  *  bcd = ushort in BCD format
169  *
170  * Returns: decimal value
171  */
172 ushort fromBCD(ushort bcd) {
173    enum int NO_OF_DIGITS = 8;
174    enum ushort MAX_VALUE = 0x9999;
175    enum ushort MIN_VALUE = 0;
176    if ((bcd > MAX_VALUE) || (bcd < MIN_VALUE)) {
177       throw new Exception("BCD out of range (should be 0..39321)");
178    } else {
179       ushort dec;
180       ushort weight = 1;
181       foreach (j; 0 .. NO_OF_DIGITS) {
182          dec += cast(ushort)((bcd & 0x0F) * weight);
183          bcd = cast(ushort)(bcd >> 4);
184          weight *= 10;
185       }
186       return dec;
187    }
188 }
189 ///
190 unittest {
191    assert(0.fromBCD() == 0);
192    assert((0x22).fromBCD() == 22);
193    assert((34).fromBCD() == 22);
194    // 17bcd
195    assert((0b0001_0111).fromBCD() == 17);
196    assert(295.fromBCD() == 127);
197    assert(39_321.fromBCD() == 9_999);
198    assert((0x9999).fromBCD() == 9_999);
199 }
200 
201 /**
202  * Takes an input range of words ($(D ushort)) and converts the first $(D T.sizeof / 2)
203  * words to $(D T).
204  * The array is consumed.
205  *
206  * Params:
207  *  T = The integral type to convert the first `T.sizeof / 2` word to.
208  *  input = The input range of words to convert
209  */
210 T pop(T, R)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ushort)) {
211    import std.traits : isIntegral, isSigned;
212 
213    static if (isIntegral!T) {
214       return popInteger!(R, T.sizeof / 2, isSigned!T)(input);
215    } else static if (is(T == float)) {
216       return uint2float(popInteger!(R, 2, false)(input));
217    } else static if (is(T == double)) {
218       return ulong2double(popInteger!(R, 4, false)(input));
219    } else {
220       static assert(false, "Unsupported type " ~ T.stringof);
221    }
222 }
223 
224 /**
225  * $(D pop!float) example
226  */
227 unittest {
228    ushort[] asPeek = [0x645A, 0x3ffb];
229    assert(asPeek.pop!float == 1.964F);
230    assert(asPeek.length == 0);
231 
232    // float.sizeOf is 4bytes => 2 word
233    ushort[] input = [0x1eb8, 0xc19d];
234    assert(input.length == 2);
235    assert(pop!float(input) == -19.64F);
236    assert(input.length == 0);
237 
238    input = [0x0, 0xBF00, 0x0, 0x3F00];
239    assert(input.pop!float == -0.5F);
240    assert(pop!float(input) == 0.5F);
241 }
242 
243 /**
244  * $(D pop!double) examples.
245  *
246  * A double has size 8 bytes => 4word
247  */
248 unittest {
249    ushort[] input = [0x0, 0x0, 0x0, 0x3FE0];
250    assert(input.length == 4);
251 
252    assert(pop!double(input) == 0.5);
253    assert(input.length == 0);
254 
255    input = [0x0, 0x0, 0x0, 0xBFE0];
256    assert(pop!double(input) == -0.5);
257 
258    input = [0x00, 0x01, 0x02, 0x03];
259    assert(input.length == 4);
260    assert(pop!int(input) == 0x10000);
261    assert(input.length == 2);
262    assert(pop!int(input) == 0x30002);
263    assert(input.length == 0);
264 }
265 
266 /**
267  * pop!ushort and short examples
268  */
269 unittest {
270    ushort[] input = [0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB];
271    assert(pop!ushort(input) == 0xFFFF);
272    assert(pop!short(input) == -1);
273    assert(pop!ushort(input) == 0xFFFB);
274    assert(pop!short(input) == -5);
275 }
276 
277 unittest {
278    ushort[] input = [0x1eb8, 0xc19d, 0x0, 0xBF00, 0x0, 0x3F00, 0x0, 0x0, 0x0, 0x3FE0, 0x0, 0x0, 0x0, 0xBFE0, 0x00,
279       0x01, 0x02, 0x03, 0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB];
280 
281    assert(pop!float(input) == -19.64F);
282    assert(pop!float(input) == -0.5F);
283    assert(pop!float(input) == 0.5F);
284 
285    assert(pop!double(input) == 0.5);
286    assert(pop!double(input) == -0.5);
287 
288    assert(pop!int(input) == 0x10000);
289    assert(pop!int(input) == 0x30002);
290 
291    assert(pop!ushort(input) == 0xFFFF);
292    assert(pop!short(input) == -1);
293    assert(pop!ushort(input) == 0xFFFB);
294    assert(pop!short(input) == -5);
295 }
296 
297 unittest {
298    ushort[] shortBuffer = [0x645A];
299    assert(shortBuffer.length == 1);
300    //shortBuffer.pop!float().shouldThrow!Exception;
301 
302    ushort[] bBuffer = [0x0001, 0x0002, 0x0003];
303    assert(bBuffer.length == 3);
304 
305    assert(bBuffer.pop!ushort == 1);
306    assert(bBuffer.length == 2);
307 
308    assert(bBuffer.pop!ushort == 2);
309    assert(bBuffer.length == 1);
310 
311    assert(bBuffer.pop!ushort == 3);
312    assert(bBuffer.length == 0);
313 }
314 
315 /**
316  * Takes an input range of words ($(D ushort)) and converts the first `numWords`
317  * word's to $(D T).
318  * The array is consumed.
319  *
320  * Params:
321  *  R = The integral type of innput range
322  *  numWords = Number of words to convert
323  *  wantSigned = Get signed value
324  *  input = The input range of word to convert
325  */
326 private auto popInteger(R, int numWords, bool wantSigned)(ref R input)
327       if ((isInputRange!R) && is(ElementType!R : const ushort)) {
328    import std.traits : Signed;
329 
330    alias T = IntegerLargerThan!(numWords);
331    T result = 0;
332 
333    foreach (i; 0 .. numWords) {
334       result |= (cast(T)(popDM(input)) << (16 * i));
335    }
336 
337    static if (wantSigned) {
338       return cast(Signed!T)result;
339    } else {
340       return result;
341    }
342 }
343 
344 unittest {
345    ushort[] input = [0x00, 0x01, 0x02, 0x03];
346    assert(popInteger!(ushort[], 2, false)(input) == 0x10000);
347    assert(popInteger!(ushort[], 2, false)(input) == 0x30002);
348    assert(input.length == 0);
349 
350    input = [0x01, 0x02, 0x03, 0x04];
351    assert(popInteger!(ushort[], 3, false)(input) == 0x300020001);
352 
353    input = [0x01, 0x02];
354    //assert(popInteger!(ushort[], 3, false)(input).shouldThrow!Exception;
355 
356    input = [0x00, 0x8000];
357    assert(popInteger!(ushort[], 2, false)(input) == 0x8000_0000);
358 
359    input = [0xFFFF, 0xFFFF];
360    assert(popInteger!(ushort[], 2, true)(input) == -1);
361 
362    input = [0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB];
363    assert(popInteger!(ushort[], 1, false)(input) == 0xFFFF);
364    assert(popInteger!(ushort[], 1, true)(input) == -1);
365    assert(popInteger!(ushort[], 1, false)(input) == 0xFFFB);
366    assert(popInteger!(ushort[], 1, true)(input) == -5);
367 }
368 
369 private template IntegerLargerThan(int numWords) if (numWords > 0 && numWords <= 4) {
370    static if (numWords == 1) {
371       alias IntegerLargerThan = ushort;
372    } else static if (numWords == 2) {
373       alias IntegerLargerThan = uint;
374    } else {
375       alias IntegerLargerThan = ulong;
376    }
377 }
378 
379 private ushort popDM(R)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ushort)) {
380    if (input.empty) {
381       throw new Exception("Expected a ushort, but found end of input");
382    }
383 
384    const(ushort) d = input.front;
385    input.popFront();
386    return d;
387 }
388 
389 /**
390  * Writes numeric type $(I T) into a output range of $(D ushort).
391  *
392  * Params:
393  *  n = The numeric type to write into output range
394  *  output = The output range of word to convert
395  *
396  * Examples:
397  * --------------------
398  * ushort[] arr;
399  * auto app = appender(arr);
400  * write!float(app, 1.0f);
401  *
402  * auto app = appender!(const(ushort)[]);
403  * app.write!ushort(5);
404  * app.data.shouldEqual([5]);
405  * --------------------
406  */
407 void write(T, R)(ref R output, T n) if (isOutputRange!(R, ushort)) {
408    import std.traits : isIntegral;
409 
410    static if (isIntegral!T) {
411       writeInteger!(R, T.sizeof / 2)(output, n);
412    } else static if (is(T == float)) {
413       writeInteger!(R, 2)(output, float2uint(n));
414    } else static if (is(T == double)) {
415       writeInteger!(R, 4)(output, double2ulong(n));
416    } else {
417       static assert(false, "Unsupported type " ~ T.stringof);
418    }
419 }
420 
421 /**
422  * Write float and double
423  */
424 unittest {
425    ushort[] arr;
426    auto app = appender(arr);
427    write!float(app, 1.0f);
428 
429    app.write!double(2.0);
430 
431    ushort[] expected = [0, 0x3f80, 0, 0, 0, 0x4000];
432    assert(app.data == expected);
433 }
434 
435 /**
436  * Write ushort and int
437  */
438 unittest {
439    import std.array : appender;
440 
441    auto app = appender!(const(ushort)[]);
442    app.write!ushort(5);
443    assert(app.data == [5]);
444 
445    app.write!float(1.964F);
446    assert(app.data == [5, 0x645A, 0x3ffb]);
447 
448    app.write!uint(0x1720_8034);
449    assert(app.data == [5, 0x645A, 0x3ffb, 0x8034, 0x1720]);
450 }
451 
452 private void writeInteger(R, int numWords)(ref R output, IntegerLargerThan!numWords n) if (isOutputRange!(R, ushort)) {
453    import std.traits : Unsigned;
454 
455    alias T = IntegerLargerThan!numWords;
456    auto u = cast(Unsigned!T)n;
457    foreach (i; 0 .. numWords) {
458       immutable(ushort) b = (u >> (i * 16)) & 0xFFFF;
459       output.put(b);
460    }
461 }
462 
463 /**
464  * Convert $(D uint) to $(D float)
465  */
466 private float uint2float(uint x) pure nothrow {
467    float_uint fi;
468    fi.i = x;
469    return fi.f;
470 }
471 
472 unittest {
473    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
474    assert(uint2float(0x24369620) == 3.959212E-17F);
475    assert(uint2float(0x3F000000) == 0.5F);
476    assert(uint2float(0xBF000000) == -0.5F);
477    assert(uint2float(0x0) == 0);
478    assert(uint2float(0x419D1EB8) == 19.64F);
479    assert(uint2float(0xC19D1EB8) == -19.64F);
480    assert(uint2float(0x358637bd) == 0.000001F);
481    assert(uint2float(0xb58637bd) == -0.000001F);
482 }
483 
484 private uint float2uint(float x) pure nothrow {
485    float_uint fi;
486    fi.f = x;
487    return fi.i;
488 }
489 
490 unittest {
491    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
492    assert(float2uint(3.959212E-17F) == 0x24369620);
493    assert(float2uint(.5F) == 0x3F000000);
494    assert(float2uint(-.5F) == 0xBF000000);
495    assert(float2uint(0x0) == 0);
496    assert(float2uint(19.64F) == 0x419D1EB8);
497    assert(float2uint(-19.64F) == 0xC19D1EB8);
498    assert(float2uint(0.000001F) == 0x358637bd);
499    assert(float2uint(-0.000001F) == 0xb58637bd);
500 }
501 
502 // read/write 64-bits float
503 private union float_uint {
504    float f;
505    uint i;
506 }
507 
508 double ulong2double(ulong x) pure nothrow {
509    double_ulong fi;
510    fi.i = x;
511    return fi.f;
512 }
513 
514 unittest {
515    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
516    assert(ulong2double(0x0) == 0);
517    assert(ulong2double(0x3fe0000000000000) == 0.5);
518    assert(ulong2double(0xbfe0000000000000) == -0.5);
519 }
520 
521 private ulong double2ulong(double x) pure nothrow {
522    double_ulong fi;
523    fi.f = x;
524    return fi.i;
525 }
526 
527 unittest {
528    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
529    assert(double2ulong(0) == 0);
530    assert(double2ulong(0.5) == 0x3fe0000000000000);
531    assert(double2ulong(-0.5) == 0xbfe0000000000000);
532 }
533 
534 private union double_ulong {
535    double f;
536    ulong i;
537 }