refactor: add snippet parser module

This commit is contained in:
CJ van den Berg 2025-12-08 21:15:03 +01:00
parent 9b33f4cbe3
commit ad496d3bd6
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 168 additions and 0 deletions

View file

@ -414,6 +414,10 @@ pub fn build_exe(
.root_source_file = b.path("src/bin_path.zig"),
});
const snippet_mod = b.createModule(.{
.root_source_file = b.path("src/snippet.zig"),
});
const Buffer_mod = b.createModule(.{
.root_source_file = b.path("src/buffer/Buffer.zig"),
.imports = &.{
@ -630,6 +634,7 @@ pub fn build_exe(
.{ .name = "zeit", .module = zeit_mod },
.{ .name = "VcsStatus", .module = VcsStatus_mod },
.{ .name = "bin_path", .module = bin_path_mod },
.{ .name = "snippet", .module = snippet_mod },
},
});

163
src/snippet.zig Normal file
View file

@ -0,0 +1,163 @@
text: []const u8,
tabstops: [][]Range,
const Snippet = @This();
const Range = struct { begin: Position, end: ?Position = null };
const Position = struct { usize };
const Tabstop = struct {
id: usize,
range: Range,
};
pub fn deinit(self: *const Snippet, allocator: std.mem.Allocator) void {
for (self.tabstops) |tabstop| allocator.free(tabstop);
allocator.free(self.tabstops);
allocator.free(self.text);
}
pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
var tabstops: std.ArrayList(struct { id: usize, range: Range }) = .empty;
defer tabstops.deinit(allocator);
var id: ?usize = null;
var content_begin: ?Position = null;
var max_id: usize = 0;
var text: std.Io.Writer.Allocating = .init(allocator);
defer text.deinit();
var state: enum {
initial,
escape,
tabstop,
placeholder,
content,
content_escape,
} = .initial;
var iter = snippet;
while (iter.len > 0) : (iter = iter[1..]) {
const c = iter[0];
fsm: switch (state) {
.initial => switch (c) {
'\\' => {
state = .escape;
},
'$' => {
state = .tabstop;
try text.writer.writeByte(c);
},
else => try text.writer.writeByte(c),
},
.escape => {
try text.writer.writeByte(c);
state = .initial;
},
.tabstop => switch (c) {
'{' => {
state = .placeholder;
},
'0'...'9' => {
const digit: usize = @intCast(c - '0');
id = if (id) |id_| (id_ * 10) + digit else digit;
},
else => {
const pos = snippet.len - iter.len;
if (id == null)
return invalid(snippet, pos, error.InvalidIdValue);
(try tabstops.addOne(allocator)).* = .{
.id = id orelse unreachable,
.range = .{ .begin = .{text.written().len} },
};
max_id = @max(id orelse unreachable, max_id);
id = null;
state = .initial;
break :fsm;
},
},
.placeholder => switch (c) {
'0'...'9' => {
const digit: usize = @intCast(c - '0');
id = if (id) |id_| (id_ * 10) + digit else digit;
},
':' => {
const pos = snippet.len - iter.len;
if (id == null)
return invalid(snippet, pos, error.InvalidIdValue);
content_begin = .{text.written().len};
state = .content;
},
else => {
const pos = snippet.len - iter.len;
return invalid(snippet, pos, error.InvalidIdValue);
},
},
.content => switch (c) {
'\\' => {
state = .content_escape;
},
'}' => {
const pos = snippet.len - iter.len;
if (id == null)
return invalid(snippet, pos, error.InvalidIdValue);
if (content_begin == null)
return invalid(snippet, pos, error.InvalidPlaceholderValue);
(try tabstops.addOne(allocator)).* = .{
.id = id orelse unreachable,
.range = .{
.begin = content_begin orelse unreachable,
.end = .{text.written().len},
},
};
max_id = @max(id orelse unreachable, max_id);
id = null;
content_begin = null;
state = .initial;
},
else => try text.writer.writeByte(c),
},
.content_escape => {
try text.writer.writeByte(c);
state = .content;
},
}
}
if (state != .initial) {
const pos = snippet.len - iter.len;
if (id == null)
return invalid(snippet, pos, error.UnexpectedEndOfDocument);
}
var result: std.ArrayList([]Range) = .empty;
defer result.deinit(allocator);
var n: usize = 1;
while (n <= max_id) : (n += 1) {
var tabstop: std.ArrayList(Range) = .empty;
errdefer tabstop.deinit(allocator);
for (tabstops.items) |item| if (item.id == n) {
(try tabstop.addOne(allocator)).* = item.range;
};
(try result.addOne(allocator)).* = try tabstop.toOwnedSlice(allocator);
}
return .{
.text = try text.toOwnedSlice(),
.tabstops = try result.toOwnedSlice(allocator),
};
}
fn invalid(snippet: []const u8, pos: usize, e: Error) Error {
log.err("invalid snippet: {s}", .{snippet});
log.err("{t} at pos {d}", .{ e, pos });
return e;
}
pub const Error = error{
WriteFailed,
OutOfMemory,
InvalidIdValue,
InvalidPlaceholderValue,
UnexpectedEndOfDocument,
};
const log = std.log.scoped(.snippet);
const std = @import("std");