lib/zen/src/quiz/parse.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const model = @import("model.zig");
  3 const scan = @import("scan.zig");
  4 const storage_mod = @import("storage.zig");
  5 
  6 pub fn parse(storage: *storage_mod.Storage, source: []const u8) model.Error!model.Quiz {
  7     var regions = try storage.acquire(source);
  8     var iterator = scan.Iterator.init(source);
  9     var question_index: usize = 0;
 10     var option_index: usize = 0;
 11     var byte_index: usize = 0;
 12     var current: ?Current = null;
 13 
 14     while (iterator.next()) |token| switch (token) {
 15         .prompt => |text| {
 16             if (current) |active| active.finish(&regions, option_index);
 17             std.debug.assert(question_index < regions.questions.len);
 18             regions.questions[question_index] = .{
 19                 .prompt = text,
 20                 .options = &.{},
 21                 .explanation = "",
 22             };
 23             current = .{
 24                 .question = question_index,
 25                 .option_start = option_index,
 26                 .active = .{ .prompt = question_index },
 27             };
 28             question_index += 1;
 29         },
 30         .option => |option| {
 31             std.debug.assert(current != null);
 32             var active = &current.?;
 33             std.debug.assert(option_index < regions.options.len);
 34             regions.options[option_index] = .{
 35                 .text = option.text,
 36                 .correct = option.correct,
 37             };
 38             active.active = .{ .option = option_index };
 39             active.joined_start = null;
 40             option_index += 1;
 41         },
 42         .explanation => |text| {
 43             std.debug.assert(current != null);
 44             var active = &current.?;
 45             switch (active.active) {
 46                 .explanation => active.join(&regions, &byte_index, text),
 47                 else => {
 48                     regions.questions[active.question].explanation = text;
 49                     active.active = .{ .explanation = active.question };
 50                     active.joined_start = null;
 51                 },
 52             }
 53         },
 54         .continuation => |text| {
 55             std.debug.assert(current != null);
 56             var active = &current.?;
 57             active.join(&regions, &byte_index, text);
 58         },
 59     };
 60     std.debug.assert(current != null);
 61     current.?.finish(&regions, option_index);
 62     std.debug.assert(question_index == regions.questions.len);
 63     std.debug.assert(option_index == regions.options.len);
 64     std.debug.assert(byte_index == regions.joined_text.len);
 65     return .{ .questions = regions.questions };
 66 }
 67 
 68 const Active = union(enum) {
 69     prompt: usize,
 70     option: usize,
 71     explanation: usize,
 72 
 73     fn target(self: Active, regions: *storage_mod.Regions) *[]const u8 {
 74         return switch (self) {
 75             .prompt => |index| &regions.questions[index].prompt,
 76             .option => |index| &regions.options[index].text,
 77             .explanation => |index| &regions.questions[index].explanation,
 78         };
 79     }
 80 };
 81 
 82 const Current = struct {
 83     question: usize,
 84     option_start: usize,
 85     active: Active,
 86     joined_start: ?usize = null,
 87 
 88     fn finish(self: Current, regions: *storage_mod.Regions, option_index: usize) void {
 89         std.debug.assert(self.question < regions.questions.len);
 90         std.debug.assert(self.option_start <= option_index);
 91         std.debug.assert(option_index <= regions.options.len);
 92         regions.questions[self.question].options = regions.options[self.option_start..option_index];
 93     }
 94 
 95     fn join(
 96         self: *Current,
 97         regions: *storage_mod.Regions,
 98         byte_index: *usize,
 99         text: []const u8,
100     ) void {
101         const target = self.active.target(regions);
102         const start = self.joined_start orelse start: {
103             const value = byte_index.*;
104             copyText(regions.joined_text, byte_index, target.*);
105             self.joined_start = value;
106             break :start value;
107         };
108         copyText(regions.joined_text, byte_index, " ");
109         copyText(regions.joined_text, byte_index, text);
110         target.* = regions.joined_text[start..byte_index.*];
111     }
112 };
113 
114 fn copyText(destination: []u8, index: *usize, bytes: []const u8) void {
115     std.debug.assert(index.* <= destination.len);
116     std.debug.assert(bytes.len <= destination.len - index.*);
117     @memcpy(destination[index.*..][0..bytes.len], bytes);
118     index.* += bytes.len;
119 }