lib/zen/src/frontmatter/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_pairs: usize,
  7     max_continuation_bytes: usize,
  8 };
  9 
 10 pub const MetadataProjection = struct {
 11     title_bytes: ?usize = null,
 12     date_bytes: ?usize = null,
 13     type_bytes: ?usize = null,
 14     draft: bool = false,
 15     curated: bool = false,
 16 };
 17 
 18 pub const Plan = struct {
 19     input_bytes: usize,
 20     metadata_start: usize,
 21     metadata_end: usize,
 22     body_start: usize,
 23     pairs: usize,
 24     continuation_bytes: usize,
 25     projection: MetadataProjection,
 26 
 27     pub fn inspect(markdown: []const u8, limits: Limits) model.Error!Plan {
 28         const split = try scan.sections(markdown);
 29         std.debug.assert(split.metadata_start <= split.metadata_end);
 30         std.debug.assert(split.metadata_end <= split.body_start);
 31         std.debug.assert(split.body_start <= markdown.len);
 32         var iterator = scan.Iterator.init(markdown[split.metadata_start..split.metadata_end]);
 33         var pair_count: usize = 0;
 34         var finalized_bytes: usize = 0;
 35         var current: ?Current = null;
 36         var projection: Projection = .{};
 37 
 38         while (iterator.next()) |field| switch (field) {
 39             .pair => |pair| {
 40                 finalized_bytes = try finalize(finalized_bytes, current);
 41                 projection.finish(current);
 42                 pair_count = try added(pair_count, 1);
 43                 if (pair_count > limits.max_pairs) return error.PairCapacityExceeded;
 44                 current = Current.init(pair.key, pair.value);
 45             },
 46             .continuation => |value| if (current) |*active| {
 47                 try active.continueWith(value);
 48                 const admitted = try added(finalized_bytes, active.joined_bytes);
 49                 if (admitted > limits.max_continuation_bytes) {
 50                     return error.ContinuationByteCapacityExceeded;
 51                 }
 52             },
 53         };
 54         finalized_bytes = try finalize(finalized_bytes, current);
 55         projection.finish(current);
 56         std.debug.assert(pair_count <= limits.max_pairs);
 57         std.debug.assert(finalized_bytes <= limits.max_continuation_bytes);
 58         const result = Plan{
 59             .input_bytes = markdown.len,
 60             .metadata_start = split.metadata_start,
 61             .metadata_end = split.metadata_end,
 62             .body_start = split.body_start,
 63             .pairs = pair_count,
 64             .continuation_bytes = finalized_bytes,
 65             .projection = projection.value,
 66         };
 67         std.debug.assert(result.input_bytes == markdown.len);
 68         std.debug.assert(result.pairs == pair_count);
 69         std.debug.assert(result.continuation_bytes == finalized_bytes);
 70         return result;
 71     }
 72 
 73     pub fn exactLimits(self: Plan) Limits {
 74         const limits = Limits{
 75             .max_pairs = self.pairs,
 76             .max_continuation_bytes = self.continuation_bytes,
 77         };
 78         std.debug.assert(limits.max_pairs == self.pairs);
 79         std.debug.assert(limits.max_continuation_bytes == self.continuation_bytes);
 80         return limits;
 81     }
 82 };
 83 
 84 const Current = struct {
 85     key: []const u8,
 86     initial_bytes: usize,
 87     joined_bytes: usize = 0,
 88     continued: bool = false,
 89     truth: Truth,
 90 
 91     fn init(key: []const u8, value: []const u8) Current {
 92         std.debug.assert(key.len != 0);
 93         var truth: Truth = .{};
 94         truth.append(value);
 95         const current = Current{ .key = key, .initial_bytes = value.len, .truth = truth };
 96         std.debug.assert(current.initial_bytes == value.len);
 97         std.debug.assert(!current.continued);
 98         return current;
 99     }
100 
101     fn continueWith(self: *Current, value: []const u8) error{CapacityOverflow}!void {
102         std.debug.assert(value.len != 0);
103         const before = self.valueBytes();
104         if (!self.continued) {
105             self.joined_bytes = self.initial_bytes;
106             if (self.initial_bytes != 0) {
107                 self.joined_bytes = try added(self.joined_bytes, 1);
108                 self.truth.append(" ");
109             }
110             self.continued = true;
111         } else {
112             self.joined_bytes = try added(self.joined_bytes, 1);
113             self.truth.append(" ");
114         }
115         self.joined_bytes = try added(self.joined_bytes, value.len);
116         self.truth.append(value);
117         std.debug.assert(self.continued);
118         std.debug.assert(self.joined_bytes >= before);
119         std.debug.assert(self.joined_bytes >= value.len);
120     }
121 
122     fn valueBytes(self: Current) usize {
123         const bytes = if (self.continued) self.joined_bytes else self.initial_bytes;
124         if (self.continued) std.debug.assert(bytes >= self.initial_bytes);
125         return bytes;
126     }
127 };
128 
129 const Projection = struct {
130     value: MetadataProjection = .{},
131     title_seen: bool = false,
132     date_seen: bool = false,
133     type_seen: bool = false,
134     draft_seen: bool = false,
135     curated_seen: bool = false,
136 
137     fn finish(self: *Projection, maybe_current: ?Current) void {
138         const current = maybe_current orelse return;
139         std.debug.assert(current.key.len != 0);
140         if (std.mem.eql(u8, current.key, "title") and !self.title_seen) {
141             self.title_seen = true;
142             self.value.title_bytes = current.valueBytes();
143         } else if (std.mem.eql(u8, current.key, "date") and !self.date_seen) {
144             self.date_seen = true;
145             self.value.date_bytes = current.valueBytes();
146         } else if (std.mem.eql(u8, current.key, "type") and !self.type_seen) {
147             self.type_seen = true;
148             self.value.type_bytes = current.valueBytes();
149         } else if (std.mem.eql(u8, current.key, "draft") and !self.draft_seen) {
150             self.draft_seen = true;
151             self.value.draft = current.truth.isTrue();
152         } else if (std.mem.eql(u8, current.key, "curated") and !self.curated_seen) {
153             self.curated_seen = true;
154             self.value.curated = current.truth.isTrue();
155         }
156     }
157 };
158 
159 const Truth = struct {
160     bytes: [4]u8 = undefined,
161     len: usize = 0,
162     possible: bool = true,
163 
164     fn append(self: *Truth, value: []const u8) void {
165         if (!self.possible) return;
166         std.debug.assert(self.len <= self.bytes.len);
167         if (value.len > self.bytes.len - self.len) {
168             self.possible = false;
169             return;
170         }
171         @memcpy(self.bytes[self.len..][0..value.len], value);
172         self.len += value.len;
173         std.debug.assert(self.len <= self.bytes.len);
174     }
175 
176     fn isTrue(self: Truth) bool {
177         return self.possible and self.len == 4 and std.mem.eql(u8, self.bytes[0..4], "true");
178     }
179 };
180 
181 fn finalize(total: usize, current: ?Current) error{CapacityOverflow}!usize {
182     const active = current orelse return total;
183     if (!active.continued) return total;
184     return added(total, active.joined_bytes);
185 }
186 
187 fn added(left: usize, right: usize) error{CapacityOverflow}!usize {
188     return std.math.add(usize, left, right) catch error.CapacityOverflow;
189 }