lib/zen/src/document/plan.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const model = @import("model.zig");
3
4 /// Maximum heading descriptors and copied text and ID bytes.
5 pub const Limits = struct {
6 /// Maximum number of headings in one document.
7 max_headings: usize,
8 /// Maximum total visible heading-text bytes.
9 max_text_bytes: usize,
10 /// Maximum total resolved heading-ID bytes.
11 max_id_bytes: usize,
12 };
13
14 /// General-purpose limits for convenience Markdown rendering.
15 pub const default_limits: Limits = .{
16 .max_headings = 4_096,
17 .max_text_bytes = 1024 * 1024,
18 .max_id_bytes = 1024 * 1024,
19 };
20
21 /// Exact storage demand measured from one source without allocation.
22 pub const Plan = struct {
23 /// Total bytes in the inspected Markdown source.
24 source_bytes: usize,
25 /// Number of heading descriptors required.
26 headings: usize = 0,
27 /// Total visible heading-text bytes copied into storage.
28 text_bytes: usize = 0,
29 /// Total resolved heading-ID bytes copied into storage.
30 id_bytes: usize = 0,
31
32 pub fn init(source_bytes: usize) Plan {
33 return .{ .source_bytes = source_bytes };
34 }
35
36 pub fn includeHeading(
37 self: *Plan,
38 text: []const u8,
39 id_bytes: usize,
40 ) error{CapacityOverflow}!void {
41 const headings = try added(self.headings, 1);
42 const text_bytes = try added(self.text_bytes, text.len);
43 const total_id_bytes = try added(self.id_bytes, id_bytes);
44 self.headings = headings;
45 self.text_bytes = text_bytes;
46 self.id_bytes = total_id_bytes;
47 }
48
49 /// Returns the smallest limits that admit this plan.
50 pub fn exactLimits(self: Plan) Limits {
51 return .{
52 .max_headings = self.headings,
53 .max_text_bytes = self.text_bytes,
54 .max_id_bytes = self.id_bytes,
55 };
56 }
57
58 /// Rejects when any measured region exceeds supplied limits.
59 pub fn require(self: Plan, limits: Limits) model.Exhaustion!void {
60 if (self.headings > limits.max_headings) return error.HeadingCapacityExceeded;
61 if (self.text_bytes > limits.max_text_bytes) {
62 return error.HeadingTextByteCapacityExceeded;
63 }
64 if (self.id_bytes > limits.max_id_bytes) {
65 return error.HeadingIdByteCapacityExceeded;
66 }
67 }
68 };
69
70 fn added(left: usize, right: usize) error{CapacityOverflow}!usize {
71 return std.math.add(usize, left, right) catch error.CapacityOverflow;
72 }
73
74 test "heading plan counts copied text and normalized identifiers" {
75 var plan = Plan.init(32);
76 try plan.includeHeading("Page", "page".len);
77 try plan.includeHeading("Section", "section".len);
78 try plan.includeHeading("?", "section".len);
79 try std.testing.expectEqual(@as(usize, 3), plan.headings);
80 try std.testing.expectEqual(@as(usize, 12), plan.text_bytes);
81 try std.testing.expectEqual(@as(usize, 18), plan.id_bytes);
82 try std.testing.expectEqual(Limits{
83 .max_headings = 3,
84 .max_text_bytes = 12,
85 .max_id_bytes = 18,
86 }, plan.exactLimits());
87 }
88
89 test "heading plan accepts an exact explicit identifier length" {
90 var plan = Plan.init(32);
91 try plan.includeHeading("Page", "tiny.zen.Page".len);
92 try std.testing.expectEqual(@as(usize, "tiny.zen.Page".len), plan.id_bytes);
93 }