~subsetpark/erasmus

4f48451ff5be3ff4870f741d07cd53af96a93697 — Zach Smith 2 months ago 2cf6bb8
Formatting and code organization
1 files changed, 221 insertions(+), 212 deletions(-)

M src/main.zig
M src/main.zig => src/main.zig +221 -212
@@ 20,30 20,142 @@ const end_braces = '}';

const CharBuffer = std.ArrayList(u8);

const BufferAndSlices = struct {
const BufferAndBraces = struct {
    buffer: CharBuffer,
    offset: usize,
    sliceBegins: std.ArrayList(usize),
    slice_begins: std.ArrayList(usize),

    fn newSlice(self: *@This(), bodyOffset: usize) !void {
        try self.sliceBegins.append(bodyOffset - self.offset);
    fn init(offset: usize, allocator: mem.Allocator) @This() {
        return @This(){
            .buffer = CharBuffer.init(allocator),
            .offset = offset,
            .slice_begins = std.ArrayList(usize).init(allocator),
        };
    }

    fn newBrace(self: *@This(), body_offset: usize) !void {
        try self.slice_begins.append(body_offset - self.offset);
    }

    fn currentBracesCount(self: @This()) usize {
        return self.slice_begins.items.len;
    }

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

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

const EscapedString = struct {
    escaped: CharBuffer,
    error_slice: ?[]const u8,
    escape_error: ?EscapeError = null,

    const EscapeError = enum { TerminalPeriod };

    fn init(to_escape: []const u8, allocator: mem.Allocator) !@This() {
        return EscapedString{
            .escaped = try CharBuffer.initCapacity(allocator, to_escape.len * 2),
        };
    }

    // Process a string and escape any delimiters for reference creation.
    fn escape(self: *@This(), s: []const u8) void {
        // Check for illegal characters. Right now the only illegal character is a
        // terminal period, which confuses vim, so this is a bit particular.
        if (s.len > 0 and s[s.len - 1] == '.') {
            self.escape_error = EscapeError.TerminalPeriod;
            return;
        }
        for (s) |char| {
            switch (char) {
                ' ' => {
                    self.escaped.appendAssumeCapacity('\\');
                },
                else => {},
            }
            const lower_char = std.ascii.toLower(char);
            self.escaped.appendAssumeCapacity(lower_char);
        }
    }

    fn handleErrors(self: @This()) void {
        if (self.escape_error) |escape_error| {
            switch (escape_error) {
                EscapeError.TerminalPeriod => {
                    std.debug.print("Note references can't end with a period", .{});
                    std.os.exit(1);
                },
            }
        }
    }
};

// Iterate through a slice and populate an ArrayList with subslices capturing
// "{<reference>}"
fn findBracedStrings(
    note_body: []const u8,
    references: *std.ArrayList([]const u8),
    allocator: mem.Allocator,
) !void {
    var brace_context: ?BufferAndBraces = null;

    var i: usize = 0;
    while (i < note_body.len) : (i += 1) {
        const char = note_body[i];
        switch (char) {
            begin_braces => {
                // Use a BufferAndBraces, which can keep track of multiple open
                // braces, to handle nested braces. We'll copy the underlying
                // contents of an outer brace into a buffer while noting (but
                // not copying) any nested braces inside it.
                if (brace_context == null) {
                    brace_context = BufferAndBraces.init(i, allocator);
                }
                if (brace_context) |*ctx| {
                    try ctx.newBrace(i);
                }
            },
            end_braces => {
                if (brace_context) |*ctx| {
                    // Subtract the number of current open braces, because
                    // those won't have been included in the current buffer.
                    const begin = ctx.pop() - ctx.currentBracesCount();
                    const reference_text = ctx.buffer.items[begin..];

                    const new_string = try allocator.alloc(u8, reference_text.len);
                    std.mem.copy(u8, new_string, reference_text);

                    try references.append(new_string);

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

const Note = struct {
    name: []const u8,
    body: CharBuffer,


@@ 51,13 163,109 @@ const Note = struct {
    backlinks: std.ArrayList([]const u8),
    references: std.ArrayList([]const u8),

    fn append(self: *Note, c: u8) !void {
    fn init(name: []const u8, allocator: mem.Allocator) @This() {
        return @This(){
            .name = name,
            .body = CharBuffer.init(allocator),
            .new_body = CharBuffer.init(allocator),
            .backlinks = std.ArrayList([]const u8).init(allocator),
            .references = std.ArrayList([]const u8).init(allocator),
        };
    }

    fn deinit(self: @This()) void {
        self.new_body.deinit();
        self.backlinks.deinit();
        self.references.deinit();
    }

    fn append(self: *@This(), c: u8) !void {
        try self.new_body.append(c);
    }

    fn appendSlice(self: *Note, slice: []u8) !void {
    fn appendSlice(self: *@This(), slice: []u8) !void {
        try self.new_body.appendSlice(slice);
    }

    // Create a reference line and add it to the note.
    fn appendRef(self: *@This(), referent: []const u8, allocator: mem.Allocator) !void {
        const ref = try fmt.allocPrint(allocator, "{s}:{s}", .{
            ref_prefix,
            referent,
        });
        try self.appendSlice(ref);
        try self.append('\n');
    }

    // Backlinks: if any other note body refers to this one, add a reference to
    // that body's name.
    fn appendBacklinks(
        self: *@This(),
        notes: *std.ArrayList(Note),
        allocator: mem.Allocator,
    ) !void {
        var found_one = false;
        const link_here = try fmt.allocPrint(allocator, "{c}{s}{c}", .{
            begin_braces,
            self.name,
            end_braces,
        });

        for (notes.items) |other_note| {
            const items = other_note.body.items;
            const lower_items = try std.ascii.allocLowerString(allocator, items);
            if (mem.indexOf(u8, lower_items, link_here) != null) {
                found_one = true;

                var escaped = try EscapedString.init(other_note.name, allocator);
                defer escaped.escaped.deinit();

                escaped.escape(other_note.name);
                escaped.handleErrors();

                try self.appendRef(escaped.escaped.items, allocator);
            }
            allocator.free(lower_items);
        }

        // Backlinks padding.
        if (found_one) {
            try self.append('\n');
        }
    }

    // References: gather every reference in the body of the note.
    fn appendReferences(
        self: *@This(),
        allocator: mem.Allocator,
    ) !void {
        var braces = std.ArrayList([]const u8).init(allocator);
        defer braces.deinit();

        try findBracedStrings(self.body.items, &braces, allocator);

        // References padding.
        if (braces.items.len > 0) {
            try self.append('\n');
        }

        var seen = std.BufSet.init(allocator);

        // Escape and add forward references to note.
        for (braces.items) |phrase| {
            var escaped = try EscapedString.init(phrase, allocator);
            defer escaped.escaped.deinit();

            escaped.escape(phrase);
            escaped.handleErrors();

            const str = escaped.escaped.items;
            if (!seen.contains(str)) {
                try seen.insert(str);
                try self.appendRef(str, allocator);
            }
        }
    }
};

const help_text =


@@ 117,200 325,6 @@ 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,
    braces: *std.StringHashMap(usize),
    allocator: mem.Allocator,
) !void {
    var current_reference: ?BufferAndSlices = null;
    var found: usize = 0;

    var i: usize = 0;
    while (i < note_body.len) : (i += 1) {
        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),
                    };
                }
                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);
                }
            },
        }
    }
}

// Create a reference line and add it to a note.
fn appendRef(note: *Note, referent: []const u8, allocator: mem.Allocator) !void {
    const ref = try fmt.allocPrint(allocator, "{s}:{s}", .{
        ref_prefix,
        referent,
    });
    try note.appendSlice(ref);
    try note.append('\n');
}

// Process a string and escape any delimiters for reference creation.
fn escape(s: []const u8, res: *EscapedString) RuntimeError!void {
    // Check for illegal characters. Right now the only illegal character is a
    // terminal period, which confuses vim, so this is a bit particular.
    if (s.len > 0) {
        switch (s[s.len - 1]) {
            '.' => {
                res.error_slice = s;
                return RuntimeError.IllegalCharacterInBrackets;
            },
            else => {},
        }
    }
    for (s) |char| {
        switch (char) {
            ' ' => {
                res.escaped.appendAssumeCapacity('\\');
            },
            else => {},
        }
        const lower_char = std.ascii.toLower(char);
        res.escaped.appendAssumeCapacity(lower_char);
    }
}

// Backlinks: if any other note body refers to this one, add a reference to
// that body's name.
fn appendBacklinks(
    note: *Note,
    notes: *std.ArrayList(Note),
    allocator: mem.Allocator,
) !void {
    var found_one = false;
    const link_here = try fmt.allocPrint(allocator, "{c}{s}{c}", .{
        begin_braces,
        note.name,
        end_braces,
    });

    for (notes.items) |other_note| {
        const items = other_note.body.items;
        const lower_items = try std.ascii.allocLowerString(allocator, items);
        if (mem.indexOf(u8, lower_items, link_here) != null) {
            found_one = true;

            var escaped = EscapedString{
                .escaped = try CharBuffer.initCapacity(allocator, other_note.name.len * 2),
                .error_slice = undefined,
            };
            defer escaped.escaped.deinit();

            escape(other_note.name, &escaped) catch {
                std.debug.print("Found illegal character in braces: `{s}`\n", .{
                    escaped.error_slice,
                });
                std.os.exit(1);
            };
            try appendRef(note, escaped.escaped.items, allocator);
        }
        allocator.free(lower_items);
    }

    // Backlinks padding.
    if (found_one) {
        try note.append('\n');
    }
}

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 braces = std.StringHashMap(usize).init(allocator);
    defer braces.deinit();

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

    // References padding.
    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.
    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 rackets: `{s}`\n", .{
                escaped.error_slice,
            });
            std.os.exit(1);
        };
        try appendRef(note, escaped.escaped.items, allocator);
    }
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();


@@ 347,13 361,7 @@ pub fn main() !void {
                directory_name,
                entry.name,
            });
            var note = Note{
                .name = entry.name,
                .body = CharBuffer.init(allocator),
                .new_body = CharBuffer.init(allocator),
                .backlinks = std.ArrayList([]const u8).init(allocator),
                .references = std.ArrayList([]const u8).init(allocator),
            };
            var note = Note.init(entry.name, allocator);
            try readBody(path, &note.body);
            try notes.append(note);
        }


@@ 362,9 370,9 @@ pub fn main() !void {
    // For each note, gather backlinks and references, and generate
    // contents.
    for (notes.items) |*note| {
        try appendBacklinks(note, &notes, allocator);
        try note.appendBacklinks(&notes, allocator);
        try note.appendSlice(note.body.items);
        try appendReferences(note, allocator);
        try note.appendReferences(allocator);

        // Optional debugging.
        if (debug_mode) {


@@ 387,5 395,6 @@ pub fn main() !void {
            note.name,
        });
        try fs.cwd().writeFile(path, note.new_body.items);
        note.deinit();
    }
}