| ... | ... | @@ -1870,20 +1870,34 @@ fn parseInternal( |
| 1870 | 1870 | const v = try parseInternal(ptrInfo.child, tok, tokens, options); |
| 1871 | 1871 | arraylist.appendAssumeCapacity(v); |
| 1872 | 1872 | } |
| 1873 | |
| 1874 | if (ptrInfo.sentinel) |some| { |
| 1875 | const sentinel_value = @ptrCast(*const ptrInfo.child, some).*; |
| 1876 | try arraylist.append(sentinel_value); |
| 1877 | const output = arraylist.toOwnedSlice(); |
| 1878 | return output[0 .. output.len - 1 :sentinel_value]; |
| 1879 | } |
| 1880 | |
| 1873 | 1881 | return arraylist.toOwnedSlice(); |
| 1874 | 1882 | }, |
| 1875 | 1883 | .String => |stringToken| { |
| 1876 | 1884 | if (ptrInfo.child != u8) return error.UnexpectedToken; |
| 1877 | 1885 | const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); |
| 1886 | const len = stringToken.decodedLength(); |
| 1887 | const output = try allocator.alloc(u8, len + @boolToInt(ptrInfo.sentinel != null)); |
| 1888 | errdefer allocator.free(output); |
| 1878 | 1889 | switch (stringToken.escapes) { |
| 1879 | | .None => return allocator.dupe(u8, source_slice), |
| 1880 | | .Some => { |
| 1881 | | const output = try allocator.alloc(u8, stringToken.decodedLength()); |
| 1882 | | errdefer allocator.free(output); |
| 1883 | | try unescapeValidString(output, source_slice); |
| 1884 | | return output; |
| 1885 | | }, |
| 1890 | .None => mem.copy(u8, output, source_slice), |
| 1891 | .Some => try unescapeValidString(output, source_slice), |
| 1886 | 1892 | } |
| 1893 | |
| 1894 | if (ptrInfo.sentinel) |some| { |
| 1895 | const char = @ptrCast(*const u8, some).*; |
| 1896 | output[len] = char; |
| 1897 | return output[0..len :char]; |
| 1898 | } |
| 1899 | |
| 1900 | return output; |
| 1887 | 1901 | }, |
| 1888 | 1902 | else => return error.UnexpectedToken, |
| 1889 | 1903 | } |
| ... | ... | @@ -2216,6 +2230,35 @@ test "parse into struct with misc fields" { |
| 2216 | 2230 | try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union); |
| 2217 | 2231 | } |
| 2218 | 2232 | |
| 2233 | test "parse into struct with strings and arrays with sentinels" { |
| 2234 | @setEvalBranchQuota(10000); |
| 2235 | const options = ParseOptions{ .allocator = testing.allocator }; |
| 2236 | const T = struct { |
| 2237 | language: [:0]const u8, |
| 2238 | language_without_sentinel: []const u8, |
| 2239 | data: [:99]const i32, |
| 2240 | simple_data: []const i32, |
| 2241 | }; |
| 2242 | const r = try parse(T, &TokenStream.init( |
| 2243 | \\{ |
| 2244 | \\ "language": "zig", |
| 2245 | \\ "language_without_sentinel": "zig again!", |
| 2246 | \\ "data": [1, 2, 3], |
| 2247 | \\ "simple_data": [4, 5, 6] |
| 2248 | \\} |
| 2249 | ), options); |
| 2250 | defer parseFree(T, r, options); |
| 2251 | |
| 2252 | try testing.expectEqualSentinel(u8, 0, "zig", r.language); |
| 2253 | |
| 2254 | const data = [_:99]i32{ 1, 2, 3 }; |
| 2255 | try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data); |
| 2256 | |
| 2257 | // Make sure that arrays who aren't supposed to have a sentinel still parse without one. |
| 2258 | try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data))); |
| 2259 | try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel))); |
| 2260 | } |
| 2261 | |
| 2219 | 2262 | test "parse into struct with duplicate field" { |
| 2220 | 2263 | // allow allocator to detect double frees by keeping bucket in use |
| 2221 | 2264 | const ballast = try testing.allocator.alloc(u64, 1); |