~subsetpark/erasmus

2cf6bb82cced8790fb5bacb4142986427c4b5b31 — Zach Smith 2 months ago 9ae94bd
Allow nested references, order references by first occurance
2 files changed, 108 insertions(+), 59 deletions(-)

M priv/zk.pant
M src/main.zig
M priv/zk.pant => priv/zk.pant +2 -2
@@ 45,8 45,8 @@ uniq xs: [Any] => [Any].
// To refer to a given name, we simply insert a line with a recognized string
// in front of it, followed by the name. For instance: "%ref:scifi\ authors".
// In particular, it's important to escape any delimiters in the name; this is
// to allow the "go to file under cursor" command to recognize the whole name
// rather than the first part, in the case of names with spaces.
// to allow the "go to file under cursor" command in a text editor to recognize
// the whole name rather than the first part, in the case of names with spaces.

ref s = "%ref:" + escape s + "\n".
is_ref l <-> some s: String => l = "%ref:" + s + "\n".

M src/main.zig => src/main.zig +106 -57
@@ 12,18 12,31 @@ const ref_prefix = "%ref";
const max_line_length = 1024;

const RuntimeError = error{
    BracketInBrackets,
    IllegalCharacterInBrackets,
};

const begin_brackets = '{';
const end_brackets = '}';
const begin_braces = '{';
const end_braces = '}';

const CharBuffer = std.ArrayList(u8);

