| author | |
| committer | |
| log | 0fb7a0a94bf6f9e329008d8b5b819a8d1d7124b0 |
| tree | 13825170aa0c520a49f006a1e0f35990ee9af6bc |
| parent | 737b13357ed04a33bfea17f58674deba8f17f51e |
7 files changed, 957 insertions(+), 981 deletions(-)
lib/std/zon.zig+1| ... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 | |
| 39 | 39 | pub const parse = @import("zon/parse.zig"); |
| 40 | 40 | pub const stringify = @import("zon/stringify.zig"); |
| 41 | pub const Serializer = @import("zon/Serializer.zig"); | |
| 41 | 42 | |
| 42 | 43 | test { |
| 43 | 44 | _ = parse; |
lib/std/zon/Serializer.zig created+929| ... | ... | @@ -0,0 +1,929 @@ |
| 1 | //! Lower level control over serialization, you can create a new instance with `serializer`. | |
| 2 | //! | |
| 3 | //! Useful when you want control over which fields are serialized, how they're represented, | |
| 4 | //! or want to write a ZON object that does not exist in memory. | |
| 5 | //! | |
| 6 | //! You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 7 | //! * `valueMaxDepth` | |
| 8 | //! * `valueArbitraryDepth` | |
| 9 | //! | |
| 10 | //! You can also serialize values using specific notations: | |
| 11 | //! * `int` | |
| 12 | //! * `float` | |
| 13 | //! * `codePoint` | |
| 14 | //! * `tuple` | |
| 15 | //! * `tupleMaxDepth` | |
| 16 | //! * `tupleArbitraryDepth` | |
| 17 | //! * `string` | |
| 18 | //! * `multilineString` | |
| 19 | //! | |
| 20 | //! For manual serialization of containers, see: | |
| 21 | //! * `beginStruct` | |
| 22 | //! * `beginTuple` | |
| 23 | ||
| 24 | options: Options = .{}, | |
| 25 | indent_level: u8 = 0, | |
| 26 | writer: *Writer, | |
| 27 | ||
| 28 | const Serializer = @This(); | |
| 29 | const std = @import("std"); | |
| 30 | const assert = std.debug.assert; | |
| 31 | const Writer = std.Io.Writer; | |
| 32 | ||
| 33 | pub const Error = Writer.Error; | |
| 34 | pub const DepthError = Error || error{ExceededMaxDepth}; | |
| 35 | ||
| 36 | pub const Options = struct { | |
| 37 | /// If false, only syntactically necessary whitespace is emitted. | |
| 38 | whitespace: bool = true, | |
| 39 | }; | |
| 40 | ||
| 41 | /// Options for manual serialization of container types. | |
| 42 | pub const ContainerOptions = struct { | |
| 43 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 44 | whitespace_style: union(enum) { | |
| 45 | /// If true, wrap every field. If false do not. | |
| 46 | wrap: bool, | |
| 47 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 48 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 49 | fields: usize, | |
| 50 | } = .{ .wrap = true }, | |
| 51 | ||
| 52 | fn shouldWrap(self: ContainerOptions) bool { | |
| 53 | return switch (self.whitespace_style) { | |
| 54 | .wrap => |wrap| wrap, | |
| 55 | .fields => |fields| fields > 2, | |
| 56 | }; | |
| 57 | } | |
| 58 | }; | |
| 59 | ||
| 60 | /// Options for serialization of an individual value. | |
| 61 | /// | |
| 62 | /// See `SerializeOptions` for more information on these options. | |
| 63 | pub const ValueOptions = struct { | |
| 64 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 65 | emit_strings_as_containers: bool = false, | |
| 66 | emit_default_optional_fields: bool = true, | |
| 67 | }; | |
| 68 | ||
| 69 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 70 | pub const EmitCodepointLiterals = enum { | |
| 71 | /// Never emit Unicode code point literals. | |
| 72 | never, | |
| 73 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 74 | printable_ascii, | |
| 75 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 76 | /// whose value is a valid non-surrogate code point. | |
| 77 | always, | |
| 78 | ||
| 79 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 80 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 81 | // Rule out incompatible integer types | |
| 82 | switch (@typeInfo(@TypeOf(val))) { | |
| 83 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 84 | return null; | |
| 85 | }, | |
| 86 | .comptime_int => {}, | |
| 87 | else => comptime unreachable, | |
| 88 | } | |
| 89 | ||
| 90 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 91 | // to a u21 if it should. | |
| 92 | switch (self) { | |
| 93 | .always => { | |
| 94 | const c = std.math.cast(u21, val) orelse return null; | |
| 95 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 96 | return c; | |
| 97 | }, | |
| 98 | .printable_ascii => { | |
| 99 | const c = std.math.cast(u8, val) orelse return null; | |
| 100 | if (!std.ascii.isPrint(c)) return null; | |
| 101 | return c; | |
| 102 | }, | |
| 103 | .never => { | |
| 104 | return null; | |
| 105 | }, | |
| 106 | } | |
| 107 | } | |
| 108 | }; | |
| 109 | ||
| 110 | /// Serialize a value, similar to `serialize`. | |
| 111 | pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 112 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 113 | return self.valueArbitraryDepth(val, options); | |
| 114 | } | |
| 115 | ||
| 116 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 117 | /// Can return `error.ExceededMaxDepth`. | |
| 118 | pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void { | |
| 119 | try checkValueDepth(val, depth); | |
| 120 | return self.valueArbitraryDepth(val, options); | |
| 121 | } | |
| 122 | ||
| 123 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 124 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 125 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 126 | switch (@typeInfo(@TypeOf(val))) { | |
| 127 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 128 | self.codePoint(c) catch |err| switch (err) { | |
| 129 | error.InvalidCodepoint => unreachable, // Already validated | |
| 130 | else => |e| return e, | |
| 131 | }; | |
| 132 | } else { | |
| 133 | try self.int(val); | |
| 134 | }, | |
| 135 | .float, .comptime_float => try self.float(val), | |
| 136 | .bool, .null => try self.writer.print("{}", .{val}), | |
| 137 | .enum_literal => try self.ident(@tagName(val)), | |
| 138 | .@"enum" => try self.ident(@tagName(val)), | |
| 139 | .pointer => |pointer| { | |
| 140 | // Try to serialize as a string | |
| 141 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 142 | .array => |array| array.child, | |
| 143 | else => if (pointer.size == .slice) pointer.child else null, | |
| 144 | }; | |
| 145 | if (item == u8 and | |
| 146 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 147 | !options.emit_strings_as_containers) | |
| 148 | { | |
| 149 | return try self.string(val); | |
| 150 | } | |
| 151 | ||
| 152 | // Serialize as either a tuple or as the child type | |
| 153 | switch (pointer.size) { | |
| 154 | .slice => try self.tupleImpl(val, options), | |
| 155 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 156 | else => comptime unreachable, | |
| 157 | } | |
| 158 | }, | |
| 159 | .array => { | |
| 160 | var container = try self.beginTuple( | |
| 161 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 162 | ); | |
| 163 | for (val) |item_val| { | |
| 164 | try container.fieldArbitraryDepth(item_val, options); | |
| 165 | } | |
| 166 | try container.end(); | |
| 167 | }, | |
| 168 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 169 | var container = try self.beginTuple( | |
| 170 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 171 | ); | |
| 172 | inline for (val) |field_value| { | |
| 173 | try container.fieldArbitraryDepth(field_value, options); | |
| 174 | } | |
| 175 | try container.end(); | |
| 176 | } else { | |
| 177 | // Decide which fields to emit | |
| 178 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 179 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 180 | } else b: { | |
| 181 | var fields = @"struct".fields.len; | |
| 182 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 183 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 184 | if (field_info.default_value_ptr) |ptr| { | |
| 185 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 186 | const field_value = @field(val, field_info.name); | |
| 187 | if (std.meta.eql(field_value, default.*)) { | |
| 188 | skip.* = true; | |
| 189 | fields -= 1; | |
| 190 | } | |
| 191 | } | |
| 192 | } | |
| 193 | break :b .{ fields, skipped }; | |
| 194 | }; | |
| 195 | ||
| 196 | // Emit those fields | |
| 197 | var container = try self.beginStruct( | |
| 198 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 199 | ); | |
| 200 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 201 | if (!skip) { | |
| 202 | try container.fieldArbitraryDepth( | |
| 203 | field_info.name, | |
| 204 | @field(val, field_info.name), | |
| 205 | options, | |
| 206 | ); | |
| 207 | } | |
| 208 | } | |
| 209 | try container.end(); | |
| 210 | }, | |
| 211 | .@"union" => |@"union"| { | |
| 212 | comptime assert(@"union".tag_type != null); | |
| 213 | switch (val) { | |
| 214 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 215 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 216 | else { | |
| 217 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 218 | ||
| 219 | try container.fieldArbitraryDepth( | |
| 220 | @tagName(tag), | |
| 221 | pl, | |
| 222 | options, | |
| 223 | ); | |
| 224 | ||
| 225 | try container.end(); | |
| 226 | }, | |
| 227 | } | |
| 228 | }, | |
| 229 | .optional => if (val) |inner| { | |
| 230 | try self.valueArbitraryDepth(inner, options); | |
| 231 | } else { | |
| 232 | try self.writer.writeAll("null"); | |
| 233 | }, | |
| 234 | .vector => |vector| { | |
| 235 | var container = try self.beginTuple( | |
| 236 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 237 | ); | |
| 238 | for (0..vector.len) |i| { | |
| 239 | try container.fieldArbitraryDepth(val[i], options); | |
| 240 | } | |
| 241 | try container.end(); | |
| 242 | }, | |
| 243 | ||
| 244 | else => comptime unreachable, | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | /// Serialize an integer. | |
| 249 | pub fn int(self: *Serializer, val: anytype) Error!void { | |
| 250 | try self.writer.printInt(val, 10, .lower, .{}); | |
| 251 | } | |
| 252 | ||
| 253 | /// Serialize a float. | |
| 254 | pub fn float(self: *Serializer, val: anytype) Error!void { | |
| 255 | switch (@typeInfo(@TypeOf(val))) { | |
| 256 | .float => if (std.math.isNan(val)) { | |
| 257 | return self.writer.writeAll("nan"); | |
| 258 | } else if (std.math.isPositiveInf(val)) { | |
| 259 | return self.writer.writeAll("inf"); | |
| 260 | } else if (std.math.isNegativeInf(val)) { | |
| 261 | return self.writer.writeAll("-inf"); | |
| 262 | } else if (std.math.isNegativeZero(val)) { | |
| 263 | return self.writer.writeAll("-0.0"); | |
| 264 | } else { | |
| 265 | try self.writer.print("{d}", .{val}); | |
| 266 | }, | |
| 267 | .comptime_float => if (val == 0) { | |
| 268 | return self.writer.writeAll("0"); | |
| 269 | } else { | |
| 270 | try self.writer.print("{d}", .{val}); | |
| 271 | }, | |
| 272 | else => comptime unreachable, | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 277 | /// | |
| 278 | /// Escapes the identifier if necessary. | |
| 279 | pub fn ident(self: *Serializer, name: []const u8) Error!void { | |
| 280 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); | |
| 281 | } | |
| 282 | ||
| 283 | pub const CodePointError = Error || error{InvalidCodepoint}; | |
| 284 | ||
| 285 | /// Serialize `val` as a Unicode codepoint. | |
| 286 | /// | |
| 287 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 288 | pub fn codePoint(self: *Serializer, val: u21) CodePointError!void { | |
| 289 | try self.writer.print("'{f}'", .{std.zig.fmtChar(val)}); | |
| 290 | } | |
| 291 | ||
| 292 | /// Like `value`, but always serializes `val` as a tuple. | |
| 293 | /// | |
| 294 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 295 | pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 296 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 297 | try self.tupleArbitraryDepth(val, options); | |
| 298 | } | |
| 299 | ||
| 300 | /// Like `tuple`, but recursive types are allowed. | |
| 301 | /// | |
| 302 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 303 | pub fn tupleMaxDepth( | |
| 304 | self: *Serializer, | |
| 305 | val: anytype, | |
| 306 | options: ValueOptions, | |
| 307 | depth: usize, | |
| 308 | ) DepthError!void { | |
| 309 | try checkValueDepth(val, depth); | |
| 310 | try self.tupleArbitraryDepth(val, options); | |
| 311 | } | |
| 312 | ||
| 313 | /// Like `tuple`, but recursive types are allowed. | |
| 314 | /// | |
| 315 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 316 | pub fn tupleArbitraryDepth( | |
| 317 | self: *Serializer, | |
| 318 | val: anytype, | |
| 319 | options: ValueOptions, | |
| 320 | ) Error!void { | |
| 321 | try self.tupleImpl(val, options); | |
| 322 | } | |
| 323 | ||
| 324 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 325 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 326 | switch (@typeInfo(@TypeOf(val))) { | |
| 327 | .@"struct" => { | |
| 328 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 329 | inline for (val) |item_val| { | |
| 330 | try container.fieldArbitraryDepth(item_val, options); | |
| 331 | } | |
| 332 | try container.end(); | |
| 333 | }, | |
| 334 | .pointer, .array => { | |
| 335 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 336 | for (val) |item_val| { | |
| 337 | try container.fieldArbitraryDepth(item_val, options); | |
| 338 | } | |
| 339 | try container.end(); | |
| 340 | }, | |
| 341 | else => comptime unreachable, | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | /// Like `value`, but always serializes `val` as a string. | |
| 346 | pub fn string(self: *Serializer, val: []const u8) Error!void { | |
| 347 | try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)}); | |
| 348 | } | |
| 349 | ||
| 350 | /// Options for formatting multiline strings. | |
| 351 | pub const MultilineStringOptions = struct { | |
| 352 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 353 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 354 | /// whitespace is true any necessary indentation follows. | |
| 355 | top_level: bool = false, | |
| 356 | }; | |
| 357 | ||
| 358 | pub const MultilineStringError = Error || error{InnerCarriageReturn}; | |
| 359 | ||
| 360 | /// Like `value`, but always serializes to a multiline string literal. | |
| 361 | /// | |
| 362 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 363 | /// since multiline strings cannot represent CR without a following newline. | |
| 364 | pub fn multilineString( | |
| 365 | self: *Serializer, | |
| 366 | val: []const u8, | |
| 367 | options: MultilineStringOptions, | |
| 368 | ) MultilineStringError!void { | |
| 369 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 370 | var i: usize = 0; | |
| 371 | while (i < val.len) : (i += 1) { | |
| 372 | if (val[i] == '\r') { | |
| 373 | if (i + 1 < val.len) { | |
| 374 | if (val[i + 1] == '\n') { | |
| 375 | i += 1; | |
| 376 | continue; | |
| 377 | } | |
| 378 | } | |
| 379 | return error.InnerCarriageReturn; | |
| 380 | } | |
| 381 | } | |
| 382 | ||
| 383 | if (!options.top_level) { | |
| 384 | try self.newline(); | |
| 385 | try self.indent(); | |
| 386 | } | |
| 387 | ||
| 388 | try self.writer.writeAll("\\\\"); | |
| 389 | for (val) |c| { | |
| 390 | if (c != '\r') { | |
| 391 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 392 | if (c == '\n') { | |
| 393 | try self.indent(); | |
| 394 | try self.writer.writeAll("\\\\"); | |
| 395 | } | |
| 396 | } | |
| 397 | } | |
| 398 | ||
| 399 | if (!options.top_level) { | |
| 400 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 401 | try self.indent(); | |
| 402 | } | |
| 403 | } | |
| 404 | ||
| 405 | /// Create a `Struct` for writing ZON structs field by field. | |
| 406 | pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct { | |
| 407 | return Struct.begin(self, options); | |
| 408 | } | |
| 409 | ||
| 410 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 411 | pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 412 | return Tuple.begin(self, options); | |
| 413 | } | |
| 414 | ||
| 415 | fn indent(self: *Serializer) Error!void { | |
| 416 | if (self.options.whitespace) { | |
| 417 | try self.writer.splatByteAll(' ', 4 * self.indent_level); | |
| 418 | } | |
| 419 | } | |
| 420 | ||
| 421 | fn newline(self: *Serializer) Error!void { | |
| 422 | if (self.options.whitespace) { | |
| 423 | try self.writer.writeByte('\n'); | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | fn newlineOrSpace(self: *Serializer, len: usize) Error!void { | |
| 428 | if (self.containerShouldWrap(len)) { | |
| 429 | try self.newline(); | |
| 430 | } else { | |
| 431 | try self.space(); | |
| 432 | } | |
| 433 | } | |
| 434 | ||
| 435 | fn space(self: *Serializer) Error!void { | |
| 436 | if (self.options.whitespace) { | |
| 437 | try self.writer.writeByte(' '); | |
| 438 | } | |
| 439 | } | |
| 440 | ||
| 441 | /// Writes ZON tuples field by field. | |
| 442 | pub const Tuple = struct { | |
| 443 | container: Container, | |
| 444 | ||
| 445 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 446 | return .{ | |
| 447 | .container = try Container.begin(parent, .anon, options), | |
| 448 | }; | |
| 449 | } | |
| 450 | ||
| 451 | /// Finishes serializing the tuple. | |
| 452 | /// | |
| 453 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 454 | pub fn end(self: *Tuple) Error!void { | |
| 455 | try self.container.end(); | |
| 456 | self.* = undefined; | |
| 457 | } | |
| 458 | ||
| 459 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 460 | pub fn field( | |
| 461 | self: *Tuple, | |
| 462 | val: anytype, | |
| 463 | options: ValueOptions, | |
| 464 | ) Error!void { | |
| 465 | try self.container.field(null, val, options); | |
| 466 | } | |
| 467 | ||
| 468 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 469 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 470 | pub fn fieldMaxDepth( | |
| 471 | self: *Tuple, | |
| 472 | val: anytype, | |
| 473 | options: ValueOptions, | |
| 474 | depth: usize, | |
| 475 | ) DepthError!void { | |
| 476 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 477 | } | |
| 478 | ||
| 479 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 480 | /// `valueArbitraryDepth`. | |
| 481 | pub fn fieldArbitraryDepth( | |
| 482 | self: *Tuple, | |
| 483 | val: anytype, | |
| 484 | options: ValueOptions, | |
| 485 | ) Error!void { | |
| 486 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 487 | } | |
| 488 | ||
| 489 | /// Starts a field with a struct as a value. Returns the struct. | |
| 490 | pub fn beginStructField( | |
| 491 | self: *Tuple, | |
| 492 | options: ContainerOptions, | |
| 493 | ) Error!Struct { | |
| 494 | try self.fieldPrefix(); | |
| 495 | return self.container.serializer.beginStruct(options); | |
| 496 | } | |
| 497 | ||
| 498 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 499 | pub fn beginTupleField( | |
| 500 | self: *Tuple, | |
| 501 | options: ContainerOptions, | |
| 502 | ) Error!Tuple { | |
| 503 | try self.fieldPrefix(); | |
| 504 | return self.container.serializer.beginTuple(options); | |
| 505 | } | |
| 506 | ||
| 507 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 508 | /// configured. Useful if you want to serialize the field value yourself. | |
| 509 | pub fn fieldPrefix(self: *Tuple) Error!void { | |
| 510 | try self.container.fieldPrefix(null); | |
| 511 | } | |
| 512 | }; | |
| 513 | ||
| 514 | /// Writes ZON structs field by field. | |
| 515 | pub const Struct = struct { | |
| 516 | container: Container, | |
| 517 | ||
| 518 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct { | |
| 519 | return .{ | |
| 520 | .container = try Container.begin(parent, .named, options), | |
| 521 | }; | |
| 522 | } | |
| 523 | ||
| 524 | /// Finishes serializing the struct. | |
| 525 | /// | |
| 526 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 527 | pub fn end(self: *Struct) Error!void { | |
| 528 | try self.container.end(); | |
| 529 | self.* = undefined; | |
| 530 | } | |
| 531 | ||
| 532 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 533 | pub fn field( | |
| 534 | self: *Struct, | |
| 535 | name: []const u8, | |
| 536 | val: anytype, | |
| 537 | options: ValueOptions, | |
| 538 | ) Error!void { | |
| 539 | try self.container.field(name, val, options); | |
| 540 | } | |
| 541 | ||
| 542 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 543 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 544 | pub fn fieldMaxDepth( | |
| 545 | self: *Struct, | |
| 546 | name: []const u8, | |
| 547 | val: anytype, | |
| 548 | options: ValueOptions, | |
| 549 | depth: usize, | |
| 550 | ) DepthError!void { | |
| 551 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 552 | } | |
| 553 | ||
| 554 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 555 | /// `valueArbitraryDepth`. | |
| 556 | pub fn fieldArbitraryDepth( | |
| 557 | self: *Struct, | |
| 558 | name: []const u8, | |
| 559 | val: anytype, | |
| 560 | options: ValueOptions, | |
| 561 | ) Error!void { | |
| 562 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 563 | } | |
| 564 | ||
| 565 | /// Starts a field with a struct as a value. Returns the struct. | |
| 566 | pub fn beginStructField( | |
| 567 | self: *Struct, | |
| 568 | name: []const u8, | |
| 569 | options: ContainerOptions, | |
| 570 | ) Error!Struct { | |
| 571 | try self.fieldPrefix(name); | |
| 572 | return self.container.serializer.beginStruct(options); | |
| 573 | } | |
| 574 | ||
| 575 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 576 | pub fn beginTupleField( | |
| 577 | self: *Struct, | |
| 578 | name: []const u8, | |
| 579 | options: ContainerOptions, | |
| 580 | ) Error!Tuple { | |
| 581 | try self.fieldPrefix(name); | |
| 582 | return self.container.serializer.beginTuple(options); | |
| 583 | } | |
| 584 | ||
| 585 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 586 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 587 | /// value yourself. | |
| 588 | pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void { | |
| 589 | try self.container.fieldPrefix(name); | |
| 590 | } | |
| 591 | }; | |
| 592 | ||
| 593 | const Container = struct { | |
| 594 | const FieldStyle = enum { named, anon }; | |
| 595 | ||
| 596 | serializer: *Serializer, | |
| 597 | field_style: FieldStyle, | |
| 598 | options: ContainerOptions, | |
| 599 | empty: bool, | |
| 600 | ||
| 601 | fn begin( | |
| 602 | sz: *Serializer, | |
| 603 | field_style: FieldStyle, | |
| 604 | options: ContainerOptions, | |
| 605 | ) Error!Container { | |
| 606 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 607 | try sz.writer.writeAll(".{"); | |
| 608 | return .{ | |
| 609 | .serializer = sz, | |
| 610 | .field_style = field_style, | |
| 611 | .options = options, | |
| 612 | .empty = true, | |
| 613 | }; | |
| 614 | } | |
| 615 | ||
| 616 | fn end(self: *Container) Error!void { | |
| 617 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 618 | if (!self.empty) { | |
| 619 | if (self.options.shouldWrap()) { | |
| 620 | if (self.serializer.options.whitespace) { | |
| 621 | try self.serializer.writer.writeByte(','); | |
| 622 | } | |
| 623 | try self.serializer.newline(); | |
| 624 | try self.serializer.indent(); | |
| 625 | } else if (!self.shouldElideSpaces()) { | |
| 626 | try self.serializer.space(); | |
| 627 | } | |
| 628 | } | |
| 629 | try self.serializer.writer.writeByte('}'); | |
| 630 | self.* = undefined; | |
| 631 | } | |
| 632 | ||
| 633 | fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void { | |
| 634 | if (!self.empty) { | |
| 635 | try self.serializer.writer.writeByte(','); | |
| 636 | } | |
| 637 | self.empty = false; | |
| 638 | if (self.options.shouldWrap()) { | |
| 639 | try self.serializer.newline(); | |
| 640 | } else if (!self.shouldElideSpaces()) { | |
| 641 | try self.serializer.space(); | |
| 642 | } | |
| 643 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 644 | if (name) |n| { | |
| 645 | try self.serializer.ident(n); | |
| 646 | try self.serializer.space(); | |
| 647 | try self.serializer.writer.writeByte('='); | |
| 648 | try self.serializer.space(); | |
| 649 | } | |
| 650 | } | |
| 651 | ||
| 652 | fn field( | |
| 653 | self: *Container, | |
| 654 | name: ?[]const u8, | |
| 655 | val: anytype, | |
| 656 | options: ValueOptions, | |
| 657 | ) Error!void { | |
| 658 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 659 | try self.fieldArbitraryDepth(name, val, options); | |
| 660 | } | |
| 661 | ||
| 662 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 663 | fn fieldMaxDepth( | |
| 664 | self: *Container, | |
| 665 | name: ?[]const u8, | |
| 666 | val: anytype, | |
| 667 | options: ValueOptions, | |
| 668 | depth: usize, | |
| 669 | ) DepthError!void { | |
| 670 | try checkValueDepth(val, depth); | |
| 671 | try self.fieldArbitraryDepth(name, val, options); | |
| 672 | } | |
| 673 | ||
| 674 | fn fieldArbitraryDepth( | |
| 675 | self: *Container, | |
| 676 | name: ?[]const u8, | |
| 677 | val: anytype, | |
| 678 | options: ValueOptions, | |
| 679 | ) Error!void { | |
| 680 | try self.fieldPrefix(name); | |
| 681 | try self.serializer.valueArbitraryDepth(val, options); | |
| 682 | } | |
| 683 | ||
| 684 | fn shouldElideSpaces(self: *const Container) bool { | |
| 685 | return switch (self.options.whitespace_style) { | |
| 686 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 687 | else => false, | |
| 688 | }; | |
| 689 | } | |
| 690 | }; | |
| 691 | ||
| 692 | test Serializer { | |
| 693 | var discarding: Writer.Discarding = .init(&.{}); | |
| 694 | var s: Serializer = .{ .writer = &discarding.writer }; | |
| 695 | var vec2 = try s.beginStruct(.{}); | |
| 696 | try vec2.field("x", 1.5, .{}); | |
| 697 | try vec2.fieldPrefix("prefix"); | |
| 698 | try s.value(2.5, .{}); | |
| 699 | try vec2.end(); | |
| 700 | } | |
| 701 | ||
| 702 | inline fn typeIsRecursive(comptime T: type) bool { | |
| 703 | return comptime typeIsRecursiveInner(T, &.{}); | |
| 704 | } | |
| 705 | ||
| 706 | fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool { | |
| 707 | for (prev_visited) |V| { | |
| 708 | if (V == T) return true; | |
| 709 | } | |
| 710 | const visited = prev_visited ++ .{T}; | |
| 711 | ||
| 712 | return switch (@typeInfo(T)) { | |
| 713 | .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited), | |
| 714 | .optional => |optional| typeIsRecursiveInner(optional.child, visited), | |
| 715 | .array => |array| typeIsRecursiveInner(array.child, visited), | |
| 716 | .vector => |vector| typeIsRecursiveInner(vector.child, visited), | |
| 717 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 718 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 719 | } else false, | |
| 720 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 721 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 722 | } else false, | |
| 723 | else => false, | |
| 724 | }; | |
| 725 | } | |
| 726 | ||
| 727 | test typeIsRecursive { | |
| 728 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 729 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 730 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 731 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 732 | try std.testing.expect(typeIsRecursive(struct { | |
| 733 | a: struct { | |
| 734 | const A = @This(); | |
| 735 | b: struct { | |
| 736 | c: *struct { | |
| 737 | a: ?A, | |
| 738 | }, | |
| 739 | }, | |
| 740 | }, | |
| 741 | })); | |
| 742 | try std.testing.expect(typeIsRecursive(struct { | |
| 743 | a: [3]*@This(), | |
| 744 | })); | |
| 745 | try std.testing.expect(typeIsRecursive(struct { | |
| 746 | a: union { a: i32, b: *@This() }, | |
| 747 | })); | |
| 748 | } | |
| 749 | ||
| 750 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 751 | if (depth == 0) return error.ExceededMaxDepth; | |
| 752 | const child_depth = depth - 1; | |
| 753 | ||
| 754 | switch (@typeInfo(@TypeOf(val))) { | |
| 755 | .pointer => |pointer| switch (pointer.size) { | |
| 756 | .one => try checkValueDepth(val.*, child_depth), | |
| 757 | .slice => for (val) |item| { | |
| 758 | try checkValueDepth(item, child_depth); | |
| 759 | }, | |
| 760 | .c, .many => {}, | |
| 761 | }, | |
| 762 | .array => for (val) |item| { | |
| 763 | try checkValueDepth(item, child_depth); | |
| 764 | }, | |
| 765 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 766 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 767 | }, | |
| 768 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 769 | return; | |
| 770 | } else switch (val) { | |
| 771 | inline else => |payload| { | |
| 772 | return checkValueDepth(payload, child_depth); | |
| 773 | }, | |
| 774 | }, | |
| 775 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 776 | else => {}, | |
| 777 | } | |
| 778 | } | |
| 779 | ||
| 780 | fn expectValueDepthEquals(expected: usize, v: anytype) !void { | |
| 781 | try checkValueDepth(v, expected); | |
| 782 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1)); | |
| 783 | } | |
| 784 | ||
| 785 | test checkValueDepth { | |
| 786 | try expectValueDepthEquals(1, 10); | |
| 787 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 788 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 789 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 790 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 791 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 792 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 793 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 794 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 795 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 796 | try expectValueDepthEquals(1, null); | |
| 797 | try expectValueDepthEquals(2, &1); | |
| 798 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 799 | ||
| 800 | const Union = union(enum) { | |
| 801 | x: u32, | |
| 802 | y: struct { x: u32 }, | |
| 803 | }; | |
| 804 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 805 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 806 | ||
| 807 | const Recurse = struct { r: ?*const @This() }; | |
| 808 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 809 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 810 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 811 | ||
| 812 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 813 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 814 | } | |
| 815 | ||
| 816 | inline fn canSerializeType(T: type) bool { | |
| 817 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 818 | } | |
| 819 | ||
| 820 | fn canSerializeTypeInner( | |
| 821 | T: type, | |
| 822 | /// Visited structs and unions, to avoid infinite recursion. | |
| 823 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 824 | visited: []const type, | |
| 825 | parent_is_optional: bool, | |
| 826 | ) bool { | |
| 827 | return switch (@typeInfo(T)) { | |
| 828 | .bool, | |
| 829 | .int, | |
| 830 | .float, | |
| 831 | .comptime_float, | |
| 832 | .comptime_int, | |
| 833 | .null, | |
| 834 | .enum_literal, | |
| 835 | => true, | |
| 836 | ||
| 837 | .noreturn, | |
| 838 | .void, | |
| 839 | .type, | |
| 840 | .undefined, | |
| 841 | .error_union, | |
| 842 | .error_set, | |
| 843 | .@"fn", | |
| 844 | .frame, | |
| 845 | .@"anyframe", | |
| 846 | .@"opaque", | |
| 847 | => false, | |
| 848 | ||
| 849 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 850 | ||
| 851 | .pointer => |pointer| switch (pointer.size) { | |
| 852 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 853 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 854 | .many, .c => false, | |
| 855 | }, | |
| 856 | ||
| 857 | .optional => |optional| if (parent_is_optional) | |
| 858 | false | |
| 859 | else | |
| 860 | canSerializeTypeInner(optional.child, visited, true), | |
| 861 | ||
| 862 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 863 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 864 | ||
| 865 | .@"struct" => |@"struct"| { | |
| 866 | for (visited) |V| if (T == V) return true; | |
| 867 | const new_visited = visited ++ .{T}; | |
| 868 | for (@"struct".fields) |field| { | |
| 869 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 870 | } | |
| 871 | return true; | |
| 872 | }, | |
| 873 | .@"union" => |@"union"| { | |
| 874 | for (visited) |V| if (T == V) return true; | |
| 875 | const new_visited = visited ++ .{T}; | |
| 876 | if (@"union".tag_type == null) return false; | |
| 877 | for (@"union".fields) |field| { | |
| 878 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 879 | return false; | |
| 880 | } | |
| 881 | } | |
| 882 | return true; | |
| 883 | }, | |
| 884 | }; | |
| 885 | } | |
| 886 | ||
| 887 | test canSerializeType { | |
| 888 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 889 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 890 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 891 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 892 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 893 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 894 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 895 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 896 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 897 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 898 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 899 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 900 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 901 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 902 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 903 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 904 | foo: ?struct { | |
| 905 | ?union(enum) { | |
| 906 | a: ?@Vector(0, ?*u8), | |
| 907 | }, | |
| 908 | ?struct { | |
| 909 | f: ?[]?u8, | |
| 910 | }, | |
| 911 | }, | |
| 912 | })); | |
| 913 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 914 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 915 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 916 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 917 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 918 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 919 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 920 | const Recursive = struct { foo: ?*@This() }; | |
| 921 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 922 | ||
| 923 | // Make sure we validate nested optional before we early out due to already having seen | |
| 924 | // a type recursion! | |
| 925 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 926 | add_to_visited: ?u8, | |
| 927 | retrieve_from_visited: ??u8, | |
| 928 | })); | |
| 929 | } |
lib/std/zon/stringify.zig+2-934| ... | ... | @@ -23,13 +23,13 @@ |
| 23 | 23 | const std = @import("std"); |
| 24 | 24 | const assert = std.debug.assert; |
| 25 | 25 | const Writer = std.Io.Writer; |
| 26 | const Serializer = std.zon.Serializer; | |
| 26 | 27 | |
| 27 | /// Options for `serialize`. | |
| 28 | 28 | pub const SerializeOptions = struct { |
| 29 | 29 | /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. |
| 30 | 30 | whitespace: bool = true, |
| 31 | 31 | /// Determines when to emit Unicode code point literals as opposed to integer literals. |
| 32 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 32 | emit_codepoint_literals: Serializer.EmitCodepointLiterals = .never, | |
| 33 | 33 | /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. |
| 34 | 34 | /// Otherwise they are serialized as string literals. |
| 35 | 35 | emit_strings_as_containers: bool = false, |
| ... | ... | @@ -93,102 +93,6 @@ pub fn serializeArbitraryDepth( |
| 93 | 93 | }); |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | inline fn typeIsRecursive(comptime T: type) bool { | |
| 97 | return comptime typeIsRecursiveInner(T, &.{}); | |
| 98 | } | |
| 99 | ||
| 100 | fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool { | |
| 101 | for (prev_visited) |V| { | |
| 102 | if (V == T) return true; | |
| 103 | } | |
| 104 | const visited = prev_visited ++ .{T}; | |
| 105 | ||
| 106 | return switch (@typeInfo(T)) { | |
| 107 | .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited), | |
| 108 | .optional => |optional| typeIsRecursiveInner(optional.child, visited), | |
| 109 | .array => |array| typeIsRecursiveInner(array.child, visited), | |
| 110 | .vector => |vector| typeIsRecursiveInner(vector.child, visited), | |
| 111 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 112 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 113 | } else false, | |
| 114 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 115 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 116 | } else false, | |
| 117 | else => false, | |
| 118 | }; | |
| 119 | } | |
| 120 | ||
| 121 | inline fn canSerializeType(T: type) bool { | |
| 122 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 123 | } | |
| 124 | ||
| 125 | fn canSerializeTypeInner( | |
| 126 | T: type, | |
| 127 | /// Visited structs and unions, to avoid infinite recursion. | |
| 128 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 129 | visited: []const type, | |
| 130 | parent_is_optional: bool, | |
| 131 | ) bool { | |
| 132 | return switch (@typeInfo(T)) { | |
| 133 | .bool, | |
| 134 | .int, | |
| 135 | .float, | |
| 136 | .comptime_float, | |
| 137 | .comptime_int, | |
| 138 | .null, | |
| 139 | .enum_literal, | |
| 140 | => true, | |
| 141 | ||
| 142 | .noreturn, | |
| 143 | .void, | |
| 144 | .type, | |
| 145 | .undefined, | |
| 146 | .error_union, | |
| 147 | .error_set, | |
| 148 | .@"fn", | |
| 149 | .frame, | |
| 150 | .@"anyframe", | |
| 151 | .@"opaque", | |
| 152 | => false, | |
| 153 | ||
| 154 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 155 | ||
| 156 | .pointer => |pointer| switch (pointer.size) { | |
| 157 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 158 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 159 | .many, .c => false, | |
| 160 | }, | |
| 161 | ||
| 162 | .optional => |optional| if (parent_is_optional) | |
| 163 | false | |
| 164 | else | |
| 165 | canSerializeTypeInner(optional.child, visited, true), | |
| 166 | ||
| 167 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 168 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 169 | ||
| 170 | .@"struct" => |@"struct"| { | |
| 171 | for (visited) |V| if (T == V) return true; | |
| 172 | const new_visited = visited ++ .{T}; | |
| 173 | for (@"struct".fields) |field| { | |
| 174 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 175 | } | |
| 176 | return true; | |
| 177 | }, | |
| 178 | .@"union" => |@"union"| { | |
| 179 | for (visited) |V| if (T == V) return true; | |
| 180 | const new_visited = visited ++ .{T}; | |
| 181 | if (@"union".tag_type == null) return false; | |
| 182 | for (@"union".fields) |field| { | |
| 183 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 184 | return false; | |
| 185 | } | |
| 186 | } | |
| 187 | return true; | |
| 188 | }, | |
| 189 | }; | |
| 190 | } | |
| 191 | ||
| 192 | 96 | fn isNestedOptional(T: type) bool { |
| 193 | 97 | comptime switch (@typeInfo(T)) { |
| 194 | 98 | .optional => |optional| return isNestedOptionalInner(optional.child), |
| ... | ... | @@ -210,842 +114,6 @@ fn isNestedOptionalInner(T: type) bool { |
| 210 | 114 | } |
| 211 | 115 | } |
| 212 | 116 | |
| 213 | test "std.zon stringify canSerializeType" { | |
| 214 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 215 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 216 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 217 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 218 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 219 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 220 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 221 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 222 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 223 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 224 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 225 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 226 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 227 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 228 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 229 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 230 | foo: ?struct { | |
| 231 | ?union(enum) { | |
| 232 | a: ?@Vector(0, ?*u8), | |
| 233 | }, | |
| 234 | ?struct { | |
| 235 | f: ?[]?u8, | |
| 236 | }, | |
| 237 | }, | |
| 238 | })); | |
| 239 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 240 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 241 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 242 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 243 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 244 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 245 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 246 | const Recursive = struct { foo: ?*@This() }; | |
| 247 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 248 | ||
| 249 | // Make sure we validate nested optional before we early out due to already having seen | |
| 250 | // a type recursion! | |
| 251 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 252 | add_to_visited: ?u8, | |
| 253 | retrieve_from_visited: ??u8, | |
| 254 | })); | |
| 255 | } | |
| 256 | ||
| 257 | test "std.zon typeIsRecursive" { | |
| 258 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 259 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 260 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 261 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 262 | try std.testing.expect(typeIsRecursive(struct { | |
| 263 | a: struct { | |
| 264 | const A = @This(); | |
| 265 | b: struct { | |
| 266 | c: *struct { | |
| 267 | a: ?A, | |
| 268 | }, | |
| 269 | }, | |
| 270 | }, | |
| 271 | })); | |
| 272 | try std.testing.expect(typeIsRecursive(struct { | |
| 273 | a: [3]*@This(), | |
| 274 | })); | |
| 275 | try std.testing.expect(typeIsRecursive(struct { | |
| 276 | a: union { a: i32, b: *@This() }, | |
| 277 | })); | |
| 278 | } | |
| 279 | ||
| 280 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 281 | if (depth == 0) return error.ExceededMaxDepth; | |
| 282 | const child_depth = depth - 1; | |
| 283 | ||
| 284 | switch (@typeInfo(@TypeOf(val))) { | |
| 285 | .pointer => |pointer| switch (pointer.size) { | |
| 286 | .one => try checkValueDepth(val.*, child_depth), | |
| 287 | .slice => for (val) |item| { | |
| 288 | try checkValueDepth(item, child_depth); | |
| 289 | }, | |
| 290 | .c, .many => {}, | |
| 291 | }, | |
| 292 | .array => for (val) |item| { | |
| 293 | try checkValueDepth(item, child_depth); | |
| 294 | }, | |
| 295 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 296 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 297 | }, | |
| 298 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 299 | return; | |
| 300 | } else switch (val) { | |
| 301 | inline else => |payload| { | |
| 302 | return checkValueDepth(payload, child_depth); | |
| 303 | }, | |
| 304 | }, | |
| 305 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 306 | else => {}, | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | fn expectValueDepthEquals(expected: usize, value: anytype) !void { | |
| 311 | try checkValueDepth(value, expected); | |
| 312 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1)); | |
| 313 | } | |
| 314 | ||
| 315 | test "std.zon checkValueDepth" { | |
| 316 | try expectValueDepthEquals(1, 10); | |
| 317 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 318 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 319 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 320 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 321 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 322 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 323 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 324 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 325 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 326 | try expectValueDepthEquals(1, null); | |
| 327 | try expectValueDepthEquals(2, &1); | |
| 328 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 329 | ||
| 330 | const Union = union(enum) { | |
| 331 | x: u32, | |
| 332 | y: struct { x: u32 }, | |
| 333 | }; | |
| 334 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 335 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 336 | ||
| 337 | const Recurse = struct { r: ?*const @This() }; | |
| 338 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 339 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 340 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 341 | ||
| 342 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 343 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 344 | } | |
| 345 | ||
| 346 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 347 | pub const EmitCodepointLiterals = enum { | |
| 348 | /// Never emit Unicode code point literals. | |
| 349 | never, | |
| 350 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 351 | printable_ascii, | |
| 352 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 353 | /// whose value is a valid non-surrogate code point. | |
| 354 | always, | |
| 355 | ||
| 356 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 357 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 358 | // Rule out incompatible integer types | |
| 359 | switch (@typeInfo(@TypeOf(val))) { | |
| 360 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 361 | return null; | |
| 362 | }, | |
| 363 | .comptime_int => {}, | |
| 364 | else => comptime unreachable, | |
| 365 | } | |
| 366 | ||
| 367 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 368 | // to a u21 if it should. | |
| 369 | switch (self) { | |
| 370 | .always => { | |
| 371 | const c = std.math.cast(u21, val) orelse return null; | |
| 372 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 373 | return c; | |
| 374 | }, | |
| 375 | .printable_ascii => { | |
| 376 | const c = std.math.cast(u8, val) orelse return null; | |
| 377 | if (!std.ascii.isPrint(c)) return null; | |
| 378 | return c; | |
| 379 | }, | |
| 380 | .never => { | |
| 381 | return null; | |
| 382 | }, | |
| 383 | } | |
| 384 | } | |
| 385 | }; | |
| 386 | ||
| 387 | /// Options for serialization of an individual value. | |
| 388 | /// | |
| 389 | /// See `SerializeOptions` for more information on these options. | |
| 390 | pub const ValueOptions = struct { | |
| 391 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 392 | emit_strings_as_containers: bool = false, | |
| 393 | emit_default_optional_fields: bool = true, | |
| 394 | }; | |
| 395 | ||
| 396 | /// Options for manual serialization of container types. | |
| 397 | pub const SerializeContainerOptions = struct { | |
| 398 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 399 | whitespace_style: union(enum) { | |
| 400 | /// If true, wrap every field. If false do not. | |
| 401 | wrap: bool, | |
| 402 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 403 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 404 | fields: usize, | |
| 405 | } = .{ .wrap = true }, | |
| 406 | ||
| 407 | fn shouldWrap(self: SerializeContainerOptions) bool { | |
| 408 | return switch (self.whitespace_style) { | |
| 409 | .wrap => |wrap| wrap, | |
| 410 | .fields => |fields| fields > 2, | |
| 411 | }; | |
| 412 | } | |
| 413 | }; | |
| 414 | ||
| 415 | /// Lower level control over serialization, you can create a new instance with `serializer`. | |
| 416 | /// | |
| 417 | /// Useful when you want control over which fields are serialized, how they're represented, | |
| 418 | /// or want to write a ZON object that does not exist in memory. | |
| 419 | /// | |
| 420 | /// You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 421 | /// * `valueMaxDepth` | |
| 422 | /// * `valueArbitraryDepth` | |
| 423 | /// | |
| 424 | /// You can also serialize values using specific notations: | |
| 425 | /// * `int` | |
| 426 | /// * `float` | |
| 427 | /// * `codePoint` | |
| 428 | /// * `tuple` | |
| 429 | /// * `tupleMaxDepth` | |
| 430 | /// * `tupleArbitraryDepth` | |
| 431 | /// * `string` | |
| 432 | /// * `multilineString` | |
| 433 | /// | |
| 434 | /// For manual serialization of containers, see: | |
| 435 | /// * `beginStruct` | |
| 436 | /// * `beginTuple` | |
| 437 | pub const Serializer = struct { | |
| 438 | options: Options = .{}, | |
| 439 | indent_level: u8 = 0, | |
| 440 | writer: *Writer, | |
| 441 | ||
| 442 | pub const Error = Writer.Error; | |
| 443 | pub const DepthError = Error || error{ExceededMaxDepth}; | |
| 444 | ||
| 445 | pub const Options = struct { | |
| 446 | /// If false, only syntactically necessary whitespace is emitted. | |
| 447 | whitespace: bool = true, | |
| 448 | }; | |
| 449 | ||
| 450 | /// Serialize a value, similar to `serialize`. | |
| 451 | pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 452 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 453 | return self.valueArbitraryDepth(val, options); | |
| 454 | } | |
| 455 | ||
| 456 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 457 | /// Can return `error.ExceededMaxDepth`. | |
| 458 | pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void { | |
| 459 | try checkValueDepth(val, depth); | |
| 460 | return self.valueArbitraryDepth(val, options); | |
| 461 | } | |
| 462 | ||
| 463 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 464 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 465 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 466 | switch (@typeInfo(@TypeOf(val))) { | |
| 467 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 468 | self.codePoint(c) catch |err| switch (err) { | |
| 469 | error.InvalidCodepoint => unreachable, // Already validated | |
| 470 | else => |e| return e, | |
| 471 | }; | |
| 472 | } else { | |
| 473 | try self.int(val); | |
| 474 | }, | |
| 475 | .float, .comptime_float => try self.float(val), | |
| 476 | .bool, .null => try self.writer.print("{}", .{val}), | |
| 477 | .enum_literal => try self.ident(@tagName(val)), | |
| 478 | .@"enum" => try self.ident(@tagName(val)), | |
| 479 | .pointer => |pointer| { | |
| 480 | // Try to serialize as a string | |
| 481 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 482 | .array => |array| array.child, | |
| 483 | else => if (pointer.size == .slice) pointer.child else null, | |
| 484 | }; | |
| 485 | if (item == u8 and | |
| 486 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 487 | !options.emit_strings_as_containers) | |
| 488 | { | |
| 489 | return try self.string(val); | |
| 490 | } | |
| 491 | ||
| 492 | // Serialize as either a tuple or as the child type | |
| 493 | switch (pointer.size) { | |
| 494 | .slice => try self.tupleImpl(val, options), | |
| 495 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 496 | else => comptime unreachable, | |
| 497 | } | |
| 498 | }, | |
| 499 | .array => { | |
| 500 | var container = try self.beginTuple( | |
| 501 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 502 | ); | |
| 503 | for (val) |item_val| { | |
| 504 | try container.fieldArbitraryDepth(item_val, options); | |
| 505 | } | |
| 506 | try container.end(); | |
| 507 | }, | |
| 508 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 509 | var container = try self.beginTuple( | |
| 510 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 511 | ); | |
| 512 | inline for (val) |field_value| { | |
| 513 | try container.fieldArbitraryDepth(field_value, options); | |
| 514 | } | |
| 515 | try container.end(); | |
| 516 | } else { | |
| 517 | // Decide which fields to emit | |
| 518 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 519 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 520 | } else b: { | |
| 521 | var fields = @"struct".fields.len; | |
| 522 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 523 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 524 | if (field_info.default_value_ptr) |ptr| { | |
| 525 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 526 | const field_value = @field(val, field_info.name); | |
| 527 | if (std.meta.eql(field_value, default.*)) { | |
| 528 | skip.* = true; | |
| 529 | fields -= 1; | |
| 530 | } | |
| 531 | } | |
| 532 | } | |
| 533 | break :b .{ fields, skipped }; | |
| 534 | }; | |
| 535 | ||
| 536 | // Emit those fields | |
| 537 | var container = try self.beginStruct( | |
| 538 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 539 | ); | |
| 540 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 541 | if (!skip) { | |
| 542 | try container.fieldArbitraryDepth( | |
| 543 | field_info.name, | |
| 544 | @field(val, field_info.name), | |
| 545 | options, | |
| 546 | ); | |
| 547 | } | |
| 548 | } | |
| 549 | try container.end(); | |
| 550 | }, | |
| 551 | .@"union" => |@"union"| { | |
| 552 | comptime assert(@"union".tag_type != null); | |
| 553 | switch (val) { | |
| 554 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 555 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 556 | else { | |
| 557 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 558 | ||
| 559 | try container.fieldArbitraryDepth( | |
| 560 | @tagName(tag), | |
| 561 | pl, | |
| 562 | options, | |
| 563 | ); | |
| 564 | ||
| 565 | try container.end(); | |
| 566 | }, | |
| 567 | } | |
| 568 | }, | |
| 569 | .optional => if (val) |inner| { | |
| 570 | try self.valueArbitraryDepth(inner, options); | |
| 571 | } else { | |
| 572 | try self.writer.writeAll("null"); | |
| 573 | }, | |
| 574 | .vector => |vector| { | |
| 575 | var container = try self.beginTuple( | |
| 576 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 577 | ); | |
| 578 | for (0..vector.len) |i| { | |
| 579 | try container.fieldArbitraryDepth(val[i], options); | |
| 580 | } | |
| 581 | try container.end(); | |
| 582 | }, | |
| 583 | ||
| 584 | else => comptime unreachable, | |
| 585 | } | |
| 586 | } | |
| 587 | ||
| 588 | /// Serialize an integer. | |
| 589 | pub fn int(self: *Serializer, val: anytype) Error!void { | |
| 590 | try self.writer.printInt(val, 10, .lower, .{}); | |
| 591 | } | |
| 592 | ||
| 593 | /// Serialize a float. | |
| 594 | pub fn float(self: *Serializer, val: anytype) Error!void { | |
| 595 | switch (@typeInfo(@TypeOf(val))) { | |
| 596 | .float => if (std.math.isNan(val)) { | |
| 597 | return self.writer.writeAll("nan"); | |
| 598 | } else if (std.math.isPositiveInf(val)) { | |
| 599 | return self.writer.writeAll("inf"); | |
| 600 | } else if (std.math.isNegativeInf(val)) { | |
| 601 | return self.writer.writeAll("-inf"); | |
| 602 | } else if (std.math.isNegativeZero(val)) { | |
| 603 | return self.writer.writeAll("-0.0"); | |
| 604 | } else { | |
| 605 | try self.writer.print("{d}", .{val}); | |
| 606 | }, | |
| 607 | .comptime_float => if (val == 0) { | |
| 608 | return self.writer.writeAll("0"); | |
| 609 | } else { | |
| 610 | try self.writer.print("{d}", .{val}); | |
| 611 | }, | |
| 612 | else => comptime unreachable, | |
| 613 | } | |
| 614 | } | |
| 615 | ||
| 616 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 617 | /// | |
| 618 | /// Escapes the identifier if necessary. | |
| 619 | pub fn ident(self: *Serializer, name: []const u8) Error!void { | |
| 620 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); | |
| 621 | } | |
| 622 | ||
| 623 | pub const CodePointError = Error || error{InvalidCodepoint}; | |
| 624 | ||
| 625 | /// Serialize `val` as a Unicode codepoint. | |
| 626 | /// | |
| 627 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 628 | pub fn codePoint(self: *Serializer, val: u21) CodePointError!void { | |
| 629 | try self.writer.print("'{f}'", .{std.zig.fmtChar(val)}); | |
| 630 | } | |
| 631 | ||
| 632 | /// Like `value`, but always serializes `val` as a tuple. | |
| 633 | /// | |
| 634 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 635 | pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 636 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 637 | try self.tupleArbitraryDepth(val, options); | |
| 638 | } | |
| 639 | ||
| 640 | /// Like `tuple`, but recursive types are allowed. | |
| 641 | /// | |
| 642 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 643 | pub fn tupleMaxDepth( | |
| 644 | self: *Serializer, | |
| 645 | val: anytype, | |
| 646 | options: ValueOptions, | |
| 647 | depth: usize, | |
| 648 | ) DepthError!void { | |
| 649 | try checkValueDepth(val, depth); | |
| 650 | try self.tupleArbitraryDepth(val, options); | |
| 651 | } | |
| 652 | ||
| 653 | /// Like `tuple`, but recursive types are allowed. | |
| 654 | /// | |
| 655 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 656 | pub fn tupleArbitraryDepth( | |
| 657 | self: *Serializer, | |
| 658 | val: anytype, | |
| 659 | options: ValueOptions, | |
| 660 | ) Error!void { | |
| 661 | try self.tupleImpl(val, options); | |
| 662 | } | |
| 663 | ||
| 664 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 665 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 666 | switch (@typeInfo(@TypeOf(val))) { | |
| 667 | .@"struct" => { | |
| 668 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 669 | inline for (val) |item_val| { | |
| 670 | try container.fieldArbitraryDepth(item_val, options); | |
| 671 | } | |
| 672 | try container.end(); | |
| 673 | }, | |
| 674 | .pointer, .array => { | |
| 675 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 676 | for (val) |item_val| { | |
| 677 | try container.fieldArbitraryDepth(item_val, options); | |
| 678 | } | |
| 679 | try container.end(); | |
| 680 | }, | |
| 681 | else => comptime unreachable, | |
| 682 | } | |
| 683 | } | |
| 684 | ||
| 685 | /// Like `value`, but always serializes `val` as a string. | |
| 686 | pub fn string(self: *Serializer, val: []const u8) Error!void { | |
| 687 | try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)}); | |
| 688 | } | |
| 689 | ||
| 690 | /// Options for formatting multiline strings. | |
| 691 | pub const MultilineStringOptions = struct { | |
| 692 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 693 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 694 | /// whitespace is true any necessary indentation follows. | |
| 695 | top_level: bool = false, | |
| 696 | }; | |
| 697 | ||
| 698 | pub const MultilineStringError = Error || error{InnerCarriageReturn}; | |
| 699 | ||
| 700 | /// Like `value`, but always serializes to a multiline string literal. | |
| 701 | /// | |
| 702 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 703 | /// since multiline strings cannot represent CR without a following newline. | |
| 704 | pub fn multilineString( | |
| 705 | self: *Serializer, | |
| 706 | val: []const u8, | |
| 707 | options: MultilineStringOptions, | |
| 708 | ) MultilineStringError!void { | |
| 709 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 710 | var i: usize = 0; | |
| 711 | while (i < val.len) : (i += 1) { | |
| 712 | if (val[i] == '\r') { | |
| 713 | if (i + 1 < val.len) { | |
| 714 | if (val[i + 1] == '\n') { | |
| 715 | i += 1; | |
| 716 | continue; | |
| 717 | } | |
| 718 | } | |
| 719 | return error.InnerCarriageReturn; | |
| 720 | } | |
| 721 | } | |
| 722 | ||
| 723 | if (!options.top_level) { | |
| 724 | try self.newline(); | |
| 725 | try self.indent(); | |
| 726 | } | |
| 727 | ||
| 728 | try self.writer.writeAll("\\\\"); | |
| 729 | for (val) |c| { | |
| 730 | if (c != '\r') { | |
| 731 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 732 | if (c == '\n') { | |
| 733 | try self.indent(); | |
| 734 | try self.writer.writeAll("\\\\"); | |
| 735 | } | |
| 736 | } | |
| 737 | } | |
| 738 | ||
| 739 | if (!options.top_level) { | |
| 740 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 741 | try self.indent(); | |
| 742 | } | |
| 743 | } | |
| 744 | ||
| 745 | /// Create a `Struct` for writing ZON structs field by field. | |
| 746 | pub fn beginStruct( | |
| 747 | self: *Serializer, | |
| 748 | options: SerializeContainerOptions, | |
| 749 | ) Error!Struct { | |
| 750 | return Struct.begin(self, options); | |
| 751 | } | |
| 752 | ||
| 753 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 754 | pub fn beginTuple( | |
| 755 | self: *Serializer, | |
| 756 | options: SerializeContainerOptions, | |
| 757 | ) Error!Tuple { | |
| 758 | return Tuple.begin(self, options); | |
| 759 | } | |
| 760 | ||
| 761 | fn indent(self: *Serializer) Error!void { | |
| 762 | if (self.options.whitespace) { | |
| 763 | try self.writer.splatByteAll(' ', 4 * self.indent_level); | |
| 764 | } | |
| 765 | } | |
| 766 | ||
| 767 | fn newline(self: *Serializer) Error!void { | |
| 768 | if (self.options.whitespace) { | |
| 769 | try self.writer.writeByte('\n'); | |
| 770 | } | |
| 771 | } | |
| 772 | ||
| 773 | fn newlineOrSpace(self: *Serializer, len: usize) Error!void { | |
| 774 | if (self.containerShouldWrap(len)) { | |
| 775 | try self.newline(); | |
| 776 | } else { | |
| 777 | try self.space(); | |
| 778 | } | |
| 779 | } | |
| 780 | ||
| 781 | fn space(self: *Serializer) Error!void { | |
| 782 | if (self.options.whitespace) { | |
| 783 | try self.writer.writeByte(' '); | |
| 784 | } | |
| 785 | } | |
| 786 | ||
| 787 | /// Writes ZON tuples field by field. | |
| 788 | pub const Tuple = struct { | |
| 789 | container: Container, | |
| 790 | ||
| 791 | fn begin(parent: *Serializer, options: SerializeContainerOptions) Error!Tuple { | |
| 792 | return .{ | |
| 793 | .container = try Container.begin(parent, .anon, options), | |
| 794 | }; | |
| 795 | } | |
| 796 | ||
| 797 | /// Finishes serializing the tuple. | |
| 798 | /// | |
| 799 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 800 | pub fn end(self: *Tuple) Error!void { | |
| 801 | try self.container.end(); | |
| 802 | self.* = undefined; | |
| 803 | } | |
| 804 | ||
| 805 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 806 | pub fn field( | |
| 807 | self: *Tuple, | |
| 808 | val: anytype, | |
| 809 | options: ValueOptions, | |
| 810 | ) Error!void { | |
| 811 | try self.container.field(null, val, options); | |
| 812 | } | |
| 813 | ||
| 814 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 815 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 816 | pub fn fieldMaxDepth( | |
| 817 | self: *Tuple, | |
| 818 | val: anytype, | |
| 819 | options: ValueOptions, | |
| 820 | depth: usize, | |
| 821 | ) DepthError!void { | |
| 822 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 823 | } | |
| 824 | ||
| 825 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 826 | /// `valueArbitraryDepth`. | |
| 827 | pub fn fieldArbitraryDepth( | |
| 828 | self: *Tuple, | |
| 829 | val: anytype, | |
| 830 | options: ValueOptions, | |
| 831 | ) Error!void { | |
| 832 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 833 | } | |
| 834 | ||
| 835 | /// Starts a field with a struct as a value. Returns the struct. | |
| 836 | pub fn beginStructField( | |
| 837 | self: *Tuple, | |
| 838 | options: SerializeContainerOptions, | |
| 839 | ) Error!Struct { | |
| 840 | try self.fieldPrefix(); | |
| 841 | return self.container.serializer.beginStruct(options); | |
| 842 | } | |
| 843 | ||
| 844 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 845 | pub fn beginTupleField( | |
| 846 | self: *Tuple, | |
| 847 | options: SerializeContainerOptions, | |
| 848 | ) Error!Tuple { | |
| 849 | try self.fieldPrefix(); | |
| 850 | return self.container.serializer.beginTuple(options); | |
| 851 | } | |
| 852 | ||
| 853 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 854 | /// configured. Useful if you want to serialize the field value yourself. | |
| 855 | pub fn fieldPrefix(self: *Tuple) Error!void { | |
| 856 | try self.container.fieldPrefix(null); | |
| 857 | } | |
| 858 | }; | |
| 859 | ||
| 860 | /// Writes ZON structs field by field. | |
| 861 | pub const Struct = struct { | |
| 862 | container: Container, | |
| 863 | ||
| 864 | fn begin(parent: *Serializer, options: SerializeContainerOptions) Error!Struct { | |
| 865 | return .{ | |
| 866 | .container = try Container.begin(parent, .named, options), | |
| 867 | }; | |
| 868 | } | |
| 869 | ||
| 870 | /// Finishes serializing the struct. | |
| 871 | /// | |
| 872 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 873 | pub fn end(self: *Struct) Error!void { | |
| 874 | try self.container.end(); | |
| 875 | self.* = undefined; | |
| 876 | } | |
| 877 | ||
| 878 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 879 | pub fn field( | |
| 880 | self: *Struct, | |
| 881 | name: []const u8, | |
| 882 | val: anytype, | |
| 883 | options: ValueOptions, | |
| 884 | ) Error!void { | |
| 885 | try self.container.field(name, val, options); | |
| 886 | } | |
| 887 | ||
| 888 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 889 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 890 | pub fn fieldMaxDepth( | |
| 891 | self: *Struct, | |
| 892 | name: []const u8, | |
| 893 | val: anytype, | |
| 894 | options: ValueOptions, | |
| 895 | depth: usize, | |
| 896 | ) DepthError!void { | |
| 897 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 898 | } | |
| 899 | ||
| 900 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 901 | /// `valueArbitraryDepth`. | |
| 902 | pub fn fieldArbitraryDepth( | |
| 903 | self: *Struct, | |
| 904 | name: []const u8, | |
| 905 | val: anytype, | |
| 906 | options: ValueOptions, | |
| 907 | ) Error!void { | |
| 908 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 909 | } | |
| 910 | ||
| 911 | /// Starts a field with a struct as a value. Returns the struct. | |
| 912 | pub fn beginStructField( | |
| 913 | self: *Struct, | |
| 914 | name: []const u8, | |
| 915 | options: SerializeContainerOptions, | |
| 916 | ) Error!Struct { | |
| 917 | try self.fieldPrefix(name); | |
| 918 | return self.container.serializer.beginStruct(options); | |
| 919 | } | |
| 920 | ||
| 921 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 922 | pub fn beginTupleField( | |
| 923 | self: *Struct, | |
| 924 | name: []const u8, | |
| 925 | options: SerializeContainerOptions, | |
| 926 | ) Error!Tuple { | |
| 927 | try self.fieldPrefix(name); | |
| 928 | return self.container.serializer.beginTuple(options); | |
| 929 | } | |
| 930 | ||
| 931 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 932 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 933 | /// value yourself. | |
| 934 | pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void { | |
| 935 | try self.container.fieldPrefix(name); | |
| 936 | } | |
| 937 | }; | |
| 938 | ||
| 939 | const Container = struct { | |
| 940 | const FieldStyle = enum { named, anon }; | |
| 941 | ||
| 942 | serializer: *Serializer, | |
| 943 | field_style: FieldStyle, | |
| 944 | options: SerializeContainerOptions, | |
| 945 | empty: bool, | |
| 946 | ||
| 947 | fn begin( | |
| 948 | sz: *Serializer, | |
| 949 | field_style: FieldStyle, | |
| 950 | options: SerializeContainerOptions, | |
| 951 | ) Error!Container { | |
| 952 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 953 | try sz.writer.writeAll(".{"); | |
| 954 | return .{ | |
| 955 | .serializer = sz, | |
| 956 | .field_style = field_style, | |
| 957 | .options = options, | |
| 958 | .empty = true, | |
| 959 | }; | |
| 960 | } | |
| 961 | ||
| 962 | fn end(self: *Container) Error!void { | |
| 963 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 964 | if (!self.empty) { | |
| 965 | if (self.options.shouldWrap()) { | |
| 966 | if (self.serializer.options.whitespace) { | |
| 967 | try self.serializer.writer.writeByte(','); | |
| 968 | } | |
| 969 | try self.serializer.newline(); | |
| 970 | try self.serializer.indent(); | |
| 971 | } else if (!self.shouldElideSpaces()) { | |
| 972 | try self.serializer.space(); | |
| 973 | } | |
| 974 | } | |
| 975 | try self.serializer.writer.writeByte('}'); | |
| 976 | self.* = undefined; | |
| 977 | } | |
| 978 | ||
| 979 | fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void { | |
| 980 | if (!self.empty) { | |
| 981 | try self.serializer.writer.writeByte(','); | |
| 982 | } | |
| 983 | self.empty = false; | |
| 984 | if (self.options.shouldWrap()) { | |
| 985 | try self.serializer.newline(); | |
| 986 | } else if (!self.shouldElideSpaces()) { | |
| 987 | try self.serializer.space(); | |
| 988 | } | |
| 989 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 990 | if (name) |n| { | |
| 991 | try self.serializer.ident(n); | |
| 992 | try self.serializer.space(); | |
| 993 | try self.serializer.writer.writeByte('='); | |
| 994 | try self.serializer.space(); | |
| 995 | } | |
| 996 | } | |
| 997 | ||
| 998 | fn field( | |
| 999 | self: *Container, | |
| 1000 | name: ?[]const u8, | |
| 1001 | val: anytype, | |
| 1002 | options: ValueOptions, | |
| 1003 | ) Error!void { | |
| 1004 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 1005 | try self.fieldArbitraryDepth(name, val, options); | |
| 1006 | } | |
| 1007 | ||
| 1008 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 1009 | fn fieldMaxDepth( | |
| 1010 | self: *Container, | |
| 1011 | name: ?[]const u8, | |
| 1012 | val: anytype, | |
| 1013 | options: ValueOptions, | |
| 1014 | depth: usize, | |
| 1015 | ) DepthError!void { | |
| 1016 | try checkValueDepth(val, depth); | |
| 1017 | try self.fieldArbitraryDepth(name, val, options); | |
| 1018 | } | |
| 1019 | ||
| 1020 | fn fieldArbitraryDepth( | |
| 1021 | self: *Container, | |
| 1022 | name: ?[]const u8, | |
| 1023 | val: anytype, | |
| 1024 | options: ValueOptions, | |
| 1025 | ) Error!void { | |
| 1026 | try self.fieldPrefix(name); | |
| 1027 | try self.serializer.valueArbitraryDepth(val, options); | |
| 1028 | } | |
| 1029 | ||
| 1030 | fn shouldElideSpaces(self: *const Container) bool { | |
| 1031 | return switch (self.options.whitespace_style) { | |
| 1032 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 1033 | else => false, | |
| 1034 | }; | |
| 1035 | } | |
| 1036 | }; | |
| 1037 | }; | |
| 1038 | ||
| 1039 | test Serializer { | |
| 1040 | var discarding: Writer.Discarding = .init(&.{}); | |
| 1041 | var s: Serializer = .{ .writer = &discarding.writer }; | |
| 1042 | var vec2 = try s.beginStruct(.{}); | |
| 1043 | try vec2.field("x", 1.5, .{}); | |
| 1044 | try vec2.fieldPrefix("prefix"); | |
| 1045 | try s.value(2.5, .{}); | |
| 1046 | try vec2.end(); | |
| 1047 | } | |
| 1048 | ||
| 1049 | 117 | fn expectSerializeEqual( |
| 1050 | 118 | expected: []const u8, |
| 1051 | 119 | value: anytype, |
src/main.zig+6-3| ... | ... | @@ -344,8 +344,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 344 | 344 | } else if (mem.eql(u8, cmd, "targets")) { |
| 345 | 345 | dev.check(.targets_command); |
| 346 | 346 | const host = std.zig.resolveTargetQueryOrFatal(.{}); |
| 347 | const stdout = fs.File.stdout().deprecatedWriter(); | |
| 348 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, &host); | |
| 347 | var stdout_writer = fs.File.stdout().writer(&stdio_buffer); | |
| 348 | try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); | |
| 349 | return stdout_writer.interface.flush(); | |
| 349 | 350 | } else if (mem.eql(u8, cmd, "version")) { |
| 350 | 351 | dev.check(.version_command); |
| 351 | 352 | try fs.File.stdout().writeAll(build_options.version ++ "\n"); |
| ... | ... | @@ -356,7 +357,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 356 | 357 | } else if (mem.eql(u8, cmd, "env")) { |
| 357 | 358 | dev.check(.env_command); |
| 358 | 359 | verifyLibcxxCorrectlyLinked(); |
| 359 | return @import("print_env.zig").cmdEnv(arena, cmd_args); | |
| 360 | var stdout_writer = fs.File.stdout().writer(&stdio_buffer); | |
| 361 | try @import("print_env.zig").cmdEnv(arena, &stdout_writer.interface); | |
| 362 | return stdout_writer.interface.flush(); | |
| 360 | 363 | } else if (mem.eql(u8, cmd, "reduce")) { |
| 361 | 364 | return jitCmd(gpa, arena, cmd_args, .{ |
| 362 | 365 | .cmd_name = "reduce", |
src/print_env.zig+14-35| ... | ... | @@ -4,8 +4,7 @@ const introspect = @import("introspect.zig"); |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const fatal = std.process.fatal; |
| 6 | 6 | |
| 7 | pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { | |
| 8 | _ = args; | |
| 7 | pub fn cmdEnv(arena: Allocator, out: *std.Io.Writer) !void { | |
| 9 | 8 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 10 | 9 | const self_exe_path = try std.fs.selfExePathAlloc(arena); |
| 11 | 10 | |
| ... | ... | @@ -21,41 +20,21 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { |
| 21 | 20 | const host = try std.zig.system.resolveTargetQuery(.{}); |
| 22 | 21 | const triple = try host.zigTriple(arena); |
| 23 | 22 | |
| 24 | var buffer: [1024]u8 = undefined; | |
| 25 | var stdout_writer = std.fs.File.stdout().writer(&buffer); | |
| 23 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 24 | var root = try serializer.beginStruct(.{}); | |
| 26 | 25 | |
| 27 | var jws: std.json.Stringify = .{ .writer = &stdout_writer.interface, .options = .{ .whitespace = .indent_1 } }; | |
| 28 | ||
| 29 | try jws.beginObject(); | |
| 30 | ||
| 31 | try jws.objectField("zig_exe"); | |
| 32 | try jws.write(self_exe_path); | |
| 33 | ||
| 34 | try jws.objectField("lib_dir"); | |
| 35 | try jws.write(zig_lib_directory.path.?); | |
| 36 | ||
| 37 | try jws.objectField("std_dir"); | |
| 38 | try jws.write(zig_std_dir); | |
| 39 | ||
| 40 | try jws.objectField("global_cache_dir"); | |
| 41 | try jws.write(global_cache_dir); | |
| 42 | ||
| 43 | try jws.objectField("version"); | |
| 44 | try jws.write(build_options.version); | |
| 45 | ||
| 46 | try jws.objectField("target"); | |
| 47 | try jws.write(triple); | |
| 48 | ||
| 49 | try jws.objectField("env"); | |
| 50 | try jws.beginObject(); | |
| 26 | try root.field("zig_exe", self_exe_path, .{}); | |
| 27 | try root.field("lib_dir", zig_lib_directory.path.?, .{}); | |
| 28 | try root.field("std_dir", zig_std_dir, .{}); | |
| 29 | try root.field("global_cache_dir", global_cache_dir, .{}); | |
| 30 | try root.field("version", build_options.version, .{}); | |
| 31 | try root.field("target", triple, .{}); | |
| 32 | var env = try root.beginStructField("env", .{}); | |
| 51 | 33 | inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { |
| 52 | try jws.objectField(field.name); | |
| 53 | try jws.write(try @field(std.zig.EnvVar, field.name).get(arena)); | |
| 34 | try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{}); | |
| 54 | 35 | } |
| 55 | try jws.endObject(); | |
| 56 | ||
| 57 | try jws.endObject(); | |
| 36 | try env.end(); | |
| 37 | try root.end(); | |
| 58 | 38 | |
| 59 | try stdout_writer.interface.writeByte('\n'); | |
| 60 | try stdout_writer.interface.flush(); | |
| 39 | try out.writeByte('\n'); | |
| 61 | 40 | } |
src/print_targets.zig+4-8| ... | ... | @@ -14,8 +14,7 @@ const introspect = @import("introspect.zig"); |
| 14 | 14 | pub fn cmdTargets( |
| 15 | 15 | allocator: Allocator, |
| 16 | 16 | args: []const []const u8, |
| 17 | /// Output stream | |
| 18 | stdout: anytype, | |
| 17 | out: *std.Io.Writer, | |
| 19 | 18 | native_target: *const Target, |
| 20 | 19 | ) !void { |
| 21 | 20 | _ = args; |
| ... | ... | @@ -38,12 +37,10 @@ pub fn cmdTargets( |
| 38 | 37 | const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents); |
| 39 | 38 | defer glibc_abi.destroy(allocator); |
| 40 | 39 | |
| 41 | var bw = io.bufferedWriter(stdout); | |
| 42 | const w = bw.writer(); | |
| 43 | var sz = std.zon.stringify.serializer(w, .{}); | |
| 40 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 44 | 41 | |
| 45 | 42 | { |
| 46 | var root_obj = try sz.beginStruct(.{}); | |
| 43 | var root_obj = try serializer.beginStruct(.{}); | |
| 47 | 44 | |
| 48 | 45 | try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{}); |
| 49 | 46 | try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{}); |
| ... | ... | @@ -136,6 +133,5 @@ pub fn cmdTargets( |
| 136 | 133 | try root_obj.end(); |
| 137 | 134 | } |
| 138 | 135 | |
| 139 | try w.writeByte('\n'); | |
| 140 | return bw.flush(); | |
| 136 | try out.writeByte('\n'); | |
| 141 | 137 | } |
src/translate_c.zig+1-1| ... | ... | @@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined |
| 3338 | 3338 | |
| 3339 | 3339 | fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node { |
| 3340 | 3340 | return Tag.char_literal.create(c.arena, if (narrow) |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) | |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(@intCast(val))}) | |
| 3342 | 3342 | else |
| 3343 | 3343 | try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val})); |
| 3344 | 3344 | } |