Compare commits

..

No commits in common. "0f4a9fcaa75658f86599492259290cab06279e87" and "e8ddee5ab22bbe0d4fe21f870b68cbaf10692c78" have entirely different histories.

5 changed files with 47 additions and 69 deletions

View File

@ -2,11 +2,4 @@
.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",
},
} }

View File

@ -21,7 +21,6 @@ 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 =
@ -99,6 +98,5 @@ 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", .{});
} }

View File

@ -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 has a default value associated with it and the // If true, if a struct field is an optional type and the corresponding mapping key
// corresponding mapping key does not exist, the object field will be set to the // does not exist, the object field will be set to `null`. By default, if the
// default value. By default, this behavior is enabled, allowing succinct // parsed document is missing a mapping key for a given field, an error will be
// representation of objects that have default fields. // raised instead.
allow_omitting_default_values: bool = true, treat_omitted_as_null: bool = false,
// 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,9 +99,7 @@ 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. Note that this setting also affects how // cannot be mixed in a single document.
// 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.

View File

@ -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.toOwnedSliceSentinel(arena_alloc, 0), .string => |*string| string.* = try state.string_builder.toOwnedSlice(arena_alloc),
// 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.toOwnedSliceSentinel(arena_alloc, 0); string.* = try state.string_builder.toOwnedSlice(arena_alloc);
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 (comptime tag == .line_string) if (tag == .line_string)
try state.string_builder.append(arena_alloc, '\n'); try state.string_builder.append(arena_alloc, '\n');
if (comptime tag == .space_string) if (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);
}, },

View File

@ -9,11 +9,6 @@
// 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;
@ -56,7 +51,7 @@ pub fn Parsed(comptime T: type) type {
} }
pub const Value = union(enum) { pub const Value = union(enum) {
pub const String = [:0]const u8; pub const String = []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.?;
@ -111,7 +106,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); return try std.fmt.parseFloat(T, str, 0);
}, },
else => return error.BadValue, else => return error.BadValue,
} }
@ -125,29 +120,27 @@ 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 (comptime ptr.child == u8) { if (ptr.child == u8) {
if (comptime ptr.sentinel) |sentinel| if (ptr.sentinel) |sent| {
if (comptime @as(*align(1) const ptr.child, @ptrCast(sentinel)).* != 0) var copy = try allocator.allocSentinel(u8, str.len, @as(*const u8, @ptrCast(sent)).*);
return error.BadValue; @memcpy(copy, str);
return copy;
}
return str; return str;
} else { } else {
return error.BadValue; return error.BadValue;
} }
}, },
.list, .inline_list => |lst| { .list, .inline_list => |lst| {
const result = try allocator.alloc(ptr.child, lst.items.len + @intFromBool(ptr.sentinel != null)); var result = try std.ArrayList(ptr.child).initCapacity(allocator, lst.items.len);
errdefer result.deinit();
for (result[0..lst.items.len], lst.items) |*res, item| { for (lst.items) |item| {
res.* = try item.convertTo(ptr.child, allocator, options); result.appendAssumeCapacity(try item.convertTo(ptr.child, allocator, options));
} }
if (ptr.sentinel) |sent| {
if (comptime ptr.sentinel) |sentinel| { return try result.toOwnedSliceSentinel(@as(*align(1) const ptr.child, @ptrCast(sent)).*);
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 result; return try result.toOwnedSlice();
} }
}, },
else => return error.BadValue, else => return error.BadValue,
@ -159,7 +152,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)), else => @compileError("Cannot deserialize into many-pointer or c-pointer " ++ @typeName(T)), // do not support many or C item pointers.
}, },
.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
@ -176,19 +169,21 @@ pub const Value = union(enum) {
} else return error.BadValue; } else return error.BadValue;
}, },
.list, .inline_list => |lst| { .list, .inline_list => |lst| {
if (lst.items.len != arr.len) return error.BadValue; var storage = try std.ArrayList(arr.child).initCapacity(allocator, arr.len);
defer storage.deinit();
var result: T = undefined; for (lst.items) |item| {
for (&result, lst.items) |*res, item| { storage.appendAssumeCapacity(try item.convertTo(arr.child, allocator, options));
res.* = try item.convertTo(arr.child, allocator, options);
} }
// this may result in a big stack allocation, which is not ideal
var result: T = undefined;
@memcpy(&result, storage.items);
return result; return result;
}, },
else => return error.BadValue, else => return error.BadValue,
} }
}, },
.Struct => |stt| { .Struct => |stt| {
if (comptime hasFn(T, "deserializeNice")) if (comptime std.meta.trait.hasFn("deserializeNice")(T))
return T.deserializeNice(self, allocator, options); return T.deserializeNice(self, allocator, options);
if (stt.is_tuple) { if (stt.is_tuple) {
@ -196,8 +191,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, &result, list.items) |field, *res, item| { inline for (stt.fields, 0..) |field, idx| {
res.* = try item.convertTo(field.type, allocator, options); result[idx] = try list.items[idx].convertTo(field.type, allocator, options);
} }
return result; return result;
}, },
@ -213,30 +208,24 @@ 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.allow_omitting_default_values) { } else if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
if (comptime field.default_value) |def| @field(result, field.name) = null;
@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 {
// TODO: consider not cloning the map here. This would // we could iterate over each map key and do an exhaustive
// result in the requirement that the raw value object // comparison with each struct field name. This would save
// not be used after it has been converted to a type, // memory and it would probably be a fair amount faster for
// based on the parse options. // small structs.
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.allow_omitting_default_values) { } else if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
if (comptime field.default_value) |def| @field(result, field.name) = null;
@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
@ -249,7 +238,7 @@ pub const Value = union(enum) {
} }
}, },
.Enum => { .Enum => {
if (comptime hasFn(T, "deserializeNice")) if (comptime std.meta.trait.hasFn("deserializeNice")(T))
return T.deserializeNice(self, allocator, options); return T.deserializeNice(self, allocator, options);
switch (self) { switch (self) {
@ -274,7 +263,7 @@ pub const Value = union(enum) {
} }
}, },
.Union => |unn| { .Union => |unn| {
if (comptime hasFn(T, "deserializeNice")) if (comptime std.meta.trait.hasFn("deserializeNice")(T))
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));
@ -353,7 +342,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.dupeZ(u8, input)); return @unionInit(Value, @tagName(classification), try alloc.dupe(u8, input));
} }
pub inline fn emptyScalar() Value { pub inline fn emptyScalar() Value {