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 }