summaryrefslogtreecommitdiff
path: root/src/root.zig
blob: 3ddfe78e75410f1dada0c9935b88c842a7550891 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const SplitIterator = std.mem.SplitIterator;
const DelimiterType = std.mem.DelimiterType;
const ArrayList = std.ArrayList;
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;

pub const tags = @import("tags.zig");
pub const utils = @import("utils.zig");

pub const ParserFn =
    fn (
        arena: *ArenaAllocator,
        io: Io,
        state: *Parser,
        reader: *Io.Reader,
        writer: *Io.Writer,
        attrs: *SplitIterator(u8, DelimiterType.scalar),
    ) anyerror!void;

pub const ParserTag = struct {
    name: []const u8,
    parse_fn: *const ParserFn,
};

pub const Parser = struct {
    component_resolver: *const fn (alloc: *ArenaAllocator, io: Io, path: []const u8) anyerror!*Io.Reader,
    exported_parts: StringArrayHashMap([]const u8),
    tags: []const ParserTag,
    tag_stack: ArrayList([]const u8),
};

pub fn parseTemplate(arena: *ArenaAllocator, io: Io, state: *Parser, reader: *Io.Reader, writer: *Io.Writer) !void {
    outer: while (true) {
        _ = reader.streamDelimiter(writer, '<') catch |err| {
            if (err == Io.Reader.Error.EndOfStream) break else return err;
        };
        // consume '<'
        _ = try reader.takeByte();
        if (try reader.peekByte() == '/') {
            // consume '/'
            _ = try reader.takeByte();
            const last_tag = state.tag_stack.getLastOrNull() orelse "";
            // if reached closing tag, send execution back up the stack
            if (std.mem.eql(u8, last_tag, try reader.peekDelimiterExclusive('>'))) {
                // consume closing tag
                _ = try reader.takeDelimiterInclusive('>');
                return;
            } else {
                try writer.writeAll("</");
                continue :outer;
            }
        }
        const inside = reader.takeDelimiterExclusive('>') catch |err| {
            if (err == error.StreamTooLong) {
                try writer.writeByte('<');
                break :outer;
            } else return err;
        };
        var it = std.mem.splitScalar(u8, inside, ' ');
        const tag_name = it.first();
        for (state.tags) |tag| {
            if (std.mem.eql(u8, tag.name, tag_name) == true) {
                // consume '>'
                _ = try reader.takeByte();
                try tag.parse_fn(arena, io, state, reader, writer, &it);
                continue :outer;
            }
        }

        try writer.writeByte('<');
        try writer.writeAll(inside);
    }
}