From b6161043ac501f609bc89ef492ff3d226a781db2 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 9 Dec 2025 20:41:50 +0100 Subject: [PATCH] refactor: add support for nested snippet placeholders --- src/snippet.zig | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/snippet.zig b/src/snippet.zig index f8cd437..bf2a2cb 100644 --- a/src/snippet.zig +++ b/src/snippet.zig @@ -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; defer tabstops.deinit(allocator); 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 text: std.Io.Writer.Allocating = .init(allocator); defer text.deinit(); @@ -31,25 +32,29 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet { tabstop, placeholder, content, - content_escape, } = .initial; + var state_stack: std.ArrayList(@TypeOf(state)) = .empty; + defer state_stack.deinit(allocator); + var iter = snippet; while (iter.len > 0) : (iter = iter[1..]) { const c = iter[0]; fsm: switch (state) { .initial => switch (c) { '\\' => { + (try state_stack.addOne(allocator)).* = state; state = .escape; }, '$' => { + (try state_stack.addOne(allocator)).* = state; state = .tabstop; }, else => try text.writer.writeByte(c), }, .escape => { try text.writer.writeByte(c); - state = .initial; + state = state_stack.pop() orelse return error.InvalidState; }, .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); id = null; - state = .initial; + state = state_stack.pop() orelse return error.InvalidState; 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; if (id == null) return invalid(snippet, pos, error.InvalidIdValue); - content_begin = .{text.written().len}; + (try content_begin.addOne(allocator)).* = .{text.written().len}; state = .content; }, else => { @@ -92,32 +97,29 @@ pub fn parse(allocator: std.mem.Allocator, snippet: []const u8) Error!Snippet { }, .content => switch (c) { '\\' => { - state = .content_escape; + (try state_stack.addOne(allocator)).* = state; + state = .escape; }, '}' => { const pos = snippet.len - iter.len; if (id == null) return invalid(snippet, pos, error.InvalidIdValue); - if (content_begin == null) + if (content_begin.items.len == 0) return invalid(snippet, pos, error.InvalidPlaceholderValue); + const begin_pos = content_begin.pop() orelse return invalid(snippet, pos, error.InvalidPlaceholderValue); (try tabstops.addOne(allocator)).* = .{ .id = id orelse unreachable, .range = .{ - .begin = content_begin orelse unreachable, + .begin = begin_pos, .end = .{text.written().len}, }, }; max_id = @max(id orelse unreachable, max_id); id = null; - content_begin = null; - state = .initial; + state = state_stack.pop() orelse return error.InvalidState; }, else => try text.writer.writeByte(c), }, - .content_escape => { - try text.writer.writeByte(c); - state = .content; - }, } } @@ -164,6 +166,7 @@ pub const Error = error{ InvalidIdValue, InvalidPlaceholderValue, UnexpectedEndOfDocument, + InvalidState, }; const log = std.log.scoped(.snippet);