TheHonestHare/zkinder
A pattern matching library for zig
A library that implements pattern matching in fully userland zig. Tested on zig version 0.14.0-dev.1391+e084c46ed but should probably work on master
const ki = @import("zkinder");
const bind = ki.bind;
const __ = ki.__;
const thing: struct {
age: u32,
birth: u64,
pos: struct { x: u8, y: u8 }
} = .{.age = 3, .birth = 2, .pos = .{.x = 90, .y = 20}};
const m = ki.match(&thing);
// bind puts the value of the variable in the result
const res = m.arm(.{.age = 3, .birth = bind("hello"), .pos = .{.x = bind("x"), .y = 20}});
try std.testing.expectEqual(2, res.?.hello);
try std.testing.expectEqual(90, res.?.x);
const res2 = m.arm(.{.age = 3, .birth = bind("hello"), .pos = bind("pos")});
try std.testing.expectEqual(2, res2.?.hello);
try std.testing.expectEqual(90, res2.?.pos.x);
try std.testing.expectEqual(20, res2.?.pos.y);
// this arm doesn't match because age = 2, will return null
const res3 = m.arm(.{.age = 2, .birth = bind("hello"), .pos = bind("pos")});
try std.testing.expectEqual(null, res3);
// __ will match on anything
const res4 = m.arm(.{.age = __, .birth = __, .pos = .{.x = __, .y = bind("y")}});
try std.testing.expectEqual(20, res4.?.y);
nonnullref_rest (see "custom array matchers")matching for no exhaustive checking, single armbind__rangebind_ifbind and __ use no special casing, you could implement yourselffn (comptime type) CustomMatcherpartial, ref,A custom matcher is any function of the form fn (comptime type) Matcher, where the input is the type it is being matched against. bind(<name>) and __ are both custom matchers implementable you could just as easily implement
For example, in the pattern,
.{.age = 2, .birth = __, .pos = __}
__ is actually a function which will take the types of thing.birth and thing.pos respectively.
Matcher is defined as such:
pub const Matcher = struct {
captures: type,
tryBind: fn (self: Matcher, val_ptr: anytype, out_ptr: anytype) bool,
};
captures is a struct type where each field is an output the matcher produces. This is needed for features like bind. All fields are combined, using comptime, into one giant struct that is returned from the arm.
tryBind is where all the magic happens.
self allows you to access the captures fieldval_ptr is a pointer to the field being matched against. The fields type will be the same type that was passed into the custom matcher.out_ptr lets you write to the out of the arm, if you had any captures. For example, out_ptr.hello = 2 would write 2 to the hello field of the output. You can only write to fields that were already declared as capturestrue if the value in val_ptr matches whatever criteria you want, or false if notThese are similar to custom matchers, but they only work in array or slice patterns. ref_rest(<name>) is a custom array matcher. Instead of having to have a pattern for the entire length of the array, you can match over part of it.
You can only have custom array matcher per array/slice pattern
const list = [_]u8{ 0, 9, 100, 140 };
const m = comptime match(&list);
const res = m.arm(.{ 0, 9, 100, bind("num") });
const res2 = m.arm(.{ ref_rest("first2"), 100, 140 });
const res3 = m.arm(.{ bind("head"), ref_rest("tail") });
const res4 = m.arm(.{ 1, ref_rest("tail") });
const res5 = m.arm(.{ 0, ref_rest("middle"), 140 });
try std.testing.expectEqual(140, res.?.num);
try std.testing.expectEqualSlices(u8, &.{ 0, 9 }, res2.?.first2);
try std.testing.expectEqual(0, res3.?.head);
try std.testing.expectEqualSlices(u8, &.{ 9, 100, 140 }, res3.?.tail);
try std.testing.expectEqual(null, res4);
try std.testing.expectEqualSlices(u8, &.{ 9, 100 }, res5.?.middle);
}
To make your own, you need a thing of type fn (comptime type, comptime ?usize) SubSliceMatcher. The first parameter is the child type of the array or slice, and the second is the length of the array, or null if it is a slice
SubSliceMatcher has a captures field similar to a custom matcher, as well as a tryBind fn. The only difference is that instead of val_ptr, it will take subslice,
If the matched against type is an array, subslice will be a pointer to the subarray that is not matched against in the rest of the pattern
If the matched againt type is a slice, subslice will be the subslice that is not matched against in the rest of the pattern