const Brackets = struct {
    brackets: std.StringHashMap(bool),
    error_slice: ?[]const u8,
const BufferAndSlices = struct {
    buffer: CharBuffer,
    offset: usize,
    sliceBegins: std.ArrayList(usize),

    fn newSlice(self: *@This(), bodyOffset: usize) !void {
        try self.sliceBegins.append(bodyOffset - self.offset);
    }

    fn pop(self: *@This()) usize {
        return self.sliceBegins.pop();
    }

    fn deinit(self: @This()) void {
        self.buffer.deinit();
        self.sliceBegins.deinit();
    }
};

const EscapedString = struct {


@@ 106,37 119,68 @@ fn readBody(entry_name: []const u8, contents: *CharBuffer) !void {

// Iterate through a slice and populate an ArrayList with subslices capturing
// "{<reference>}"
fn findBracketedStrings(note_body: []const u8, brackets: *Brackets) !bool {
    var found = false;
fn findBracketedStrings(
    note_body: []const u8,
    braces: *std.StringHashMap(usize),
    allocator: mem.Allocator,
) !void {
    var current_reference: ?BufferAndSlices = null;
    var found: usize = 0;

    var i: usize = 0;
    var current_slice: ?usize = null;
    while (i < note_body.len) : (i += 1) {
        if (note_body[i] == begin_brackets) {
            if (current_slice) |slice_start| {
                // We tried to make a reference inside an existing one.
                brackets.error_slice = note_body[slice_start .. i + 1];
                return RuntimeError.BracketInBrackets;
            } else {
                // Open a new reference.
                current_slice = i;
            }
        } else if (note_body[i] == end_brackets) {
            if (current_slice) |slice_start| {
                if (i - slice_start > 1) {
                    // If we are closing an existing reference and the brackets
                    // aren't empty, push a new slice.
                    found = true;
                    const slice = note_body[slice_start + 1 .. i];
                    try brackets.brackets.put(slice, true);
        const char = note_body[i];
        switch (char) {
            begin_braces => {
                if (current_reference == null) {
                    current_reference = BufferAndSlices{
                        .buffer = CharBuffer.init(allocator),
                        .offset = i,
                        .sliceBegins = std.ArrayList(usize).init(allocator),
                    };
                }
                current_slice = null;
            }
        } else if (note_body[i] == '\n') {
            // References can't cross over lines.
            current_slice = null;
                if (current_reference) |*reference| {
                    try reference.newSlice(i);
                }
            },
            end_braces => {
                if (current_reference) |*reference| {
                    // Subtract the number of current open braces, because
                    // those won't have been included in the current buffer.
                    const begin = reference.pop() - reference.sliceBegins.items.len;
                    const reference_text = reference.buffer.items[begin..];

                    if (!braces.contains(reference_text)) {
                        const new_string = try allocator.alloc(u8, reference_text.len);
                        std.mem.copy(u8, new_string, reference_text);

                        found += 1;

                        try braces.put(new_string, found);
                    }

                    if (reference.sliceBegins.items.len == 0) {
                        reference.deinit();
                        current_reference = null;
                    }
                }
            },
            '\n' => {
                // References can't cross over lines.
                if (current_reference) |reference| {
                    reference.deinit();
                    current_reference = null;
                }
            },
            else => {
                // While we're currently in open braces, collect the non-brace
                // strings.
                if (current_reference) |*reference| {
                    try reference.buffer.append(char);
                }
            },
        }
    }
    return found;
}

// Create a reference line and add it to a note.


@@ 183,9 227,9 @@ fn appendBacklinks(
) !void {
    var found_one = false;
    const link_here = try fmt.allocPrint(allocator, "{c}{s}{c}", .{
        begin_brackets,
        begin_braces,
        note.name,
        end_brackets,
        end_braces,
    });

    for (notes.items) |other_note| {


@@ 201,7 245,7 @@ fn appendBacklinks(
            defer escaped.escaped.deinit();

            escape(other_note.name, &escaped) catch {
                std.debug.print("Found illegal character in brackets: `{s}`\n", .{
                std.debug.print("Found illegal character in braces: `{s}`\n", .{
                    escaped.error_slice,
                });
                std.os.exit(1);


@@ 217,43 261,48 @@ fn appendBacklinks(
    }
}

const OrderedRef = struct { ref: []const u8, i: usize };

fn compareReference(_: void, a: OrderedRef, b: OrderedRef) bool {
    return a.i < b.i;
}

// References: gather every reference in the body of the note.
fn appendReferences(
    note: *Note,
    allocator: mem.Allocator,
) !void {
    var brackets = Brackets{
        .brackets = std.StringHashMap(bool).init(allocator),
        .error_slice = undefined,
    };
    defer brackets.brackets.deinit();

    const found_one = findBracketedStrings(note.body.items, &brackets) catch |err| switch (err) {
        RuntimeError.BracketInBrackets => {
            std.debug.print("Found improperly nested brackets: {s}.\n", .{
                brackets.error_slice,
            });
            std.os.exit(1);
        },
        else => {
            return err;
        },
    };
    var braces = std.StringHashMap(usize).init(allocator);
    defer braces.deinit();

    try findBracketedStrings(note.body.items, &braces, allocator);

    // References padding.
    if (found_one) {
    if (braces.count() > 0) {
        try note.append('\n');
    }

    // Sort references by first appearance.
    var entries = braces.iterator();
    var references = std.ArrayList(OrderedRef).init(allocator);
    defer references.deinit();

    while (entries.next()) |entry| {
        try references.append(OrderedRef{ .ref = entry.key_ptr.*, .i = entry.value_ptr.* });
    }
    std.sort.sort(OrderedRef, references.items, {}, compareReference);

    // Escape and add forward references to note.
    var keys = brackets.brackets.keyIterator();
    while (keys.next()) |phrase| {
    for (references.items) |reference| {
        const phrase = reference.ref;
        var escaped = EscapedString{
            .escaped = try CharBuffer.initCapacity(allocator, phrase.len * 2),
            .error_slice = undefined,
        };
        defer escaped.escaped.deinit();

        escape(phrase.*, &escaped) catch {
            std.debug.print("Found illegal character in brackets: `{s}`\n", .{
        escape(phrase, &escaped) catch {
            std.debug.print("Found illegal character in rackets: `{s}`\n", .{
                escaped.error_slice,
            });
            std.os.exit(1);