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