Compare commits
8 Commits
e8ddee5ab2
...
0f4a9fcaa7
Author | SHA1 | Date | |
---|---|---|---|
0f4a9fcaa7 | |||
bd079b42d9 | |||
bd0d74ee6a | |||
2208079355 | |||
98eac68929 | |||
39619e7d6b | |||
33ab092a06 | |||
21a9753d46 |
@ -2,4 +2,11 @@
|
||||
.name = "nice-data",
|
||||
.version = "0.1.0-pre",
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"license",
|
||||
"readme.md",
|
||||
},
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ const Example = struct {
|
||||
again: ?bool,
|
||||
array: [5]i16,
|
||||
nested: [3]struct { index: usize, title: []const u8 },
|
||||
default: u64 = 0xDEADCAFE,
|
||||
};
|
||||
|
||||
const source =
|
||||
@ -98,5 +99,6 @@ pub fn main() !void {
|
||||
std.debug.print(" {{ index: {d}, title: {s} }}\n", .{ item.index, item.title });
|
||||
}
|
||||
std.debug.print(" ]\n", .{});
|
||||
std.debug.print(" default: 0x{X}\n", .{loaded.value.default});
|
||||
std.debug.print("}}\n", .{});
|
||||
}
|
||||
|
@ -61,11 +61,11 @@ pub const Options = struct {
|
||||
ignore_extra_fields: bool = true,
|
||||
|
||||
// Only used by the parseTo family of functions.
|
||||
// If true, if a struct field is an optional type and the corresponding mapping key
|
||||
// does not exist, the object field will be set to `null`. By default, if the
|
||||
// parsed document is missing a mapping key for a given field, an error will be
|
||||
// raised instead.
|
||||
treat_omitted_as_null: bool = false,
|
||||
// If true, if a struct field has a default value associated with it and the
|
||||
// corresponding mapping key does not exist, the object field will be set to the
|
||||
// default value. By default, this behavior is enabled, allowing succinct
|
||||
// representation of objects that have default fields.
|
||||
allow_omitting_default_values: bool = true,
|
||||
|
||||
// Only used by the parseTo family of functions.
|
||||
// If true, strings may be coerced into other scalar types, like booleans or
|
||||
@ -99,7 +99,9 @@ pub const Options = struct {
|
||||
// look like source code enum literals. Any enum value missing the leading `.` will
|
||||
// result in a conversion error. If set to false, no preprocessing will be done
|
||||
// and enum values will be converted from the literal scalar/string. These two styles
|
||||
// cannot be mixed in a single document.
|
||||
// cannot be mixed in a single document. Note that this setting also affects how
|
||||
// tagged unions are parsed (specifically, the union's field name must also have the
|
||||
// leading `.` if this option is enabled.)
|
||||
expect_enum_dot: bool = true,
|
||||
|
||||
// Only used by the parseTo family of functions.
|
||||
|
@ -70,7 +70,7 @@ pub const State = struct {
|
||||
},
|
||||
.value => switch (state.value_stack.getLast().*) {
|
||||
// we have an in-progress string, finish it.
|
||||
.string => |*string| string.* = try state.string_builder.toOwnedSlice(arena_alloc),
|
||||
.string => |*string| string.* = try state.string_builder.toOwnedSliceSentinel(arena_alloc, 0),
|
||||
// if we have a dangling -, attach an empty scalar to it
|
||||
.list => |*list| if (state.expect_shift == .indent) try list.append(Value.emptyScalar()),
|
||||
// if we have a dangling "key:", attach an empty scalar to it
|
||||
@ -185,7 +185,7 @@ pub const State = struct {
|
||||
|
||||
if (firstpass and line.shift == .dedent) {
|
||||
// copy the string into the document proper
|
||||
string.* = try state.string_builder.toOwnedSlice(arena_alloc);
|
||||
string.* = try state.string_builder.toOwnedSliceSentinel(arena_alloc, 0);
|
||||
|
||||
var dedent_depth = line.shift.dedent;
|
||||
while (dedent_depth > 0) : (dedent_depth -= 1)
|
||||
@ -199,9 +199,9 @@ pub const State = struct {
|
||||
.in_line => |in_line| switch (in_line) {
|
||||
.empty => unreachable,
|
||||
inline .line_string, .space_string, .concat_string => |str, tag| {
|
||||
if (tag == .line_string)
|
||||
if (comptime tag == .line_string)
|
||||
try state.string_builder.append(arena_alloc, '\n');
|
||||
if (tag == .space_string)
|
||||
if (comptime tag == .space_string)
|
||||
try state.string_builder.append(arena_alloc, ' ');
|
||||
try state.string_builder.appendSlice(arena_alloc, str);
|
||||
},
|
||||
|
@ -9,6 +9,11 @@
|
||||
// CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
const std = @import("std");
|
||||
const hasFn = if (@hasDecl(std.meta, "trait")) struct {
|
||||
fn hasFn(comptime T: type, comptime name: []const u8) bool {
|
||||
return std.meta.trait.hasFn(name)(T);
|
||||
}
|
||||
}.hasFn else std.meta.hasFn;
|
||||
|
||||
const Options = @import("../parser.zig").Options;
|
||||
|
||||
@ -51,7 +56,7 @@ pub fn Parsed(comptime T: type) type {
|
||||
}
|
||||
|
||||
pub const Value = union(enum) {
|
||||
pub const String = []const u8;
|
||||
pub const String = [:0]const u8;
|
||||
pub const Map = std.StringArrayHashMap(Value);
|
||||
pub const List = std.ArrayList(Value);
|
||||
pub const TagType = @typeInfo(Value).Union.tag_type.?;
|
||||
@ -106,7 +111,7 @@ pub const Value = union(enum) {
|
||||
switch (self) {
|
||||
inline .scalar, .string => |str, tag| {
|
||||
if (tag == .string and !options.coerce_strings) return error.BadValue;
|
||||
return try std.fmt.parseFloat(T, str, 0);
|
||||
return try std.fmt.parseFloat(T, str);
|
||||
},
|
||||
else => return error.BadValue,
|
||||
}
|
||||
@ -120,27 +125,29 @@ pub const Value = union(enum) {
|
||||
// probably be solved in the zig stdlib or similar.
|
||||
switch (self) {
|
||||
.scalar, .string => |str| {
|
||||
if (ptr.child == u8) {
|
||||
if (ptr.sentinel) |sent| {
|
||||
var copy = try allocator.allocSentinel(u8, str.len, @as(*const u8, @ptrCast(sent)).*);
|
||||
@memcpy(copy, str);
|
||||
return copy;
|
||||
}
|
||||
if (comptime ptr.child == u8) {
|
||||
if (comptime ptr.sentinel) |sentinel|
|
||||
if (comptime @as(*align(1) const ptr.child, @ptrCast(sentinel)).* != 0)
|
||||
return error.BadValue;
|
||||
|
||||
return str;
|
||||
} else {
|
||||
return error.BadValue;
|
||||
}
|
||||
},
|
||||
.list, .inline_list => |lst| {
|
||||
var result = try std.ArrayList(ptr.child).initCapacity(allocator, lst.items.len);
|
||||
errdefer result.deinit();
|
||||
for (lst.items) |item| {
|
||||
result.appendAssumeCapacity(try item.convertTo(ptr.child, allocator, options));
|
||||
const result = try allocator.alloc(ptr.child, lst.items.len + @intFromBool(ptr.sentinel != null));
|
||||
|
||||
for (result[0..lst.items.len], lst.items) |*res, item| {
|
||||
res.* = try item.convertTo(ptr.child, allocator, options);
|
||||
}
|
||||
if (ptr.sentinel) |sent| {
|
||||
return try result.toOwnedSliceSentinel(@as(*align(1) const ptr.child, @ptrCast(sent)).*);
|
||||
|
||||
if (comptime ptr.sentinel) |sentinel| {
|
||||
const sval = @as(*align(1) const ptr.child, @ptrCast(sentinel)).*;
|
||||
result[lst.items.len] = sval;
|
||||
return result[0..lst.items.len :sval];
|
||||
} else {
|
||||
return try result.toOwnedSlice();
|
||||
return result;
|
||||
}
|
||||
},
|
||||
else => return error.BadValue,
|
||||
@ -152,7 +159,7 @@ pub const Value = union(enum) {
|
||||
result.* = try self.convertTo(ptr.child, allocator, options);
|
||||
return result;
|
||||
},
|
||||
else => @compileError("Cannot deserialize into many-pointer or c-pointer " ++ @typeName(T)), // do not support many or C item pointers.
|
||||
else => @compileError("Cannot deserialize into many-pointer or c-pointer " ++ @typeName(T)),
|
||||
},
|
||||
.Array => |arr| {
|
||||
// TODO: There is ambiguity here because a document expecting a list
|
||||
@ -169,21 +176,19 @@ pub const Value = union(enum) {
|
||||
} else return error.BadValue;
|
||||
},
|
||||
.list, .inline_list => |lst| {
|
||||
var storage = try std.ArrayList(arr.child).initCapacity(allocator, arr.len);
|
||||
defer storage.deinit();
|
||||
for (lst.items) |item| {
|
||||
storage.appendAssumeCapacity(try item.convertTo(arr.child, allocator, options));
|
||||
}
|
||||
// this may result in a big stack allocation, which is not ideal
|
||||
if (lst.items.len != arr.len) return error.BadValue;
|
||||
|
||||
var result: T = undefined;
|
||||
@memcpy(&result, storage.items);
|
||||
for (&result, lst.items) |*res, item| {
|
||||
res.* = try item.convertTo(arr.child, allocator, options);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
else => return error.BadValue,
|
||||
}
|
||||
},
|
||||
.Struct => |stt| {
|
||||
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
|
||||
if (comptime hasFn(T, "deserializeNice"))
|
||||
return T.deserializeNice(self, allocator, options);
|
||||
|
||||
if (stt.is_tuple) {
|
||||
@ -191,8 +196,8 @@ pub const Value = union(enum) {
|
||||
.list, .inline_list => |list| {
|
||||
if (list.items.len != stt.fields.len) return error.BadValue;
|
||||
var result: T = undefined;
|
||||
inline for (stt.fields, 0..) |field, idx| {
|
||||
result[idx] = try list.items[idx].convertTo(field.type, allocator, options);
|
||||
inline for (stt.fields, &result, list.items) |field, *res, item| {
|
||||
res.* = try item.convertTo(field.type, allocator, options);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
@ -208,24 +213,30 @@ pub const Value = union(enum) {
|
||||
inline for (stt.fields) |field| {
|
||||
if (map.get(field.name)) |value| {
|
||||
@field(result, field.name) = try value.convertTo(field.type, allocator, options);
|
||||
} else if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
|
||||
@field(result, field.name) = null;
|
||||
} else if (options.allow_omitting_default_values) {
|
||||
if (comptime field.default_value) |def|
|
||||
@field(result, field.name) = @as(*align(1) const field.type, @ptrCast(def)).*
|
||||
else
|
||||
return error.BadValue;
|
||||
} else {
|
||||
return error.BadValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we could iterate over each map key and do an exhaustive
|
||||
// comparison with each struct field name. This would save
|
||||
// memory and it would probably be a fair amount faster for
|
||||
// small structs.
|
||||
// TODO: consider not cloning the map here. This would
|
||||
// result in the requirement that the raw value object
|
||||
// not be used after it has been converted to a type,
|
||||
// based on the parse options.
|
||||
var clone = try map.clone();
|
||||
defer clone.deinit();
|
||||
inline for (stt.fields) |field| {
|
||||
if (clone.fetchSwapRemove(field.name)) |kv| {
|
||||
@field(result, field.name) = try kv.value.convertTo(field.type, allocator, options);
|
||||
} else if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
|
||||
@field(result, field.name) = null;
|
||||
} else if (options.allow_omitting_default_values) {
|
||||
if (comptime field.default_value) |def|
|
||||
@field(result, field.name) = @as(*align(1) const field.type, @ptrCast(def)).*
|
||||
else
|
||||
return error.BadValue;
|
||||
} else return error.BadValue;
|
||||
}
|
||||
// there were extra fields in the data
|
||||
@ -238,7 +249,7 @@ pub const Value = union(enum) {
|
||||
}
|
||||
},
|
||||
.Enum => {
|
||||
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
|
||||
if (comptime hasFn(T, "deserializeNice"))
|
||||
return T.deserializeNice(self, allocator, options);
|
||||
|
||||
switch (self) {
|
||||
@ -263,7 +274,7 @@ pub const Value = union(enum) {
|
||||
}
|
||||
},
|
||||
.Union => |unn| {
|
||||
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
|
||||
if (comptime hasFn(T, "deserializeNice"))
|
||||
return T.deserializeNice(self, allocator, options);
|
||||
|
||||
if (unn.tag_type == null) @compileError("Cannot deserialize into untagged union " ++ @typeName(T));
|
||||
@ -342,7 +353,7 @@ pub const Value = union(enum) {
|
||||
}
|
||||
|
||||
inline fn _fromScalarOrString(alloc: std.mem.Allocator, comptime classification: TagType, input: []const u8) !Value {
|
||||
return @unionInit(Value, @tagName(classification), try alloc.dupe(u8, input));
|
||||
return @unionInit(Value, @tagName(classification), try alloc.dupeZ(u8, input));
|
||||
}
|
||||
|
||||
pub inline fn emptyScalar() Value {
|
||||
|
Loading…
x
Reference in New Issue
Block a user