1 /*
2     License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
3     Authors:   Adam Búš
4 */
5 module btl..string.encoding;
6 
7 import std.traits : isSomeChar;
8 
9 
10 
11 /*
12     Same as std.utf.isValidDchar.
13 */
14 public bool isValidDchar(dchar c) pure nothrow @safe @nogc{
15     return (c < 0xD800) || (0xDFFF < c && c <= 0x10FFFF);
16 }
17 
18 
19 
20 /*
21     Same as std.utf.codeLength.
22 */
23 public ubyte codeLength(C)(dchar c) @safe pure nothrow @nogc
24 if(isSomeChar!C){
25     static if (C.sizeof == 1)
26     {
27         if (c <= 0x7F) return 1;
28         if (c <= 0x7FF) return 2;
29         if (c <= 0xFFFF) return 3;
30         if (c <= 0x10FFFF) return 4;
31         assert(false);
32     }
33     else static if (C.sizeof == 2)
34     {
35         return c <= 0xFFFF ? 1 : 2;
36     }
37     else
38     {
39         static assert(C.sizeof == 4);
40         return 1;
41     }
42 }
43 
44 
45 private enum dchar replacementDchar = '\uFFFD';
46 
47 
48 /*
49     Modification of std.utf.encode where output buffer is not fixed array but slice.
50 */
51 public template encode(To){
52     //import std.utf : isValidDchar;
53 
54     size_t encode(dchar from, To[] to)pure nothrow @safe @nogc{
55         return encode_impl(to, from);
56     }
57 
58 
59 
60     //https://github.com/dlang/phobos/blob/master/std/utf.d#L2264
61     size_t encode_impl(char[] buf, dchar c) @safe pure nothrow @nogc{
62         if (c <= 0x7F)
63         {
64             assert(isValidDchar(c));
65             buf[0] = cast(char) c;
66             return 1;
67         }
68         if (c <= 0x7FF)
69         {
70             assert(isValidDchar(c));
71             buf[0] = cast(char)(0xC0 | (c >> 6));
72             buf[1] = cast(char)(0x80 | (c & 0x3F));
73             return 2;
74         }
75         if (c <= 0xFFFF)
76         {
77             if (0xD800 <= c && c <= 0xDFFF)
78                 c = replacementDchar;   //_utfException!useReplacementDchar("Encoding a surrogate code point in UTF-8", c);
79 
80             assert(isValidDchar(c));
81         L3:
82             buf[0] = cast(char)(0xE0 | (c >> 12));
83             buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
84             buf[2] = cast(char)(0x80 | (c & 0x3F));
85             return 3;
86         }
87         if (c <= 0x10FFFF)
88         {
89             assert(isValidDchar(c));
90             buf[0] = cast(char)(0xF0 | (c >> 18));
91             buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
92             buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
93             buf[3] = cast(char)(0x80 | (c & 0x3F));
94             return 4;
95         }
96 
97         assert(!isValidDchar(c));
98         c = replacementDchar;   //_utfException!useReplacementDchar("Encoding an invalid code point in UTF-8", c);
99         goto L3;
100     }
101 
102     //https://github.com/dlang/phobos/blob/master/std/utf.d#L2398
103     size_t encode_impl(wchar[] buf, dchar c) @safe pure nothrow @nogc{
104         if (c <= 0xFFFF)
105         {
106             if (0xD800 <= c && c <= 0xDFFF)
107                 c = replacementDchar;   //_utfException!useReplacementDchar("Encoding an isolated surrogate code point in UTF-16", c);
108 
109             assert(isValidDchar(c));
110         L1:
111             buf[0] = cast(wchar) c;
112             return 1;
113         }
114         if (c <= 0x10FFFF)
115         {
116             assert(isValidDchar(c));
117             buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
118             buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
119             return 2;
120         }
121 
122         c = replacementDchar;   //_utfException!useReplacementDchar("Encoding an invalid code point in UTF-16", c);
123         goto L1;
124     }
125 
126     //https://github.com/dlang/phobos/blob/master/std/utf.d#L2451
127     size_t encode_impl(dchar[] buf, dchar c) @safe pure nothrow @nogc{
128         if ((0xD800 <= c && c <= 0xDFFF) || 0x10FFFF < c)
129             c = replacementDchar;   //_utfException!useReplacementDchar("Encoding an invalid code point in UTF-32", c);
130         else
131             assert(isValidDchar(c));
132         buf[0] = c;
133         return 1;
134     }
135 }
136 
137 
138 
139 /*
140     Same as std.utf.decodeFront.
141 */
142 public template decode(S){
143     dchar decode(ref S str)pure nothrow @trusted @nogc{
144         import std.typecons : Yes;
145 
146         size_t numCodeUnits;
147         return decodeFront(str, numCodeUnits);
148     }
149 
150     //import std.typecons : Yes, No, Flag;
151     import std.meta : AliasSeq;
152     import std.range : ElementEncodingType, ElementType, isInputRange, empty, isRandomAccessRange, hasSlicing, hasLength;
153     import std.traits : isSomeString, isSomeChar;
154 
155 
156     private template codeUnitLimit(S)
157     if (isSomeChar!(ElementEncodingType!S)){
158         static if (is(immutable ElementEncodingType!S == immutable char))
159             enum char codeUnitLimit = 0x80;
160         else static if (is(immutable ElementEncodingType!S == immutable wchar))
161             enum wchar codeUnitLimit = 0xD800;
162         else
163             enum dchar codeUnitLimit = 0xD800;
164     }
165 
166     private dchar decodeFront(S)(ref S str, out size_t numCodeUnits)
167     if (!isSomeString!S && isInputRange!S && isSomeChar!(ElementType!S))
168     in(!str.empty)
169     out (result; isValidDchar(result)){
170         immutable fst = str.front;
171 
172         if (fst < codeUnitLimit!S)
173         {
174             str.popFront();
175             numCodeUnits = 1;
176             return fst;
177         }
178         else
179         {
180             // https://issues.dlang.org/show_bug.cgi?id=14447 forces canIndex to be
181             // done outside of decodeImpl, which is undesirable, since not all
182             // overloads of decodeImpl need it. So, it should be moved back into
183             // decodeImpl once https://issues.dlang.org/show_bug.cgi?id=8521
184             // has been fixed.
185             enum canIndex = is(S : const char[]) || isRandomAccessRange!S && hasSlicing!S && hasLength!S;
186             immutable retval = decode_impl!(canIndex)(str, numCodeUnits);
187 
188             // The other range types were already popped by decodeImpl.
189             static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
190                 str = str[numCodeUnits .. str.length];
191 
192             return retval;
193         }
194     }
195 
196 
197     private dchar decodeFront(S)(ref S str, out size_t numCodeUnits) @trusted pure
198     if (isSomeString!S)
199     in(!str.empty)
200     out (result; isValidDchar(result)){
201 
202         if (str[0] < codeUnitLimit!S)
203         {
204             numCodeUnits = 1;
205             immutable retval = str[0];
206             str = str[1 .. $];
207             return retval;
208         }
209         else static if (is(immutable S == immutable C[], C))
210         {
211             immutable retval = decode_impl!(true)(cast(const(C)[]) str, numCodeUnits);
212             str = str[numCodeUnits .. $];
213             return retval;
214         }
215     }
216 
217 
218     private dchar decode_impl(bool canIndex, S)(auto ref S str, ref size_t index)
219     if (is(S : const char[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable char))){
220         /* The following encodings are valid, except for the 5 and 6 byte
221          * combinations:
222          *  0xxxxxxx
223          *  110xxxxx 10xxxxxx
224          *  1110xxxx 10xxxxxx 10xxxxxx
225          *  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
226          *  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
227          *  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
228          */
229 
230         /* Dchar bitmask for different numbers of UTF-8 code units.
231          */
232         alias bitMask = AliasSeq!((1 << 7) - 1, (1 << 11) - 1, (1 << 16) - 1, (1 << 21) - 1);
233 
234         static if (is(S : const char[]))
235             auto pstr = str.ptr + index;    // this is what makes decodeImpl() @system code
236         else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
237             auto pstr = str[index .. str.length];
238         else
239             alias pstr = str;
240 
241         // https://issues.dlang.org/show_bug.cgi?id=14447 forces this to be done
242         // outside of decodeImpl
243         //enum canIndex = is(S : const char[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S);
244 
245         static if (canIndex){
246             immutable length = str.length - index;
247             ubyte fst = pstr[0];
248         }
249         else{
250             ubyte fst = pstr.front;
251             pstr.popFront();
252         }
253 
254 
255 
256         if ((fst & 0b1100_0000) != 0b1100_0000){
257             ++index;            // always consume bad input to avoid infinite loops
258             return replacementDchar;    //throw invalidUTF(); // starter must have at least 2 first bits set
259         }
260         ubyte tmp = void;
261         dchar d = fst; // upper control bits are masked out later
262         fst <<= 1;
263 
264         foreach (i; AliasSeq!(1, 2, 3)){
265 
266             static if (canIndex){
267                 if (i == length){
268                     index += i;
269                     return replacementDchar;    //outOfBounds
270                 }
271             }
272             else{
273                 if (pstr.empty){
274                     index += i;
275                     return replacementDchar;    //outOfBounds
276                 }
277             }
278 
279             static if (canIndex)
280                 tmp = pstr[i];
281             else{
282                 tmp = pstr.front;
283                 pstr.popFront();
284             }
285 
286             if ((tmp & 0xC0) != 0x80){
287                 index += i + 1;
288                 return replacementDchar;    //invalidUTF
289             }
290 
291             d = (d << 6) | (tmp & 0x3F);
292             fst <<= 1;
293 
294             if (!(fst & 0x80)){ // no more bytes
295                 d &= bitMask[i]; // mask out control bits
296 
297                 // overlong, could have been encoded with i bytes
298                 if ((d & ~bitMask[i - 1]) == 0){
299                     index += i + 1;
300                     return replacementDchar;    //invalidUTF
301                 }
302 
303                 // check for surrogates only needed for 3 bytes
304                 static if (i == 2){
305                     if (!isValidDchar(d)){
306                         index += i + 1;
307                         return replacementDchar;    //invalidUTF
308                     }
309                 }
310 
311                 index += i + 1;
312                 static if (i == 3){
313                     if (d > dchar.max){
314                         d = replacementDchar;   //invalidUTF
315                     }
316                 }
317 
318                 return d;
319             }
320         }
321 
322         index += 4;             // read 4 chars by now
323         return replacementDchar;    //invalidUTF
324     }
325 
326     private dchar decode_impl(bool canIndex, S)(auto ref S str, ref size_t index)
327     if (is(S : const wchar[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable wchar))){
328 
329         static if (is(S : const wchar[]))
330             auto pstr = str.ptr + index;
331         else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S)
332             auto pstr = str[index .. str.length];
333         else
334             alias pstr = str;
335 
336         // https://issues.dlang.org/show_bug.cgi?id=14447 forces this to be done
337         // outside of decodeImpl
338         //enum canIndex = is(S : const wchar[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S);
339 
340         static if (canIndex){
341             immutable length = str.length - index;
342             uint u = pstr[0];
343         }
344         else{
345             uint u = pstr.front;
346             pstr.popFront();
347         }
348 
349 
350         // The < case must be taken care of before decodeImpl is called.
351         assert(u >= 0xD800);
352 
353         if (u <= 0xDBFF){
354             static if (canIndex)
355                 immutable onlyOneCodeUnit = length == 1;
356             else
357                 immutable onlyOneCodeUnit = pstr.empty;
358 
359             if (onlyOneCodeUnit){
360                 ++index;
361                 return replacementDchar;    //throw exception("surrogate UTF-16 high value past end of string");
362             }
363 
364             static if (canIndex)
365                 immutable uint u2 = pstr[1];
366             else{
367                 immutable uint u2 = pstr.front;
368                 pstr.popFront();
369             }
370 
371             if (u2 < 0xDC00 || u2 > 0xDFFF)
372                 u = replacementDchar;   //throw exception("surrogate UTF-16 low value out of range");
373             else
374                 u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00);
375             ++index;
376         }
377         else if (u >= 0xDC00 && u <= 0xDFFF)
378             u = replacementDchar;   //throw exception("unpaired surrogate UTF-16 value");
379 
380         ++index;
381 
382         // Note: u+FFFE and u+FFFF are specifically permitted by the
383         // Unicode standard for application internal use (see isValidDchar)
384 
385         return cast(dchar) u;
386     }
387 
388     private dchar decode_impl(bool canIndex, S)(auto ref S str, ref size_t index)
389     if (is(S : const dchar[]) || (isInputRange!S && is(immutable ElementEncodingType!S == immutable dchar))){
390         static if (is(S : const dchar[]))
391             auto pstr = str.ptr;
392         else
393             alias pstr = str;
394 
395         static if (is(S : const dchar[]) || isRandomAccessRange!S){
396             dchar dc = pstr[index];
397             if (!isValidDchar(dc))
398                 dc = replacementDchar;  //throw new UTFException("Invalid UTF-32 value").setSequence(dc);
399 
400             ++index;
401             return dc;
402         }
403         else{
404             dchar dc = pstr.front;
405             if (!isValidDchar(dc))
406                 dc = replacementDchar;  //throw new UTFException("Invalid UTF-32 value").setSequence(dc);
407 
408             ++index;
409             pstr.popFront();
410             return dc;
411         }
412     }
413 
414 }
415 
416 
417 
418 public bool validate(S)(auto ref S str)pure nothrow @trusted @nogc{
419     import std.range : empty;
420 
421 
422     while(!str.empty){
423         const d = decode(str);
424         if(d == replacementDchar)
425             return false;
426     }
427 
428     return true;
429 }
430 
431 
432 
433 /*
434     Return number of valid code units at end of string str, 0 if last code point is invalid
435 */
436 public ubyte strideBack(scope const(char)[] str)pure nothrow @safe @nogc{
437     import std.range : empty;
438 
439     /* The following encodings are valid, except for the 5 and 6 byte
440      * combinations:
441      *  0xxxxxxx
442      *  110xxxxx 10xxxxxx
443      *  1110xxxx 10xxxxxx 10xxxxxx
444      *  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
445      *  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
446      *  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
447      */
448     assert(!str.empty);
449 
450     const size_t index = str.length;
451 
452     if((str[index - 1] & 0b1000_0000) == 0)
453         return 1;
454 
455     if((str[index - 1] & 0b1100_0000) != 0b1000_0000)
456         return 0;   //error
457 
458 
459     if(index >= 4){
460         static foreach (i; 2 .. 5){{
461             const ubyte c = str[index-i];
462             if ((c & 0b1100_0000) != 0b1000_0000){
463 
464                 static if(i == 2)
465                     return ((c & 0b1110_0000) == 0b1100_0000) ? i : 0;
466                 else static if(i == 3)
467                     return ((c & 0b1111_0000) == 0b1110_0000) ? i : 0;
468                 else static if(i == 4)
469                     return ((c & 0b1111_1000) == 0b1111_0000) ? i : 0;
470                 else return 0;
471             }
472         }}
473 
474         return 0;
475 
476     }
477     else{
478         static foreach (i; 2 .. 4){{
479             if(index < i)
480                 return 0;
481 
482             const ubyte c = str[index-i];
483             if ((c & 0b1100_0000) != 0b1000_0000){
484 
485                 static if(i == 2)
486                     return ((c & 0b1110_0000) == 0b1100_0000) ? i : 0;
487                 else static if(i == 3)
488                     return ((c & 0b1111_0000) == 0b1110_0000) ? i : 0;
489                 else static if(i == 4)
490                     return ((c & 0b1111_1000) == 0b1111_0000) ? i : 0;
491                 else return 0;
492             }
493         }}
494 
495         return 0;
496     }
497 }
498 
499 // ditto
500 public ubyte strideBack(scope const(wchar)[] str)pure nothrow @safe @nogc{
501     import std.range : empty;
502 
503     assert(!str.empty);
504 
505     const size_t index = str.length;
506 
507     const uint u2 = str[index-1];
508 
509     if(u2 < 0xD800)
510         return 0;
511 
512     if (u2 <= 0xDBFF)
513         return 0;   //u2 is first character of 2 character sequence
514 
515     //2 character sequence:
516     if(0xDC00 <= u2 && u2 < 0xE000){
517         if(str.length == 1)
518             return 0;   //error
519 
520         const uint u = str[index-2];
521         if (u <= 0xDBFF)
522             return 2;
523 
524         /+if(u < 0xD800)
525             return 0;   //error
526 
527         if (0xDC00 <= u && u < 0xE000)
528             return 0;   //unpaired surrogate UTF-16 value+/
529 
530         return 0;
531     }
532 
533     //1 character sequence:
534     return 1;
535 }
536 
537 // ditto
538 public ubyte strideBack(scope const(dchar)[] str)pure nothrow @safe @nogc{
539     import std.range : empty;
540     //import std.utf : isValidDchar;
541     assert(!str.empty);
542 
543     return isValidDchar(str[$-1]);
544 }