static.zig (12623B)
1 const std = @import("std"); 2 3 const Scanner = @import("Scanner.zig"); 4 const AllocWhen = Scanner.AllocWhen; 5 const Token = Scanner.Token; 6 const default_max_value_len = Scanner.default_max_value_len; 7 8 /// Controls how to deal with various inconsistencies between the YAML 9 /// document and the Zig struct type passed in. 10 /// For unknown fields, set options in this struct. 11 /// For missing fields, give the Zig struct fields default values. 12 pub const ParseOptions = struct { 13 /// If false, finding an unknown field returns `error.UnknownField` 14 ignore_unknown_fields: bool = false, 15 16 /// Passed to `yaml.Scanner.nextAllocMax`. 17 /// The default for `parseFromSlice` or `parseFromTokenSource` with a 18 /// `*yaml.Scanner` input is the length of the input slice, which means 19 /// `error.ValueTooLong` will never be returned. 20 max_value_len: ?usize = null, 21 22 /// This determines whether strings should always be copied, or if a 23 /// reference to the given buffer should be preferred if possible. 24 /// The default for `parseFromSlice` or `parseFromTokenSource` with a 25 /// `*yaml.Scanner` input is `.alloc_if_needed`. 26 allocate: ?AllocWhen = null, 27 28 /// When parsing to a `yaml.Value`, set this option to false to always 29 /// emit YAML numbers as unparsed `yaml.Value.number_string`. Otherwise, 30 /// YAML numbers are parsed as either `yaml.Value.integer`, 31 /// `yaml.Value.float`, or left unparsed as `yaml.Value.number_string`. 32 /// When this option is true, YAML numbers encoded as floats may lose 33 /// precision when being parsed into `yaml.Value.float`. 34 parse_numbers: bool = true, 35 }; 36 37 pub fn Parsed(comptime T: type) type { 38 return struct{ 39 arena: *std.heap.ArenaAllocator, 40 value: T, 41 42 pub fn deinit(self: @This()) void { 43 const allocator = self.arena.child_allocator; 44 self.arena.deinit(); 45 allocator.destroy(self.arena); 46 } 47 }; 48 } 49 50 pub fn parseFromSlice( 51 comptime T: type, 52 allocator: std.mem.Allocator, 53 s: []const u8, 54 options: ParseOptions, 55 ) !Parsed(T) { 56 var scanner = Scanner.initCompleteInput(s); 57 //defer scanner.deinit(); 58 59 return parseFromTokenSource(T, allocator, &scanner, options); 60 } 61 62 pub fn parseFromTokenSource( 63 comptime T: type, 64 allocator: std.mem.Allocator, 65 scanner_or_reader: anytype, 66 options: ParseOptions, 67 ) !Parsed(T) { 68 var parsed = Parsed(T){ 69 .arena = try allocator.create(std.heap.ArenaAllocator), 70 .value = undefined, 71 }; 72 errdefer allocator.destroy(parsed.arena); 73 parsed.arena.* = std.heap.ArenaAllocator.init(allocator); 74 errdefer parsed.arena.deinit(); 75 76 parsed.value = try parseFromTokenSourceLeaky( 77 T, 78 parsed.arena.allocator(), 79 scanner_or_reader, 80 options, 81 ); 82 83 return parsed; 84 } 85 86 pub fn parseFromTokenSourceLeaky( 87 comptime T: type, 88 allocator: std.mem.Allocator, 89 scanner_or_reader: anytype, 90 options: ParseOptions, 91 ) !T { 92 //if (@TypeOf(scanner_or_reader.*) == Scanner) { 93 // std.debug.assert(scanner_or_reader.is_end_of_input); 94 //} 95 var resolved_options = options; 96 if (resolved_options.max_value_len == null) { 97 if (@TypeOf(scanner_or_reader.*) == Scanner) { 98 resolved_options.max_value_len = scanner_or_reader.input.len; 99 } else { 100 resolved_options.max_value_len = default_max_value_len; 101 } 102 } 103 if (resolved_options.allocate == null) { 104 if (@TypeOf(scanner_or_reader.*) == Scanner) { 105 resolved_options.allocate = .alloc_if_needed; 106 } else { 107 resolved_options.allocate = .alloc_always; 108 } 109 } 110 111 const value = try innerParse( 112 T, 113 allocator, 114 scanner_or_reader, 115 resolved_options, 116 ); 117 118 std.debug.assert(try scanner_or_reader.next() == .document_end); 119 120 return value; 121 } 122 123 /// This is an internal function called recursively during the implementation 124 /// of `parseFromTokenSourceLeaky` and similar. 125 /// It is exposed primarily to enable custom `yamlParse()` methods to call 126 /// back into the `parseFrom*` system such as if you're implementing a custom 127 /// container of type `T`; you can call `innerParse(T, ...)` for each of the 128 /// container's items. Note that `null` fields are not allowed on the 129 /// `options` when calling this function. (The `options` you get in your 130 /// `yamlParse` method has no `null` fields.) 131 pub fn innerParse( 132 comptime T: type, 133 allocator: std.mem.Allocator, 134 source: anytype, 135 options: ParseOptions, 136 ) !T { 137 switch (@typeInfo(T)) { 138 .bool => { 139 return switch (try source.next()) { 140 .true => true, 141 .false => false, 142 else => error.UnexpectedToken, 143 }; 144 }, 145 .optional => |optional_info| { 146 switch (try source.peekNextTokenType()) { 147 .null => { 148 _ = try source.next(); 149 return null; 150 }, 151 else => { 152 return try innerParse( 153 optional_info.child, 154 allocator, 155 source, 156 options, 157 ); 158 }, 159 } 160 }, 161 .@"enum" => { 162 if (std.meta.hasFn(T, "yamlParse")) { 163 return T.yamlParse(allocator, source, options); 164 } 165 166 // Default parsing 167 const token = try source.nextAllocMax( 168 allocator, 169 .alloc_if_needed, 170 options.max_value_len.?, 171 ); 172 defer freeIfAlloc(allocator, token); 173 // defer free 174 const slice = switch (token) { 175 inline 176 .number, 177 .allocated_number, 178 .string, 179 .allocated_string => |slice| slice, 180 else => return error.UnexpectedToken, 181 }; 182 return sliceToEnum(T, slice); 183 }, 184 .@"union" => |union_info| { 185 if (std.meta.hasFn(T, "yamlParse")) { 186 return T.yamlParse(allocator, source, options); 187 } 188 if (union_info.tag_type == null) { 189 @compileError( 190 "Unable to parse into untagged union '" ++ @typeName(T) ++ "'" 191 ); 192 } 193 194 var result: ?T = null; 195 const name_token = try source.nextAllocMax( 196 allocator, 197 .alloc_if_needed, 198 options.max_value_len.?, 199 ); 200 defer freeIfAlloc(allocator, name_token); 201 202 const field_name = switch(name_token.?) { 203 inline .string, .allocated_string => |slice| slice, 204 else => return error.UnexpectedToken, 205 }; 206 207 inline for (union_info.fields) |field| { 208 if (std.mem.eql(u8, field.name, field_name)) { 209 if (field.type == void) { 210 result = @unionInit(T, field.name, {}); 211 } else { 212 // Recurse 213 const token = try innerParse( 214 field.type, 215 allocator, 216 source, 217 options, 218 ); 219 result = @unionInit( 220 T, 221 field.name, 222 token, 223 ); 224 } 225 break; 226 } 227 } else { 228 // Didn't match anything 229 return error.UnknownField; 230 } 231 232 return result.?; 233 }, 234 .@"struct" => |struct_info| { 235 if (struct_info.is_tuple) { 236 return error.Unimplemented; 237 } 238 if (std.meta.hasFn(T, "yamlParse")) { 239 return T.yamlParse(allocator, source, options); 240 } 241 242 var result: T = undefined; 243 var fields_seen = [_]bool{false} ** struct_info.fields.len; 244 245 while (true) { 246 const name_token = try source.nextAllocMax( 247 allocator, 248 .alloc_if_needed, 249 options.max_value_len.?, 250 ); 251 defer freeIfAlloc(allocator, name_token); 252 const field_name = switch(name_token) { 253 inline .string, .allocated_string => |slice| slice, 254 else => return error.UnexpectedToken, 255 }; 256 inline for (struct_info.fields, 0..) |field, i| { 257 if (field.is_comptime) { 258 @compileError( 259 "comptile fields are not supported: " ++ @typeName(T) ++ "." ++ field.name 260 ); 261 } 262 if (std.mem.eql(u8, field.name, field_name)) { 263 if (fields_seen[i]) return error.DuplicateField; 264 @field(result, field.name) = try innerParse( 265 field.type, 266 allocator, 267 source, 268 options, 269 ); 270 fields_seen[i] = true; 271 break; 272 } 273 } else { 274 // Didn't match anything 275 return error.UnknownField; 276 } 277 } 278 }, 279 .array => |array_info| { 280 switch (try source.peek()) { 281 .string => { 282 if (array_info.child != u8) return error.UnexpectedToken; 283 // Fixed-length string 284 }, 285 else => return error.Unimplemented, 286 } 287 }, 288 .pointer => |ptr_info| { 289 switch (ptr_info.size) { 290 .one => { 291 const result = try allocator.create(ptr_info.child); 292 result.* = try innerParse( 293 ptr_info.child, 294 allocator, 295 source, 296 options, 297 ); 298 return result; 299 }, 300 .slice => { 301 switch (try source.peek()) { 302 .string, 303 .partial_string, 304 .partial_string_escaped_1 => { 305 if (ptr_info.child != u8) { 306 return error.UnexpectedToken; 307 } 308 // Dynamic-length string 309 if (ptr_info.sentinel()) |_| { 310 // Use our own array list so we can append 311 // the sentinel. 312 return error.Unimplemented; 313 } 314 var alloc = options.allocate.?; 315 if (!ptr_info.is_const) { 316 // Have to allocate to get a mutable copy 317 alloc = .alloc_always; 318 } 319 const result = try source.nextAllocMax( 320 allocator, 321 alloc, 322 options.max_value_len.?, 323 ); 324 return switch (result) { 325 inline 326 .string, 327 .allocated_string => |slice| slice, 328 else => unreachable 329 }; 330 }, 331 else => return error.UnexpectedToken, 332 } 333 }, 334 } 335 }, 336 else => return error.Unimplemented, 337 } 338 } 339 340 fn sliceToEnum(comptime T: type, slice: []const u8) !T { 341 if (std.meta.stringToEnum(T, slice)) |value| return value; 342 const n = std.fmt.parseInt( 343 @typeInfo(T).@"enum".tag_type, 344 slice, 345 10 346 ) catch return error.InvalidEnumTag; 347 return std.enums.fromInt(T, n) orelse return error.InvalidEnumTag; 348 } 349 350 fn freeIfAlloc(allocator: std.mem.Allocator, token: Token) void { 351 switch (token) { 352 inline .allocated_number, .allocated_string => |slice| { 353 allocator.free(slice); 354 }, 355 else => {}, 356 } 357 } 358