| ... | @@ -2163,45 +2163,51 @@ fn outputUnicodeEscape( | ... | @@ -2163,45 +2163,51 @@ fn outputUnicodeEscape( |
| 2163 | } | 2163 | } |
| 2164 | } | 2164 | } |
| 2165 | | 2165 | |
| 2166 | fn outputJsonString(value: []const u8, options: StringifyOptions, out_stream: anytype) !void { | 2166 | /// Write `string` to `writer` as a JSON encoded string. |
| 2167 | try out_stream.writeByte('\"'); | 2167 | pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void { |
| | 2168 | try writer.writeByte('\"'); |
| | 2169 | try encodeJsonStringChars(string, options, writer); |
| | 2170 | try writer.writeByte('\"'); |
| | 2171 | } |
| | 2172 | |
| | 2173 | /// Write `chars` to `writer` as JSON encoded string characters. |
| | 2174 | pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void { |
| 2168 | var i: usize = 0; | 2175 | var i: usize = 0; |
| 2169 | while (i < value.len) : (i += 1) { | 2176 | while (i < chars.len) : (i += 1) { |
| 2170 | switch (value[i]) { | 2177 | switch (chars[i]) { |
| 2171 | // normal ascii character | 2178 | // normal ascii character |
| 2172 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c), | 2179 | 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c), |
| 2173 | // only 2 characters that *must* be escaped | 2180 | // only 2 characters that *must* be escaped |
| 2174 | '\\' => try out_stream.writeAll("\\\\"), | 2181 | '\\' => try writer.writeAll("\\\\"), |
| 2175 | '\"' => try out_stream.writeAll("\\\""), | 2182 | '\"' => try writer.writeAll("\\\""), |
| 2176 | // solidus is optional to escape | 2183 | // solidus is optional to escape |
| 2177 | '/' => { | 2184 | '/' => { |
| 2178 | if (options.string.String.escape_solidus) { | 2185 | if (options.string.String.escape_solidus) { |
| 2179 | try out_stream.writeAll("\\/"); | 2186 | try writer.writeAll("\\/"); |
| 2180 | } else { | 2187 | } else { |
| 2181 | try out_stream.writeByte('/'); | 2188 | try writer.writeByte('/'); |
| 2182 | } | 2189 | } |
| 2183 | }, | 2190 | }, |
| 2184 | // control characters with short escapes | 2191 | // control characters with short escapes |
| 2185 | // TODO: option to switch between unicode and 'short' forms? | 2192 | // TODO: option to switch between unicode and 'short' forms? |
| 2186 | 0x8 => try out_stream.writeAll("\\b"), | 2193 | 0x8 => try writer.writeAll("\\b"), |
| 2187 | 0xC => try out_stream.writeAll("\\f"), | 2194 | 0xC => try writer.writeAll("\\f"), |
| 2188 | '\n' => try out_stream.writeAll("\\n"), | 2195 | '\n' => try writer.writeAll("\\n"), |
| 2189 | '\r' => try out_stream.writeAll("\\r"), | 2196 | '\r' => try writer.writeAll("\\r"), |
| 2190 | '\t' => try out_stream.writeAll("\\t"), | 2197 | '\t' => try writer.writeAll("\\t"), |
| 2191 | else => { | 2198 | else => { |
| 2192 | const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable; | 2199 | const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; |
| 2193 | // control characters (only things left with 1 byte length) should always be printed as unicode escapes | 2200 | // control characters (only things left with 1 byte length) should always be printed as unicode escapes |
| 2194 | if (ulen == 1 or options.string.String.escape_unicode) { | 2201 | if (ulen == 1 or options.string.String.escape_unicode) { |
| 2195 | const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable; | 2202 | const codepoint = std.unicode.utf8Decode(chars[i .. i + ulen]) catch unreachable; |
| 2196 | try outputUnicodeEscape(codepoint, out_stream); | 2203 | try outputUnicodeEscape(codepoint, writer); |
| 2197 | } else { | 2204 | } else { |
| 2198 | try out_stream.writeAll(value[i .. i + ulen]); | 2205 | try writer.writeAll(chars[i .. i + ulen]); |
| 2199 | } | 2206 | } |
| 2200 | i += ulen - 1; | 2207 | i += ulen - 1; |
| 2201 | }, | 2208 | }, |
| 2202 | } | 2209 | } |
| 2203 | } | 2210 | } |
| 2204 | try out_stream.writeByte('\"'); | | |
| 2205 | } | 2211 | } |
| 2206 | | 2212 | |
| 2207 | pub fn stringify( | 2213 | pub fn stringify( |
| ... | @@ -2288,7 +2294,7 @@ pub fn stringify( | ... | @@ -2288,7 +2294,7 @@ pub fn stringify( |
| 2288 | if (child_options.whitespace) |child_whitespace| { | 2294 | if (child_options.whitespace) |child_whitespace| { |
| 2289 | try child_whitespace.outputIndent(out_stream); | 2295 | try child_whitespace.outputIndent(out_stream); |
| 2290 | } | 2296 | } |
| 2291 | try outputJsonString(Field.name, options, out_stream); | 2297 | try encodeJsonString(Field.name, options, out_stream); |
| 2292 | try out_stream.writeByte(':'); | 2298 | try out_stream.writeByte(':'); |
| 2293 | if (child_options.whitespace) |child_whitespace| { | 2299 | if (child_options.whitespace) |child_whitespace| { |
| 2294 | if (child_whitespace.separator) { | 2300 | if (child_whitespace.separator) { |
| ... | @@ -2321,7 +2327,7 @@ pub fn stringify( | ... | @@ -2321,7 +2327,7 @@ pub fn stringify( |
| 2321 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) | 2327 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2322 | .Slice => { | 2328 | .Slice => { |
| 2323 | if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) { | 2329 | if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) { |
| 2324 | try outputJsonString(value, options, out_stream); | 2330 | try encodeJsonString(value, options, out_stream); |
| 2325 | return; | 2331 | return; |
| 2326 | } | 2332 | } |
| 2327 | | 2333 | |