| 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,6 +38,7 @@ |
| 38 | 38 | ||
| 39 | pub const parse = @import("zon/parse.zig"); | 39 | pub const parse = @import("zon/parse.zig"); |
| 40 | pub const stringify = @import("zon/stringify.zig"); | 40 | pub const stringify = @import("zon/stringify.zig"); |
| 41 | pub const Serializer = @import("zon/Serializer.zig"); | ||
| 41 | 42 | ||
| 42 | test { | 43 | test { |
| 43 | _ = parse; | 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,13 +23,13 @@ |
| 23 | const std = @import("std"); | 23 | const std = @import("std"); |
| 24 | const assert = std.debug.assert; | 24 | const assert = std.debug.assert; |
| 25 | const Writer = std.Io.Writer; | 25 | const Writer = std.Io.Writer; |
| 26 | const Serializer = std.zon.Serializer; | ||
| 26 | 27 | ||
| 27 | /// Options for `serialize`. | ||
| 28 | pub const SerializeOptions = struct { | 28 | pub const SerializeOptions = struct { |
| 29 | /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. | 29 | /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. |
| 30 | whitespace: bool = true, | 30 | whitespace: bool = true, |
| 31 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | 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 | /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. | 33 | /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. |
| 34 | /// Otherwise they are serialized as string literals. | 34 | /// Otherwise they are serialized as string literals. |
| 35 | emit_strings_as_containers: bool = false, | 35 | emit_strings_as_containers: bool = false, |
| ... | @@ -93,102 +93,6 @@ pub fn serializeArbitraryDepth( | ... | @@ -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 | fn isNestedOptional(T: type) bool { | 96 | fn isNestedOptional(T: type) bool { |
| 193 | comptime switch (@typeInfo(T)) { | 97 | comptime switch (@typeInfo(T)) { |
| 194 | .optional => |optional| return isNestedOptionalInner(optional.child), | 98 | .optional => |optional| return isNestedOptionalInner(optional.child), |
| ... | @@ -210,842 +114,6 @@ fn isNestedOptionalInner(T: type) bool { | ... | @@ -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 | fn expectSerializeEqual( | 117 | fn expectSerializeEqual( |
| 1050 | expected: []const u8, | 118 | expected: []const u8, |
| 1051 | value: anytype, | 119 | value: anytype, |
src/main.zig+6-3| ... | @@ -344,8 +344,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -344,8 +344,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 344 | } else if (mem.eql(u8, cmd, "targets")) { | 344 | } else if (mem.eql(u8, cmd, "targets")) { |
| 345 | dev.check(.targets_command); | 345 | dev.check(.targets_command); |
| 346 | const host = std.zig.resolveTargetQueryOrFatal(.{}); | 346 | const host = std.zig.resolveTargetQueryOrFatal(.{}); |
| 347 | const stdout = fs.File.stdout().deprecatedWriter(); | 347 | var stdout_writer = fs.File.stdout().writer(&stdio_buffer); |
| 348 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, &host); | 348 | try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); |
| 349 | return stdout_writer.interface.flush(); | ||
| 349 | } else if (mem.eql(u8, cmd, "version")) { | 350 | } else if (mem.eql(u8, cmd, "version")) { |
| 350 | dev.check(.version_command); | 351 | dev.check(.version_command); |
| 351 | try fs.File.stdout().writeAll(build_options.version ++ "\n"); | 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,7 +357,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 356 | } else if (mem.eql(u8, cmd, "env")) { | 357 | } else if (mem.eql(u8, cmd, "env")) { |
| 357 | dev.check(.env_command); | 358 | dev.check(.env_command); |
| 358 | verifyLibcxxCorrectlyLinked(); | 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 | } else if (mem.eql(u8, cmd, "reduce")) { | 363 | } else if (mem.eql(u8, cmd, "reduce")) { |
| 361 | return jitCmd(gpa, arena, cmd_args, .{ | 364 | return jitCmd(gpa, arena, cmd_args, .{ |
| 362 | .cmd_name = "reduce", | 365 | .cmd_name = "reduce", |
src/print_env.zig+14-35| ... | @@ -4,8 +4,7 @@ const introspect = @import("introspect.zig"); | ... | @@ -4,8 +4,7 @@ const introspect = @import("introspect.zig"); |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const fatal = std.process.fatal; | 5 | const fatal = std.process.fatal; |
| 6 | 6 | ||
| 7 | pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { | 7 | pub fn cmdEnv(arena: Allocator, out: *std.Io.Writer) !void { |
| 8 | _ = args; | ||
| 9 | const cwd_path = try introspect.getResolvedCwd(arena); | 8 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 10 | const self_exe_path = try std.fs.selfExePathAlloc(arena); | 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,41 +20,21 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { |
| 21 | const host = try std.zig.system.resolveTargetQuery(.{}); | 20 | const host = try std.zig.system.resolveTargetQuery(.{}); |
| 22 | const triple = try host.zigTriple(arena); | 21 | const triple = try host.zigTriple(arena); |
| 23 | 22 | ||
| 24 | var buffer: [1024]u8 = undefined; | 23 | var serializer: std.zon.Serializer = .{ .writer = out }; |
| 25 | var stdout_writer = std.fs.File.stdout().writer(&buffer); | 24 | var root = try serializer.beginStruct(.{}); |
| 26 | 25 | ||
| 27 | var jws: std.json.Stringify = .{ .writer = &stdout_writer.interface, .options = .{ .whitespace = .indent_1 } }; | 26 | try root.field("zig_exe", self_exe_path, .{}); |
| 28 | 27 | try root.field("lib_dir", zig_lib_directory.path.?, .{}); | |
| 29 | try jws.beginObject(); | 28 | try root.field("std_dir", zig_std_dir, .{}); |
| 30 | 29 | try root.field("global_cache_dir", global_cache_dir, .{}); | |
| 31 | try jws.objectField("zig_exe"); | 30 | try root.field("version", build_options.version, .{}); |
| 32 | try jws.write(self_exe_path); | 31 | try root.field("target", triple, .{}); |
| 33 | 32 | var env = try root.beginStructField("env", .{}); | |
| 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(); | ||
| 51 | inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { | 33 | inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { |
| 52 | try jws.objectField(field.name); | 34 | try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{}); |
| 53 | try jws.write(try @field(std.zig.EnvVar, field.name).get(arena)); | ||
| 54 | } | 35 | } |
| 55 | try jws.endObject(); | 36 | try env.end(); |
| 56 | 37 | try root.end(); | |
| 57 | try jws.endObject(); | ||
| 58 | 38 | ||
| 59 | try stdout_writer.interface.writeByte('\n'); | 39 | try out.writeByte('\n'); |
| 60 | try stdout_writer.interface.flush(); | ||
| 61 | } | 40 | } |
src/print_targets.zig+4-8| ... | @@ -14,8 +14,7 @@ const introspect = @import("introspect.zig"); | ... | @@ -14,8 +14,7 @@ const introspect = @import("introspect.zig"); |
| 14 | pub fn cmdTargets( | 14 | pub fn cmdTargets( |
| 15 | allocator: Allocator, | 15 | allocator: Allocator, |
| 16 | args: []const []const u8, | 16 | args: []const []const u8, |
| 17 | /// Output stream | 17 | out: *std.Io.Writer, |
| 18 | stdout: anytype, | ||
| 19 | native_target: *const Target, | 18 | native_target: *const Target, |
| 20 | ) !void { | 19 | ) !void { |
| 21 | _ = args; | 20 | _ = args; |
| ... | @@ -38,12 +37,10 @@ pub fn cmdTargets( | ... | @@ -38,12 +37,10 @@ pub fn cmdTargets( |
| 38 | const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents); | 37 | const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents); |
| 39 | defer glibc_abi.destroy(allocator); | 38 | defer glibc_abi.destroy(allocator); |
| 40 | 39 | ||
| 41 | var bw = io.bufferedWriter(stdout); | 40 | var serializer: std.zon.Serializer = .{ .writer = out }; |
| 42 | const w = bw.writer(); | ||
| 43 | var sz = std.zon.stringify.serializer(w, .{}); | ||
| 44 | 41 | ||
| 45 | { | 42 | { |
| 46 | var root_obj = try sz.beginStruct(.{}); | 43 | var root_obj = try serializer.beginStruct(.{}); |
| 47 | 44 | ||
| 48 | try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{}); | 45 | try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{}); |
| 49 | try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{}); | 46 | try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{}); |
| ... | @@ -136,6 +133,5 @@ pub fn cmdTargets( | ... | @@ -136,6 +133,5 @@ pub fn cmdTargets( |
| 136 | try root_obj.end(); | 133 | try root_obj.end(); |
| 137 | } | 134 | } |
| 138 | 135 | ||
| 139 | try w.writeByte('\n'); | 136 | try out.writeByte('\n'); |
| 140 | return bw.flush(); | ||
| 141 | } | 137 | } |
src/translate_c.zig+1-1| ... | @@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined | ... | @@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined |
| 3338 | 3338 | ||
| 3339 | fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node { | 3339 | fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node { |
| 3340 | return Tag.char_literal.create(c.arena, if (narrow) | 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 | else | 3342 | else |
| 3343 | try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val})); | 3343 | try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val})); |
| 3344 | } | 3344 | } |