Allow Path to handle numeric entity ids

main
copygirl 1 year ago
parent edea471785
commit 4dbb7b84a8
  1. 107
      src/path.zig

@ -11,7 +11,7 @@ pub const Path = struct {
/// Note that a relative path can still be interpreted as absolute.
absolute: bool,
/// The string parts that make up the path.
parts: []const []const u8,
parts: []const EntityPart,
/// The allocator that was used to allocate `parts`.
alloc: ?Allocator = null,
/// Whether the parts outer array itself is owned by this `Path`.
@ -19,6 +19,12 @@ pub const Path = struct {
/// Whether the parts inner strings are owned by this `Path`.
owns_parts: bool = false,
/// Represents an `Entity` in a `Path`, either by name or its numeric id.
pub const EntityPart = union(enum) {
name: []const u8,
id: u32,
};
/// Format used to parse and stringify `Path`s.
///
/// When this type is formatted, you may use any of the `FormatOption`
@ -49,7 +55,7 @@ pub const Path = struct {
/// Creates a new `Path` from the specified string parts.
///
/// The resulting path does not own any of the given slices.
pub fn fromParts(absolute: bool, parts: []const []const u8) !Path {
pub fn fromParts(absolute: bool, parts: []const EntityPart) !Path {
std.debug.assert(parts.len > 0);
return .{ .absolute = absolute, .parts = parts };
}
@ -79,11 +85,15 @@ pub const Path = struct {
}
const parts_len = std.mem.count(u8, remaining, opt.sep) + 1;
const parts = try alloc.alloc([]const u8, parts_len);
const parts = try alloc.alloc(EntityPart, parts_len);
var i: usize = 0;
var it = std.mem.splitSequence(u8, remaining, opt.sep);
while (it.next()) |part| : (i += 1) parts[i] = part;
while (it.next()) |str| : (i += 1)
parts[i] = if (parseNumericId(str)) |id|
.{ .id = id }
else
.{ .name = str };
return .{
.absolute = absolute,
@ -95,15 +105,16 @@ pub const Path = struct {
/// Creates a deep clone of this `Path` using the specified `Allocator`.
pub fn clone(orig: Path, alloc: Allocator) !Path {
var parts = try alloc.dupe([]const u8, orig.parts);
var parts = try alloc.dupe(EntityPart, orig.parts);
errdefer alloc.free(parts);
var num_allocated: usize = 0;
errdefer for (parts[0..num_allocated]) |part|
alloc.free(part);
if (part == .name) alloc.free(part.name);
for (parts) |*part| {
part.* = try alloc.dupe(u8, part.*);
if (part.* == .name)
part.* = .{ .name = try alloc.dupe(u8, part.name) };
num_allocated += 1;
}
@ -120,7 +131,8 @@ pub const Path = struct {
pub fn deinit(self: Path) void {
if (self.owns_parts)
for (self.parts) |part|
self.alloc.?.free(part);
if (part == .name)
self.alloc.?.free(part.name);
if (self.owns_array)
self.alloc.?.free(self.parts);
}
@ -166,31 +178,71 @@ pub const Path = struct {
}
// Parts.
for (self.parts) |part|
result += part.len;
result += switch (part) {
.name => |name| name.len,
.id => |id| numDigits(id),
};
return result;
}
fn write(self: Path, opt: FormatOptions, writer: anytype) !void {
// Write prefix (if applicable),
// Write prefix (if applicable).
if (self.absolute) {
if (opt.prefix) |p|
try writer.writeAll(p);
}
// Write first part.
try writer.writeAll(self.parts[0]);
// Write other parts, each preceeded by separator.
// Write the first part.
switch (self.parts[0]) {
.name => |name| try writer.writeAll(name),
.id => |id| try writer.writeIntNative(u32, id),
}
// Write the remaining parts, each preceeded bu separator.
for (self.parts[1..]) |part| {
try writer.writeAll(opt.sep);
try writer.writeAll(part);
switch (part) {
.name => |name| try writer.writeAll(name),
.id => |id| try writer.writeIntNative(u32, id),
}
}
}
/// Attempts to parse the specified string as a numeric entity id.
fn parseNumericId(str: []const u8) ?u32 {
if (str.len == 0 or str.len > 10) return null;
var result: u32 = 0;
var place: u32 = 1;
var it = std.mem.reverseIterator(str);
while (it.next()) |chr| {
const d = std.fmt.charToDigit(chr, 10) catch return null;
result = std.math.add(u32, result, d * place) catch return null;
place *%= 10; // Wrapping to avoid overflow error.
}
return result;
}
/// Calculates the numbers of digits of an entity id when formatted as a
/// string. Since entity ids tend to be small this is technically optimized
/// for smaller numbers but performance likely doesn't matter much.
fn numDigits(n: u32) usize {
// zig fmt: off
return if (n < 10) 1
else if (n < 100) 2
else if (n < 1000) 3
else if (n < 10000) 4
else if (n < 100000) 5
else if (n < 1000000) 6
else if (n < 10000000) 7
else if (n < 100000000) 8
else if (n < 1000000000) 9
else 10;
// zig fmt: on
}
};
test Path {
const alloc = std.testing.allocator;
const expectFmt = std.testing.expectFmt;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectEqualStrings = std.testing.expectEqualStrings;
// Paths may be constructed by parsing strings.
@ -200,7 +252,7 @@ test Path {
// Alternatively they can be made by specifying the individual component
// parts they're made of, as well as an argument specifying whether it's
// an absolute path.
const absolute1 = try Path.fromParts(true, &.{ "I'm", "absolute!" });
const absolute1 = try Path.fromParts(true, &.{ .{ .name = "I'm" }, .{ .name = "absolute!" } });
// No need for `deinit()`, does not own outer array nor inner string parts.
// With `options` unspecified, it's not possible to represent an absolute
@ -217,15 +269,20 @@ test Path {
// The internal component parts of the path can be accessed with `.parts`.
try expectEqual(@as(usize, 3), relative.parts.len);
try expectEqualStrings("some", relative.parts[0]);
try expectEqualStrings("relative", relative.parts[1]);
try expectEqualStrings("path", relative.parts[2]);
try expectEqualDeep(@as([]const []const u8, &.{ "I'm", "absolute!" }), absolute1.parts);
try expectEqualDeep(@as([]const []const u8, &.{ "home", "copygirl" }), absolute2.parts);
// Paths are formattable, and as the `fmt` specifier you can either use one
// of the pre-defined ones defined in the `FormatOptions` type, or just an
// empty string to use the default.
try expectEqualStrings("some", relative.parts[0].name);
try expectEqualStrings("relative", relative.parts[1].name);
try expectEqualStrings("path", relative.parts[2].name);
// Parts can also be numeric ids, used for entities that don't have a name.
const numeric = try Path.fromString("100.101.bar", null, alloc);
defer numeric.deinit();
try expectEqual(@as(usize, 3), numeric.parts.len);
try expectEqual(@as(u32, 100), numeric.parts[0].id);
try expectEqual(@as(u32, 101), numeric.parts[1].id);
try expectEqualStrings("bar", numeric.parts[2].name);
// Paths are formattable. As format specifier you can use options defined
// on the `FormatOptions` type, or an empty string to use the default.
try expectFmt("some.relative.path", "{}", .{relative});
try expectFmt("::I'm::absolute!", "{flecs_cpp}", .{absolute1});
try expectFmt("/home/copygirl", "{unix}", .{absolute2});

Loading…
Cancel
Save