refactor: add support for nested snippet placeholders

This commit is contained in:
CJ van den Berg 2025-12-09 20:41:50 +01:00
parent 4a44838b88
commit b6161043ac
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -20,7 +20,8 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
var tabstops: std.ArrayList(struct { id: usize, range: Range }) = .empty; var tabstops: std.ArrayList(struct { id: usize, range: Range }) = .empty;
defer tabstops.deinit(allocator); defer tabstops.deinit(allocator);
var id: ?usize = null; var id: ?usize = null;
var content_begin: ?Position = null; var content_begin: std.ArrayList(Position) = .empty;
defer content_begin.deinit(allocator);
var max_id: usize = 0; var max_id: usize = 0;
var text: std.Io.Writer.Allocating = .init(allocator); var text: std.Io.Writer.Allocating = .init(allocator);
defer text.deinit(); defer text.deinit();
@ -31,25 +32,29 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
tabstop, tabstop,
placeholder, placeholder,
content, content,
content_escape,
} = .initial; } = .initial;
var state_stack: std.ArrayList(@TypeOf(state)) = .empty;
defer state_stack.deinit(allocator);
var iter = snippet; var iter = snippet;
while (iter.len > 0) : (iter = iter[1..]) { while (iter.len > 0) : (iter = iter[1..]) {
const c = iter[0]; const c = iter[0];
fsm: switch (state) { fsm: switch (state) {
.initial => switch (c) { .initial => switch (c) {
'\\' => { '\\' => {
(try state_stack.addOne(allocator)).* = state;
state = .escape; state = .escape;
}, },
'$' => { '$' => {
(try state_stack.addOne(allocator)).* = state;
state = .tabstop; state = .tabstop;
}, },
else => try text.writer.writeByte(c), else => try text.writer.writeByte(c),
}, },
.escape => { .escape => {
try text.writer.writeByte(c); try text.writer.writeByte(c);
state = .initial; state = state_stack.pop() orelse return error.InvalidState;
}, },
.tabstop => switch (c) { .tabstop => switch (c) {
'{' => { '{' => {
@ -69,7 +74,7 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
}; };
max_id = @max(id orelse unreachable, max_id); max_id = @max(id orelse unreachable, max_id);
id = null; id = null;
state = .initial; state = state_stack.pop() orelse return error.InvalidState;
continue :fsm .initial; continue :fsm .initial;
}, },
}, },
@ -82,7 +87,7 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
const pos = snippet.len - iter.len; const pos = snippet.len - iter.len;
if (id == null) if (id == null)
return invalid(snippet, pos, error.InvalidIdValue); return invalid(snippet, pos, error.InvalidIdValue);
content_begin = .{text.written().len}; (try content_begin.addOne(allocator)).* = .{text.written().len};
state = .content; state = .content;
}, },
else => { else => {
@ -92,32 +97,29 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet {
}, },
.content => switch (c) { .content => switch (c) {
'\\' => { '\\' => {
state = .content_escape; (try state_stack.addOne(allocator)).* = state;
state = .escape;
}, },
'}' => { '}' => {
const pos = snippet.len - iter.len; const pos = snippet.len - iter.len;
if (id == null) if (id == null)
return invalid(snippet, pos, error.InvalidIdValue); return invalid(snippet, pos, error.InvalidIdValue);
if (content_begin == null) if (content_begin.items.len == 0)
return invalid(snippet, pos, error.InvalidPlaceholderValue); return invalid(snippet, pos, error.InvalidPlaceholderValue);
const begin_pos = content_begin.pop() orelse return invalid(snippet, pos, error.InvalidPlaceholderValue);
(try tabstops.addOne(allocator)).* = .{ (try tabstops.addOne(allocator)).* = .{
.id = id orelse unreachable, .id = id orelse unreachable,
.range = .{ .range = .{
.begin = content_begin orelse unreachable, .begin = begin_pos,
.end = .{text.written().len}, .end = .{text.written().len},
}, },
}; };
max_id = @max(id orelse unreachable, max_id); max_id = @max(id orelse unreachable, max_id);
id = null; id = null;
content_begin = null; state = state_stack.pop() orelse return error.InvalidState;
state = .initial;
}, },
else => try text.writer.writeByte(c), else => try text.writer.writeByte(c),
}, },
.content_escape => {
try text.writer.writeByte(c);
state = .content;
},
} }
} }
@ -164,6 +166,7 @@ pub const Error = error{
InvalidIdValue, InvalidIdValue,
InvalidPlaceholderValue, InvalidPlaceholderValue,
UnexpectedEndOfDocument, UnexpectedEndOfDocument,
InvalidState,
}; };
const log = std.log.scoped(.snippet); const log = std.log.scoped(.snippet);