Created
April 5, 2026 17:52
-
-
Save tkersey/8f44f81c9e2c77a8f6b5c0591723acdf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const std = @import("std"); | |
| // ---------------------------- | |
| // Core "POLY" interface pieces | |
| // ---------------------------- | |
| // A simple 2D position record used by several shapes. | |
| pub const Position = struct { | |
| x: f64, | |
| y: f64, | |
| }; | |
| // Our "Shape" is a tagged union over a few concrete variants. | |
| // Add more variants freely; the visitors below will adapt. | |
| pub const Shape = union(enum) { | |
| Circle: struct { center: Position, radius: f64 }, | |
| Rect: struct { min: Position, max: Position }, | |
| Point: Position, | |
| }; | |
| // A tiny, generic "map" that transforms any T -> U inside containers | |
| // we define below, using comptime reflection to recurse without runtime vtables. | |
| pub fn map(comptime T: type, comptime U: type, value: T, f: fn (T) U) U { | |
| // base case: if T exactly matches the input to f, just call f | |
| if (@typeInfo(T) == @typeInfo(@TypeOf(value))) { | |
| return f(value); | |
| } | |
| // fallback — but for clarity we keep map for (T->U) direct use in containers. | |
| return f(value); | |
| } | |
| // ---------------------------- | |
| // Shape utilities (no vtables) | |
| // ---------------------------- | |
| pub fn area(s: Shape) f64 { | |
| return switch (s) { | |
| .Circle => |c| std.math.pi * c.radius * c.radius, | |
| .Rect => |r| (r.max.x - r.min.x) * (r.max.y - r.min.y), | |
| .Point => 0.0, | |
| }; | |
| } | |
| pub fn translate(s: Shape, dx: f64, dy: f64) Shape { | |
| return switch (s) { | |
| .Circle => |c| .{ .Circle = .{ | |
| .center = .{ .x = c.center.x + dx, .y = c.center.y + dy }, | |
| .radius = c.radius, | |
| }}, | |
| .Rect => |r| .{ .Rect = .{ | |
| .min = .{ .x = r.min.x + dx, .y = r.min.y + dy }, | |
| .max = .{ .x = r.max.x + dx, .y = r.max.y + dy }, | |
| }}, | |
| .Point => |p| .{ .Point = .{ .x = p.x + dx, .y = p.y + dy } }, | |
| }; | |
| } | |
| // ---------------------------- | |
| // Small generic containers | |
| // ---------------------------- | |
| // A minimal forward list to hold *any* T, implemented without allocator tricks. | |
| pub fn List(comptime T: type) type { | |
| return struct { | |
| const Self = @This(); | |
| head: ?*Node = null, | |
| const Node = struct { | |
| value: T, | |
| next: ?*Node = null, | |
| }; | |
| pub fn pushFront(self: *Self, arena: *std.heap.ArenaAllocator, v: T) !void { | |
| const n = try arena.allocator().create(Node); | |
| n.* = .{ .value = v, .next = self.head }; | |
| self.head = n; | |
| } | |
| pub fn iter(self: *const Self) Iterator { | |
| return .{ .curr = self.head }; | |
| } | |
| pub const Iterator = struct { | |
| curr: ?*Node, | |
| pub fn next(it: *Iterator) ?*const T { | |
| if (it.curr) |n| { | |
| it.curr = n.next; | |
| return &n.value; | |
| } | |
| return null; | |
| } | |
| }; | |
| }; | |
| } | |
| // A simple RoseTree (multi-child tree) with generic payload T. | |
| pub fn RoseTree(comptime T: type) type { | |
| return struct { | |
| const Self = @This(); | |
| value: T, | |
| children: []Self = &.{}, | |
| // Map over the tree without runtime dispatch. | |
| pub fn mapTree(comptime U: type, self: *const Self, allocator: std.mem.Allocator, f: fn (T) U) !RoseTree(U) { | |
| var out: RoseTree(U) = .{ .value = f(self.value), .children = &.{} }; | |
| if (self.children.len > 0) { | |
| out.children = try allocator.alloc(RoseTree(U), self.children.len); | |
| for (self.children, 0..) |child, i| { | |
| out.children[i] = try child.mapTree(U, allocator, f); | |
| } | |
| } | |
| return out; | |
| } | |
| }; | |
| } | |
| // ---------------------------- | |
| // Reflection helpers (demo) | |
| // ---------------------------- | |
| // Pretty-print the tag name of any union(enum) value at comptime. | |
| pub fn tagNameOfUnion(v: anytype) []const u8 { | |
| const T = @TypeOf(v); | |
| const info = @typeInfo(T); | |
| if (info == .Union and info.Union.tag_type) |_| { | |
| return @tagName(v); | |
| } | |
| return "not-a-tagged-union"; | |
| } | |
| // ---------------------------- | |
| // Tests (run: `zig test thisfile.zig`) | |
| // ---------------------------- | |
| test "area & translate for shapes" { | |
| const s1 = Shape{ .Circle = .{ .center = .{ .x = 0, .y = 0 }, .radius = 2 } }; | |
| const s2 = Shape{ .Rect = .{ .min = .{ .x = 0, .y = 0 }, .max = .{ .x = 3, .y = 4 } } }; | |
| const s3 = Shape{ .Point = .{ .x = 1, .y = 1 } }; | |
| try std.testing.expectApproxEqAbs(std.math.pi * 4, area(s1), 1e-9); | |
| try std.testing.expectEqual(@as(f64, 12), area(s2)); | |
| try std.testing.expectEqual(@as(f64, 0), area(s3)); | |
| const moved = translate(s2, 1, -2); | |
| switch (moved) { | |
| .Rect => |r| { | |
| try std.testing.expectEqual(@as(f64, 1), r.min.x); | |
| try std.testing.expectEqual(@as(f64, -2), r.min.y); | |
| try std.testing.expectEqual(@as(f64, 4), r.max.x); | |
| try std.testing.expectEqual(@as(f64, 2), r.max.y); | |
| }, | |
| else => unreachable, | |
| } | |
| } | |
| test "List(T) push & iterate" { | |
| var gpa = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| defer gpa.deinit(); | |
| var list = List(i32){ .head = null }; | |
| try list.pushFront(&gpa, 10); | |
| try list.pushFront(&gpa, 20); | |
| try list.pushFront(&gpa, 30); | |
| var it = list.iter(); | |
| const a = it.next().?; | |
| const b = it.next().?; | |
| const c = it.next().?; | |
| try std.testing.expectEqual(@as(i32, 30), a.*); | |
| try std.testing.expectEqual(@as(i32, 20), b.*); | |
| try std.testing.expectEqual(@as(i32, 10), c.*); | |
| try std.testing.expect(it.next() == null); | |
| } | |
| test "RoseTree mapTree" { | |
| var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | |
| defer _ = gpa.deinit(); | |
| const A = RoseTree(i32); | |
| const B = RoseTree([]const u8); | |
| var t: A = .{ | |
| .value = 1, | |
| .children = &.{ | |
| .{ .value = 2, .children = &.{} }, | |
| .{ .value = 3, .children = &.{ | |
| .{ .value = 4, .children = &.{} }, | |
| }}, | |
| }, | |
| }; | |
| const mapped = try t.mapTree([]const u8, gpa.allocator(), struct { | |
| pub fn f(x: i32) []const u8 { | |
| return switch (x) { | |
| 1 => "one", | |
| 2 => "two", | |
| 3 => "three", | |
| 4 => "four", | |
| else => "other", | |
| }; | |
| } | |
| }.f); | |
| try std.testing.expect(std.mem.eql(u8, mapped.value, "one")); | |
| try std.testing.expect(std.mem.eql(u8, mapped.children[0].value, "two")); | |
| try std.testing.expect(std.mem.eql(u8, mapped.children[1].value, "three")); | |
| try std.testing.expect(std.mem.eql(u8, mapped.children[1].children[0].value, "four")); | |
| // Show off reflection helper on a tagged union: | |
| const u = Shape{ .Point = .{ .x = 0, .y = 0 } }; | |
| try std.testing.expect(std.mem.eql(u8, tagNameOfUnion(u), "Point")); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment