1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 //==============================================================================
27 #if JUCE_WINDOWS && ! DOXYGEN
28  #define JUCE_NATIVE_WCHAR_IS_UTF8      0
29  #define JUCE_NATIVE_WCHAR_IS_UTF16     1
30  #define JUCE_NATIVE_WCHAR_IS_UTF32     0
31 #else
32  /** This macro will be set to 1 if the compiler's native wchar_t is an 8-bit type. */
33  #define JUCE_NATIVE_WCHAR_IS_UTF8      0
34  /** This macro will be set to 1 if the compiler's native wchar_t is a 16-bit type. */
35  #define JUCE_NATIVE_WCHAR_IS_UTF16     0
36  /** This macro will be set to 1 if the compiler's native wchar_t is a 32-bit type. */
37  #define JUCE_NATIVE_WCHAR_IS_UTF32     1
38 #endif
39 
40 #if JUCE_NATIVE_WCHAR_IS_UTF32 || DOXYGEN
41  /** A platform-independent 32-bit unicode character type. */
42  using juce_wchar = wchar_t;
43 #else
44  using juce_wchar = uint32;
45 #endif
46 
47 #ifndef DOXYGEN
48  /** This macro is deprecated, but preserved for compatibility with old code. */
49  #define JUCE_T(stringLiteral)   (L##stringLiteral)
50 #endif
51 
52 #if JUCE_DEFINE_T_MACRO
53  /** The 'T' macro is an alternative for using the "L" prefix in front of a string literal.
54 
55      This macro is deprecated, but available for compatibility with old code if you set
56      JUCE_DEFINE_T_MACRO = 1. The fastest, most portable and best way to write your string
57      literals is as standard char strings, using escaped utf-8 character sequences for extended
58      characters, rather than trying to store them as wide-char strings.
59  */
60  #define T(stringLiteral)   JUCE_T(stringLiteral)
61 #endif
62 
63 #if ! DOXYGEN
64 
65 //==============================================================================
66 // GNU libstdc++ does not have std::make_unsigned
67 namespace internal
68 {
69     template <typename Type> struct make_unsigned               { using type = Type; };
70     template <> struct make_unsigned<signed char>               { using type = unsigned char; };
71     template <> struct make_unsigned<char>                      { using type = unsigned char; };
72     template <> struct make_unsigned<short>                     { using type = unsigned short; };
73     template <> struct make_unsigned<int>                       { using type = unsigned int; };
74     template <> struct make_unsigned<long>                      { using type = unsigned long; };
75     template <> struct make_unsigned<long long>                 { using type = unsigned long long; };
76 }
77 
78 #endif
79 
80 //==============================================================================
81 /**
82     A collection of functions for manipulating characters and character strings.
83 
84     Most of these methods are designed for internal use by the String and CharPointer
85     classes, but some of them may be useful to call directly.
86 
87     @see String, CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32
88 
89     @tags{Core}
90 */
91 class JUCE_API  CharacterFunctions
92 {
93 public:
94     //==============================================================================
95     /** Converts a character to upper-case. */
96     static juce_wchar toUpperCase (juce_wchar character) noexcept;
97     /** Converts a character to lower-case. */
98     static juce_wchar toLowerCase (juce_wchar character) noexcept;
99 
100     /** Checks whether a unicode character is upper-case. */
101     static bool isUpperCase (juce_wchar character) noexcept;
102     /** Checks whether a unicode character is lower-case. */
103     static bool isLowerCase (juce_wchar character) noexcept;
104 
105     /** Checks whether a character is whitespace. */
106     static bool isWhitespace (char character) noexcept;
107     /** Checks whether a character is whitespace. */
108     static bool isWhitespace (juce_wchar character) noexcept;
109 
110     /** Checks whether a character is a digit. */
111     static bool isDigit (char character) noexcept;
112     /** Checks whether a character is a digit. */
113     static bool isDigit (juce_wchar character) noexcept;
114 
115     /** Checks whether a character is alphabetic. */
116     static bool isLetter (char character) noexcept;
117     /** Checks whether a character is alphabetic. */
118     static bool isLetter (juce_wchar character) noexcept;
119 
120     /** Checks whether a character is alphabetic or numeric. */
121     static bool isLetterOrDigit (char character) noexcept;
122     /** Checks whether a character is alphabetic or numeric. */
123     static bool isLetterOrDigit (juce_wchar character) noexcept;
124 
125     /** Checks whether a character is a printable character, i.e. alphabetic, numeric,
126         a punctuation character or a space.
127     */
128     static bool isPrintable (char character) noexcept;
129 
130     /** Checks whether a character is a printable character, i.e. alphabetic, numeric,
131         a punctuation character or a space.
132     */
133     static bool isPrintable (juce_wchar character) noexcept;
134 
135     /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */
136     static int getHexDigitValue (juce_wchar digit) noexcept;
137 
138     /** Converts a byte of Windows 1252 codepage to unicode. */
139     static juce_wchar getUnicodeCharFromWindows1252Codepage (uint8 windows1252Char) noexcept;
140 
141     //==============================================================================
142     /** Parses a character string to read a floating-point number.
143         Note that this will advance the pointer that is passed in, leaving it at
144         the end of the number.
145     */
146     template <typename CharPointerType>
147     static double readDoubleValue (CharPointerType& text) noexcept
148     {
149        #if JUCE_MINGW
150         bool isNegative = false;
151        #else
152         constexpr const int maxSignificantDigits = 17 + 1; // An additional digit for rounding
153         constexpr const int bufferSize = maxSignificantDigits + 7 + 1; // -.E-XXX and a trailing null-terminator
154         char buffer[(size_t) bufferSize] = {};
155         char* currentCharacter = &(buffer[0]);
156        #endif
157 
158         text = text.findEndOfWhitespace();
159         auto c = *text;
160 
161         switch (c)
162         {
163             case '-':
164                #if JUCE_MINGW
165                 isNegative = true;
166                #else
167                 *currentCharacter++ = '-';
168                #endif
169                 JUCE_FALLTHROUGH
170             case '+':
171                 c = *++text;
172                 break;
173             default:
174                 break;
175         }
176 
177         switch (c)
178         {
179             case 'n':
180             case 'N':
181                 if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N'))
182                     return std::numeric_limits<double>::quiet_NaN();
183                 break;
184 
185             case 'i':
186             case 'I':
187                 if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F'))
188                     return std::numeric_limits<double>::infinity();
189                 break;
190 
191             default:
192                 break;
193         }
194 
195        #if JUCE_MINGW
196         // MinGW does not have access to the locale functions required for strtold, so we parse the doubles
197         // ourselves. There are some edge cases where the least significant digit will be wrong!
198         double result[3] = { 0 }, accumulator[2] = { 0 };
199         int exponentAdjustment[2] = { 0 }, exponentAccumulator[2] = { -1, -1 };
200         int exponent = 0, decPointIndex = 0, digit = 0;
201         int lastDigit = 0, numSignificantDigits = 0;
202         bool digitsFound = false;
203         constexpr const int maxSignificantDigits = 17 + 1;
204 
205         for (;;)
206         {
207             if (text.isDigit())
208             {
209                 lastDigit = digit;
210                 digit = (int) text.getAndAdvance() - '0';
211                 digitsFound = true;
212 
213                 if (decPointIndex != 0)
214                     exponentAdjustment[1]++;
215 
216                 if (numSignificantDigits == 0 && digit == 0)
217                     continue;
218 
219                 if (++numSignificantDigits > maxSignificantDigits)
220                 {
221                     if (digit > 5)
222                         ++accumulator [decPointIndex];
223                     else if (digit == 5 && (lastDigit & 1) != 0)
224                         ++accumulator [decPointIndex];
225 
226                     if (decPointIndex > 0)
227                         exponentAdjustment[1]--;
228                     else
229                         exponentAdjustment[0]++;
230 
231                     while (text.isDigit())
232                     {
233                         ++text;
234                         if (decPointIndex == 0)
235                             exponentAdjustment[0]++;
236                     }
237                 }
238                 else
239                 {
240                     const auto maxAccumulatorValue = (double) ((std::numeric_limits<unsigned int>::max() - 9) / 10);
241                     if (accumulator [decPointIndex] > maxAccumulatorValue)
242                     {
243                         result [decPointIndex] = mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex])
244                                                  + accumulator [decPointIndex];
245                         accumulator [decPointIndex] = 0;
246                         exponentAccumulator [decPointIndex] = 0;
247                     }
248 
249                     accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit;
250                     exponentAccumulator [decPointIndex]++;
251                 }
252             }
253             else if (decPointIndex == 0 && *text == '.')
254             {
255                 ++text;
256                 decPointIndex = 1;
257 
258                 if (numSignificantDigits > maxSignificantDigits)
259                 {
260                     while (text.isDigit())
261                         ++text;
262                     break;
263                 }
264             }
265             else
266             {
267                 break;
268             }
269         }
270 
271         result[0] = mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0];
272 
273         if (decPointIndex != 0)
274             result[1] = mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1];
275 
276         c = *text;
277         if ((c == 'e' || c == 'E') && digitsFound)
278         {
279             auto negativeExponent = false;
280 
281             switch (*++text)
282             {
283                 case '-':   negativeExponent = true; JUCE_FALLTHROUGH
284                 case '+':   ++text;
285             }
286 
287             while (text.isDigit())
288                 exponent = (exponent * 10) + ((int) text.getAndAdvance() - '0');
289 
290             if (negativeExponent)
291                 exponent = -exponent;
292         }
293 
294         auto r = mulexp10 (result[0], exponent + exponentAdjustment[0]);
295         if (decPointIndex != 0)
296             r += mulexp10 (result[1], exponent - exponentAdjustment[1]);
297 
298         return isNegative ? -r : r;
299 
300        #else   // ! JUCE_MINGW
301 
302         int numSigFigs = 0;
303         bool decimalPointFound = false;
304         int extraExponent = 0;
305 
306         for (;;)
307         {
308             if (text.isDigit())
309             {
310                 auto digit = (int) text.getAndAdvance() - '0';
311 
312                 if (decimalPointFound)
313                 {
314                     if (numSigFigs >= maxSignificantDigits)
315                         continue;
316                 }
317                 else
318                 {
319                     if (numSigFigs >= maxSignificantDigits)
320                     {
321                         ++extraExponent;
322                         continue;
323                     }
324 
325                     if (numSigFigs == 0 && digit == 0)
326                         continue;
327                 }
328 
329                 *currentCharacter++ = (char) ('0' + (char) digit);
330                 numSigFigs++;
331             }
332             else if ((! decimalPointFound) && *text == '.')
333             {
334                 ++text;
335                 *currentCharacter++ = '.';
336                 decimalPointFound = true;
337             }
338             else
339             {
340                 break;
341             }
342         }
343 
344         c = *text;
345 
346         auto writeExponentDigits = [] (int exponent, char* destination)
347         {
348             auto exponentDivisor = 100;
349 
350             while (exponentDivisor > 1)
351             {
352                 auto digit = exponent / exponentDivisor;
353                 *destination++ = (char) ('0' + (char) digit);
354                 exponent -= digit * exponentDivisor;
355                 exponentDivisor /= 10;
356             }
357 
358             *destination++ = (char) ('0' + (char) exponent);
359         };
360 
361         if ((c == 'e' || c == 'E') && numSigFigs > 0)
362         {
363             *currentCharacter++ = 'e';
364             bool parsedExponentIsPositive = true;
365 
366             switch (*++text)
367             {
368                 case '-':  parsedExponentIsPositive = false; JUCE_FALLTHROUGH
369                 case '+':  ++text; break;
370                 default:   break;
371             }
372 
373             int exponent = 0;
374 
375             while (text.isDigit())
376             {
377                 auto digit = (int) text.getAndAdvance() - '0';
378 
379                 if (digit != 0 || exponent != 0)
380                     exponent = (exponent * 10) + digit;
381             }
382 
383             exponent = extraExponent + (parsedExponentIsPositive ? exponent : -exponent);
384 
385             if (exponent < 0)
386                 *currentCharacter++ = '-';
387 
388             exponent = std::abs (exponent);
389 
390             if (exponent > std::numeric_limits<double>::max_exponent10)
391                 return std::numeric_limits<double>::quiet_NaN();
392 
393             writeExponentDigits (exponent, currentCharacter);
394         }
395         else if (extraExponent > 0)
396         {
397             *currentCharacter++ = 'e';
398             writeExponentDigits (extraExponent, currentCharacter);
399         }
400 
401        #if JUCE_WINDOWS
402         static _locale_t locale = _create_locale (LC_ALL, "C");
403         return _strtod_l (&buffer[0], nullptr, locale);
404        #else
405         static locale_t locale = newlocale (LC_ALL_MASK, "C", nullptr);
406         #if JUCE_ANDROID
407         return (double) strtold_l (&buffer[0], nullptr, locale);
408         #else
409         return strtod_l (&buffer[0], nullptr, locale);
410         #endif
411        #endif
412 
413        #endif   // JUCE_MINGW
414     }
415 
416     /** Parses a character string, to read a floating-point value. */
417     template <typename CharPointerType>
418     static double getDoubleValue (CharPointerType text) noexcept
419     {
420         return readDoubleValue (text);
421     }
422 
423     //==============================================================================
424     /** Parses a character string, to read an integer value. */
425     template <typename IntType, typename CharPointerType>
426     static IntType getIntValue (const CharPointerType text) noexcept
427     {
428         using UIntType = typename internal::make_unsigned<IntType>::type;
429 
430         UIntType v = 0;
431         auto s = text.findEndOfWhitespace();
432         const bool isNeg = *s == '-';
433 
434         if (isNeg)
435             ++s;
436 
437         for (;;)
438         {
439             auto c = s.getAndAdvance();
440 
441             if (c >= '0' && c <= '9')
442                 v = v * 10 + (UIntType) (c - '0');
443             else
444                 break;
445         }
446 
447         return isNeg ? - (IntType) v : (IntType) v;
448     }
449 
450     /** Parses a character string, to read a hexadecimal value. */
451     template <typename ResultType>
452     struct HexParser
453     {
454         template <typename CharPointerType>
455         static ResultType parse (CharPointerType t) noexcept
456         {
457             ResultType result = 0;
458 
459             while (! t.isEmpty())
460             {
461                 auto hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance());
462 
463                 if (hexValue >= 0)
464                     result = (result << 4) | hexValue;
465             }
466 
467             return result;
468         }
469     };
470 
471     //==============================================================================
472     /** Counts the number of characters in a given string, stopping if the count exceeds
473         a specified limit. */
474     template <typename CharPointerType>
475     static size_t lengthUpTo (CharPointerType text, const size_t maxCharsToCount) noexcept
476     {
477         size_t len = 0;
478 
479         while (len < maxCharsToCount && text.getAndAdvance() != 0)
480             ++len;
481 
482         return len;
483     }
484 
485     /** Counts the number of characters in a given string, stopping if the count exceeds
486         a specified end-pointer. */
487     template <typename CharPointerType>
488     static size_t lengthUpTo (CharPointerType start, const CharPointerType end) noexcept
489     {
490         size_t len = 0;
491 
492         while (start < end && start.getAndAdvance() != 0)
493             ++len;
494 
495         return len;
496     }
497 
498     /** Copies null-terminated characters from one string to another. */
499     template <typename DestCharPointerType, typename SrcCharPointerType>
500     static void copyAll (DestCharPointerType& dest, SrcCharPointerType src) noexcept
501     {
502         while (auto c = src.getAndAdvance())
503             dest.write (c);
504 
505         dest.writeNull();
506     }
507 
508     /** Copies characters from one string to another, up to a null terminator
509         or a given byte size limit. */
510     template <typename DestCharPointerType, typename SrcCharPointerType>
511     static size_t copyWithDestByteLimit (DestCharPointerType& dest, SrcCharPointerType src, size_t maxBytesToWrite) noexcept
512     {
513         auto startAddress = dest.getAddress();
514         auto maxBytes = (ssize_t) maxBytesToWrite;
515         maxBytes -= (ssize_t) sizeof (typename DestCharPointerType::CharType); // (allow for a terminating null)
516 
517         for (;;)
518         {
519             auto c = src.getAndAdvance();
520             auto bytesNeeded = (ssize_t) DestCharPointerType::getBytesRequiredFor (c);
521             maxBytes -= bytesNeeded;
522 
523             if (c == 0 || maxBytes < 0)
524                 break;
525 
526             dest.write (c);
527         }
528 
529         dest.writeNull();
530 
531         return (size_t) getAddressDifference (dest.getAddress(), startAddress)
532                  + sizeof (typename DestCharPointerType::CharType);
533     }
534 
535     /** Copies characters from one string to another, up to a null terminator
536         or a given maximum number of characters. */
537     template <typename DestCharPointerType, typename SrcCharPointerType>
538     static void copyWithCharLimit (DestCharPointerType& dest, SrcCharPointerType src, int maxChars) noexcept
539     {
540         while (--maxChars > 0)
541         {
542             auto c = src.getAndAdvance();
543 
544             if (c == 0)
545                 break;
546 
547             dest.write (c);
548         }
549 
550         dest.writeNull();
551     }
552 
553     /** Compares two characters. */
554     static int compare (juce_wchar char1, juce_wchar char2) noexcept
555     {
556         if (auto diff = static_cast<int> (char1) - static_cast<int> (char2))
557             return diff < 0 ? -1 : 1;
558 
559         return 0;
560     }
561 
562     /** Compares two null-terminated character strings. */
563     template <typename CharPointerType1, typename CharPointerType2>
564     static int compare (CharPointerType1 s1, CharPointerType2 s2) noexcept
565     {
566         for (;;)
567         {
568             auto c1 = s1.getAndAdvance();
569 
570             if (auto diff = compare (c1, s2.getAndAdvance()))
571                 return diff;
572 
573             if (c1 == 0)
574                 break;
575         }
576 
577         return 0;
578     }
579 
580     /** Compares two null-terminated character strings, up to a given number of characters. */
581     template <typename CharPointerType1, typename CharPointerType2>
582     static int compareUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept
583     {
584         while (--maxChars >= 0)
585         {
586             auto c1 = s1.getAndAdvance();
587 
588             if (auto diff = compare (c1, s2.getAndAdvance()))
589                 return diff;
590 
591             if (c1 == 0)
592                 break;
593         }
594 
595         return 0;
596     }
597 
598     /** Compares two characters, using a case-independant match. */
599     static int compareIgnoreCase (juce_wchar char1, juce_wchar char2) noexcept
600     {
601         return char1 != char2 ? compare (toUpperCase (char1), toUpperCase (char2)) : 0;
602     }
603 
604     /** Compares two null-terminated character strings, using a case-independant match. */
605     template <typename CharPointerType1, typename CharPointerType2>
606     static int compareIgnoreCase (CharPointerType1 s1, CharPointerType2 s2) noexcept
607     {
608         for (;;)
609         {
610             auto c1 = s1.getAndAdvance();
611 
612             if (auto diff = compareIgnoreCase (c1, s2.getAndAdvance()))
613                 return diff;
614 
615             if (c1 == 0)
616                 break;
617         }
618 
619         return 0;
620     }
621 
622     /** Compares two null-terminated character strings, using a case-independent match. */
623     template <typename CharPointerType1, typename CharPointerType2>
624     static int compareIgnoreCaseUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept
625     {
626         while (--maxChars >= 0)
627         {
628             auto c1 = s1.getAndAdvance();
629 
630             if (auto diff = compareIgnoreCase (c1, s2.getAndAdvance()))
631                 return diff;
632 
633             if (c1 == 0)
634                 break;
635         }
636 
637         return 0;
638     }
639 
640     /** Finds the character index of a given substring in another string.
641         Returns -1 if the substring is not found.
642     */
643     template <typename CharPointerType1, typename CharPointerType2>
644     static int indexOf (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept
645     {
646         int index = 0;
647         auto substringLength = (int) substringToLookFor.length();
648 
649         for (;;)
650         {
651             if (textToSearch.compareUpTo (substringToLookFor, substringLength) == 0)
652                 return index;
653 
654             if (textToSearch.getAndAdvance() == 0)
655                 return -1;
656 
657             ++index;
658         }
659     }
660 
661     /** Returns a pointer to the first occurrence of a substring in a string.
662         If the substring is not found, this will return a pointer to the string's
663         null terminator.
664     */
665     template <typename CharPointerType1, typename CharPointerType2>
666     static CharPointerType1 find (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept
667     {
668         auto substringLength = (int) substringToLookFor.length();
669 
670         while (textToSearch.compareUpTo (substringToLookFor, substringLength) != 0
671                  && ! textToSearch.isEmpty())
672             ++textToSearch;
673 
674         return textToSearch;
675     }
676 
677     /** Returns a pointer to the first occurrence of a substring in a string.
678         If the substring is not found, this will return a pointer to the string's
679         null terminator.
680     */
681     template <typename CharPointerType>
682     static CharPointerType find (CharPointerType textToSearch, const juce_wchar charToLookFor) noexcept
683     {
684         for (;; ++textToSearch)
685         {
686             auto c = *textToSearch;
687 
688             if (c == charToLookFor || c == 0)
689                 break;
690         }
691 
692         return textToSearch;
693     }
694 
695     /** Finds the character index of a given substring in another string, using
696         a case-independent match.
697         Returns -1 if the substring is not found.
698     */
699     template <typename CharPointerType1, typename CharPointerType2>
700     static int indexOfIgnoreCase (CharPointerType1 haystack, const CharPointerType2 needle) noexcept
701     {
702         int index = 0;
703         auto needleLength = (int) needle.length();
704 
705         for (;;)
706         {
707             if (haystack.compareIgnoreCaseUpTo (needle, needleLength) == 0)
708                 return index;
709 
710             if (haystack.getAndAdvance() == 0)
711                 return -1;
712 
713             ++index;
714         }
715     }
716 
717     /** Finds the character index of a given character in another string.
718         Returns -1 if the character is not found.
719     */
720     template <typename Type>
721     static int indexOfChar (Type text, const juce_wchar charToFind) noexcept
722     {
723         int i = 0;
724 
725         while (! text.isEmpty())
726         {
727             if (text.getAndAdvance() == charToFind)
728                 return i;
729 
730             ++i;
731         }
732 
733         return -1;
734     }
735 
736     /** Finds the character index of a given character in another string, using
737         a case-independent match.
738         Returns -1 if the character is not found.
739     */
740     template <typename Type>
741     static int indexOfCharIgnoreCase (Type text, juce_wchar charToFind) noexcept
742     {
743         charToFind = CharacterFunctions::toLowerCase (charToFind);
744         int i = 0;
745 
746         while (! text.isEmpty())
747         {
748             if (text.toLowerCase() == charToFind)
749                 return i;
750 
751             ++text;
752             ++i;
753         }
754 
755         return -1;
756     }
757 
758     /** Returns a pointer to the first non-whitespace character in a string.
759         If the string contains only whitespace, this will return a pointer
760         to its null terminator.
761     */
762     template <typename Type>
763     static Type findEndOfWhitespace (Type text) noexcept
764     {
765         while (text.isWhitespace())
766             ++text;
767 
768         return text;
769     }
770 
771     /** Returns a pointer to the first character in the string which is found in
772         the breakCharacters string.
773     */
774     template <typename Type, typename BreakType>
775     static Type findEndOfToken (Type text, BreakType breakCharacters, Type quoteCharacters)
776     {
777         juce_wchar currentQuoteChar = 0;
778 
779         while (! text.isEmpty())
780         {
781             auto c = text.getAndAdvance();
782 
783             if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0)
784             {
785                 --text;
786                 break;
787             }
788 
789             if (quoteCharacters.indexOf (c) >= 0)
790             {
791                 if (currentQuoteChar == 0)
792                     currentQuoteChar = c;
793                 else if (currentQuoteChar == c)
794                     currentQuoteChar = 0;
795             }
796         }
797 
798         return text;
799     }
800 
801 private:
802     static double mulexp10 (double value, int exponent) noexcept;
803 };
804 
805 } // namespace juce
806