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. /// Note that a relative path can still be interpreted as absolute.
absolute: bool, absolute: bool,
/// The string parts that make up the path. /// The string parts that make up the path.
parts: []const []const u8, parts: []const EntityPart,
/// The allocator that was used to allocate `parts`. /// The allocator that was used to allocate `parts`.
alloc: ?Allocator = null, alloc: ?Allocator = null,
/// Whether the parts outer array itself is owned by this `Path`. /// 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`. /// Whether the parts inner strings are owned by this `Path`.
owns_parts: bool = false, 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. /// Format used to parse and stringify `Path`s.
/// ///
/// When this type is formatted, you may use any of the `FormatOption` /// 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. /// Creates a new `Path` from the specified string parts.
/// ///
/// The resulting path does not own any of the given slices. /// 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); std.debug.assert(parts.len > 0);
return .{ .absolute = absolute, .parts = parts }; 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_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 i: usize = 0;
var it = std.mem.splitSequence(u8, remaining, opt.sep); 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 .{ return .{
.absolute = absolute, .absolute = absolute,
@ -95,15 +105,16 @@ pub const Path = struct {
/// Creates a deep clone of this `Path` using the specified `Allocator`. /// Creates a deep clone of this `Path` using the specified `Allocator`.
pub fn clone(orig: Path, alloc: Allocator) !Path { 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); errdefer alloc.free(parts);
var num_allocated: usize = 0; var num_allocated: usize = 0;
errdefer for (parts[0..num_allocated]) |part| errdefer for (parts[0..num_allocated]) |part|
alloc.free(part); if (part == .name) alloc.free(part.name);
for (parts) |*part| { for (parts) |*part| {
part.* = try alloc.dupe(u8, part.*); if (part.* == .name)
part.* = .{ .name = try alloc.dupe(u8, part.name) };
num_allocated += 1; num_allocated += 1;
} }
@ -120,7 +131,8 @@ pub const Path = struct {
pub fn deinit(self: Path) void { pub fn deinit(self: Path) void {
if (self.owns_parts) if (self.owns_parts)
for (self.parts) |part| for (self.parts) |part|
self.alloc.?.free(part); if (part == .name)
self.alloc.?.free(part.name);
if (self.owns_array) if (self.owns_array)
self.alloc.?.free(self.parts); self.alloc.?.free(self.parts);
} }
@ -166,23 +178,64 @@ pub const Path = struct {
} }
// Parts. // Parts.
for (self.parts) |part| for (self.parts) |part|
result += part.len; result += switch (part) {
.name => |name| name.len,
.id => |id| numDigits(id),
};
return result; return result;
} }
fn write(self: Path, opt: FormatOptions, writer: anytype) !void { fn write(self: Path, opt: FormatOptions, writer: anytype) !void {
// Write prefix (if applicable), // Write prefix (if applicable).
if (self.absolute) { if (self.absolute) {
if (opt.prefix) |p| if (opt.prefix) |p|
try writer.writeAll(p); try writer.writeAll(p);
} }
// Write first part. // Write the first part.
try writer.writeAll(self.parts[0]); switch (self.parts[0]) {
// Write other parts, each preceeded by separator. .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| { for (self.parts[1..]) |part| {
try writer.writeAll(opt.sep); 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
} }
}; };
@ -190,7 +243,6 @@ test Path {
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
const expectFmt = std.testing.expectFmt; const expectFmt = std.testing.expectFmt;
const expectEqual = std.testing.expectEqual; const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectEqualStrings = std.testing.expectEqualStrings; const expectEqualStrings = std.testing.expectEqualStrings;
// Paths may be constructed by parsing strings. // Paths may be constructed by parsing strings.
@ -200,7 +252,7 @@ test Path {
// Alternatively they can be made by specifying the individual component // Alternatively they can be made by specifying the individual component
// parts they're made of, as well as an argument specifying whether it's // parts they're made of, as well as an argument specifying whether it's
// an absolute path. // 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. // No need for `deinit()`, does not own outer array nor inner string parts.
// With `options` unspecified, it's not possible to represent an absolute // 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`. // The internal component parts of the path can be accessed with `.parts`.
try expectEqual(@as(usize, 3), relative.parts.len); try expectEqual(@as(usize, 3), relative.parts.len);
try expectEqualStrings("some", relative.parts[0]); try expectEqualStrings("some", relative.parts[0].name);
try expectEqualStrings("relative", relative.parts[1]); try expectEqualStrings("relative", relative.parts[1].name);
try expectEqualStrings("path", relative.parts[2]); try expectEqualStrings("path", relative.parts[2].name);
try expectEqualDeep(@as([]const []const u8, &.{ "I'm", "absolute!" }), absolute1.parts);
try expectEqualDeep(@as([]const []const u8, &.{ "home", "copygirl" }), absolute2.parts); // 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);
// Paths are formattable, and as the `fmt` specifier you can either use one defer numeric.deinit();
// of the pre-defined ones defined in the `FormatOptions` type, or just an try expectEqual(@as(usize, 3), numeric.parts.len);
// empty string to use the default. 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("some.relative.path", "{}", .{relative});
try expectFmt("::I'm::absolute!", "{flecs_cpp}", .{absolute1}); try expectFmt("::I'm::absolute!", "{flecs_cpp}", .{absolute1});
try expectFmt("/home/copygirl", "{unix}", .{absolute2}); try expectFmt("/home/copygirl", "{unix}", .{absolute2});

Loading…
Cancel
Save