lib/zen/src/footnote/plan.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const model = @import("model.zig");
  3 const scan = @import("scan.zig");
  4 
  5 pub const Limits = struct {
  6     max_definitions: usize,
  7     max_joined_text_bytes: usize,
  8 };
  9 
 10 pub const default_limits: Limits = .{
 11     .max_definitions = 512,
 12     .max_joined_text_bytes = 64 * 1024,
 13 };
 14 
 15 pub const Plan = struct {
 16     source_bytes: usize,
 17     definitions: usize,
 18     joined_text_bytes: usize,
 19 
 20     pub fn inspect(source: []const u8, limits: Limits) model.Error!Plan {
 21         var state = State{};
 22         var iterator = scan.Iterator.init(source);
 23         while (iterator.next()) |event| switch (event) {
 24             .definition => |definition| {
 25                 state.definitions = try added(state.definitions, 1);
 26                 if (state.definitions > limits.max_definitions) {
 27                     return error.FootnoteDefinitionCapacityExceeded;
 28                 }
 29                 state.current_text_bytes = definition.text.len;
 30                 state.current_joined = false;
 31             },
 32             .continuation => |text| try state.join(text, limits.max_joined_text_bytes),
 33         };
 34         std.debug.assert(state.definitions <= limits.max_definitions);
 35         std.debug.assert(state.joined_text_bytes <= limits.max_joined_text_bytes);
 36         return .{
 37             .source_bytes = source.len,
 38             .definitions = state.definitions,
 39             .joined_text_bytes = state.joined_text_bytes,
 40         };
 41     }
 42 
 43     pub fn exactLimits(self: Plan) Limits {
 44         return .{
 45             .max_definitions = self.definitions,
 46             .max_joined_text_bytes = self.joined_text_bytes,
 47         };
 48     }
 49 };
 50 
 51 const State = struct {
 52     definitions: usize = 0,
 53     joined_text_bytes: usize = 0,
 54     current_text_bytes: usize = 0,
 55     current_joined: bool = false,
 56 
 57     fn join(self: *State, text: []const u8, max_joined_text_bytes: usize) model.Error!void {
 58         var joined_text_bytes = self.joined_text_bytes;
 59         if (!self.current_joined) {
 60             joined_text_bytes = try added(joined_text_bytes, self.current_text_bytes);
 61             if (self.current_text_bytes != 0) joined_text_bytes = try added(joined_text_bytes, 1);
 62         } else {
 63             joined_text_bytes = try added(joined_text_bytes, 1);
 64         }
 65         joined_text_bytes = try added(joined_text_bytes, text.len);
 66         if (joined_text_bytes > max_joined_text_bytes) {
 67             return error.FootnoteJoinedTextByteCapacityExceeded;
 68         }
 69         self.joined_text_bytes = joined_text_bytes;
 70         self.current_text_bytes = if (self.current_text_bytes == 0)
 71             text.len
 72         else
 73             try added(try added(self.current_text_bytes, 1), text.len);
 74         self.current_joined = true;
 75     }
 76 };
 77 
 78 fn added(left: usize, right: usize) error{CapacityOverflow}!usize {
 79     return std.math.add(usize, left, right) catch error.CapacityOverflow;
 80 }
 81 
 82 test "footnote plan counts only text requiring a join" {
 83     const source =
 84         "[^paper]: A [paper](/paper)\n" ++
 85         "  with `code`.\n" ++
 86         "[^block]:\n" ++
 87         "    Block style.\n" ++
 88         "[^unused]: Hidden.\n";
 89     const plan = try Plan.inspect(source, default_limits);
 90     try std.testing.expectEqual(@as(usize, 3), plan.definitions);
 91     try std.testing.expectEqual(@as(usize, 42), plan.joined_text_bytes);
 92     try std.testing.expectEqual(Limits{
 93         .max_definitions = 3,
 94         .max_joined_text_bytes = 42,
 95     }, plan.exactLimits());
 96 }
 97 
 98 test "footnote plan borrows every uncontinued definition" {
 99     const plan = try Plan.inspect("[^one]: First\n[^two]: Second\n", default_limits);
100     try std.testing.expectEqual(@as(usize, 2), plan.definitions);
101     try std.testing.expectEqual(@as(usize, 0), plan.joined_text_bytes);
102 }