aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/Element.zig1
-rw-r--r--src/eval/blocks.zig41
-rw-r--r--src/lexer/Token.zig2
-rw-r--r--src/paragraph.zig2
-rw-r--r--src/parser.zig5
-rw-r--r--src/quote.zig58
6 files changed, 106 insertions, 3 deletions
diff --git a/src/eval/Element.zig b/src/eval/Element.zig
index 4600abf..73dfb94 100644
--- a/src/eval/Element.zig
+++ b/src/eval/Element.zig
@@ -10,6 +10,7 @@ const blocks = @import("blocks.zig");
pub const Code = blocks.Code;
pub const Figure = blocks.Figure;
pub const Callout = blocks.Callout;
+pub const Quote = blocks.Quote;
pub const Node = struct {
ptr: *anyopaque,
diff --git a/src/eval/blocks.zig b/src/eval/blocks.zig
index cbb95b1..32f2de3 100644
--- a/src/eval/blocks.zig
+++ b/src/eval/blocks.zig
@@ -138,3 +138,44 @@ pub const Callout = struct {
return el.element();
}
};
+
+pub const Quote = struct {
+ content: Element,
+ attribution: ?Element = null,
+ node: Node = .{
+ .ptr = undefined,
+ .vtable = .{ .element = fromNode },
+ },
+
+ const Self = @This();
+
+ pub fn init(alloc: Allocator, content: Element) !*Self {
+ const v = try alloc.create(Self);
+ v.* = .{ .content = content };
+ v.node.ptr = v;
+ return v;
+ }
+
+ pub fn element(self: *Self) Element {
+ return .{ .ptr = self, .vtable = .{ .html = html, .node = getNode } };
+ }
+
+ fn getNode(context: *anyopaque) *Node {
+ const self: *Self = @ptrCast(@alignCast(context));
+ return &self.node;
+ }
+
+ fn fromNode(context: *anyopaque) Element {
+ const self: *Self = @ptrCast(@alignCast(context));
+ return self.element();
+ }
+
+ fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML {
+ const self: *Self = @ptrCast(@alignCast(context));
+ const quote = try Element.Simple("blockquote").init(alloc);
+ quote.content = self.content;
+ var el = try Figure.init(alloc, quote.element());
+ el.caption = self.attribution;
+ return try el.element().html(alloc);
+ }
+};
diff --git a/src/lexer/Token.zig b/src/lexer/Token.zig
index 3162869..8ac5d34 100644
--- a/src/lexer/Token.zig
+++ b/src/lexer/Token.zig
@@ -28,7 +28,7 @@ pub const Kind = enum {
};
}
- pub inline fn isPar(self: @This()) bool {
+ pub inline fn isInParagraph(self: @This()) bool {
return switch (self) {
.literal, .link, .code, .math, .bold, .italic, .ref => true,
else => false,
diff --git a/src/paragraph.zig b/src/paragraph.zig
index f8c3727..2e5383a 100644
--- a/src/paragraph.zig
+++ b/src/paragraph.zig
@@ -22,7 +22,7 @@ pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
.weak_delimiter => {
l.consume();
const future = l.peek() orelse return el.element();
- if (!future.kind.isPar()) return el.element();
+ if (!future.kind.isInParagraph()) return el.element();
root.append((try Element.Literal.init(alloc, " ")).element());
},
else => root.append(try parseLine(alloc, l)),
diff --git a/src/parser.zig b/src/parser.zig
index c8f1c81..e7a3aff 100644
--- a/src/parser.zig
+++ b/src/parser.zig
@@ -9,6 +9,7 @@ const link = @import("link.zig");
const list = @import("list.zig");
const code = @import("code.zig");
const callout = @import("callout.zig");
+const quote = @import("quote.zig");
pub const Error = error{FeatureNotSupported} ||
Lexer.Error ||
@@ -19,6 +20,7 @@ pub const Error = error{FeatureNotSupported} ||
link.ImageError ||
code.Error ||
callout.Error ||
+ quote.Error ||
Allocator.Error;
pub const Document = Element.Root;
@@ -46,13 +48,14 @@ fn gen(parent: Allocator, l: *Lexer) Error!*Document {
.list_unordored => try list.parseUnordored(alloc, l),
.image => try link.parseImage(alloc, l),
.code_block => try code.parse(alloc, l),
+ .quote => try quote.parse(alloc, l),
.weak_delimiter, .strong_delimiter => {
l.consume();
continue :base;
},
else =>
// block paragraph
- if (it.kind.isPar())
+ if (it.kind.isInParagraph())
try paragraph.parse(alloc, l)
else
return Error.FeatureNotSupported,
diff --git a/src/quote.zig b/src/quote.zig
new file mode 100644
index 0000000..8c24c7e
--- /dev/null
+++ b/src/quote.zig
@@ -0,0 +1,58 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Token = @import("lexer/Token.zig");
+const Lexer = @import("lexer/Lexer.zig");
+const Element = @import("eval/Element.zig");
+const paragraph = @import("paragraph.zig");
+const testing = @import("testing.zig");
+const doTest = testing.do;
+const doTestError = testing.doError;
+
+pub const Error = paragraph.Error || Allocator.Error;
+
+pub fn parse(alloc: Allocator, l: *Lexer) Error!Element {
+ const root = try Element.Root.init(alloc);
+ while (l.peek()) |next| switch (next.kind) {
+ .quote => {
+ l.consume();
+ continue;
+ },
+ .weak_delimiter => {
+ l.consume();
+ if (l.peek()) |it| if (it.kind != .quote) break;
+ root.append((try Element.Literal.init(alloc, " ")).element());
+ continue;
+ },
+ .strong_delimiter => break,
+ else => root.append(try paragraph.parseLine(alloc, l)),
+ };
+ const el = try Element.Quote.init(alloc, root.element());
+ const v = l.peek() orelse return el.element();
+ if (v.kind == .strong_delimiter) {
+ l.consume();
+ return el.element();
+ }
+ const attr = try paragraph.parse(alloc, l);
+ const p_el: *Element.paragraph.Block = @ptrCast(@alignCast(attr.ptr));
+ el.attribution = (try p_el.toRoot(alloc)).element();
+ return el.element();
+}
+
+test {
+ const alloc = std.testing.allocator;
+
+ try doTest(parse, alloc, "> hello world", "<figure><blockquote>hello world</blockquote></figure>");
+ try doTest(parse, alloc, ">hello world", "<figure><blockquote>hello world</blockquote></figure>");
+ try doTest(parse, alloc, "> hello world", "<figure><blockquote>hello world</blockquote></figure>");
+
+ try doTest(parse, alloc,
+ \\> hello
+ \\>world
+ , "<figure><blockquote>hello world</blockquote></figure>");
+ try doTest(parse, alloc,
+ \\> hello
+ \\>world
+ \\attribution sur
+ \\plusieurs lignes
+ , "<figure><blockquote>hello world</blockquote><figcaption>attribution sur plusieurs lignes</figcaption></figure>");
+}