/src/jsonnet/core/string_utils.cpp
Line | Count | Source |
1 | | /* |
2 | | Copyright 2015 Google Inc. All rights reserved. |
3 | | |
4 | | Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | you may not use this file except in compliance with the License. |
6 | | You may obtain a copy of the License at |
7 | | |
8 | | http://www.apache.org/licenses/LICENSE-2.0 |
9 | | |
10 | | Unless required by applicable law or agreed to in writing, software |
11 | | distributed under the License is distributed on an "AS IS" BASIS, |
12 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | See the License for the specific language governing permissions and |
14 | | limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <iomanip> |
18 | | |
19 | | #include "static_error.h" |
20 | | #include "string_utils.h" |
21 | | |
22 | | namespace jsonnet::internal { |
23 | | |
24 | | UString jsonnet_string_unparse(const UString &str, bool single) |
25 | 1.52M | { |
26 | 1.52M | UStringStream ss; |
27 | 1.52M | ss << (single ? U'\'' : U'\"'); |
28 | 1.52M | ss << jsonnet_string_escape(str, single); |
29 | 1.52M | ss << (single ? U'\'' : U'\"'); |
30 | 1.52M | return ss.str(); |
31 | 1.52M | } |
32 | | |
33 | | UString jsonnet_string_escape(const UString &str, bool single) |
34 | 1.52M | { |
35 | 1.52M | UStringStream ss; |
36 | 2.26G | for (std::size_t i = 0; i < str.length(); ++i) { |
37 | 2.26G | char32_t c = str[i]; |
38 | 2.26G | switch (c) { |
39 | 1.72M | case U'\"': ss << (single ? U"\"" : U"\\\""); break; |
40 | 989k | case U'\'': ss << (single ? U"\\\'" : U"\'"); break; |
41 | 2.07G | case U'\\': ss << U"\\\\"; break; |
42 | 1.47M | case U'\b': ss << U"\\b"; break; |
43 | 1.56M | case U'\f': ss << U"\\f"; break; |
44 | 18.7M | case U'\n': ss << U"\\n"; break; |
45 | 773k | case U'\r': ss << U"\\r"; break; |
46 | 2.59M | case U'\t': ss << U"\\t"; break; |
47 | 202k | case U'\0': ss << U"\\u0000"; break; |
48 | 159M | default: { |
49 | 159M | if (c < 0x20 || (c >= 0x7f && c <= 0x9f)) { |
50 | | // Unprintable, use \u |
51 | 27.0M | std::stringstream ss8; |
52 | 27.0M | ss8 << "\\u" << std::hex << std::setfill('0') << std::setw(4) |
53 | 27.0M | << (unsigned long)(c); |
54 | 27.0M | ss << decode_utf8(ss8.str()); |
55 | 132M | } else { |
56 | | // Printable, write verbatim |
57 | 132M | ss << c; |
58 | 132M | } |
59 | 159M | } |
60 | 2.26G | } |
61 | 2.26G | } |
62 | 1.52M | return ss.str(); |
63 | 1.52M | } |
64 | | |
65 | | unsigned long jsonnet_string_parse_unicode(const LocationRange &loc, const char32_t *c) |
66 | 68.6k | { |
67 | 68.6k | unsigned long codepoint = 0; |
68 | | // Expect 4 hex digits. |
69 | 342k | for (unsigned i = 0; i < 4; ++i) { |
70 | 274k | auto x = (unsigned char)(c[i]); |
71 | 274k | unsigned digit; |
72 | 274k | if (x == '\0') { |
73 | 60 | auto msg = "Truncated unicode escape sequence in string literal."; |
74 | 60 | throw StaticError(loc, msg); |
75 | 274k | } else if (x >= '0' && x <= '9') { |
76 | 214k | digit = x - '0'; |
77 | 214k | } else if (x >= 'a' && x <= 'f') { |
78 | 25.4k | digit = x - 'a' + 10; |
79 | 34.3k | } else if (x >= 'A' && x <= 'F') { |
80 | 34.2k | digit = x - 'A' + 10; |
81 | 34.2k | } else { |
82 | 110 | std::stringstream ss; |
83 | 110 | ss << "Malformed unicode escape character, " |
84 | 110 | << "should be hex: '" << x << "'"; |
85 | 110 | throw StaticError(loc, ss.str()); |
86 | 110 | } |
87 | 274k | codepoint *= 16; |
88 | 274k | codepoint += digit; |
89 | 274k | } |
90 | 68.4k | return codepoint; |
91 | 68.6k | } |
92 | | |
93 | | bool is_bmp_codepoint(const unsigned long codepoint) |
94 | 67.4k | { |
95 | 67.4k | return codepoint < 0xd800 || (codepoint >= 0xe000 && codepoint < 0x10000); |
96 | 67.4k | } |
97 | | |
98 | | char32_t decode_utf16_surrogates(const LocationRange &loc, const unsigned long high, const unsigned long low) |
99 | 1.07k | { |
100 | 1.07k | if (high >= 0xd800 && high < 0xdc00 && low >= 0xdc00 && low < 0xe000) { |
101 | 1.03k | return 0x10000 + ((high & 0x03ff) << 10) + (low & 0x03ff); |
102 | 1.03k | } else { |
103 | 42 | std::stringstream ss; |
104 | 42 | ss << "Invalid UTF-16 bytes"; |
105 | 42 | throw StaticError(loc, ss.str()); |
106 | 42 | } |
107 | 1.07k | } |
108 | | |
109 | | UString jsonnet_string_unescape(const LocationRange &loc, const UString &s) |
110 | 9.45M | { |
111 | 9.45M | UString r; |
112 | 9.45M | const char32_t *s_ptr = s.c_str(); |
113 | 177M | for (const char32_t *c = s_ptr; *c != U'\0'; ++c) { |
114 | 168M | switch (*c) { |
115 | 1.17M | case '\\': |
116 | 1.17M | switch (*(++c)) { |
117 | 42.4k | case '"': |
118 | 51.8k | case '\'': r += *c; break; |
119 | | |
120 | 240k | case '\\': r += *c; break; |
121 | | |
122 | 7.29k | case '/': r += *c; break; |
123 | | |
124 | 30.4k | case 'b': r += '\b'; break; |
125 | | |
126 | 79.5k | case 'f': r += '\f'; break; |
127 | | |
128 | 552k | case 'n': r += '\n'; break; |
129 | | |
130 | 40.7k | case 'r': r += '\r'; break; |
131 | | |
132 | 101k | case 't': r += '\t'; break; |
133 | | |
134 | 67.5k | case 'u': { |
135 | 67.5k | ++c; // Consume the 'u'. |
136 | 67.5k | unsigned long codepoint = jsonnet_string_parse_unicode(loc, c); |
137 | | |
138 | | // Leave us on the last char, ready for the ++c at |
139 | | // the outer for loop. |
140 | 67.5k | c += 3; |
141 | 67.5k | if (!is_bmp_codepoint(codepoint)) { |
142 | 1.14k | if (*(++c) != '\\') { |
143 | 41 | std::stringstream ss; |
144 | 41 | ss << "Invalid non-BMP Unicode escape in string literal"; |
145 | 41 | throw StaticError(loc, ss.str()); |
146 | 41 | } |
147 | 1.10k | if (*(++c) != 'u') { |
148 | 18 | std::stringstream ss; |
149 | 18 | ss << "Invalid non-BMP Unicode escape in string literal"; |
150 | 18 | throw StaticError(loc, ss.str()); |
151 | 18 | } |
152 | 1.08k | ++c; |
153 | 1.08k | unsigned long codepoint2 = jsonnet_string_parse_unicode(loc, c); |
154 | 1.08k | c += 3; |
155 | 1.08k | codepoint = decode_utf16_surrogates(loc, codepoint, codepoint2); |
156 | 1.08k | } |
157 | 67.5k | r += codepoint; |
158 | 67.5k | } break; |
159 | | |
160 | 10 | case '\0': { |
161 | 10 | auto msg = "Truncated escape sequence in string literal."; |
162 | 10 | throw StaticError(loc, msg); |
163 | 67.5k | } |
164 | | |
165 | 366 | default: { |
166 | 366 | std::stringstream ss; |
167 | 366 | std::string utf8; |
168 | 366 | encode_utf8(*c, utf8); |
169 | 366 | ss << "Unknown escape sequence in string literal: '" << utf8 << "'"; |
170 | 366 | throw StaticError(loc, ss.str()); |
171 | 67.5k | } |
172 | 1.17M | } |
173 | 1.17M | break; |
174 | | |
175 | 166M | default: |
176 | | // Just a regular letter. |
177 | 166M | r += *c; |
178 | 168M | } |
179 | 168M | } |
180 | 9.45M | return r; |
181 | 9.45M | } |
182 | | |
183 | | } // namespace jsonnet::internal |