1const std = @import("std");
2const Token = @import("./tokenize.zig").Token;
3const extras = @import("extras");
4
5const string = []const u8;
6
7//
8//
9
10pub const Value = union(enum) {
11 element: Element,
12 attr: Attr,
13 string: string,
14 replacement: Replacement,
15 block: Block,
16 body: Body,
17 function: Fn,
18};
19
20pub const Element = struct {
21 name: string,
22 attrs: []const Attr,
23 children: Body,
24};
25
26pub const Attr = struct {
27 key: string,
28 value: union(enum) { string: string, body: Body },
29};
30
31pub const Replacement = struct {
32 arms: []const string,
33 raw: bool = false,
34};
35
36pub const Block = struct {
37 name: Type,
38 func: ?string,
39 args: []const Arg,
40 body: Body,
41 bttm: Body,
42
43 pub const Type = enum {
44 each,
45 @"if",
46 ifnot,
47 ifequal,
48 ifnotequal,
49 };
50};
51
52pub const Body = []const Value;
53
54pub const Fn = struct {
55 name: string,
56 raw: bool,
57 args: []const Arg,
58};
59
60pub const Arg = union(enum) {
61 lookup: []const string,
62 plain: string,
63 int: u64,
64 value: []const Value,
65};
66
67//
68//
69
70pub fn do(comptime tokens: []const Token) Element {
71 var parser = Parser{ .tokens = tokens, .index = 0 };
72 return parser.doElement();
73}
74
75//
76//
77
78const Parser = struct {
79 tokens: []const Token,
80 index: usize,
81
82 pub fn doElement(comptime self: *Parser) Element {
83 return Element{
84 .name = self.eat(.word),
85 .attrs = self.doAttrs(),
86 .children = self.doChildren(),
87 };
88 }
89
90 pub fn doAttrs(comptime self: *Parser) []const Attr {
91 var ret: []const Attr = &[_]Attr{};
92
93 if (self.tryEatSymbol("[")) {
94 inline while (true) {
95 if (self.tryEatSymbol("]")) break;
96 ret = ret ++ &[_]Attr{self.doAttr()};
97 }
98 }
99
100 return ret;
101 }
102
103 fn raise(comptime line: usize, comptime pos: usize, comptime expected: []const u8, comptime found: []const u8) void {
104 @compileError(std.fmt.comptimePrint("pek: file:{d}:{d}: expected {s}, found {s}", .{ line, pos, expected, found }));
105 }
106
107 fn expect(comptime expected: []const u8, comptime found: []const u8, comptime line: usize, comptime pos: usize) void {
108 if (std.mem.eql(u8, found, expected)) return;
109 raise(line, pos, expected, found);
110 }
111
112 fn tryEatSymbol(comptime self: *Parser, comptime needle: string) bool {
113 if (self.index >= self.tokens.len) return false;
114 switch (self.tokens[self.index].data) {
115 .symbol => |sym| {
116 if (std.mem.eql(u8, sym, needle)) {
117 self.index += 1;
118 return true;
119 }
120 return false;
121 },
122 else => {
123 return false;
124 },
125 }
126 }
127
128 pub fn doAttr(comptime self: *Parser) Attr {
129 const k = self.eat(.word);
130 self.eatSymbol("=");
131 const body = self.doChildren();
132 if (body.len > 0) {
133 return Attr{
134 .key = k,
135 .value = .{ .body = body },
136 };
137 }
138 const v = self.eat(.string);
139 return Attr{
140 .key = k,
141 .value = .{ .string = v },
142 };
143 }
144
145 pub fn eatSymbol(comptime self: *Parser, comptime needle: string) void {
146 const tok = self.tokens[self.index];
147 expect(needle, self.eat(.symbol), tok.line, tok.pos);
148 }
149
150 pub fn eatWord(comptime self: *Parser, comptime needle: string) void {
151 const tok = self.tokens[self.index];
152 expect(needle, self.eat(.word), tok.line, tok.pos);
153 }
154
155 pub fn doChildren(comptime self: *Parser) []const Value {
156 var ret: []const Value = &[_]Value{};
157
158 if (self.tryEatSymbol("(")) {
159 inline while (true) {
160 if (self.tryEatSymbol(")")) break;
161 ret = ret ++ &[_]Value{self.doValue()};
162 }
163 }
164
165 return ret;
166 }
167
168 pub fn doValue(comptime self: *Parser) Value {
169 if (self.tokens[self.index].data == .string) {
170 return Value{ .string = self.eat(.string) };
171 }
172 if (self.tryEatSymbol("{")) {
173 if (self.tryEatSymbol("#")) {
174 const fraw = self.tryEatSymbol("#");
175 const w = self.eat(.word);
176 std.debug.assert(w.len > 0);
177 std.debug.assert(w[0] != '_');
178 if (std.meta.stringToEnum(Block.Type, w)) |name| {
179 const func = if (self.tryEatSymbol("#")) self.eat(.word) else null;
180 const args = self.doArgs();
181 var children: []const Value = &.{};
182 var bottom: []const Value = &.{};
183 var top = true;
184 while (!self.tryEatSymbol("/")) {
185 if (self.tryEatSymbol("<")) {
186 self.eatWord("else");
187 self.eatSymbol(">");
188 top = false;
189 }
190 if (top) {
191 children = children ++ &[_]Value{self.doValue()};
192 } else {
193 bottom = bottom ++ &[_]Value{self.doValue()};
194 }
195 }
196 self.eatWord(@tagName(name));
197 self.eatSymbol("/");
198 return Value{ .block = Block{
199 .name = name,
200 .func = func,
201 .args = args,
202 .body = children,
203 .bttm = bottom,
204 } };
205 }
206 return Value{ .function = .{
207 .name = w,
208 .raw = fraw,
209 .args = self.doArgs(),
210 } };
211 }
212 if (self.tryEatSymbol("{")) {
213 defer self.eatSymbol("}");
214 return Value{ .replacement = .{ .arms = self.doReplacement(), .raw = true } };
215 }
216 return Value{ .replacement = .{ .arms = self.doReplacement() } };
217 }
218 return Value{ .element = self.doElement() };
219 }
220
221 pub fn doArgs(comptime self: *Parser) []const Arg {
222 var ret: []const Arg = &.{};
223 var temp: []const string = &.{};
224 while (!self.tryEatSymbol("}")) {
225 if (self.nextIs(.string)) {
226 if (temp.len > 0) {
227 ret = ret ++ &[_]Arg{.{ .lookup = temp }};
228 temp = &.{};
229 }
230 ret = ret ++ &[_]Arg{.{ .plain = self.eat(.string) }};
231 continue;
232 }
233 if (self.tryEatSymbol("(")) {
234 self.index -= 1;
235 ret = ret ++ &[_]Arg{.{ .value = self.doChildren() }};
236 continue;
237 }
238 if (temp.len == 0 and self.nextIs(.word)) {
239 const next_word = self.eat(.word);
240 if (extras.matchesAll(u8, next_word, std.ascii.isDigit)) {
241 ret = ret ++ &[_]Arg{.{ .int = extras.parseDigits(u64, next_word, 10) catch unreachable }};
242 continue;
243 }
244 temp = temp ++ &[_]string{next_word};
245 }
246 if (self.tryEatSymbol(".")) {
247 temp = temp ++ &[_]string{self.eat(.word)};
248 } else {
249 ret = ret ++ &[_]Arg{.{ .lookup = temp }};
250 temp = &.{};
251 }
252 }
253 if (temp.len > 0) ret = ret ++ &[_]Arg{.{ .lookup = temp }};
254 return ret;
255 }
256
257 pub fn doReplacement(comptime self: *Parser) []const string {
258 var ret: []const string = &.{};
259 ret = ret ++ &[_]string{self.eat(.word)};
260 while (!self.tryEatSymbol("}")) {
261 self.eatSymbol(".");
262 ret = ret ++ &[_]string{self.eat(.word)};
263 }
264 return ret;
265 }
266
267 fn eat(comptime self: *Parser, comptime typ: std.meta.Tag(Token.Data)) string {
268 defer self.index += 1;
269 const tok = self.tokens[self.index];
270 const tag = std.meta.activeTag(tok.data);
271 if (tag != typ) raise(tok.line, tok.pos, @tagName(typ), @tagName(tag));
272 return @field(tok.data, @tagName(typ));
273 }
274
275 fn nextIs(comptime self: *Parser, comptime typ: std.meta.Tag(Token.Data)) bool {
276 return self.tokens[self.index].data == typ;
277 }
278};