yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Yong HeMake interface types non c-style in Slang2026. (#7260)812e47898

master
31.5 KiB1218 linesraw
1#include "slang-string-escape-util.h"
2
3#include "slang-char-util.h"
4#include "slang-com-helper.h"
5#include "slang-memory-arena.h"
6#include "slang-text-io.h"
7
8namespace Slang
9{
10
11// !!!!!!!!!!!!!!!!!!!!!!!!!! SpaceStringEscapeHandler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12
13class SpaceStringEscapeHandler : public StringEscapeHandler
14{
15public:
16    typedef StringEscapeHandler Super;
17
18    virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE
19    {
20        return isEscapingNeeded(slice);
21    }
22
23    virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
24    virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
25
26    virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out)
27        SLANG_OVERRIDE;
28    virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out)
29        SLANG_OVERRIDE;
30    virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE;
31
32    SpaceStringEscapeHandler()
33        : Super('"')
34    {
35    }
36};
37
38bool SpaceStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice)
39{
40    return slice.indexOf(' ') >= 0;
41}
42
43bool SpaceStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice)
44{
45    SLANG_UNUSED(slice);
46    // As it stands we never have to unescape
47    return false;
48}
49
50SlangResult SpaceStringEscapeHandler::appendUnescaped(
51    const UnownedStringSlice& slice,
52    StringBuilder& out)
53{
54    if (slice.indexOf('"') >= 0)
55    {
56        return SLANG_FAIL;
57    }
58
59    out.append(slice);
60    return SLANG_OK;
61}
62
63SlangResult SpaceStringEscapeHandler::appendEscaped(
64    const UnownedStringSlice& slice,
65    StringBuilder& out)
66{
67    if (slice.indexOf('"') >= 0)
68    {
69        return SLANG_FAIL;
70    }
71    out.append(slice);
72    return SLANG_OK;
73}
74
75/* static */ SlangResult SpaceStringEscapeHandler::lexQuoted(
76    const char* cursor,
77    const char** outCursor)
78{
79    *outCursor = cursor;
80
81    if (*cursor != m_quoteChar)
82    {
83        return SLANG_FAIL;
84    }
85    cursor++;
86
87    for (;;)
88    {
89        const char c = *cursor;
90        if (c == m_quoteChar)
91        {
92            *outCursor = cursor + 1;
93            return SLANG_OK;
94        }
95        switch (c)
96        {
97        case 0:
98        case '\n':
99        case '\r':
100            {
101                // Didn't hit closing quote!
102                return SLANG_FAIL;
103            }
104        default:
105            {
106                ++cursor;
107                break;
108            }
109        }
110    }
111}
112
113
114// !!!!!!!!!!!!!!!!!!!!!!!!!! CppStringEscapeHandler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
115
116class CppStringEscapeHandler : public StringEscapeHandler
117{
118public:
119    typedef StringEscapeHandler Super;
120
121    virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE
122    {
123        SLANG_UNUSED(slice);
124        return true;
125    }
126    virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
127    virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
128    virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out)
129        SLANG_OVERRIDE;
130    virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out)
131        SLANG_OVERRIDE;
132    virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE;
133
134    CppStringEscapeHandler()
135        : Super('"')
136    {
137    }
138};
139
140static char _getCppEscapedChar(char c)
141{
142    switch (c)
143    {
144    case '\b':
145        return 'b';
146    case '\f':
147        return 'f';
148    case '\n':
149        return 'n';
150    case '\r':
151        return 'r';
152    case '\a':
153        return 'a';
154    case '\t':
155        return 't';
156    case '\v':
157        return 'v';
158    case '\'':
159        return '\'';
160    case '\"':
161        return '"';
162    case '\\':
163        return '\\';
164    default:
165        return 0;
166    }
167}
168
169static char _getCppUnescapedChar(char c)
170{
171    switch (c)
172    {
173    case 'b':
174        return '\b';
175    case 'f':
176        return '\f';
177    case 'n':
178        return '\n';
179    case 'r':
180        return '\r';
181    case 'a':
182        return '\a';
183    case 't':
184        return '\t';
185    case 'v':
186        return '\v';
187    case '\'':
188        return '\'';
189    case '\"':
190        return '"';
191    case '\\':
192        return '\\';
193    default:
194        return 0;
195    }
196}
197
198bool CppStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice)
199{
200    return slice.indexOf('\\') >= 0;
201}
202
203/* static */ bool CppStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice)
204{
205    const char* cur = slice.begin();
206    const char* const end = slice.end();
207
208    for (; cur < end; ++cur)
209    {
210        const char c = *cur;
211
212        switch (c)
213        {
214        case '\'':
215        case '\"':
216        case '\\':
217            {
218                // Strictly speaking ' shouldn't need a quote if in a C style string.
219                return true;
220            }
221        default:
222            {
223                if (c < ' ' || c >= 0x7e)
224                {
225                    return true;
226                }
227                break;
228            }
229        }
230    }
231    return false;
232}
233
234SlangResult CppStringEscapeHandler::appendEscaped(
235    const UnownedStringSlice& slice,
236    StringBuilder& out)
237{
238    const char* start = slice.begin();
239    const char* cur = start;
240    const char* const end = slice.end();
241
242    // TODO(JS): A cleverer implementation might support U and u prefixing for unicode characters.
243    // For now we just stick with hex if it's not 'regular' ascii.
244
245    for (; cur < end; ++cur)
246    {
247        const char c = *cur;
248        const char escapedChar = _getCppEscapedChar(c);
249
250        if (escapedChar)
251        {
252            // Flush
253            if (start < cur)
254            {
255                out.append(start, cur);
256            }
257
258            out.appendChar('\\');
259            out.appendChar(escapedChar);
260
261            start = cur + 1;
262        }
263        else if (c < ' ' || c > 126)
264        {
265            // Flush
266            if (start < cur)
267            {
268                out.append(start, cur);
269            }
270
271            // NOTE! There is a possible flaw around checking 'next' character (used for outputting
272            // oct and hex) If a string is constructed appended in parts, the next character is not
273            // available so the problem below can still occur.
274
275            // Another solution to this problem would be to output "", but that makes some other
276            // assumptions For example Slang doesn't support that style.
277
278            // C++ greedily consumes hex/octal digits. This is a problem if we have bytes
279            // 0, '1' as by default this will output as
280            // "\x001" which is the single character byte 1.
281
282            // Note this claims \x is followed with up to 3 hex digits
283            // https://msdn.microsoft.com/en-us/library/69ze775t.aspx
284            // But the following claims otherwise
285            // https://en.cppreference.com/w/cpp/language/string_literal
286
287            // On testing in Visual Studio hex can indeed be more than 3 digits
288
289            // There is a problem outputting values in hex, because C++ allows *any* amount of hex
290            // digits. We could work around with \u \U but they are later extensions (C++11) and
291            // have other issue
292
293            // The solution taken here is to always output as octal, because octal can be at most 3
294            // digits.
295
296            // Special case handling of 0
297            if (c == 0 && !(cur + 1 < end && CharUtil::isOctalDigit(cur[1])))
298            {
299                // We can just output as (octal) "\0"
300                out.append("\\0");
301            }
302            else
303            {
304                // A slightly more sophisticated implementation could output less digits if needed,
305                // if not followed by an octal digit, but for now we go simple and output all 3
306                // digits
307
308                const uint32_t v = uint32_t(c);
309
310                char buf[4];
311                buf[0] = '\\';
312                buf[1] = ((v >> 6) & 3) + '0';
313                buf[2] = ((v >> 3) & 7) + '0';
314                buf[3] = ((v >> 0) & 7) + '0';
315
316                out.append(buf, buf + 4);
317            }
318
319            start = cur + 1;
320        }
321    }
322
323    // Flush anything remaining
324    if (start < end)
325    {
326        out.append(start, end);
327    }
328    return SLANG_OK;
329}
330
331SlangResult CppStringEscapeHandler::appendUnescaped(
332    const UnownedStringSlice& slice,
333    StringBuilder& out)
334{
335    const char* start = slice.begin();
336    const char* cur = start;
337    const char* const end = slice.end();
338
339    while (cur < end)
340    {
341        const char c = *cur;
342
343        if (c == '\\')
344        {
345            // Flush
346            if (start < cur)
347            {
348                out.append(start, cur);
349            }
350
351            /// Next
352            cur++;
353
354            if (cur >= end)
355            {
356                // Missing character following '\'
357                return SLANG_FAIL;
358            }
359
360            const char nextC = *cur++;
361
362            // Need to handle various escape sequence cases
363            switch (nextC)
364            {
365            case '\'':
366            case '\"':
367            case '\\':
368            case '?':
369            case 'a':
370            case 'b':
371            case 'f':
372            case 'n':
373            case 'r':
374            case 't':
375            case 'v':
376                {
377                    const char unescapedChar = _getCppUnescapedChar(nextC);
378                    if (unescapedChar == 0)
379                    {
380                        // Don't know how to unescape that char
381                        return SLANG_FAIL;
382                    }
383                    out.appendChar(unescapedChar);
384
385                    start = cur;
386                    break;
387                }
388            case '0':
389            case '1':
390            case '2':
391            case '3':
392            case '4':
393            case '5':
394            case '6':
395            case '7':
396                {
397                    // Rewind back a character, as first digit is the 'nextC'
398                    --cur;
399
400                    // Don't need to check for enough characters, because there must be 1 - the
401                    // nextC
402
403                    // octal escape: up to 3 characters
404                    int value = 0;
405
406                    const char* octEnd = cur + 3;
407                    octEnd = (octEnd > end) ? end : octEnd;
408
409                    for (; cur < octEnd; ++cur)
410                    {
411                        const int digitValue = CharUtil::getOctalDigitValue(*cur);
412                        if (digitValue < 0)
413                        {
414                            break;
415                        }
416                        value = (value << 3) | digitValue;
417                    }
418                    out.appendChar(char(value));
419
420                    // Reset start
421                    start = cur;
422                    break;
423                }
424            case 'x':
425                {
426                    /// In the C++ standard we consume hex digits until we hit a non hex digit
427                    uint32_t value = 0;
428                    for (; cur < end && CharUtil::isHexDigit(*cur); ++cur)
429                    {
430                        const int digitValue = CharUtil::getHexDigitValue(*cur);
431                        if (digitValue < 0)
432                        {
433                            return SLANG_FAIL;
434                        }
435
436                        value = (value << 4) | digitValue;
437                    }
438
439                    // If it's ascii, just output it
440                    if (value < 0x80)
441                    {
442                        out.appendChar(char(value));
443                    }
444                    else
445                    {
446                        // It's arguable what is appropriate. We only decode/encode 4, which the
447                        // current spec has, but 6 are possible, so lets go large.
448                        const Index maxUtf8EncodeCount = 6;
449
450                        char* chars = out.prepareForAppend(maxUtf8EncodeCount);
451                        int numChars = encodeUnicodePointToUTF8(Char32(value), chars);
452                        out.appendInPlace(chars, numChars);
453                    }
454
455                    // Reset start
456                    start = cur;
457                    break;
458                }
459            case 'u':
460            case 'U':
461                {
462                    // u implies 4 hex digits
463                    // U implies 6.
464
465                    // Work out how many digits we need
466                    const Count digitCount = (nextC == 'u') ? 4 : 6;
467
468                    // Do we have enough?
469                    if (end - cur < digitCount)
470                    {
471                        return SLANG_FAIL;
472                    }
473
474                    uint32_t value = 0;
475                    for (Index i = 0; i < digitCount; ++i)
476                    {
477                        const int digitValue = CharUtil::getHexDigitValue(cur[i]);
478                        if (digitValue < 0)
479                        {
480                            return SLANG_FAIL;
481                        }
482                        value = (value << 4) | digitValue;
483                    }
484                    cur += digitCount;
485
486                    // Encode to Utf8
487                    // If it's ascii, just output it
488                    if (value < 0x80)
489                    {
490                        out.appendChar(char(value));
491                    }
492                    else
493                    {
494                        // It's arguable what is appropriate. We only decode/encode 4, which the
495                        // current spec has, but 6 are possible, so lets go large.
496                        const Index maxUtf8EncodeCount = 6;
497
498                        char* chars = out.prepareForAppend(maxUtf8EncodeCount);
499                        int numChars = encodeUnicodePointToUTF8(Char32(value), chars);
500                        out.appendInPlace(chars, numChars);
501                    }
502
503                    // Reset start
504                    start = cur;
505                    break;
506                }
507            default:
508                {
509                    return SLANG_FAIL;
510                }
511            }
512        }
513        else
514        {
515            // Next char
516            ++cur;
517        }
518    }
519
520    if (start < end)
521    {
522        out.append(start, end);
523    }
524
525    return SLANG_OK;
526}
527
528SlangResult CppStringEscapeHandler::lexQuoted(const char* cursor, const char** outCursor)
529{
530    *outCursor = cursor;
531
532    if (*cursor != m_quoteChar)
533    {
534        return SLANG_FAIL;
535    }
536    cursor++;
537
538    for (;;)
539    {
540        const char c = *cursor;
541        if (c == m_quoteChar)
542        {
543            *outCursor = cursor + 1;
544            return SLANG_OK;
545        }
546        switch (c)
547        {
548        case 0:
549        case '\n':
550        case '\r':
551            {
552                // Didn't hit closing quote!
553                return SLANG_FAIL;
554            }
555        case '\\':
556            {
557                ++cursor;
558                // Need to handle various escape sequence cases
559                switch (*cursor)
560                {
561                case '\'':
562                case '\"':
563                case '\\':
564                case '?':
565                case 'a':
566                case 'b':
567                case 'f':
568                case 'n':
569                case 'r':
570                case 't':
571                case 'v':
572                    {
573                        ++cursor;
574                        break;
575                    }
576                case '0':
577                case '1':
578                case '2':
579                case '3':
580                case '4':
581                case '5':
582                case '6':
583                case '7':
584                    {
585                        // octal escape: up to 3 characters
586                        ++cursor;
587                        for (int ii = 0; ii < 3; ++ii)
588                        {
589                            const char d = *cursor;
590                            if (('0' <= d) && (d <= '7'))
591                            {
592                                ++cursor;
593                                continue;
594                            }
595                            else
596                            {
597                                break;
598                            }
599                        }
600                        break;
601                    }
602                case 'x':
603                    {
604                        // hexadecimal escape: any number of characters
605                        ++cursor;
606                        for (; CharUtil::isHexDigit(*cursor); ++cursor)
607                            ;
608
609                        // TODO: Unicode escape sequences
610                        break;
611                    }
612                }
613                break;
614            }
615        default:
616            {
617                ++cursor;
618                break;
619            }
620        }
621    }
622}
623
624// !!!!!!!!!!!!!!!!!!!!!!!!!! JSONStringEscapeHandler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
625
626class JSONStringEscapeHandler : public StringEscapeHandler
627{
628public:
629    typedef StringEscapeHandler Super;
630
631    virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE
632    {
633        SLANG_UNUSED(slice);
634        return true;
635    }
636    virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
637    virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE;
638    virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out)
639        SLANG_OVERRIDE;
640    virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out)
641        SLANG_OVERRIDE;
642    virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE;
643
644    JSONStringEscapeHandler()
645        : Super('"')
646    {
647    }
648};
649
650bool JSONStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice)
651{
652    return slice.indexOf('\\') >= 0;
653}
654
655bool JSONStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice)
656{
657    const char* cur = slice.begin();
658    const char* const end = slice.end();
659
660    for (; cur < end; ++cur)
661    {
662        const char c = *cur;
663
664        switch (c)
665        {
666        case '\"':
667        case '\\':
668        case '/':
669            {
670                return true;
671            }
672        default:
673            {
674                if (c < ' ' || c >= 0x7e)
675                {
676                    return true;
677                }
678                break;
679            }
680        }
681    }
682    return false;
683}
684
685SlangResult JSONStringEscapeHandler::lexQuoted(const char* cursor, const char** outCursor)
686{
687    // We've skipped the first "
688    while (true)
689    {
690        const char c = *cursor++;
691
692        switch (c)
693        {
694        case 0:
695            return SLANG_FAIL;
696        case '"':
697            {
698                *outCursor = cursor;
699                return SLANG_OK;
700            }
701        case '\\':
702            {
703                const char nextC = *cursor;
704                switch (nextC)
705                {
706                case '"':
707                case '\\':
708                case '/':
709                case 'b':
710                case 'f':
711                case 'n':
712                case 'r':
713                case 't':
714                    {
715                        ++cursor;
716                        break;
717                    }
718                case 'u':
719                    {
720                        cursor++;
721                        for (Index i = 0; i < 4; ++i)
722                        {
723                            if (!CharUtil::isHexDigit(cursor[i]))
724                            {
725                                return SLANG_FAIL;
726                            }
727                        }
728                        cursor += 4;
729                        break;
730                    }
731                }
732            }
733        // Somewhat surprisingly it appears it's valid to have \r\n inside of quotes.
734        default:
735            break;
736        }
737    }
738}
739
740static char _getJSONEscapedChar(char c)
741{
742    switch (c)
743    {
744    case '\b':
745        return 'b';
746    case '\f':
747        return 'f';
748    case '\n':
749        return 'n';
750    case '\r':
751        return 'r';
752    case '\t':
753        return 't';
754    case '\\':
755        return '\\';
756    case '/':
757        return '/';
758    case '"':
759        return '"';
760    default:
761        return 0;
762    }
763}
764
765static char _getJSONUnescapedChar(char c)
766{
767    switch (c)
768    {
769    case 'b':
770        return '\b';
771    case 'f':
772        return '\f';
773    case 'n':
774        return '\n';
775    case 'r':
776        return '\r';
777    case 't':
778        return '\t';
779    case '\\':
780        return '\\';
781    case '/':
782        return '/';
783    case '"':
784        return '"';
785    default:
786        return 0;
787    }
788}
789
790static const char s_hex[] = "0123456789abcdef";
791
792// Outputs ioSlice with the chars remaining after utf8 encoded value
793// Returns ~uint32_t(0) if can't decode
794static uint32_t _getUnicodePointFromUTF8(UnownedStringSlice& ioSlice)
795{
796    const Index length = ioSlice.getLength();
797    SLANG_ASSERT(length > 0);
798    const char* cur = ioSlice.begin();
799
800    uint32_t codePoint = 0;
801    unsigned int leading = cur[0];
802    unsigned int mask = 0x80;
803
804    Index count = 0;
805    while (leading & mask)
806    {
807        count++;
808        mask >>= 1;
809    }
810
811    if (count > length)
812    {
813        SLANG_ASSERT(!"Can't decode");
814        ioSlice = UnownedStringSlice(ioSlice.end(), ioSlice.end());
815        return ~uint32_t(0);
816    }
817
818    codePoint = (leading & (mask - 1));
819    for (Index i = 1; i <= count - 1; i++)
820    {
821        codePoint <<= 6;
822        codePoint += (cur[i] & 0x3F);
823    }
824
825    ioSlice = UnownedStringSlice(cur + count, ioSlice.end());
826    return codePoint;
827}
828
829static void _appendHex16(uint32_t value, StringBuilder& out)
830{
831    // Let's go with hex
832    char buf[] = "\\u0000";
833
834    buf[2] = s_hex[(value >> 12) & 0xf];
835    buf[3] = s_hex[(value >> 8) & 0xf];
836    buf[4] = s_hex[(value >> 4) & 0xf];
837    buf[5] = s_hex[(value >> 0) & 0xf];
838
839    out.append(UnownedStringSlice(buf, 6));
840}
841
842SlangResult JSONStringEscapeHandler::appendEscaped(
843    const UnownedStringSlice& slice,
844    StringBuilder& out)
845{
846    const char* start = slice.begin();
847    const char* cur = start;
848    const char* const end = slice.end();
849
850    for (; cur < end; ++cur)
851    {
852        const char c = *cur;
853
854        const char escapedChar = _getJSONEscapedChar(c);
855
856        if (escapedChar)
857        {
858            // Flush
859            if (start < cur)
860            {
861                out.append(start, cur);
862            }
863            out.appendChar('\\');
864            out.appendChar(escapedChar);
865
866            start = cur + 1;
867        }
868        else if (uint8_t(c) & 0x80)
869        {
870            // Flush
871            if (start < cur)
872            {
873                out.append(start, cur);
874            }
875
876            // UTF8
877            UnownedStringSlice remainingSlice(cur, end);
878            uint32_t codePoint = _getUnicodePointFromUTF8(remainingSlice);
879
880            // We only support up to 16 bit unicode values for now...
881            SLANG_ASSERT(codePoint < 0x10000);
882
883            _appendHex16(codePoint, out);
884
885            cur = remainingSlice.begin() - 1;
886            start = cur + 1;
887        }
888        else if (uint8_t(c) < ' ' || (c >= 0x7e))
889        {
890            if (start < cur)
891            {
892                out.append(start, cur);
893            }
894
895            _appendHex16(uint32_t(c), out);
896
897            start = cur + 1;
898        }
899        else
900        {
901            // Can go out as it is
902        }
903    }
904
905    // Flush at the end
906    if (start < end)
907    {
908        out.append(start, end);
909    }
910    return SLANG_OK;
911}
912
913SlangResult JSONStringEscapeHandler::appendUnescaped(
914    const UnownedStringSlice& slice,
915    StringBuilder& out)
916{
917    const char* start = slice.begin();
918    const char* cur = start;
919    const char* const end = slice.end();
920
921    for (; cur < end; ++cur)
922    {
923        const char c = *cur;
924
925        if (c == '\\')
926        {
927            // Flush
928            if (start < cur)
929            {
930                out.append(start, cur);
931            }
932
933            /// Next
934            cur++;
935
936            if (cur >= end)
937            {
938                return SLANG_FAIL;
939            }
940
941            // Need to handle various escape sequence cases
942            switch (*cur)
943            {
944            case '\"':
945            case '\\':
946            case '/':
947            case 'b':
948            case 'f':
949            case 'n':
950            case 'r':
951            case 't':
952                {
953                    const char unescapedChar = _getJSONUnescapedChar(*cur);
954                    if (unescapedChar == 0)
955                    {
956                        // Don't know how to unescape that char
957                        return SLANG_FAIL;
958                    }
959                    out.appendChar(unescapedChar);
960
961                    start = cur + 1;
962                    break;
963                }
964            case 'u':
965                {
966                    uint32_t value = 0;
967                    cur++;
968
969                    if (cur + 4 > end)
970                    {
971                        return SLANG_FAIL;
972                    }
973
974                    for (Index i = 0; i < 4; ++i)
975                    {
976                        const char digitC = cur[i];
977
978                        uint32_t digitValue;
979                        if (digitC >= '0' && digitC <= '9')
980                        {
981                            digitValue = digitC - '0';
982                        }
983                        else if (digitC >= 'a' && digitC <= 'f')
984                        {
985                            digitValue = digitC - 'a' + 10;
986                        }
987                        else if (digitC >= 'A' && digitC <= 'F')
988                        {
989                            digitValue = digitC - 'A' + 10;
990                        }
991                        else
992                        {
993                            return SLANG_FAIL;
994                        }
995                        SLANG_ASSERT(digitValue < 0x10);
996                        value = (value << 4) | digitValue;
997                    }
998                    cur += 4;
999
1000                    // NOTE! Strictly speaking we may want to combine 2 UTF16 surrogates to make a
1001                    // single UTF8 encoded char.
1002
1003                    // Need to encode in UTF8 to concat
1004
1005                    char buf[8];
1006                    int len = encodeUnicodePointToUTF8(Char32(value), buf);
1007
1008                    out.append(buf, buf + len);
1009
1010                    start = cur;
1011                    cur--;
1012                    break;
1013                }
1014            default:
1015                {
1016                    // Can't decode
1017                    return SLANG_FAIL;
1018                }
1019            }
1020        }
1021    }
1022
1023    // Flush
1024    if (start < end)
1025    {
1026        out.append(start, end);
1027    }
1028
1029    return SLANG_OK;
1030}
1031
1032// !!!!!!!!!!!!!!!!!!!!!!!!!! StringEscapeUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1033
1034static CppStringEscapeHandler g_cppHandler;
1035static SpaceStringEscapeHandler g_spaceHandler;
1036static JSONStringEscapeHandler g_jsonHandler;
1037
1038StringEscapeUtil::Handler* StringEscapeUtil::getHandler(Style style)
1039{
1040    switch (style)
1041    {
1042    case Style::Cpp:
1043        return &g_cppHandler;
1044    case Style::Space:
1045        return &g_spaceHandler;
1046    case Style::JSON:
1047        return &g_jsonHandler;
1048    // TODO(JS): For now we make Slang language string encoding/decoding the same as C++
1049    // That may not be desirable because C++ has a variety of surprising edge cases (for example
1050    // around \x)
1051    case Style::Slang:
1052        return &g_cppHandler;
1053    default:
1054        return nullptr;
1055    }
1056}
1057
1058/* static */ SlangResult StringEscapeUtil::appendQuoted(
1059    Handler* handler,
1060    const UnownedStringSlice& slice,
1061    StringBuilder& out)
1062{
1063    const char quoteChar = handler->getQuoteChar();
1064    out.appendChar(quoteChar);
1065    SlangResult res = handler->appendEscaped(slice, out);
1066    out.appendChar(quoteChar);
1067    return res;
1068}
1069
1070/* static */ SlangResult StringEscapeUtil::appendUnquoted(
1071    Handler* handler,
1072    const UnownedStringSlice& slice,
1073    StringBuilder& out)
1074{
1075    const Index len = slice.getLength();
1076
1077    const char quoteChar = handler->getQuoteChar();
1078    SLANG_UNUSED(quoteChar);
1079
1080    // Must have quote characters around if
1081    SLANG_ASSERT(len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar);
1082
1083    return handler->appendUnescaped(slice.subString(1, len - 2), out);
1084}
1085
1086/* static */ SlangResult StringEscapeUtil::appendMaybeQuoted(
1087    Handler* handler,
1088    const UnownedStringSlice& slice,
1089    StringBuilder& out)
1090{
1091    if (handler->isQuotingNeeded(slice))
1092    {
1093        return appendQuoted(handler, slice, out);
1094    }
1095    else
1096    {
1097        out.append(slice);
1098        return SLANG_OK;
1099    }
1100}
1101
1102/* static */ UnownedStringSlice StringEscapeUtil::maybeUnquoteCommandLineArg(
1103    UnownedStringSlice slice)
1104{
1105    // If the slice is quoted, unquote it, else return as is
1106    if (slice.startsWith("\'") || slice.startsWith("\""))
1107    {
1108        const Index len = slice.getLength();
1109        if (len >= 2 && slice[len - 1] == slice[0])
1110        {
1111            // Unquote it
1112            return UnownedStringSlice(slice.begin() + 1, len - 2);
1113        }
1114    }
1115    return slice;
1116}
1117
1118/* static */ bool StringEscapeUtil::isQuoted(char quoteChar, UnownedStringSlice& slice)
1119{
1120    const Index len = slice.getLength();
1121    return len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar;
1122}
1123
1124/* static */ UnownedStringSlice StringEscapeUtil::unquote(
1125    char quoteChar,
1126    const UnownedStringSlice& slice)
1127{
1128    const Index len = slice.getLength();
1129    if (len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar)
1130    {
1131        return UnownedStringSlice(slice.begin() + 1, len - 2);
1132    }
1133    SLANG_ASSERT(!"Not quoted!");
1134    return UnownedStringSlice();
1135}
1136
1137/* static */ SlangResult StringEscapeUtil::appendMaybeUnquoted(
1138    Handler* handler,
1139    const UnownedStringSlice& slice,
1140    StringBuilder& out)
1141{
1142    const char quoteChar = handler->getQuoteChar();
1143
1144    const Index len = slice.getLength();
1145
1146    if (len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar)
1147    {
1148        return appendUnquoted(handler, slice, out);
1149    }
1150    else
1151    {
1152        out.append(slice);
1153        return SLANG_OK;
1154    }
1155}
1156
1157/* static */ SlangResult StringEscapeUtil::isUnescapeShellLikeNeeded(
1158    Handler* handler,
1159    const UnownedStringSlice& slice)
1160{
1161    return slice.indexOf(handler->getQuoteChar()) >= 0;
1162}
1163
1164/* static */ SlangResult StringEscapeUtil::unescapeShellLike(
1165    Handler* handler,
1166    const UnownedStringSlice& slice,
1167    StringBuilder& out)
1168{
1169    StringBuilder buf;
1170    const char quoteChar = handler->getQuoteChar();
1171
1172    UnownedStringSlice remaining(slice);
1173
1174    while (remaining.getLength())
1175    {
1176        const Index index = remaining.indexOf(quoteChar);
1177
1178        if (index < 0)
1179        {
1180            out.append(remaining);
1181            return SLANG_OK;
1182        }
1183
1184        // Append the bit before
1185        out.append(remaining.head(index));
1186
1187        // Okay we need to lex to the end
1188
1189        const char* quotedEnd = nullptr;
1190        SLANG_RETURN_ON_FAIL(handler->lexQuoted(remaining.begin() + index, &quotedEnd));
1191
1192        // Unescape it
1193        SLANG_RETURN_ON_FAIL(
1194            appendUnquoted(handler, UnownedStringSlice(remaining.begin() + index, quotedEnd), out));
1195
1196        // Fix up remaining
1197        remaining = UnownedStringSlice(quotedEnd, remaining.end());
1198    }
1199
1200    return SLANG_OK;
1201}
1202
1203String StringEscapeUtil::escapeString(UnownedStringSlice input, StringEscapeUtil::Style style)
1204{
1205    StringBuilder sb;
1206    auto handler = StringEscapeUtil::getHandler(style);
1207    StringEscapeUtil::appendQuoted(handler, input, sb);
1208    return sb.produceString();
1209}
1210
1211String StringEscapeUtil::unescapeString(UnownedStringSlice input, StringEscapeUtil::Style style)
1212{
1213    StringBuilder sb;
1214    auto handler = StringEscapeUtil::getHandler(style);
1215    StringEscapeUtil::appendUnquoted(handler, input, sb);
1216    return sb.produceString();
1217}
1218} // namespace Slang