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",
.version = "0.1.0-pre",
.dependencies = .{},
.paths = .{
"src",
"build.zig",
"build.zig.zon",
"license",
"readme.md",
},
}

View File

@ -21,7 +21,6 @@ const Example = struct {
again: ?bool,
array: [5]i16,
nested: [3]struct { index: usize, title: []const u8 },
default: u64 = 0xDEADCAFE,
};
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(" ]\n", .{});
std.debug.print(" default: 0x{X}\n", .{loaded.value.default});
std.debug.print("}}\n", .{});
}

View File

@ -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 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,
// 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,
// Only used by the parseTo family of functions.
// 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
// 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. 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.)
// cannot be mixed in a single document.
expect_enum_dot: bool = true,
// Only used by the parseTo family of functions.

View File

@ -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.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
.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.toOwnedSliceSentinel(arena_alloc, 0);
string.* = try state.string_builder.toOwnedSlice(arena_alloc);
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 (comptime tag == .line_string)
if (tag == .line_string)
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.appendSlice(arena_alloc, str);
},

View File

@ -9,11 +9,6 @@
// 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;
@ -56,7 +51,7 @@ pub fn Parsed(comptime T: type) type {
}
pub const Value = union(enum) {
pub const String = [:0]const u8;
pub const String = []const u8;
pub const Map = std.StringArrayHashMap(Value);
pub const List = std.ArrayList(Value);
pub const TagType = @typeInfo(Value).Union.tag_type.?;
@ -111,7 +106,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);
return try std.fmt.parseFloat(T, str, 0);
},
else => return error.BadValue,
}
@ -125,29 +120,27 @@ pub const Value = union(enum) {
// probably be solved in the zig stdlib or similar.
switch (self) {
.scalar, .string => |str| {
if (comptime ptr.child == u8) {
if (comptime ptr.sentinel) |sentinel|
if (comptime @as(*align(1) const ptr.child, @ptrCast(sentinel)).* != 0)
return error.BadValue;
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;
}
return str;
} else {
return error.BadValue;
}
},
.list, .inline_list => |lst| {
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);
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));
}
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];
if (ptr.sentinel) |sent| {
return try result.toOwnedSliceSentinel(@as(*align(1) const ptr.child, @ptrCast(sent)).*);
} else {
return result;
return try result.toOwnedSlice();
}
},
else => return error.BadValue,
@ -159,7 +152,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)),
else => @compileError("Cannot deserialize into many-pointer or c-pointer " ++ @typeName(T)), // do not support many or C item pointers.
},
.Array => |arr| {
// TODO: There is ambiguity here because a document expecting a list
@ -176,19 +169,21 @@ pub const Value = union(enum) {
} else return error.BadValue;
},
.list, .inline_list => |lst| {
if (lst.items.len != arr.len) return error.BadValue;
var result: T = undefined;
for (&result, lst.items) |*res, item| {
res.* = try item.convertTo(arr.child, allocator, options);
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
var result: T = undefined;
@memcpy(&result, storage.items);
return result;
},
else => return error.BadValue,
}
},
.Struct => |stt| {
if (comptime hasFn(T, "deserializeNice"))
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
return T.deserializeNice(self, allocator, options);
if (stt.is_tuple) {
@ -196,8 +191,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, &result, list.items) |field, *res, item| {
res.* = try item.convertTo(field.type, allocator, options);
inline for (stt.fields, 0..) |field, idx| {
result[idx] = try list.items[idx].convertTo(field.type, allocator, options);
}
return result;
},
@ -213,30 +208,24 @@ 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.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 if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
@field(result, field.name) = null;
} else {
return error.BadValue;
}
}
} else {
// 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.
// 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.
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.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 if (options.treat_omitted_as_null and @typeInfo(field.type) == .Optional) {
@field(result, field.name) = null;
} else return error.BadValue;
}
// there were extra fields in the data
@ -249,7 +238,7 @@ pub const Value = union(enum) {
}
},
.Enum => {
if (comptime hasFn(T, "deserializeNice"))
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
return T.deserializeNice(self, allocator, options);
switch (self) {
@ -274,7 +263,7 @@ pub const Value = union(enum) {
}
},
.Union => |unn| {
if (comptime hasFn(T, "deserializeNice"))
if (comptime std.meta.trait.hasFn("deserializeNice")(T))
return T.deserializeNice(self, allocator, options);
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 {
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 {