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 }