From e154408e8ddeaee83242002f4c7af68b29d3a677 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Mon, 27 Apr 2026 20:49:13 +0200 Subject: feat(): support callout --- src/callout.zig | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/callout.zig (limited to 'src/callout.zig') diff --git a/src/callout.zig b/src/callout.zig new file mode 100644 index 0000000..2761944 --- /dev/null +++ b/src/callout.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const eql = std.mem.eql; +const Token = @import("lexer/Token.zig"); +const Lexer = @import("lexer/Lexer.zig"); +const Element = @import("eval/Element.zig"); +const testing = @import("testing.zig"); +const paragraph = @import("paragraph.zig"); +const doTest = testing.do; +const doTestError = testing.doError; + +pub const Error = error{InvalidCallout} || paragraph.Error || Allocator.Error; + +pub fn parse(alloc: Allocator, l: *Lexer) Error!Element { + _ = l.next(); + var beg = l.next() orelse return Error.InvalidCallout; + var kind: ?[]const u8 = null; + var title: ?[]const u8 = null; + switch (beg.kind) { + .literal => { + var iter = std.mem.splitAny(u8, beg.content, " "); + kind = iter.first(); + if (iter.peek() != null) title = iter.buffer[iter.index.?..]; + beg = l.next() orelse return Error.InvalidCallout; + if (!beg.kind.isDelimiter()) return Error.InvalidCallout; + }, + else => if (!beg.kind.isDelimiter()) return Error.InvalidCallout, + } + var root = try Element.Root.init(alloc); + while (l.peek()) |it| { + if (it.kind == .callout) { + l.consume(); + break; + } + if (it.kind.isDelimiter()) { + const next = l.peek() orelse return Error.InvalidCallout; + if (next.kind == .callout) { + l.consume(); + break; + } + } + try root.append(try paragraph.parse(root.allocator(), l)); + _ = l.peek() orelse return Error.InvalidCallout; + } + var el = try Element.Callout.init(alloc, root.element()); + el.kind = kind; + el.title = title; + const end = l.next() orelse return el.element(); + if (!end.kind.isDelimiter()) return Error.InvalidCallout; + return el.element(); +} + +test "callout" { + const alloc = std.testing.allocator; + + try doTest(parse, alloc, + \\::: + \\hey + \\::: + , "

hey

"); + try doTest(parse, alloc, + \\:::info + \\hey + \\::: + , "

hey

"); + try doTest(parse, alloc, + \\:::info Title + \\hey + \\::: + , "

hey

"); + // cannot test content with \n + + try doTestError(parse, alloc, ":::", Error.InvalidCallout); + try doTestError(parse, alloc, + \\::: + \\hey + , Error.InvalidCallout); + try doTestError(parse, alloc, + \\::: + \\hey::: + , Error.IllegalPlacement); + try doTestError(parse, alloc, + \\::: + \\hey + \\::: nope + , Error.InvalidCallout); +} -- cgit v1.2.3