yaml-z

git clone git://git.electrosoup.com/yaml-z
Log | Files | Refs | Submodules

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