authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-31 22:12:22+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-04-01 08:31:16+02:00
log9ea04f4f1ca2507c75f1d276c37028aa53bfa201
tree31b50a83ee65ee0304d78f024f985b71aaf3014b
parent9cb2919d500520d280e09d0990809d538aa32b56

tapi: update yaml parser

https://github.com/kubkon/zig-yaml/commit/5de8b0b3a2cdb86f9a173118efa7e5e0747cca14

5 files changed, 1752 insertions(+), 1121 deletions(-)

src/link/tapi/Tokenizer.zig+338-223
......@@ -1,7 +1,7 @@
11const Tokenizer = @This();
22
33const std = @import("std");
4const log = std.log.scoped(.tapi);
4const log = std.log.scoped(.yaml);
55const testing = std.testing;
66
77buffer: []const u8,
......@@ -13,29 +13,31 @@ pub const Token = struct {
1313 end: usize,
1414
1515 pub const Id = enum {
16 Eof,
17
18 NewLine,
19 DocStart, // ---
20 DocEnd, // ...
21 SeqItemInd, // -
22 MapValueInd, // :
23 FlowMapStart, // {
24 FlowMapEnd, // }
25 FlowSeqStart, // [
26 FlowSeqEnd, // ]
27
28 Comma,
29 Space,
30 Tab,
31 Comment, // #
32 Alias, // *
33 Anchor, // &
34 Tag, // !
35 SingleQuote, // '
36 DoubleQuote, // "
37
38 Literal,
16 // zig fmt: off
17 eof,
18
19 new_line,
20 doc_start, // ---
21 doc_end, // ...
22 seq_item_ind, // -
23 map_value_ind, // :
24 flow_map_start, // {
25 flow_map_end, // }
26 flow_seq_start, // [
27 flow_seq_end, // ]
28
29 comma,
30 space,
31 tab,
32 comment, // #
33 alias, // *
34 anchor, // &
35 tag, // !
36
37 single_quoted, // '...'
38 double_quoted, // "..."
39 literal,
40 // zig fmt: on
3941 };
4042};
4143
......@@ -45,8 +47,8 @@ pub const TokenIterator = struct {
4547 buffer: []const Token,
4648 pos: TokenIndex = 0,
4749
48 pub fn next(self: *TokenIterator) Token {
49 const token = self.buffer[self.pos];
50 pub fn next(self: *TokenIterator) ?Token {
51 const token = self.peek() orelse return null;
5052 self.pos += 1;
5153 return token;
5254 }
......@@ -74,180 +76,212 @@ pub const TokenIterator = struct {
7476 }
7577};
7678
79fn stringMatchesPattern(comptime pattern: []const u8, slice: []const u8) bool {
80 comptime var count: usize = 0;
81 inline while (count < pattern.len) : (count += 1) {
82 if (count >= slice.len) return false;
83 const c = slice[count];
84 if (pattern[count] != c) return false;
85 }
86 return true;
87}
88
89fn matchesPattern(self: Tokenizer, comptime pattern: []const u8) bool {
90 return stringMatchesPattern(pattern, self.buffer[self.index..]);
91}
92
7793pub fn next(self: *Tokenizer) Token {
7894 var result = Token{
79 .id = .Eof,
95 .id = .eof,
8096 .start = self.index,
8197 .end = undefined,
8298 };
8399
84 var state: union(enum) {
85 Start,
86 NewLine,
87 Space,
88 Tab,
89 Hyphen: usize,
90 Dot: usize,
91 Literal,
92 } = .Start;
100 var state: enum {
101 start,
102 new_line,
103 space,
104 tab,
105 comment,
106 single_quoted,
107 double_quoted,
108 literal,
109 } = .start;
93110
94111 while (self.index < self.buffer.len) : (self.index += 1) {
95112 const c = self.buffer[self.index];
96113 switch (state) {
97 .Start => switch (c) {
114 .start => switch (c) {
98115 ' ' => {
99 state = .Space;
116 state = .space;
100117 },
101118 '\t' => {
102 state = .Tab;
119 state = .tab;
103120 },
104121 '\n' => {
105 result.id = .NewLine;
122 result.id = .new_line;
106123 self.index += 1;
107124 break;
108125 },
109126 '\r' => {
110 state = .NewLine;
127 state = .new_line;
111128 },
112 '-' => {
113 state = .{ .Hyphen = 1 };
129
130 '-' => if (self.matchesPattern("---")) {
131 result.id = .doc_start;
132 self.index += "---".len;
133 break;
134 } else if (self.matchesPattern("- ")) {
135 result.id = .seq_item_ind;
136 self.index += "- ".len;
137 break;
138 } else {
139 state = .literal;
114140 },
115 '.' => {
116 state = .{ .Dot = 1 };
141
142 '.' => if (self.matchesPattern("...")) {
143 result.id = .doc_end;
144 self.index += "...".len;
145 break;
146 } else {
147 state = .literal;
117148 },
149
118150 ',' => {
119 result.id = .Comma;
151 result.id = .comma;
120152 self.index += 1;
121153 break;
122154 },
123155 '#' => {
124 result.id = .Comment;
125 self.index += 1;
126 break;
156 state = .comment;
127157 },
128158 '*' => {
129 result.id = .Alias;
159 result.id = .alias;
130160 self.index += 1;
131161 break;
132162 },
133163 '&' => {
134 result.id = .Anchor;
164 result.id = .anchor;
135165 self.index += 1;
136166 break;
137167 },
138168 '!' => {
139 result.id = .Tag;
140 self.index += 1;
141 break;
142 },
143 '\'' => {
144 result.id = .SingleQuote;
145 self.index += 1;
146 break;
147 },
148 '"' => {
149 result.id = .DoubleQuote;
169 result.id = .tag;
150170 self.index += 1;
151171 break;
152172 },
153173 '[' => {
154 result.id = .FlowSeqStart;
174 result.id = .flow_seq_start;
155175 self.index += 1;
156176 break;
157177 },
158178 ']' => {
159 result.id = .FlowSeqEnd;
179 result.id = .flow_seq_end;
160180 self.index += 1;
161181 break;
162182 },
163183 ':' => {
164 result.id = .MapValueInd;
184 result.id = .map_value_ind;
165185 self.index += 1;
166186 break;
167187 },
168188 '{' => {
169 result.id = .FlowMapStart;
189 result.id = .flow_map_start;
170190 self.index += 1;
171191 break;
172192 },
173193 '}' => {
174 result.id = .FlowMapEnd;
194 result.id = .flow_map_end;
175195 self.index += 1;
176196 break;
177197 },
198 '\'' => {
199 state = .single_quoted;
200 },
201 '"' => {
202 state = .double_quoted;
203 },
178204 else => {
179 state = .Literal;
205 state = .literal;
206 },
207 },
208
209 .comment => switch (c) {
210 '\r', '\n' => {
211 result.id = .comment;
212 break;
180213 },
214 else => {},
181215 },
182 .Space => switch (c) {
216
217 .space => switch (c) {
183218 ' ' => {},
184219 else => {
185 result.id = .Space;
220 result.id = .space;
186221 break;
187222 },
188223 },
189 .Tab => switch (c) {
224
225 .tab => switch (c) {
190226 '\t' => {},
191227 else => {
192 result.id = .Tab;
228 result.id = .tab;
193229 break;
194230 },
195231 },
196 .NewLine => switch (c) {
232
233 .new_line => switch (c) {
197234 '\n' => {
198 result.id = .NewLine;
235 result.id = .new_line;
199236 self.index += 1;
200237 break;
201238 },
202239 else => {}, // TODO this should be an error condition
203240 },
204 .Hyphen => |*count| switch (c) {
205 ' ' => {
206 result.id = .SeqItemInd;
241
242 .single_quoted => switch (c) {
243 '\'' => if (!self.matchesPattern("''")) {
244 result.id = .single_quoted;
207245 self.index += 1;
208246 break;
247 } else {
248 self.index += "''".len - 1;
209249 },
210 '-' => {
211 count.* += 1;
212
213 if (count.* == 3) {
214 result.id = .DocStart;
215 self.index += 1;
216 break;
217 }
218 },
219 else => {
220 state = .Literal;
221 },
250 else => {},
222251 },
223 .Dot => |*count| switch (c) {
224 '.' => {
225 count.* += 1;
226252
227 if (count.* == 3) {
228 result.id = .DocEnd;
253 .double_quoted => switch (c) {
254 '"' => {
255 if (stringMatchesPattern("\\", self.buffer[self.index - 1 ..])) {
256 self.index += 1;
257 } else {
258 result.id = .double_quoted;
229259 self.index += 1;
230260 break;
231261 }
232262 },
233 else => {
234 state = .Literal;
235 },
263 else => {},
236264 },
237 .Literal => switch (c) {
265
266 .literal => switch (c) {
238267 '\r', '\n', ' ', '\'', '"', ',', ':', ']', '}' => {
239 result.id = .Literal;
268 result.id = .literal;
240269 break;
241270 },
242271 else => {
243 result.id = .Literal;
272 result.id = .literal;
244273 },
245274 },
246275 }
247276 }
248277
249 if (state == .Literal and result.id == .Eof) {
250 result.id = .Literal;
278 if (self.index >= self.buffer.len) {
279 switch (state) {
280 .literal => {
281 result.id = .literal;
282 },
283 else => {},
284 }
251285 }
252286
253287 result.end = self.index;
......@@ -263,22 +297,24 @@ fn testExpected(source: []const u8, expected: []const Token.Id) !void {
263297 .buffer = source,
264298 };
265299
266 var token_len: usize = 0;
267 for (expected) |exp| {
268 token_len += 1;
300 var given = std.ArrayList(Token.Id).init(testing.allocator);
301 defer given.deinit();
302
303 while (true) {
269304 const token = tokenizer.next();
270 try testing.expectEqual(exp, token.id);
305 try given.append(token.id);
306 if (token.id == .eof) break;
271307 }
272308
273 while (tokenizer.next().id != .Eof) {
274 token_len += 1; // consume all tokens
275 }
309 try testing.expectEqualSlices(Token.Id, expected, given.items);
310}
276311
277 try testing.expectEqual(expected.len, token_len);
312test {
313 std.testing.refAllDecls(@This());
278314}
279315
280316test "empty doc" {
281 try testExpected("", &[_]Token.Id{.Eof});
317 try testExpected("", &[_]Token.Id{.eof});
282318}
283319
284320test "empty doc with explicit markers" {
......@@ -286,7 +322,22 @@ test "empty doc with explicit markers" {
286322 \\---
287323 \\...
288324 , &[_]Token.Id{
289 .DocStart, .NewLine, .DocEnd, .Eof,
325 .doc_start, .new_line, .doc_end, .eof,
326 });
327}
328
329test "empty doc with explicit markers and a directive" {
330 try testExpected(
331 \\--- !tbd-v1
332 \\...
333 , &[_]Token.Id{
334 .doc_start,
335 .space,
336 .tag,
337 .literal,
338 .new_line,
339 .doc_end,
340 .eof,
290341 });
291342}
292343
......@@ -296,15 +347,15 @@ test "sequence of values" {
296347 \\- 1
297348 \\- 2
298349 , &[_]Token.Id{
299 .SeqItemInd,
300 .Literal,
301 .NewLine,
302 .SeqItemInd,
303 .Literal,
304 .NewLine,
305 .SeqItemInd,
306 .Literal,
307 .Eof,
350 .seq_item_ind,
351 .literal,
352 .new_line,
353 .seq_item_ind,
354 .literal,
355 .new_line,
356 .seq_item_ind,
357 .literal,
358 .eof,
308359 });
309360}
310361
......@@ -313,24 +364,24 @@ test "sequence of sequences" {
313364 \\- [ val1, val2]
314365 \\- [val3, val4 ]
315366 , &[_]Token.Id{
316 .SeqItemInd,
317 .FlowSeqStart,
318 .Space,
319 .Literal,
320 .Comma,
321 .Space,
322 .Literal,
323 .FlowSeqEnd,
324 .NewLine,
325 .SeqItemInd,
326 .FlowSeqStart,
327 .Literal,
328 .Comma,
329 .Space,
330 .Literal,
331 .Space,
332 .FlowSeqEnd,
333 .Eof,
367 .seq_item_ind,
368 .flow_seq_start,
369 .space,
370 .literal,
371 .comma,
372 .space,
373 .literal,
374 .flow_seq_end,
375 .new_line,
376 .seq_item_ind,
377 .flow_seq_start,
378 .literal,
379 .comma,
380 .space,
381 .literal,
382 .space,
383 .flow_seq_end,
384 .eof,
334385 });
335386}
336387
......@@ -339,16 +390,16 @@ test "mappings" {
339390 \\key1: value1
340391 \\key2: value2
341392 , &[_]Token.Id{
342 .Literal,
343 .MapValueInd,
344 .Space,
345 .Literal,
346 .NewLine,
347 .Literal,
348 .MapValueInd,
349 .Space,
350 .Literal,
351 .Eof,
393 .literal,
394 .map_value_ind,
395 .space,
396 .literal,
397 .new_line,
398 .literal,
399 .map_value_ind,
400 .space,
401 .literal,
402 .eof,
352403 });
353404}
354405
......@@ -357,21 +408,21 @@ test "inline mapped sequence of values" {
357408 \\key : [ val1,
358409 \\ val2 ]
359410 , &[_]Token.Id{
360 .Literal,
361 .Space,
362 .MapValueInd,
363 .Space,
364 .FlowSeqStart,
365 .Space,
366 .Literal,
367 .Comma,
368 .Space,
369 .NewLine,
370 .Space,
371 .Literal,
372 .Space,
373 .FlowSeqEnd,
374 .Eof,
411 .literal,
412 .space,
413 .map_value_ind,
414 .space,
415 .flow_seq_start,
416 .space,
417 .literal,
418 .comma,
419 .space,
420 .new_line,
421 .space,
422 .literal,
423 .space,
424 .flow_seq_end,
425 .eof,
375426 });
376427}
377428
......@@ -388,52 +439,50 @@ test "part of tbd" {
388439 \\install-name: '/usr/lib/libSystem.B.dylib'
389440 \\...
390441 , &[_]Token.Id{
391 .DocStart,
392 .Space,
393 .Tag,
394 .Literal,
395 .NewLine,
396 .Literal,
397 .MapValueInd,
398 .Space,
399 .Literal,
400 .NewLine,
401 .Literal,
402 .MapValueInd,
403 .Space,
404 .FlowSeqStart,
405 .Space,
406 .Literal,
407 .Space,
408 .FlowSeqEnd,
409 .NewLine,
410 .NewLine,
411 .Literal,
412 .MapValueInd,
413 .NewLine,
414 .Space,
415 .SeqItemInd,
416 .Literal,
417 .MapValueInd,
418 .Space,
419 .Literal,
420 .NewLine,
421 .Space,
422 .Literal,
423 .MapValueInd,
424 .Space,
425 .Literal,
426 .NewLine,
427 .NewLine,
428 .Literal,
429 .MapValueInd,
430 .Space,
431 .SingleQuote,
432 .Literal,
433 .SingleQuote,
434 .NewLine,
435 .DocEnd,
436 .Eof,
442 .doc_start,
443 .space,
444 .tag,
445 .literal,
446 .new_line,
447 .literal,
448 .map_value_ind,
449 .space,
450 .literal,
451 .new_line,
452 .literal,
453 .map_value_ind,
454 .space,
455 .flow_seq_start,
456 .space,
457 .literal,
458 .space,
459 .flow_seq_end,
460 .new_line,
461 .new_line,
462 .literal,
463 .map_value_ind,
464 .new_line,
465 .space,
466 .seq_item_ind,
467 .literal,
468 .map_value_ind,
469 .space,
470 .literal,
471 .new_line,
472 .space,
473 .literal,
474 .map_value_ind,
475 .space,
476 .literal,
477 .new_line,
478 .new_line,
479 .literal,
480 .map_value_ind,
481 .space,
482 .single_quoted,
483 .new_line,
484 .doc_end,
485 .eof,
437486 });
438487}
439488
......@@ -443,18 +492,84 @@ test "Unindented list" {
443492 \\- foo: 1
444493 \\c: 1
445494 , &[_]Token.Id{
446 .Literal,
447 .MapValueInd,
448 .NewLine,
449 .SeqItemInd,
450 .Literal,
451 .MapValueInd,
452 .Space,
453 .Literal,
454 .NewLine,
455 .Literal,
456 .MapValueInd,
457 .Space,
458 .Literal,
495 .literal,
496 .map_value_ind,
497 .new_line,
498 .seq_item_ind,
499 .literal,
500 .map_value_ind,
501 .space,
502 .literal,
503 .new_line,
504 .literal,
505 .map_value_ind,
506 .space,
507 .literal,
508 .eof,
509 });
510}
511
512test "escape sequences" {
513 try testExpected(
514 \\a: 'here''s an apostrophe'
515 \\b: "a newline\nand a\ttab"
516 \\c: "\"here\" and there"
517 , &[_]Token.Id{
518 .literal,
519 .map_value_ind,
520 .space,
521 .single_quoted,
522 .new_line,
523 .literal,
524 .map_value_ind,
525 .space,
526 .double_quoted,
527 .new_line,
528 .literal,
529 .map_value_ind,
530 .space,
531 .double_quoted,
532 .eof,
533 });
534}
535
536test "comments" {
537 try testExpected(
538 \\key: # some comment about the key
539 \\# first value
540 \\- val1
541 \\# second value
542 \\- val2
543 , &[_]Token.Id{
544 .literal,
545 .map_value_ind,
546 .space,
547 .comment,
548 .new_line,
549 .comment,
550 .new_line,
551 .seq_item_ind,
552 .literal,
553 .new_line,
554 .comment,
555 .new_line,
556 .seq_item_ind,
557 .literal,
558 .eof,
559 });
560}
561
562test "quoted literals" {
563 try testExpected(
564 \\'#000000'
565 \\'[000000'
566 \\"&someString"
567 , &[_]Token.Id{
568 .single_quoted,
569 .new_line,
570 .single_quoted,
571 .new_line,
572 .double_quoted,
573 .eof,
459574 });
460575}
src/link/tapi/parse.zig+378-323
......@@ -1,8 +1,7 @@
11const std = @import("std");
22const assert = std.debug.assert;
3const log = std.log.scoped(.tapi);
3const log = std.log.scoped(.yaml);
44const mem = std.mem;
5const testing = std.testing;
65
76const Allocator = mem.Allocator;
87const Tokenizer = @import("Tokenizer.zig");
......@@ -11,9 +10,9 @@ const TokenIndex = Tokenizer.TokenIndex;
1110const TokenIterator = Tokenizer.TokenIterator;
1211
1312pub const ParseError = error{
13 InvalidEscapeSequence,
1414 MalformedYaml,
1515 NestedDocuments,
16 UnexpectedTag,
1716 UnexpectedEof,
1817 UnexpectedToken,
1918 Unhandled,
......@@ -22,6 +21,8 @@ pub const ParseError = error{
2221pub const Node = struct {
2322 tag: Tag,
2423 tree: *const Tree,
24 start: TokenIndex,
25 end: TokenIndex,
2526
2627 pub const Tag = enum {
2728 doc,
......@@ -61,9 +62,12 @@ pub const Node = struct {
6162 }
6263
6364 pub const Doc = struct {
64 base: Node = Node{ .tag = Tag.doc, .tree = undefined },
65 start: ?TokenIndex = null,
66 end: ?TokenIndex = null,
65 base: Node = Node{
66 .tag = Tag.doc,
67 .tree = undefined,
68 .start = undefined,
69 .end = undefined,
70 },
6771 directive: ?TokenIndex = null,
6872 value: ?*Node = null,
6973
......@@ -86,10 +90,8 @@ pub const Node = struct {
8690 _ = fmt;
8791 if (self.directive) |id| {
8892 try std.fmt.format(writer, "{{ ", .{});
89 const directive = self.base.tree.tokens[id];
90 try std.fmt.format(writer, ".directive = {s}, ", .{
91 self.base.tree.source[directive.start..directive.end],
92 });
93 const directive = self.base.tree.getRaw(id, id);
94 try std.fmt.format(writer, ".directive = {s}, ", .{directive});
9395 }
9496 if (self.value) |node| {
9597 try std.fmt.format(writer, "{}", .{node});
......@@ -101,22 +103,27 @@ pub const Node = struct {
101103 };
102104
103105 pub const Map = struct {
104 base: Node = Node{ .tag = Tag.map, .tree = undefined },
105 start: ?TokenIndex = null,
106 end: ?TokenIndex = null,
106 base: Node = Node{
107 .tag = Tag.map,
108 .tree = undefined,
109 .start = undefined,
110 .end = undefined,
111 },
107112 values: std.ArrayListUnmanaged(Entry) = .{},
108113
109114 pub const base_tag: Node.Tag = .map;
110115
111116 pub const Entry = struct {
112117 key: TokenIndex,
113 value: *Node,
118 value: ?*Node,
114119 };
115120
116121 pub fn deinit(self: *Map, allocator: Allocator) void {
117122 for (self.values.items) |entry| {
118 entry.value.deinit(allocator);
119 allocator.destroy(entry.value);
123 if (entry.value) |value| {
124 value.deinit(allocator);
125 allocator.destroy(value);
126 }
120127 }
121128 self.values.deinit(allocator);
122129 }
......@@ -131,20 +138,24 @@ pub const Node = struct {
131138 _ = fmt;
132139 try std.fmt.format(writer, "{{ ", .{});
133140 for (self.values.items) |entry| {
134 const key = self.base.tree.tokens[entry.key];
135 try std.fmt.format(writer, "{s} => {}, ", .{
136 self.base.tree.source[key.start..key.end],
137 entry.value,
138 });
141 const key = self.base.tree.getRaw(entry.key, entry.key);
142 if (entry.value) |value| {
143 try std.fmt.format(writer, "{s} => {}, ", .{ key, value });
144 } else {
145 try std.fmt.format(writer, "{s} => null, ", .{key});
146 }
139147 }
140148 return std.fmt.format(writer, " }}", .{});
141149 }
142150 };
143151
144152 pub const List = struct {
145 base: Node = Node{ .tag = Tag.list, .tree = undefined },
146 start: ?TokenIndex = null,
147 end: ?TokenIndex = null,
153 base: Node = Node{
154 .tag = Tag.list,
155 .tree = undefined,
156 .start = undefined,
157 .end = undefined,
158 },
148159 values: std.ArrayListUnmanaged(*Node) = .{},
149160
150161 pub const base_tag: Node.Tag = .list;
......@@ -174,15 +185,18 @@ pub const Node = struct {
174185 };
175186
176187 pub const Value = struct {
177 base: Node = Node{ .tag = Tag.value, .tree = undefined },
178 start: ?TokenIndex = null,
179 end: ?TokenIndex = null,
188 base: Node = Node{
189 .tag = Tag.value,
190 .tree = undefined,
191 .start = undefined,
192 .end = undefined,
193 },
194 string_value: std.ArrayListUnmanaged(u8) = .{},
180195
181196 pub const base_tag: Node.Tag = .value;
182197
183198 pub fn deinit(self: *Value, allocator: Allocator) void {
184 _ = self;
185 _ = allocator;
199 self.string_value.deinit(allocator);
186200 }
187201
188202 pub fn format(
......@@ -193,11 +207,8 @@ pub const Node = struct {
193207 ) !void {
194208 _ = options;
195209 _ = fmt;
196 const start = self.base.tree.tokens[self.start.?];
197 const end = self.base.tree.tokens[self.end.?];
198 return std.fmt.format(writer, "{s}", .{
199 self.base.tree.source[start.start..end.end],
200 });
210 const raw = self.base.tree.getRaw(self.base.start, self.base.end);
211 return std.fmt.format(writer, "{s}", .{raw});
201212 }
202213 };
203214};
......@@ -233,6 +244,21 @@ pub const Tree = struct {
233244 self.docs.deinit(self.allocator);
234245 }
235246
247 pub fn getDirective(self: Tree, doc_index: usize) ?[]const u8 {
248 assert(doc_index < self.docs.items.len);
249 const doc = self.docs.items[doc_index].cast(Node.Doc) orelse return null;
250 const id = doc.directive orelse return null;
251 return self.getRaw(id, id);
252 }
253
254 pub fn getRaw(self: Tree, start: TokenIndex, end: TokenIndex) []const u8 {
255 assert(start <= end);
256 assert(start < self.tokens.len and end < self.tokens.len);
257 const start_token = self.tokens[start];
258 const end_token = self.tokens[end];
259 return self.source[start_token.start..end_token.end];
260 }
261
236262 pub fn parse(self: *Tree, source: []const u8) !void {
237263 var tokenizer = Tokenizer{ .buffer = source };
238264 var tokens = std.ArrayList(Token).init(self.allocator);
......@@ -252,8 +278,8 @@ pub const Tree = struct {
252278 });
253279
254280 switch (token.id) {
255 .Eof => break,
256 .NewLine => {
281 .eof => break,
282 .new_line => {
257283 line += 1;
258284 prev_line_last_col = token.end;
259285 },
......@@ -272,20 +298,20 @@ pub const Tree = struct {
272298 .line_cols = &self.line_cols,
273299 };
274300
275 while (true) {
276 if (parser.token_it.peek() == null) return;
301 parser.eatCommentsAndSpace(&.{});
277302
278 const pos = parser.token_it.pos;
279 const token = parser.token_it.next();
303 while (true) {
304 parser.eatCommentsAndSpace(&.{});
305 const token = parser.token_it.next() orelse break;
280306
281 log.debug("Next token: {}, {}", .{ pos, token });
307 log.debug("(main) next {s}@{d}", .{ @tagName(token.id), parser.token_it.pos - 1 });
282308
283309 switch (token.id) {
284 .Space, .Comment, .NewLine => {},
285 .Eof => break,
310 .eof => break,
286311 else => {
287 const doc = try parser.doc(pos);
288 try self.docs.append(self.allocator, &doc.base);
312 parser.token_it.seekBy(-1);
313 const doc = try parser.doc();
314 try self.docs.append(self.allocator, doc);
289315 },
290316 }
291317 }
......@@ -298,355 +324,308 @@ const Parser = struct {
298324 token_it: *TokenIterator,
299325 line_cols: *const std.AutoHashMap(TokenIndex, LineCol),
300326
301 fn doc(self: *Parser, start: TokenIndex) ParseError!*Node.Doc {
327 fn value(self: *Parser) ParseError!?*Node {
328 self.eatCommentsAndSpace(&.{});
329
330 const pos = self.token_it.pos;
331 const token = self.token_it.next() orelse return error.UnexpectedEof;
332
333 log.debug(" next {s}@{d}", .{ @tagName(token.id), pos });
334
335 switch (token.id) {
336 .literal => if (self.eatToken(.map_value_ind, &.{ .new_line, .comment })) |_| {
337 // map
338 self.token_it.seekTo(pos);
339 return self.map();
340 } else {
341 // leaf value
342 self.token_it.seekTo(pos);
343 return self.leaf_value();
344 },
345 .single_quoted, .double_quoted => {
346 // leaf value
347 self.token_it.seekBy(-1);
348 return self.leaf_value();
349 },
350 .seq_item_ind => {
351 // list
352 self.token_it.seekBy(-1);
353 return self.list();
354 },
355 .flow_seq_start => {
356 // list
357 self.token_it.seekBy(-1);
358 return self.list_bracketed();
359 },
360 else => return null,
361 }
362 }
363
364 fn doc(self: *Parser) ParseError!*Node {
302365 const node = try self.allocator.create(Node.Doc);
303366 errdefer self.allocator.destroy(node);
304 node.* = .{ .start = start };
367 node.* = .{};
305368 node.base.tree = self.tree;
369 node.base.start = self.token_it.pos;
306370
307 self.token_it.seekTo(start);
308
309 log.debug("Doc start: {}, {}", .{ start, self.tree.tokens[start] });
371 log.debug("(doc) begin {s}@{d}", .{ @tagName(self.tree.tokens[node.base.start].id), node.base.start });
310372
311 const explicit_doc: bool = if (self.eatToken(.DocStart)) |_| explicit_doc: {
312 if (self.eatToken(.Tag)) |_| {
313 node.directive = try self.expectToken(.Literal);
373 // Parse header
374 const explicit_doc: bool = if (self.eatToken(.doc_start, &.{})) |doc_pos| explicit_doc: {
375 if (self.getCol(doc_pos) > 0) return error.MalformedYaml;
376 if (self.eatToken(.tag, &.{ .new_line, .comment })) |_| {
377 node.directive = try self.expectToken(.literal, &.{ .new_line, .comment });
314378 }
315 _ = try self.expectToken(.NewLine);
316379 break :explicit_doc true;
317380 } else false;
318381
319 while (true) {
320 const pos = self.token_it.pos;
321 const token = self.token_it.next();
322
323 log.debug("Next token: {}, {}", .{ pos, token });
382 // Parse value
383 node.value = try self.value();
384 if (node.value == null) {
385 self.token_it.seekBy(-1);
386 }
387 errdefer if (node.value) |val| {
388 val.deinit(self.allocator);
389 self.allocator.destroy(val);
390 };
324391
325 switch (token.id) {
326 .Tag => {
327 return error.UnexpectedTag;
328 },
329 .Literal, .SingleQuote, .DoubleQuote => {
330 _ = try self.expectToken(.MapValueInd);
331 const map_node = try self.map(pos);
332 node.value = &map_node.base;
333 },
334 .SeqItemInd => {
335 const list_node = try self.list(pos);
336 node.value = &list_node.base;
337 },
338 .FlowSeqStart => {
339 const list_node = try self.list_bracketed(pos);
340 node.value = &list_node.base;
341 },
342 .DocEnd => {
343 if (explicit_doc) break;
344 return error.UnexpectedToken;
345 },
346 .DocStart, .Eof => {
347 self.token_it.seekBy(-1);
348 break;
349 },
350 else => {
351 return error.UnexpectedToken;
352 },
392 // Parse footer
393 footer: {
394 if (self.eatToken(.doc_end, &.{})) |pos| {
395 if (!explicit_doc) return error.UnexpectedToken;
396 if (self.getCol(pos) > 0) return error.MalformedYaml;
397 node.base.end = pos;
398 break :footer;
399 }
400 if (self.eatToken(.doc_start, &.{})) |pos| {
401 if (!explicit_doc) return error.UnexpectedToken;
402 if (self.getCol(pos) > 0) return error.MalformedYaml;
403 self.token_it.seekBy(-1);
404 node.base.end = pos - 1;
405 break :footer;
406 }
407 if (self.eatToken(.eof, &.{})) |pos| {
408 node.base.end = pos - 1;
409 break :footer;
353410 }
411 return error.UnexpectedToken;
354412 }
355413
356 node.end = self.token_it.pos - 1;
414 log.debug("(doc) end {s}@{d}", .{ @tagName(self.tree.tokens[node.base.end].id), node.base.end });
357415
358 log.debug("Doc end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
359
360 return node;
416 return &node.base;
361417 }
362418
363 fn map(self: *Parser, start: TokenIndex) ParseError!*Node.Map {
419 fn map(self: *Parser) ParseError!*Node {
364420 const node = try self.allocator.create(Node.Map);
365421 errdefer self.allocator.destroy(node);
366 node.* = .{ .start = start };
422 node.* = .{};
367423 node.base.tree = self.tree;
424 node.base.start = self.token_it.pos;
425 errdefer {
426 for (node.values.items) |entry| {
427 if (entry.value) |val| {
428 val.deinit(self.allocator);
429 self.allocator.destroy(val);
430 }
431 }
432 node.values.deinit(self.allocator);
433 }
368434
369 self.token_it.seekTo(start);
370
371 log.debug("Map start: {}, {}", .{ start, self.tree.tokens[start] });
435 log.debug("(map) begin {s}@{d}", .{ @tagName(self.tree.tokens[node.base.start].id), node.base.start });
372436
373 const col = self.getCol(start);
437 const col = self.getCol(node.base.start);
374438
375439 while (true) {
376 self.eatCommentsAndSpace();
440 self.eatCommentsAndSpace(&.{});
377441
378 // Parse key.
442 // Parse key
379443 const key_pos = self.token_it.pos;
380 if (self.getCol(key_pos) != col) {
444 if (self.getCol(key_pos) < col) {
381445 break;
382446 }
383447
384 const key = self.token_it.next();
448 const key = self.token_it.next() orelse return error.UnexpectedEof;
385449 switch (key.id) {
386 .Literal => {},
387 else => {
450 .literal => {},
451 .doc_start, .doc_end, .eof => {
388452 self.token_it.seekBy(-1);
389453 break;
390454 },
455 else => {
456 // TODO key not being a literal
457 return error.Unhandled;
458 },
391459 }
392460
393 log.debug("Map key: {}, '{s}'", .{ key, self.tree.source[key.start..key.end] });
461 log.debug("(map) key {s}@{d}", .{ self.tree.getRaw(key_pos, key_pos), key_pos });
394462
395463 // Separator
396 _ = try self.expectToken(.MapValueInd);
397
398 // Parse value.
399 const value: *Node = value: {
400 if (self.eatToken(.NewLine)) |_| {
401 self.eatCommentsAndSpace();
402
403 // Explicit, complex value such as list or map.
404 const value_pos = self.token_it.pos;
405 const value = self.token_it.next();
406 switch (value.id) {
407 .Literal, .SingleQuote, .DoubleQuote => {
408 // Assume nested map.
409 const map_node = try self.map(value_pos);
410 break :value &map_node.base;
411 },
412 .SeqItemInd => {
413 // Assume list of values.
414 const list_node = try self.list(value_pos);
415 break :value &list_node.base;
416 },
417 else => {
418 log.err("{}", .{key});
419 return error.Unhandled;
420 },
421 }
422 } else {
423 self.eatCommentsAndSpace();
424
425 const value_pos = self.token_it.pos;
426 const value = self.token_it.next();
427 switch (value.id) {
428 .Literal, .SingleQuote, .DoubleQuote => {
429 // Assume leaf value.
430 const leaf_node = try self.leaf_value(value_pos);
431 break :value &leaf_node.base;
432 },
433 .FlowSeqStart => {
434 const list_node = try self.list_bracketed(value_pos);
435 break :value &list_node.base;
436 },
437 else => {
438 log.err("{}", .{key});
439 return error.Unhandled;
440 },
464 _ = try self.expectToken(.map_value_ind, &.{ .new_line, .comment });
465
466 // Parse value
467 const val = try self.value();
468 errdefer if (val) |v| {
469 v.deinit(self.allocator);
470 self.allocator.destroy(v);
471 };
472
473 if (val) |v| {
474 if (self.getCol(v.start) < self.getCol(key_pos)) {
475 return error.MalformedYaml;
476 }
477 if (v.cast(Node.Value)) |_| {
478 if (self.getCol(v.start) == self.getCol(key_pos)) {
479 return error.MalformedYaml;
441480 }
442481 }
443 };
444 log.debug("Map value: {}", .{value});
482 }
445483
446484 try node.values.append(self.allocator, .{
447485 .key = key_pos,
448 .value = value,
486 .value = val,
449487 });
450
451 _ = self.eatToken(.NewLine);
452488 }
453489
454 node.end = self.token_it.pos - 1;
490 node.base.end = self.token_it.pos - 1;
455491
456 log.debug("Map end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
492 log.debug("(map) end {s}@{d}", .{ @tagName(self.tree.tokens[node.base.end].id), node.base.end });
457493
458 return node;
494 return &node.base;
459495 }
460496
461 fn list(self: *Parser, start: TokenIndex) ParseError!*Node.List {
497 fn list(self: *Parser) ParseError!*Node {
462498 const node = try self.allocator.create(Node.List);
463499 errdefer self.allocator.destroy(node);
464 node.* = .{
465 .start = start,
466 };
500 node.* = .{};
467501 node.base.tree = self.tree;
502 node.base.start = self.token_it.pos;
503 errdefer {
504 for (node.values.items) |val| {
505 val.deinit(self.allocator);
506 self.allocator.destroy(val);
507 }
508 node.values.deinit(self.allocator);
509 }
468510
469 self.token_it.seekTo(start);
470
471 log.debug("List start: {}, {}", .{ start, self.tree.tokens[start] });
472
473 const col = self.getCol(start);
511 log.debug("(list) begin {s}@{d}", .{ @tagName(self.tree.tokens[node.base.start].id), node.base.start });
474512
475513 while (true) {
476 self.eatCommentsAndSpace();
514 self.eatCommentsAndSpace(&.{});
477515
478 if (self.getCol(self.token_it.pos) != col) {
479 break;
480 }
481 _ = self.eatToken(.SeqItemInd) orelse {
482 break;
483 };
516 _ = self.eatToken(.seq_item_ind, &.{}) orelse break;
484517
485 const pos = self.token_it.pos;
486 const token = self.token_it.next();
487 const value: *Node = value: {
488 switch (token.id) {
489 .Literal, .SingleQuote, .DoubleQuote => {
490 if (self.eatToken(.MapValueInd)) |_| {
491 // nested map
492 const map_node = try self.map(pos);
493 break :value &map_node.base;
494 } else {
495 // standalone (leaf) value
496 const leaf_node = try self.leaf_value(pos);
497 break :value &leaf_node.base;
498 }
499 },
500 .FlowSeqStart => {
501 const list_node = try self.list_bracketed(pos);
502 break :value &list_node.base;
503 },
504 else => {
505 log.err("{}", .{token});
506 return error.Unhandled;
507 },
508 }
509 };
510 try node.values.append(self.allocator, value);
511
512 _ = self.eatToken(.NewLine);
518 const val = (try self.value()) orelse return error.MalformedYaml;
519 try node.values.append(self.allocator, val);
513520 }
514521
515 node.end = self.token_it.pos - 1;
522 node.base.end = self.token_it.pos - 1;
516523
517 log.debug("List end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
524 log.debug("(list) end {s}@{d}", .{ @tagName(self.tree.tokens[node.base.end].id), node.base.end });
518525
519 return node;
526 return &node.base;
520527 }
521528
522 fn list_bracketed(self: *Parser, start: TokenIndex) ParseError!*Node.List {
529 fn list_bracketed(self: *Parser) ParseError!*Node {
523530 const node = try self.allocator.create(Node.List);
524531 errdefer self.allocator.destroy(node);
525 node.* = .{ .start = start };
532 node.* = .{};
526533 node.base.tree = self.tree;
534 node.base.start = self.token_it.pos;
535 errdefer {
536 for (node.values.items) |val| {
537 val.deinit(self.allocator);
538 self.allocator.destroy(val);
539 }
540 node.values.deinit(self.allocator);
541 }
527542
528 self.token_it.seekTo(start);
529
530 log.debug("List start: {}, {}", .{ start, self.tree.tokens[start] });
543 log.debug("(list) begin {s}@{d}", .{ @tagName(self.tree.tokens[node.base.start].id), node.base.start });
531544
532 _ = try self.expectToken(.FlowSeqStart);
545 _ = try self.expectToken(.flow_seq_start, &.{});
533546
534547 while (true) {
535 _ = self.eatToken(.NewLine);
536 self.eatCommentsAndSpace();
548 self.eatCommentsAndSpace(&.{.comment});
537549
538 const pos = self.token_it.pos;
539 const token = self.token_it.next();
540
541 log.debug("Next token: {}, {}", .{ pos, token });
550 if (self.eatToken(.flow_seq_end, &.{.comment})) |pos| {
551 node.base.end = pos;
552 break;
553 }
554 _ = self.eatToken(.comma, &.{.comment});
542555
543 const value: *Node = value: {
544 switch (token.id) {
545 .FlowSeqStart => {
546 const list_node = try self.list_bracketed(pos);
547 break :value &list_node.base;
548 },
549 .FlowSeqEnd => {
550 break;
551 },
552 .Literal, .SingleQuote, .DoubleQuote => {
553 const leaf_node = try self.leaf_value(pos);
554 _ = self.eatToken(.Comma);
555 // TODO newline
556 break :value &leaf_node.base;
557 },
558 else => {
559 log.err("{}", .{token});
560 return error.Unhandled;
561 },
562 }
563 };
564 try node.values.append(self.allocator, value);
556 const val = (try self.value()) orelse return error.MalformedYaml;
557 try node.values.append(self.allocator, val);
565558 }
566559
567 node.end = self.token_it.pos - 1;
568
569 log.debug("List end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
560 log.debug("(list) end {s}@{d}", .{ @tagName(self.tree.tokens[node.base.end].id), node.base.end });
570561
571 return node;
562 return &node.base;
572563 }
573564
574 fn leaf_value(self: *Parser, start: TokenIndex) ParseError!*Node.Value {
565 fn leaf_value(self: *Parser) ParseError!*Node {
575566 const node = try self.allocator.create(Node.Value);
576567 errdefer self.allocator.destroy(node);
577 node.* = .{ .start = start };
568 node.* = .{ .string_value = .{} };
578569 node.base.tree = self.tree;
579
580 self.token_it.seekTo(start);
581
582 log.debug("Leaf start: {}, {}", .{ node.start.?, self.tree.tokens[node.start.?] });
583
584 parse: {
585 if (self.eatToken(.SingleQuote)) |_| {
586 node.start = node.start.? + 1;
587 while (true) {
588 const tok = self.token_it.next();
589 switch (tok.id) {
590 .SingleQuote => {
591 node.end = self.token_it.pos - 2;
592 break :parse;
593 },
594 .NewLine => return error.UnexpectedToken,
595 else => {},
596 }
597 }
598 }
599
600 if (self.eatToken(.DoubleQuote)) |_| {
601 node.start = node.start.? + 1;
602 while (true) {
603 const tok = self.token_it.next();
604 switch (tok.id) {
605 .DoubleQuote => {
606 node.end = self.token_it.pos - 2;
607 break :parse;
608 },
609 .NewLine => return error.UnexpectedToken,
610 else => {},
611 }
612 }
613 }
614
615 // TODO handle multiline strings in new block scope
616 while (true) {
617 const tok = self.token_it.next();
618 switch (tok.id) {
619 .Literal => {},
620 .Space => {
621 const trailing = self.token_it.pos - 2;
622 self.eatCommentsAndSpace();
623 if (self.token_it.peek()) |peek| {
624 if (peek.id != .Literal) {
625 node.end = trailing;
626 break;
627 }
570 node.base.start = self.token_it.pos;
571 errdefer node.string_value.deinit(self.allocator);
572
573 // TODO handle multiline strings in new block scope
574 while (self.token_it.next()) |tok| {
575 switch (tok.id) {
576 .single_quoted => {
577 node.base.end = self.token_it.pos - 1;
578 const raw = self.tree.getRaw(node.base.start, node.base.end);
579 try self.parseSingleQuoted(node, raw);
580 break;
581 },
582 .double_quoted => {
583 node.base.end = self.token_it.pos - 1;
584 const raw = self.tree.getRaw(node.base.start, node.base.end);
585 try self.parseDoubleQuoted(node, raw);
586 break;
587 },
588 .literal => {},
589 .space => {
590 const trailing = self.token_it.pos - 2;
591 self.eatCommentsAndSpace(&.{});
592 if (self.token_it.peek()) |peek| {
593 if (peek.id != .literal) {
594 node.base.end = trailing;
595 const raw = self.tree.getRaw(node.base.start, node.base.end);
596 try node.string_value.appendSlice(self.allocator, raw);
597 break;
628598 }
629 },
630 else => {
631 self.token_it.seekBy(-1);
632 node.end = self.token_it.pos - 1;
633 break;
634 },
635 }
599 }
600 },
601 else => {
602 self.token_it.seekBy(-1);
603 node.base.end = self.token_it.pos - 1;
604 const raw = self.tree.getRaw(node.base.start, node.base.end);
605 try node.string_value.appendSlice(self.allocator, raw);
606 break;
607 },
636608 }
637609 }
638610
639 log.debug("Leaf end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
611 log.debug("(leaf) {s}", .{self.tree.getRaw(node.base.start, node.base.end)});
640612
641 return node;
613 return &node.base;
642614 }
643615
644 fn eatCommentsAndSpace(self: *Parser) void {
645 while (true) {
646 _ = self.token_it.peek() orelse return;
647 const token = self.token_it.next();
616 fn eatCommentsAndSpace(self: *Parser, comptime exclusions: []const Token.Id) void {
617 log.debug("eatCommentsAndSpace", .{});
618 outer: while (self.token_it.next()) |token| {
619 log.debug(" (token '{s}')", .{@tagName(token.id)});
648620 switch (token.id) {
649 .Comment, .Space => {},
621 .comment, .space, .new_line => |space| {
622 inline for (exclusions) |excl| {
623 if (excl == space) {
624 self.token_it.seekBy(-1);
625 break :outer;
626 }
627 } else continue;
628 },
650629 else => {
651630 self.token_it.seekBy(-1);
652631 break;
......@@ -655,25 +634,24 @@ const Parser = struct {
655634 }
656635 }
657636
658 fn eatToken(self: *Parser, id: Token.Id) ?TokenIndex {
659 while (true) {
660 const pos = self.token_it.pos;
661 _ = self.token_it.peek() orelse return null;
662 const token = self.token_it.next();
663 switch (token.id) {
664 .Comment, .Space => continue,
665 else => |next_id| if (next_id == id) {
666 return pos;
667 } else {
668 self.token_it.seekTo(pos);
669 return null;
670 },
671 }
637 fn eatToken(self: *Parser, id: Token.Id, comptime exclusions: []const Token.Id) ?TokenIndex {
638 log.debug("eatToken('{s}')", .{@tagName(id)});
639 self.eatCommentsAndSpace(exclusions);
640 const pos = self.token_it.pos;
641 const token = self.token_it.next() orelse return null;
642 if (token.id == id) {
643 log.debug(" (found at {d})", .{pos});
644 return pos;
645 } else {
646 log.debug(" (not found)", .{});
647 self.token_it.seekBy(-1);
648 return null;
672649 }
673650 }
674651
675 fn expectToken(self: *Parser, id: Token.Id) ParseError!TokenIndex {
676 return self.eatToken(id) orelse error.UnexpectedToken;
652 fn expectToken(self: *Parser, id: Token.Id, comptime exclusions: []const Token.Id) ParseError!TokenIndex {
653 log.debug("expectToken('{s}')", .{@tagName(id)});
654 return self.eatToken(id, exclusions) orelse error.UnexpectedToken;
677655 }
678656
679657 fn getLine(self: *Parser, index: TokenIndex) usize {
......@@ -683,8 +661,85 @@ const Parser = struct {
683661 fn getCol(self: *Parser, index: TokenIndex) usize {
684662 return self.line_cols.get(index).?.col;
685663 }
664
665 fn parseSingleQuoted(self: *Parser, node: *Node.Value, raw: []const u8) ParseError!void {
666 assert(raw[0] == '\'' and raw[raw.len - 1] == '\'');
667
668 const raw_no_quotes = raw[1 .. raw.len - 1];
669 try node.string_value.ensureTotalCapacity(self.allocator, raw_no_quotes.len);
670
671 var state: enum {
672 start,
673 escape,
674 } = .start;
675 var index: usize = 0;
676
677 while (index < raw_no_quotes.len) : (index += 1) {
678 const c = raw_no_quotes[index];
679 switch (state) {
680 .start => switch (c) {
681 '\'' => {
682 state = .escape;
683 },
684 else => {
685 node.string_value.appendAssumeCapacity(c);
686 },
687 },
688 .escape => switch (c) {
689 '\'' => {
690 state = .start;
691 node.string_value.appendAssumeCapacity(c);
692 },
693 else => return error.InvalidEscapeSequence,
694 },
695 }
696 }
697 }
698
699 fn parseDoubleQuoted(self: *Parser, node: *Node.Value, raw: []const u8) ParseError!void {
700 assert(raw[0] == '"' and raw[raw.len - 1] == '"');
701
702 const raw_no_quotes = raw[1 .. raw.len - 1];
703 try node.string_value.ensureTotalCapacity(self.allocator, raw_no_quotes.len);
704
705 var state: enum {
706 start,
707 escape,
708 } = .start;
709
710 var index: usize = 0;
711 while (index < raw_no_quotes.len) : (index += 1) {
712 const c = raw_no_quotes[index];
713 switch (state) {
714 .start => switch (c) {
715 '\\' => {
716 state = .escape;
717 },
718 else => {
719 node.string_value.appendAssumeCapacity(c);
720 },
721 },
722 .escape => switch (c) {
723 'n' => {
724 state = .start;
725 node.string_value.appendAssumeCapacity('\n');
726 },
727 't' => {
728 state = .start;
729 node.string_value.appendAssumeCapacity('\t');
730 },
731 '"' => {
732 state = .start;
733 node.string_value.appendAssumeCapacity('"');
734 },
735 else => return error.InvalidEscapeSequence,
736 },
737 }
738 }
739 }
686740};
687741
688742test {
743 std.testing.refAllDecls(@This());
689744 _ = @import("parse/test.zig");
690745}
src/link/tapi/parse/test.zig+385-179
......@@ -21,45 +21,45 @@ test "explicit doc" {
2121 try testing.expectEqual(tree.docs.items.len, 1);
2222
2323 const doc = tree.docs.items[0].cast(Node.Doc).?;
24 try testing.expectEqual(doc.start.?, 0);
25 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
24 try testing.expectEqual(doc.base.start, 0);
25 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
2626
2727 const directive = tree.tokens[doc.directive.?];
28 try testing.expectEqual(directive.id, .Literal);
29 try testing.expect(mem.eql(u8, "tapi-tbd", tree.source[directive.start..directive.end]));
28 try testing.expectEqual(directive.id, .literal);
29 try testing.expectEqualStrings("tapi-tbd", tree.source[directive.start..directive.end]);
3030
3131 try testing.expect(doc.value != null);
3232 try testing.expectEqual(doc.value.?.tag, .map);
3333
3434 const map = doc.value.?.cast(Node.Map).?;
35 try testing.expectEqual(map.start.?, 5);
36 try testing.expectEqual(map.end.?, 14);
35 try testing.expectEqual(map.base.start, 5);
36 try testing.expectEqual(map.base.end, 14);
3737 try testing.expectEqual(map.values.items.len, 2);
3838
3939 {
4040 const entry = map.values.items[0];
4141
4242 const key = tree.tokens[entry.key];
43 try testing.expectEqual(key.id, .Literal);
44 try testing.expect(mem.eql(u8, "tbd-version", tree.source[key.start..key.end]));
43 try testing.expectEqual(key.id, .literal);
44 try testing.expectEqualStrings("tbd-version", tree.source[key.start..key.end]);
4545
46 const value = entry.value.cast(Node.Value).?;
47 const value_tok = tree.tokens[value.start.?];
48 try testing.expectEqual(value_tok.id, .Literal);
49 try testing.expect(mem.eql(u8, "4", tree.source[value_tok.start..value_tok.end]));
46 const value = entry.value.?.cast(Node.Value).?;
47 const value_tok = tree.tokens[value.base.start];
48 try testing.expectEqual(value_tok.id, .literal);
49 try testing.expectEqualStrings("4", tree.source[value_tok.start..value_tok.end]);
5050 }
5151
5252 {
5353 const entry = map.values.items[1];
5454
5555 const key = tree.tokens[entry.key];
56 try testing.expectEqual(key.id, .Literal);
57 try testing.expect(mem.eql(u8, "abc-version", tree.source[key.start..key.end]));
56 try testing.expectEqual(key.id, .literal);
57 try testing.expectEqualStrings("abc-version", tree.source[key.start..key.end]);
5858
59 const value = entry.value.cast(Node.Value).?;
60 const value_tok = tree.tokens[value.start.?];
61 try testing.expectEqual(value_tok.id, .Literal);
62 try testing.expect(mem.eql(u8, "5", tree.source[value_tok.start..value_tok.end]));
59 const value = entry.value.?.cast(Node.Value).?;
60 const value_tok = tree.tokens[value.base.start];
61 try testing.expectEqual(value_tok.id, .literal);
62 try testing.expectEqualStrings("5", tree.source[value_tok.start..value_tok.end]);
6363 }
6464}
6565
......@@ -77,39 +77,31 @@ test "leaf in quotes" {
7777 try testing.expectEqual(tree.docs.items.len, 1);
7878
7979 const doc = tree.docs.items[0].cast(Node.Doc).?;
80 try testing.expectEqual(doc.start.?, 0);
81 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
80 try testing.expectEqual(doc.base.start, 0);
81 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
8282 try testing.expect(doc.directive == null);
8383
8484 try testing.expect(doc.value != null);
8585 try testing.expectEqual(doc.value.?.tag, .map);
8686
8787 const map = doc.value.?.cast(Node.Map).?;
88 try testing.expectEqual(map.start.?, 0);
89 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
88 try testing.expectEqual(map.base.start, 0);
89 try testing.expectEqual(map.base.end, tree.tokens.len - 2);
9090 try testing.expectEqual(map.values.items.len, 3);
9191
9292 {
9393 const entry = map.values.items[0];
9494
9595 const key = tree.tokens[entry.key];
96 try testing.expectEqual(key.id, .Literal);
97 try testing.expect(mem.eql(
98 u8,
99 "key1",
100 tree.source[key.start..key.end],
101 ));
102
103 const value = entry.value.cast(Node.Value).?;
104 const start = tree.tokens[value.start.?];
105 const end = tree.tokens[value.end.?];
106 try testing.expectEqual(start.id, .Literal);
107 try testing.expectEqual(end.id, .Literal);
108 try testing.expect(mem.eql(
109 u8,
110 "no quotes",
111 tree.source[start.start..end.end],
112 ));
96 try testing.expectEqual(key.id, .literal);
97 try testing.expectEqualStrings("key1", tree.source[key.start..key.end]);
98
99 const value = entry.value.?.cast(Node.Value).?;
100 const start = tree.tokens[value.base.start];
101 const end = tree.tokens[value.base.end];
102 try testing.expectEqual(start.id, .literal);
103 try testing.expectEqual(end.id, .literal);
104 try testing.expectEqualStrings("no quotes", tree.source[start.start..end.end]);
113105 }
114106}
115107
......@@ -128,70 +120,60 @@ test "nested maps" {
128120 try testing.expectEqual(tree.docs.items.len, 1);
129121
130122 const doc = tree.docs.items[0].cast(Node.Doc).?;
131 try testing.expectEqual(doc.start.?, 0);
132 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
123 try testing.expectEqual(doc.base.start, 0);
124 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
133125 try testing.expect(doc.directive == null);
134126
135127 try testing.expect(doc.value != null);
136128 try testing.expectEqual(doc.value.?.tag, .map);
137129
138130 const map = doc.value.?.cast(Node.Map).?;
139 try testing.expectEqual(map.start.?, 0);
140 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
131 try testing.expectEqual(map.base.start, 0);
132 try testing.expectEqual(map.base.end, tree.tokens.len - 2);
141133 try testing.expectEqual(map.values.items.len, 2);
142134
143135 {
144136 const entry = map.values.items[0];
145137
146138 const key = tree.tokens[entry.key];
147 try testing.expectEqual(key.id, .Literal);
148 try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));
139 try testing.expectEqual(key.id, .literal);
140 try testing.expectEqualStrings("key1", tree.source[key.start..key.end]);
149141
150 const nested_map = entry.value.cast(Node.Map).?;
151 try testing.expectEqual(nested_map.start.?, 4);
152 try testing.expectEqual(nested_map.end.?, 16);
142 const nested_map = entry.value.?.cast(Node.Map).?;
143 try testing.expectEqual(nested_map.base.start, 4);
144 try testing.expectEqual(nested_map.base.end, 16);
153145 try testing.expectEqual(nested_map.values.items.len, 2);
154146
155147 {
156148 const nested_entry = nested_map.values.items[0];
157149
158150 const nested_key = tree.tokens[nested_entry.key];
159 try testing.expectEqual(nested_key.id, .Literal);
160 try testing.expect(mem.eql(
161 u8,
162 "key1_1",
163 tree.source[nested_key.start..nested_key.end],
164 ));
165
166 const nested_value = nested_entry.value.cast(Node.Value).?;
167 const nested_value_tok = tree.tokens[nested_value.start.?];
168 try testing.expectEqual(nested_value_tok.id, .Literal);
169 try testing.expect(mem.eql(
170 u8,
151 try testing.expectEqual(nested_key.id, .literal);
152 try testing.expectEqualStrings("key1_1", tree.source[nested_key.start..nested_key.end]);
153
154 const nested_value = nested_entry.value.?.cast(Node.Value).?;
155 const nested_value_tok = tree.tokens[nested_value.base.start];
156 try testing.expectEqual(nested_value_tok.id, .literal);
157 try testing.expectEqualStrings(
171158 "value1_1",
172159 tree.source[nested_value_tok.start..nested_value_tok.end],
173 ));
160 );
174161 }
175162
176163 {
177164 const nested_entry = nested_map.values.items[1];
178165
179166 const nested_key = tree.tokens[nested_entry.key];
180 try testing.expectEqual(nested_key.id, .Literal);
181 try testing.expect(mem.eql(
182 u8,
183 "key1_2",
184 tree.source[nested_key.start..nested_key.end],
185 ));
186
187 const nested_value = nested_entry.value.cast(Node.Value).?;
188 const nested_value_tok = tree.tokens[nested_value.start.?];
189 try testing.expectEqual(nested_value_tok.id, .Literal);
190 try testing.expect(mem.eql(
191 u8,
167 try testing.expectEqual(nested_key.id, .literal);
168 try testing.expectEqualStrings("key1_2", tree.source[nested_key.start..nested_key.end]);
169
170 const nested_value = nested_entry.value.?.cast(Node.Value).?;
171 const nested_value_tok = tree.tokens[nested_value.base.start];
172 try testing.expectEqual(nested_value_tok.id, .literal);
173 try testing.expectEqualStrings(
192174 "value1_2",
193175 tree.source[nested_value_tok.start..nested_value_tok.end],
194 ));
176 );
195177 }
196178 }
197179
......@@ -199,17 +181,13 @@ test "nested maps" {
199181 const entry = map.values.items[1];
200182
201183 const key = tree.tokens[entry.key];
202 try testing.expectEqual(key.id, .Literal);
203 try testing.expect(mem.eql(u8, "key2", tree.source[key.start..key.end]));
204
205 const value = entry.value.cast(Node.Value).?;
206 const value_tok = tree.tokens[value.start.?];
207 try testing.expectEqual(value_tok.id, .Literal);
208 try testing.expect(mem.eql(
209 u8,
210 "value2",
211 tree.source[value_tok.start..value_tok.end],
212 ));
184 try testing.expectEqual(key.id, .literal);
185 try testing.expectEqualStrings("key2", tree.source[key.start..key.end]);
186
187 const value = entry.value.?.cast(Node.Value).?;
188 const value_tok = tree.tokens[value.base.start];
189 try testing.expectEqual(value_tok.id, .literal);
190 try testing.expectEqualStrings("value2", tree.source[value_tok.start..value_tok.end]);
213191 }
214192}
215193
......@@ -227,46 +205,46 @@ test "map of list of values" {
227205 try testing.expectEqual(tree.docs.items.len, 1);
228206
229207 const doc = tree.docs.items[0].cast(Node.Doc).?;
230 try testing.expectEqual(doc.start.?, 0);
231 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
208 try testing.expectEqual(doc.base.start, 0);
209 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
232210
233211 try testing.expect(doc.value != null);
234212 try testing.expectEqual(doc.value.?.tag, .map);
235213
236214 const map = doc.value.?.cast(Node.Map).?;
237 try testing.expectEqual(map.start.?, 0);
238 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
215 try testing.expectEqual(map.base.start, 0);
216 try testing.expectEqual(map.base.end, tree.tokens.len - 2);
239217 try testing.expectEqual(map.values.items.len, 1);
240218
241219 const entry = map.values.items[0];
242220 const key = tree.tokens[entry.key];
243 try testing.expectEqual(key.id, .Literal);
244 try testing.expect(mem.eql(u8, "ints", tree.source[key.start..key.end]));
221 try testing.expectEqual(key.id, .literal);
222 try testing.expectEqualStrings("ints", tree.source[key.start..key.end]);
245223
246 const value = entry.value.cast(Node.List).?;
247 try testing.expectEqual(value.start.?, 4);
248 try testing.expectEqual(value.end.?, tree.tokens.len - 2);
224 const value = entry.value.?.cast(Node.List).?;
225 try testing.expectEqual(value.base.start, 4);
226 try testing.expectEqual(value.base.end, tree.tokens.len - 2);
249227 try testing.expectEqual(value.values.items.len, 3);
250228
251229 {
252230 const elem = value.values.items[0].cast(Node.Value).?;
253 const leaf = tree.tokens[elem.start.?];
254 try testing.expectEqual(leaf.id, .Literal);
255 try testing.expect(mem.eql(u8, "0", tree.source[leaf.start..leaf.end]));
231 const leaf = tree.tokens[elem.base.start];
232 try testing.expectEqual(leaf.id, .literal);
233 try testing.expectEqualStrings("0", tree.source[leaf.start..leaf.end]);
256234 }
257235
258236 {
259237 const elem = value.values.items[1].cast(Node.Value).?;
260 const leaf = tree.tokens[elem.start.?];
261 try testing.expectEqual(leaf.id, .Literal);
262 try testing.expect(mem.eql(u8, "1", tree.source[leaf.start..leaf.end]));
238 const leaf = tree.tokens[elem.base.start];
239 try testing.expectEqual(leaf.id, .literal);
240 try testing.expectEqualStrings("1", tree.source[leaf.start..leaf.end]);
263241 }
264242
265243 {
266244 const elem = value.values.items[2].cast(Node.Value).?;
267 const leaf = tree.tokens[elem.start.?];
268 try testing.expectEqual(leaf.id, .Literal);
269 try testing.expect(mem.eql(u8, "2", tree.source[leaf.start..leaf.end]));
245 const leaf = tree.tokens[elem.base.start];
246 try testing.expectEqual(leaf.id, .literal);
247 try testing.expectEqualStrings("2", tree.source[leaf.start..leaf.end]);
270248 }
271249}
272250
......@@ -285,64 +263,64 @@ test "map of list of maps" {
285263 try testing.expectEqual(tree.docs.items.len, 1);
286264
287265 const doc = tree.docs.items[0].cast(Node.Doc).?;
288 try testing.expectEqual(doc.start.?, 0);
289 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
266 try testing.expectEqual(doc.base.start, 0);
267 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
290268
291269 try testing.expect(doc.value != null);
292270 try testing.expectEqual(doc.value.?.tag, .map);
293271
294272 const map = doc.value.?.cast(Node.Map).?;
295 try testing.expectEqual(map.start.?, 0);
296 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
273 try testing.expectEqual(map.base.start, 0);
274 try testing.expectEqual(map.base.end, tree.tokens.len - 2);
297275 try testing.expectEqual(map.values.items.len, 1);
298276
299277 const entry = map.values.items[0];
300278 const key = tree.tokens[entry.key];
301 try testing.expectEqual(key.id, .Literal);
302 try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));
279 try testing.expectEqual(key.id, .literal);
280 try testing.expectEqualStrings("key1", tree.source[key.start..key.end]);
303281
304 const value = entry.value.cast(Node.List).?;
305 try testing.expectEqual(value.start.?, 3);
306 try testing.expectEqual(value.end.?, tree.tokens.len - 2);
282 const value = entry.value.?.cast(Node.List).?;
283 try testing.expectEqual(value.base.start, 3);
284 try testing.expectEqual(value.base.end, tree.tokens.len - 2);
307285 try testing.expectEqual(value.values.items.len, 3);
308286
309287 {
310288 const elem = value.values.items[0].cast(Node.Map).?;
311289 const nested = elem.values.items[0];
312290 const nested_key = tree.tokens[nested.key];
313 try testing.expectEqual(nested_key.id, .Literal);
314 try testing.expect(mem.eql(u8, "key2", tree.source[nested_key.start..nested_key.end]));
291 try testing.expectEqual(nested_key.id, .literal);
292 try testing.expectEqualStrings("key2", tree.source[nested_key.start..nested_key.end]);
315293
316 const nested_v = nested.value.cast(Node.Value).?;
317 const leaf = tree.tokens[nested_v.start.?];
318 try testing.expectEqual(leaf.id, .Literal);
319 try testing.expect(mem.eql(u8, "value2", tree.source[leaf.start..leaf.end]));
294 const nested_v = nested.value.?.cast(Node.Value).?;
295 const leaf = tree.tokens[nested_v.base.start];
296 try testing.expectEqual(leaf.id, .literal);
297 try testing.expectEqualStrings("value2", tree.source[leaf.start..leaf.end]);
320298 }
321299
322300 {
323301 const elem = value.values.items[1].cast(Node.Map).?;
324302 const nested = elem.values.items[0];
325303 const nested_key = tree.tokens[nested.key];
326 try testing.expectEqual(nested_key.id, .Literal);
327 try testing.expect(mem.eql(u8, "key3", tree.source[nested_key.start..nested_key.end]));
304 try testing.expectEqual(nested_key.id, .literal);
305 try testing.expectEqualStrings("key3", tree.source[nested_key.start..nested_key.end]);
328306
329 const nested_v = nested.value.cast(Node.Value).?;
330 const leaf = tree.tokens[nested_v.start.?];
331 try testing.expectEqual(leaf.id, .Literal);
332 try testing.expect(mem.eql(u8, "value3", tree.source[leaf.start..leaf.end]));
307 const nested_v = nested.value.?.cast(Node.Value).?;
308 const leaf = tree.tokens[nested_v.base.start];
309 try testing.expectEqual(leaf.id, .literal);
310 try testing.expectEqualStrings("value3", tree.source[leaf.start..leaf.end]);
333311 }
334312
335313 {
336314 const elem = value.values.items[2].cast(Node.Map).?;
337315 const nested = elem.values.items[0];
338316 const nested_key = tree.tokens[nested.key];
339 try testing.expectEqual(nested_key.id, .Literal);
340 try testing.expect(mem.eql(u8, "key4", tree.source[nested_key.start..nested_key.end]));
317 try testing.expectEqual(nested_key.id, .literal);
318 try testing.expectEqualStrings("key4", tree.source[nested_key.start..nested_key.end]);
341319
342 const nested_v = nested.value.cast(Node.Value).?;
343 const leaf = tree.tokens[nested_v.start.?];
344 try testing.expectEqual(leaf.id, .Literal);
345 try testing.expect(mem.eql(u8, "value4", tree.source[leaf.start..leaf.end]));
320 const nested_v = nested.value.?.cast(Node.Value).?;
321 const leaf = tree.tokens[nested_v.base.start];
322 try testing.expectEqual(leaf.id, .literal);
323 try testing.expectEqualStrings("value4", tree.source[leaf.start..leaf.end]);
346324 }
347325}
348326
......@@ -360,15 +338,15 @@ test "list of lists" {
360338 try testing.expectEqual(tree.docs.items.len, 1);
361339
362340 const doc = tree.docs.items[0].cast(Node.Doc).?;
363 try testing.expectEqual(doc.start.?, 0);
364 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
341 try testing.expectEqual(doc.base.start, 0);
342 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
365343
366344 try testing.expect(doc.value != null);
367345 try testing.expectEqual(doc.value.?.tag, .list);
368346
369347 const list = doc.value.?.cast(Node.List).?;
370 try testing.expectEqual(list.start.?, 0);
371 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
348 try testing.expectEqual(list.base.start, 0);
349 try testing.expectEqual(list.base.end, tree.tokens.len - 2);
372350 try testing.expectEqual(list.values.items.len, 3);
373351
374352 {
......@@ -379,22 +357,22 @@ test "list of lists" {
379357 {
380358 try testing.expectEqual(nested.values.items[0].tag, .value);
381359 const value = nested.values.items[0].cast(Node.Value).?;
382 const leaf = tree.tokens[value.start.?];
383 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
360 const leaf = tree.tokens[value.base.start];
361 try testing.expectEqualStrings("name", tree.source[leaf.start..leaf.end]);
384362 }
385363
386364 {
387365 try testing.expectEqual(nested.values.items[1].tag, .value);
388366 const value = nested.values.items[1].cast(Node.Value).?;
389 const leaf = tree.tokens[value.start.?];
390 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
367 const leaf = tree.tokens[value.base.start];
368 try testing.expectEqualStrings("hr", tree.source[leaf.start..leaf.end]);
391369 }
392370
393371 {
394372 try testing.expectEqual(nested.values.items[2].tag, .value);
395373 const value = nested.values.items[2].cast(Node.Value).?;
396 const leaf = tree.tokens[value.start.?];
397 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
374 const leaf = tree.tokens[value.base.start];
375 try testing.expectEqualStrings("avg", tree.source[leaf.start..leaf.end]);
398376 }
399377 }
400378
......@@ -406,23 +384,23 @@ test "list of lists" {
406384 {
407385 try testing.expectEqual(nested.values.items[0].tag, .value);
408386 const value = nested.values.items[0].cast(Node.Value).?;
409 const start = tree.tokens[value.start.?];
410 const end = tree.tokens[value.end.?];
411 try testing.expect(mem.eql(u8, "Mark McGwire", tree.source[start.start..end.end]));
387 const start = tree.tokens[value.base.start];
388 const end = tree.tokens[value.base.end];
389 try testing.expectEqualStrings("Mark McGwire", tree.source[start.start..end.end]);
412390 }
413391
414392 {
415393 try testing.expectEqual(nested.values.items[1].tag, .value);
416394 const value = nested.values.items[1].cast(Node.Value).?;
417 const leaf = tree.tokens[value.start.?];
418 try testing.expect(mem.eql(u8, "65", tree.source[leaf.start..leaf.end]));
395 const leaf = tree.tokens[value.base.start];
396 try testing.expectEqualStrings("65", tree.source[leaf.start..leaf.end]);
419397 }
420398
421399 {
422400 try testing.expectEqual(nested.values.items[2].tag, .value);
423401 const value = nested.values.items[2].cast(Node.Value).?;
424 const leaf = tree.tokens[value.start.?];
425 try testing.expect(mem.eql(u8, "0.278", tree.source[leaf.start..leaf.end]));
402 const leaf = tree.tokens[value.base.start];
403 try testing.expectEqualStrings("0.278", tree.source[leaf.start..leaf.end]);
426404 }
427405 }
428406
......@@ -434,23 +412,23 @@ test "list of lists" {
434412 {
435413 try testing.expectEqual(nested.values.items[0].tag, .value);
436414 const value = nested.values.items[0].cast(Node.Value).?;
437 const start = tree.tokens[value.start.?];
438 const end = tree.tokens[value.end.?];
439 try testing.expect(mem.eql(u8, "Sammy Sosa", tree.source[start.start..end.end]));
415 const start = tree.tokens[value.base.start];
416 const end = tree.tokens[value.base.end];
417 try testing.expectEqualStrings("Sammy Sosa", tree.source[start.start..end.end]);
440418 }
441419
442420 {
443421 try testing.expectEqual(nested.values.items[1].tag, .value);
444422 const value = nested.values.items[1].cast(Node.Value).?;
445 const leaf = tree.tokens[value.start.?];
446 try testing.expect(mem.eql(u8, "63", tree.source[leaf.start..leaf.end]));
423 const leaf = tree.tokens[value.base.start];
424 try testing.expectEqualStrings("63", tree.source[leaf.start..leaf.end]);
447425 }
448426
449427 {
450428 try testing.expectEqual(nested.values.items[2].tag, .value);
451429 const value = nested.values.items[2].cast(Node.Value).?;
452 const leaf = tree.tokens[value.start.?];
453 try testing.expect(mem.eql(u8, "0.288", tree.source[leaf.start..leaf.end]));
430 const leaf = tree.tokens[value.base.start];
431 try testing.expectEqualStrings("0.288", tree.source[leaf.start..leaf.end]);
454432 }
455433 }
456434}
......@@ -467,36 +445,36 @@ test "inline list" {
467445 try testing.expectEqual(tree.docs.items.len, 1);
468446
469447 const doc = tree.docs.items[0].cast(Node.Doc).?;
470 try testing.expectEqual(doc.start.?, 0);
471 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
448 try testing.expectEqual(doc.base.start, 0);
449 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
472450
473451 try testing.expect(doc.value != null);
474452 try testing.expectEqual(doc.value.?.tag, .list);
475453
476454 const list = doc.value.?.cast(Node.List).?;
477 try testing.expectEqual(list.start.?, 0);
478 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
455 try testing.expectEqual(list.base.start, 0);
456 try testing.expectEqual(list.base.end, tree.tokens.len - 2);
479457 try testing.expectEqual(list.values.items.len, 3);
480458
481459 {
482460 try testing.expectEqual(list.values.items[0].tag, .value);
483461 const value = list.values.items[0].cast(Node.Value).?;
484 const leaf = tree.tokens[value.start.?];
485 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
462 const leaf = tree.tokens[value.base.start];
463 try testing.expectEqualStrings("name", tree.source[leaf.start..leaf.end]);
486464 }
487465
488466 {
489467 try testing.expectEqual(list.values.items[1].tag, .value);
490468 const value = list.values.items[1].cast(Node.Value).?;
491 const leaf = tree.tokens[value.start.?];
492 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
469 const leaf = tree.tokens[value.base.start];
470 try testing.expectEqualStrings("hr", tree.source[leaf.start..leaf.end]);
493471 }
494472
495473 {
496474 try testing.expectEqual(list.values.items[2].tag, .value);
497475 const value = list.values.items[2].cast(Node.Value).?;
498 const leaf = tree.tokens[value.start.?];
499 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
476 const leaf = tree.tokens[value.base.start];
477 try testing.expectEqualStrings("avg", tree.source[leaf.start..leaf.end]);
500478 }
501479}
502480
......@@ -514,45 +492,273 @@ test "inline list as mapping value" {
514492 try testing.expectEqual(tree.docs.items.len, 1);
515493
516494 const doc = tree.docs.items[0].cast(Node.Doc).?;
517 try testing.expectEqual(doc.start.?, 0);
518 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
495 try testing.expectEqual(doc.base.start, 0);
496 try testing.expectEqual(doc.base.end, tree.tokens.len - 2);
519497
520498 try testing.expect(doc.value != null);
521499 try testing.expectEqual(doc.value.?.tag, .map);
522500
523501 const map = doc.value.?.cast(Node.Map).?;
524 try testing.expectEqual(map.start.?, 0);
525 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
502 try testing.expectEqual(map.base.start, 0);
503 try testing.expectEqual(map.base.end, tree.tokens.len - 2);
526504 try testing.expectEqual(map.values.items.len, 1);
527505
528506 const entry = map.values.items[0];
529507 const key = tree.tokens[entry.key];
530 try testing.expectEqual(key.id, .Literal);
531 try testing.expect(mem.eql(u8, "key", tree.source[key.start..key.end]));
508 try testing.expectEqual(key.id, .literal);
509 try testing.expectEqualStrings("key", tree.source[key.start..key.end]);
532510
533 const list = entry.value.cast(Node.List).?;
534 try testing.expectEqual(list.start.?, 4);
535 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
511 const list = entry.value.?.cast(Node.List).?;
512 try testing.expectEqual(list.base.start, 4);
513 try testing.expectEqual(list.base.end, tree.tokens.len - 2);
536514 try testing.expectEqual(list.values.items.len, 3);
537515
538516 {
539517 try testing.expectEqual(list.values.items[0].tag, .value);
540518 const value = list.values.items[0].cast(Node.Value).?;
541 const leaf = tree.tokens[value.start.?];
542 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
519 const leaf = tree.tokens[value.base.start];
520 try testing.expectEqualStrings("name", tree.source[leaf.start..leaf.end]);
543521 }
544522
545523 {
546524 try testing.expectEqual(list.values.items[1].tag, .value);
547525 const value = list.values.items[1].cast(Node.Value).?;
548 const leaf = tree.tokens[value.start.?];
549 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
526 const leaf = tree.tokens[value.base.start];
527 try testing.expectEqualStrings("hr", tree.source[leaf.start..leaf.end]);
550528 }
551529
552530 {
553531 try testing.expectEqual(list.values.items[2].tag, .value);
554532 const value = list.values.items[2].cast(Node.Value).?;
555 const leaf = tree.tokens[value.start.?];
556 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
533 const leaf = tree.tokens[value.base.start];
534 try testing.expectEqualStrings("avg", tree.source[leaf.start..leaf.end]);
557535 }
558536}
537
538fn parseSuccess(comptime source: []const u8) !void {
539 var tree = Tree.init(testing.allocator);
540 defer tree.deinit();
541 try tree.parse(source);
542}
543
544fn parseError(comptime source: []const u8, err: parse.ParseError) !void {
545 var tree = Tree.init(testing.allocator);
546 defer tree.deinit();
547 try testing.expectError(err, tree.parse(source));
548}
549
550test "empty doc with spaces and comments" {
551 try parseSuccess(
552 \\
553 \\
554 \\ # this is a comment in a weird place
555 \\# and this one is too
556 );
557}
558
559test "comment between --- and ! in document start" {
560 try parseError(
561 \\--- # what is it?
562 \\!
563 , error.UnexpectedToken);
564}
565
566test "correct doc start with tag" {
567 try parseSuccess(
568 \\--- !some-tag
569 \\
570 );
571}
572
573test "doc close without explicit doc open" {
574 try parseError(
575 \\
576 \\
577 \\# something cool
578 \\...
579 , error.UnexpectedToken);
580}
581
582test "doc open and close are ok" {
583 try parseSuccess(
584 \\---
585 \\# first doc
586 \\
587 \\
588 \\---
589 \\# second doc
590 \\
591 \\
592 \\...
593 );
594}
595
596test "doc with a single string is ok" {
597 try parseSuccess(
598 \\a string of some sort
599 \\
600 );
601}
602
603test "explicit doc with a single string is ok" {
604 try parseSuccess(
605 \\--- !anchor
606 \\# nothing to see here except one string
607 \\ # not a lot to go on with
608 \\a single string
609 \\...
610 );
611}
612
613test "doc with two string is bad" {
614 try parseError(
615 \\first
616 \\second
617 \\# this should fail already
618 , error.UnexpectedToken);
619}
620
621test "single quote string can have new lines" {
622 try parseSuccess(
623 \\'what is this
624 \\ thing?'
625 );
626}
627
628test "single quote string on one line is fine" {
629 try parseSuccess(
630 \\'here''s an apostrophe'
631 );
632}
633
634test "double quote string can have new lines" {
635 try parseSuccess(
636 \\"what is this
637 \\ thing?"
638 );
639}
640
641test "double quote string on one line is fine" {
642 try parseSuccess(
643 \\"a newline\nand a\ttab"
644 );
645}
646
647test "map with key and value literals" {
648 try parseSuccess(
649 \\key1: val1
650 \\key2 : val2
651 );
652}
653
654test "map of maps" {
655 try parseSuccess(
656 \\
657 \\# the first key
658 \\key1:
659 \\ # the first subkey
660 \\ key1_1: 0
661 \\ key1_2: 1
662 \\# the second key
663 \\key2:
664 \\ key2_1: -1
665 \\ key2_2: -2
666 \\# the end of map
667 );
668}
669
670test "map value indicator needs to be on the same line" {
671 try parseError(
672 \\a
673 \\ : b
674 , error.UnexpectedToken);
675}
676
677test "value needs to be indented" {
678 try parseError(
679 \\a:
680 \\b
681 , error.MalformedYaml);
682}
683
684test "comment between a key and a value is fine" {
685 try parseSuccess(
686 \\a:
687 \\ # this is a value
688 \\ b
689 );
690}
691
692test "simple list" {
693 try parseSuccess(
694 \\# first el
695 \\- a
696 \\# second el
697 \\- b
698 \\# third el
699 \\- c
700 );
701}
702
703test "list indentation matters" {
704 try parseSuccess(
705 \\ - a
706 \\- b
707 );
708
709 try parseSuccess(
710 \\- a
711 \\ - b
712 );
713}
714
715test "unindented list is fine too" {
716 try parseSuccess(
717 \\a:
718 \\- 0
719 \\- 1
720 );
721}
722
723test "empty values in a map" {
724 try parseSuccess(
725 \\a:
726 \\b:
727 \\- 0
728 );
729}
730
731test "weirdly nested map of maps of lists" {
732 try parseSuccess(
733 \\a:
734 \\ b:
735 \\ - 0
736 \\ - 1
737 );
738}
739
740test "square brackets denote a list" {
741 try parseSuccess(
742 \\[ a,
743 \\ b, c ]
744 );
745}
746
747test "empty list" {
748 try parseSuccess(
749 \\[ ]
750 );
751}
752
753test "comment within a bracketed list is an error" {
754 try parseError(
755 \\[ # something
756 \\]
757 , error.MalformedYaml);
758}
759
760test "mixed ints with floats in a list" {
761 try parseSuccess(
762 \\[0, 1.0]
763 );
764}
src/link/tapi/yaml.zig+176-396
......@@ -2,8 +2,7 @@ const std = @import("std");
22const assert = std.debug.assert;
33const math = std.math;
44const mem = std.mem;
5const testing = std.testing;
6const log = std.log.scoped(.tapi);
5const log = std.log.scoped(.yaml);
76
87const Allocator = mem.Allocator;
98const ArenaAllocator = std.heap.ArenaAllocator;
......@@ -17,22 +16,15 @@ const ParseError = parse.ParseError;
1716
1817pub const YamlError = error{
1918 UnexpectedNodeType,
19 DuplicateMapKey,
2020 OutOfMemory,
21 CannotEncodeValue,
2122} || ParseError || std.fmt.ParseIntError;
2223
23pub const ValueType = enum {
24 empty,
25 int,
26 float,
27 string,
28 list,
29 map,
30};
31
3224pub const List = []Value;
33pub const Map = std.StringArrayHashMap(Value);
25pub const Map = std.StringHashMap(Value);
3426
35pub const Value = union(ValueType) {
27pub const Value = union(enum) {
3628 empty,
3729 int: i64,
3830 float: f64,
......@@ -70,9 +62,7 @@ pub const Value = union(ValueType) {
7062 should_inline_first_key: bool = false,
7163 };
7264
73 pub const StringifyError = std.os.WriteError;
74
75 pub fn stringify(self: Value, writer: anytype, args: StringifyArgs) StringifyError!void {
65 pub fn stringify(self: Value, writer: anytype, args: StringifyArgs) anyerror!void {
7666 switch (self) {
7767 .empty => return,
7868 .int => |int| return writer.print("{}", .{int}),
......@@ -83,7 +73,7 @@ pub const Value = union(ValueType) {
8373 if (len == 0) return;
8474
8575 const first = list[0];
86 if (first.is_compound()) {
76 if (first.isCompound()) {
8777 for (list, 0..) |elem, i| {
8878 try writer.writeByteNTimes(' ', args.indentation);
8979 try writer.writeAll("- ");
......@@ -108,20 +98,23 @@ pub const Value = union(ValueType) {
10898 try writer.writeAll(" ]");
10999 },
110100 .map => |map| {
111 const keys = map.keys();
112 const len = keys.len;
101 const len = map.count();
113102 if (len == 0) return;
114103
115 for (keys, 0..) |key, i| {
104 var i: usize = 0;
105 var it = map.iterator();
106 while (it.next()) |entry| {
107 const key = entry.key_ptr.*;
108 const value = entry.value_ptr.*;
109
116110 if (!args.should_inline_first_key or i != 0) {
117111 try writer.writeByteNTimes(' ', args.indentation);
118112 }
119113 try writer.print("{s}: ", .{key});
120114
121 const value = map.get(key) orelse unreachable;
122115 const should_inline = blk: {
123 if (!value.is_compound()) break :blk true;
124 if (value == .list and value.list.len > 0 and !value.list[0].is_compound()) break :blk true;
116 if (!value.isCompound()) break :blk true;
117 if (value == .list and value.list.len > 0 and !value.list[0].isCompound()) break :blk true;
125118 break :blk false;
126119 };
127120
......@@ -137,35 +130,44 @@ pub const Value = union(ValueType) {
137130 if (i < len - 1) {
138131 try writer.writeByte('\n');
139132 }
133
134 i += 1;
140135 }
141136 },
142137 }
143138 }
144139
145 fn is_compound(self: Value) bool {
140 fn isCompound(self: Value) bool {
146141 return switch (self) {
147142 .list, .map => true,
148143 else => false,
149144 };
150145 }
151146
152 fn fromNode(arena: Allocator, tree: *const Tree, node: *const Node, type_hint: ?ValueType) YamlError!Value {
147 fn fromNode(arena: Allocator, tree: *const Tree, node: *const Node) YamlError!Value {
153148 if (node.cast(Node.Doc)) |doc| {
154149 const inner = doc.value orelse {
155150 // empty doc
156151 return Value{ .empty = {} };
157152 };
158 return Value.fromNode(arena, tree, inner, null);
153 return Value.fromNode(arena, tree, inner);
159154 } else if (node.cast(Node.Map)) |map| {
160 var out_map = std.StringArrayHashMap(Value).init(arena);
161 try out_map.ensureUnusedCapacity(map.values.items.len);
155 // TODO use ContextAdapted HashMap and do not duplicate keys, intern
156 // in a contiguous string buffer.
157 var out_map = std.StringHashMap(Value).init(arena);
158 try out_map.ensureUnusedCapacity(math.cast(u32, map.values.items.len) orelse return error.Overflow);
162159
163160 for (map.values.items) |entry| {
164 const key_tok = tree.tokens[entry.key];
165 const key = try arena.dupe(u8, tree.source[key_tok.start..key_tok.end]);
166 const value = try Value.fromNode(arena, tree, entry.value, null);
167
168 out_map.putAssumeCapacityNoClobber(key, value);
161 const key = try arena.dupe(u8, tree.getRaw(entry.key, entry.key));
162 const gop = out_map.getOrPutAssumeCapacity(key);
163 if (gop.found_existing) {
164 return error.DuplicateMapKey;
165 }
166 const value = if (entry.value) |value|
167 try Value.fromNode(arena, tree, value)
168 else
169 .empty;
170 gop.value_ptr.* = value;
169171 }
170172
171173 return Value{ .map = out_map };
......@@ -173,56 +175,124 @@ pub const Value = union(ValueType) {
173175 var out_list = std.ArrayList(Value).init(arena);
174176 try out_list.ensureUnusedCapacity(list.values.items.len);
175177
176 if (list.values.items.len > 0) {
177 const hint = if (list.values.items[0].cast(Node.Value)) |value| hint: {
178 const start = tree.tokens[value.start.?];
179 const end = tree.tokens[value.end.?];
180 const raw = tree.source[start.start..end.end];
181 _ = std.fmt.parseInt(i64, raw, 10) catch {
182 _ = std.fmt.parseFloat(f64, raw) catch {
183 break :hint ValueType.string;
184 };
185 break :hint ValueType.float;
186 };
187 break :hint ValueType.int;
188 } else null;
189
190 for (list.values.items) |elem| {
191 const value = try Value.fromNode(arena, tree, elem, hint);
192 out_list.appendAssumeCapacity(value);
193 }
178 for (list.values.items) |elem| {
179 const value = try Value.fromNode(arena, tree, elem);
180 out_list.appendAssumeCapacity(value);
194181 }
195182
196183 return Value{ .list = try out_list.toOwnedSlice() };
197184 } else if (node.cast(Node.Value)) |value| {
198 const start = tree.tokens[value.start.?];
199 const end = tree.tokens[value.end.?];
200 const raw = tree.source[start.start..end.end];
201
202 if (type_hint) |hint| {
203 return switch (hint) {
204 .int => Value{ .int = try std.fmt.parseInt(i64, raw, 10) },
205 .float => Value{ .float = try std.fmt.parseFloat(f64, raw) },
206 .string => Value{ .string = try arena.dupe(u8, raw) },
207 else => unreachable,
208 };
209 }
185 const raw = tree.getRaw(node.start, node.end);
210186
211187 try_int: {
212188 // TODO infer base for int
213189 const int = std.fmt.parseInt(i64, raw, 10) catch break :try_int;
214190 return Value{ .int = int };
215191 }
192
216193 try_float: {
217194 const float = std.fmt.parseFloat(f64, raw) catch break :try_float;
218195 return Value{ .float = float };
219196 }
220 return Value{ .string = try arena.dupe(u8, raw) };
197
198 return Value{ .string = try arena.dupe(u8, value.string_value.items) };
221199 } else {
222200 log.err("Unexpected node type: {}", .{node.tag});
223201 return error.UnexpectedNodeType;
224202 }
225203 }
204
205 fn encode(arena: Allocator, input: anytype) YamlError!?Value {
206 switch (@typeInfo(@TypeOf(input))) {
207 .ComptimeInt,
208 .Int,
209 => return Value{ .int = math.cast(i64, input) orelse return error.Overflow },
210
211 .Float => return Value{ .float = math.lossyCast(f64, input) },
212
213 .Struct => |info| if (info.is_tuple) {
214 var list = std.ArrayList(Value).init(arena);
215 errdefer list.deinit();
216 try list.ensureTotalCapacityPrecise(info.fields.len);
217
218 inline for (info.fields) |field| {
219 if (try encode(arena, @field(input, field.name))) |value| {
220 list.appendAssumeCapacity(value);
221 }
222 }
223
224 return Value{ .list = try list.toOwnedSlice() };
225 } else {
226 var map = Map.init(arena);
227 errdefer map.deinit();
228 try map.ensureTotalCapacity(info.fields.len);
229
230 inline for (info.fields) |field| {
231 if (try encode(arena, @field(input, field.name))) |value| {
232 const key = try arena.dupe(u8, field.name);
233 map.putAssumeCapacityNoClobber(key, value);
234 }
235 }
236
237 return Value{ .map = map };
238 },
239
240 .Union => |info| if (info.tag_type) |tag_type| {
241 inline for (info.fields) |field| {
242 if (@field(tag_type, field.name) == input) {
243 return try encode(arena, @field(input, field.name));
244 }
245 } else unreachable;
246 } else return error.UntaggedUnion,
247
248 .Array => return encode(arena, &input),
249
250 .Pointer => |info| switch (info.size) {
251 .One => switch (@typeInfo(info.child)) {
252 .Array => |child_info| {
253 const Slice = []const child_info.child;
254 return encode(arena, @as(Slice, input));
255 },
256 else => {
257 @compileError("Unhandled type: {s}" ++ @typeName(info.child));
258 },
259 },
260 .Slice => {
261 if (info.child == u8) {
262 return Value{ .string = try arena.dupe(u8, input) };
263 }
264
265 var list = std.ArrayList(Value).init(arena);
266 errdefer list.deinit();
267 try list.ensureTotalCapacityPrecise(input.len);
268
269 for (input) |elem| {
270 if (try encode(arena, elem)) |value| {
271 list.appendAssumeCapacity(value);
272 } else {
273 log.err("Could not encode value in a list: {any}", .{elem});
274 return error.CannotEncodeValue;
275 }
276 }
277
278 return Value{ .list = try list.toOwnedSlice() };
279 },
280 else => {
281 @compileError("Unhandled type: {s}" ++ @typeName(@TypeOf(input)));
282 },
283 },
284
285 // TODO we should probably have an option to encode `null` and also
286 // allow for some default value too.
287 .Optional => return if (input) |val| encode(arena, val) else null,
288
289 .Null => return null,
290
291 else => {
292 @compileError("Unhandled type: {s}" ++ @typeName(@TypeOf(input)));
293 },
294 }
295 }
226296};
227297
228298pub const Yaml = struct {
......@@ -234,30 +304,18 @@ pub const Yaml = struct {
234304 self.arena.deinit();
235305 }
236306
237 pub fn stringify(self: Yaml, writer: anytype) !void {
238 for (self.docs.items) |doc| {
239 // if (doc.directive) |directive| {
240 // try writer.print("--- !{s}\n", .{directive});
241 // }
242 try doc.stringify(writer, .{});
243 // if (doc.directive != null) {
244 // try writer.writeAll("...\n");
245 // }
246 }
247 }
248
249307 pub fn load(allocator: Allocator, source: []const u8) !Yaml {
250308 var arena = ArenaAllocator.init(allocator);
251 const arena_allocator = arena.allocator();
309 errdefer arena.deinit();
252310
253 var tree = Tree.init(arena_allocator);
311 var tree = Tree.init(arena.allocator());
254312 try tree.parse(source);
255313
256 var docs = std.ArrayList(Value).init(arena_allocator);
257 try docs.ensureUnusedCapacity(tree.docs.items.len);
314 var docs = std.ArrayList(Value).init(arena.allocator());
315 try docs.ensureTotalCapacityPrecise(tree.docs.items.len);
258316
259317 for (tree.docs.items) |node| {
260 const value = try Value.fromNode(arena_allocator, &tree, node, null);
318 const value = try Value.fromNode(arena.allocator(), &tree, node);
261319 docs.appendAssumeCapacity(value);
262320 }
263321
......@@ -316,17 +374,19 @@ pub const Yaml = struct {
316374
317375 fn parseValue(self: *Yaml, comptime T: type, value: Value) Error!T {
318376 return switch (@typeInfo(T)) {
319 .Int => math.cast(T, try value.asInt()) orelse error.Overflow,
320 .Float => math.lossyCast(T, try value.asFloat()),
377 .Int => math.cast(T, try value.asInt()) orelse return error.Overflow,
378 .Float => if (value.asFloat()) |float| {
379 return math.lossyCast(T, float);
380 } else |_| {
381 return math.lossyCast(T, try value.asInt());
382 },
321383 .Struct => self.parseStruct(T, try value.asMap()),
322384 .Union => self.parseUnion(T, value),
323385 .Array => self.parseArray(T, try value.asList()),
324 .Pointer => {
325 if (value.asList()) |list| {
326 return self.parsePointer(T, .{ .list = list });
327 } else |_| {
328 return self.parsePointer(T, .{ .string = try value.asString() });
329 }
386 .Pointer => if (value.asList()) |list| {
387 return self.parsePointer(T, .{ .list = list });
388 } else |_| {
389 return self.parsePointer(T, .{ .string = try value.asString() });
330390 },
331391 .Void => error.TypeMismatch,
332392 .Optional => unreachable,
......@@ -372,7 +432,7 @@ pub const Yaml = struct {
372432 }
373433
374434 const unwrapped = value orelse {
375 log.debug("missing struct field: {s}: {s}", .{ field.name, @typeName(field.type) });
435 log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.type) });
376436 return error.StructFieldMissing;
377437 };
378438 @field(parsed, field.name) = try self.parseValue(field.type, unwrapped);
......@@ -387,8 +447,7 @@ pub const Yaml = struct {
387447
388448 switch (ptr_info.size) {
389449 .Slice => {
390 const child_info = @typeInfo(ptr_info.child);
391 if (child_info == .Int and child_info.Int.bits == 8) {
450 if (ptr_info.child == u8) {
392451 return value.asString();
393452 }
394453
......@@ -413,315 +472,36 @@ pub const Yaml = struct {
413472
414473 return parsed;
415474 }
416};
417
418test {
419 testing.refAllDecls(@This());
420}
421
422test "simple list" {
423 const source =
424 \\- a
425 \\- b
426 \\- c
427 ;
428
429 var yaml = try Yaml.load(testing.allocator, source);
430 defer yaml.deinit();
431475
432 try testing.expectEqual(yaml.docs.items.len, 1);
433
434 const list = yaml.docs.items[0].list;
435 try testing.expectEqual(list.len, 3);
436
437 try testing.expect(mem.eql(u8, list[0].string, "a"));
438 try testing.expect(mem.eql(u8, list[1].string, "b"));
439 try testing.expect(mem.eql(u8, list[2].string, "c"));
440}
441
442test "simple list typed as array of strings" {
443 const source =
444 \\- a
445 \\- b
446 \\- c
447 ;
448
449 var yaml = try Yaml.load(testing.allocator, source);
450 defer yaml.deinit();
451
452 try testing.expectEqual(yaml.docs.items.len, 1);
453
454 const arr = try yaml.parse([3][]const u8);
455 try testing.expectEqual(arr.len, 3);
456 try testing.expect(mem.eql(u8, arr[0], "a"));
457 try testing.expect(mem.eql(u8, arr[1], "b"));
458 try testing.expect(mem.eql(u8, arr[2], "c"));
459}
460
461test "simple list typed as array of ints" {
462 const source =
463 \\- 0
464 \\- 1
465 \\- 2
466 ;
467
468 var yaml = try Yaml.load(testing.allocator, source);
469 defer yaml.deinit();
470
471 try testing.expectEqual(yaml.docs.items.len, 1);
472
473 const arr = try yaml.parse([3]u8);
474 try testing.expectEqual(arr.len, 3);
475 try testing.expectEqual(arr[0], 0);
476 try testing.expectEqual(arr[1], 1);
477 try testing.expectEqual(arr[2], 2);
478}
479
480test "list of mixed sign integer" {
481 const source =
482 \\- 0
483 \\- -1
484 \\- 2
485 ;
486
487 var yaml = try Yaml.load(testing.allocator, source);
488 defer yaml.deinit();
489
490 try testing.expectEqual(yaml.docs.items.len, 1);
491
492 const arr = try yaml.parse([3]i8);
493 try testing.expectEqual(arr.len, 3);
494 try testing.expectEqual(arr[0], 0);
495 try testing.expectEqual(arr[1], -1);
496 try testing.expectEqual(arr[2], 2);
497}
498
499test "simple map untyped" {
500 const source =
501 \\a: 0
502 ;
503
504 var yaml = try Yaml.load(testing.allocator, source);
505 defer yaml.deinit();
506
507 try testing.expectEqual(yaml.docs.items.len, 1);
508
509 const map = yaml.docs.items[0].map;
510 try testing.expect(map.contains("a"));
511 try testing.expectEqual(map.get("a").?.int, 0);
512}
513
514test "simple map untyped with a list of maps" {
515 const source =
516 \\a: 0
517 \\b:
518 \\ - foo: 1
519 \\ bar: 2
520 \\ - foo: 3
521 \\ bar: 4
522 \\c: 1
523 ;
524
525 var yaml = try Yaml.load(testing.allocator, source);
526 defer yaml.deinit();
527
528 try testing.expectEqual(yaml.docs.items.len, 1);
529
530 const map = yaml.docs.items[0].map;
531 try testing.expect(map.contains("a"));
532 try testing.expect(map.contains("b"));
533 try testing.expect(map.contains("c"));
534 try testing.expectEqual(map.get("a").?.int, 0);
535 try testing.expectEqual(map.get("c").?.int, 1);
536 try testing.expectEqual(map.get("b").?.list[0].map.get("foo").?.int, 1);
537 try testing.expectEqual(map.get("b").?.list[0].map.get("bar").?.int, 2);
538 try testing.expectEqual(map.get("b").?.list[1].map.get("foo").?.int, 3);
539 try testing.expectEqual(map.get("b").?.list[1].map.get("bar").?.int, 4);
540}
541
542test "simple map untyped with a list of maps. no indent" {
543 const source =
544 \\b:
545 \\- foo: 1
546 \\c: 1
547 ;
548
549 var yaml = try Yaml.load(testing.allocator, source);
550 defer yaml.deinit();
551
552 try testing.expectEqual(yaml.docs.items.len, 1);
553
554 const map = yaml.docs.items[0].map;
555 try testing.expect(map.contains("b"));
556 try testing.expect(map.contains("c"));
557 try testing.expectEqual(map.get("c").?.int, 1);
558 try testing.expectEqual(map.get("b").?.list[0].map.get("foo").?.int, 1);
559}
560
561test "simple map untyped with a list of maps. no indent 2" {
562 const source =
563 \\a: 0
564 \\b:
565 \\- foo: 1
566 \\ bar: 2
567 \\- foo: 3
568 \\ bar: 4
569 \\c: 1
570 ;
571
572 var yaml = try Yaml.load(testing.allocator, source);
573 defer yaml.deinit();
574
575 try testing.expectEqual(yaml.docs.items.len, 1);
576
577 const map = yaml.docs.items[0].map;
578 try testing.expect(map.contains("a"));
579 try testing.expect(map.contains("b"));
580 try testing.expect(map.contains("c"));
581 try testing.expectEqual(map.get("a").?.int, 0);
582 try testing.expectEqual(map.get("c").?.int, 1);
583 try testing.expectEqual(map.get("b").?.list[0].map.get("foo").?.int, 1);
584 try testing.expectEqual(map.get("b").?.list[0].map.get("bar").?.int, 2);
585 try testing.expectEqual(map.get("b").?.list[1].map.get("foo").?.int, 3);
586 try testing.expectEqual(map.get("b").?.list[1].map.get("bar").?.int, 4);
587}
588
589test "simple map typed" {
590 const source =
591 \\a: 0
592 \\b: hello there
593 \\c: 'wait, what?'
594 ;
595
596 var yaml = try Yaml.load(testing.allocator, source);
597 defer yaml.deinit();
598
599 const simple = try yaml.parse(struct { a: usize, b: []const u8, c: []const u8 });
600 try testing.expectEqual(simple.a, 0);
601 try testing.expect(mem.eql(u8, simple.b, "hello there"));
602 try testing.expect(mem.eql(u8, simple.c, "wait, what?"));
603}
604
605test "typed nested structs" {
606 const source =
607 \\a:
608 \\ b: hello there
609 \\ c: 'wait, what?'
610 ;
611
612 var yaml = try Yaml.load(testing.allocator, source);
613 defer yaml.deinit();
614
615 const simple = try yaml.parse(struct {
616 a: struct {
617 b: []const u8,
618 c: []const u8,
619 },
620 });
621 try testing.expect(mem.eql(u8, simple.a.b, "hello there"));
622 try testing.expect(mem.eql(u8, simple.a.c, "wait, what?"));
623}
624
625test "multidoc typed as a slice of structs" {
626 const source =
627 \\---
628 \\a: 0
629 \\---
630 \\a: 1
631 \\...
632 ;
633
634 var yaml = try Yaml.load(testing.allocator, source);
635 defer yaml.deinit();
636
637 {
638 const result = try yaml.parse([2]struct { a: usize });
639 try testing.expectEqual(result.len, 2);
640 try testing.expectEqual(result[0].a, 0);
641 try testing.expectEqual(result[1].a, 1);
642 }
643
644 {
645 const result = try yaml.parse([]struct { a: usize });
646 try testing.expectEqual(result.len, 2);
647 try testing.expectEqual(result[0].a, 0);
648 try testing.expectEqual(result[1].a, 1);
476 pub fn stringify(self: Yaml, writer: anytype) !void {
477 for (self.docs.items, 0..) |doc, i| {
478 try writer.writeAll("---");
479 if (self.tree.?.getDirective(i)) |directive| {
480 try writer.print(" !{s}", .{directive});
481 }
482 try writer.writeByte('\n');
483 try doc.stringify(writer, .{});
484 try writer.writeByte('\n');
485 }
486 try writer.writeAll("...\n");
649487 }
650}
651
652test "multidoc typed as a struct is an error" {
653 const source =
654 \\---
655 \\a: 0
656 \\---
657 \\b: 1
658 \\...
659 ;
660
661 var yaml = try Yaml.load(testing.allocator, source);
662 defer yaml.deinit();
663
664 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize }));
665 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { b: usize }));
666 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize, b: usize }));
667}
668
669test "multidoc typed as a slice of structs with optionals" {
670 const source =
671 \\---
672 \\a: 0
673 \\c: 1.0
674 \\---
675 \\a: 1
676 \\b: different field
677 \\...
678 ;
679
680 var yaml = try Yaml.load(testing.allocator, source);
681 defer yaml.deinit();
682
683 const result = try yaml.parse([]struct { a: usize, b: ?[]const u8, c: ?f16 });
684 try testing.expectEqual(result.len, 2);
685
686 try testing.expectEqual(result[0].a, 0);
687 try testing.expect(result[0].b == null);
688 try testing.expect(result[0].c != null);
689 try testing.expectEqual(result[0].c.?, 1.0);
690
691 try testing.expectEqual(result[1].a, 1);
692 try testing.expect(result[1].b != null);
693 try testing.expect(mem.eql(u8, result[1].b.?, "different field"));
694 try testing.expect(result[1].c == null);
695}
696
697test "empty yaml can be represented as void" {
698 const source = "";
699 var yaml = try Yaml.load(testing.allocator, source);
700 defer yaml.deinit();
701 const result = try yaml.parse(void);
702 try testing.expect(@TypeOf(result) == void);
703}
488};
704489
705test "nonempty yaml cannot be represented as void" {
706 const source =
707 \\a: b
708 ;
490pub fn stringify(allocator: Allocator, input: anytype, writer: anytype) !void {
491 var arena = ArenaAllocator.init(allocator);
492 defer arena.deinit();
709493
710 var yaml = try Yaml.load(testing.allocator, source);
711 defer yaml.deinit();
494 var maybe_value = try Value.encode(arena.allocator(), input);
712495
713 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(void));
496 if (maybe_value) |value| {
497 // TODO should we output as an explicit doc?
498 // How can allow the user to specify?
499 try value.stringify(writer, .{});
500 }
714501}
715502
716test "typed array size mismatch" {
717 const source =
718 \\- 0
719 \\- 0
720 ;
721
722 var yaml = try Yaml.load(testing.allocator, source);
723 defer yaml.deinit();
724
725 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([1]usize));
726 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([5]usize));
503test {
504 std.testing.refAllDecls(Tokenizer);
505 std.testing.refAllDecls(parse);
506 _ = @import("yaml/test.zig");
727507}
src/link/tapi/yaml/test.zig created+475
......@@ -0,0 +1,475 @@
1const std = @import("std");
2const mem = std.mem;
3const testing = std.testing;
4
5const yaml_mod = @import("../yaml.zig");
6const Yaml = yaml_mod.Yaml;
7
8test "simple list" {
9 const source =
10 \\- a
11 \\- b
12 \\- c
13 ;
14
15 var yaml = try Yaml.load(testing.allocator, source);
16 defer yaml.deinit();
17
18 try testing.expectEqual(yaml.docs.items.len, 1);
19
20 const list = yaml.docs.items[0].list;
21 try testing.expectEqual(list.len, 3);
22
23 try testing.expectEqualStrings("a", list[0].string);
24 try testing.expectEqualStrings("b", list[1].string);
25 try testing.expectEqualStrings("c", list[2].string);
26}
27
28test "simple list typed as array of strings" {
29 const source =
30 \\- a
31 \\- b
32 \\- c
33 ;
34
35 var yaml = try Yaml.load(testing.allocator, source);
36 defer yaml.deinit();
37
38 try testing.expectEqual(yaml.docs.items.len, 1);
39
40 const arr = try yaml.parse([3][]const u8);
41 try testing.expectEqual(3, arr.len);
42 try testing.expectEqualStrings("a", arr[0]);
43 try testing.expectEqualStrings("b", arr[1]);
44 try testing.expectEqualStrings("c", arr[2]);
45}
46
47test "simple list typed as array of ints" {
48 const source =
49 \\- 0
50 \\- 1
51 \\- 2
52 ;
53
54 var yaml = try Yaml.load(testing.allocator, source);
55 defer yaml.deinit();
56
57 try testing.expectEqual(yaml.docs.items.len, 1);
58
59 const arr = try yaml.parse([3]u8);
60 try testing.expectEqualSlices(u8, &[_]u8{ 0, 1, 2 }, &arr);
61}
62
63test "list of mixed sign integer" {
64 const source =
65 \\- 0
66 \\- -1
67 \\- 2
68 ;
69
70 var yaml = try Yaml.load(testing.allocator, source);
71 defer yaml.deinit();
72
73 try testing.expectEqual(yaml.docs.items.len, 1);
74
75 const arr = try yaml.parse([3]i8);
76 try testing.expectEqualSlices(i8, &[_]i8{ 0, -1, 2 }, &arr);
77}
78
79test "simple map untyped" {
80 const source =
81 \\a: 0
82 ;
83
84 var yaml = try Yaml.load(testing.allocator, source);
85 defer yaml.deinit();
86
87 try testing.expectEqual(yaml.docs.items.len, 1);
88
89 const map = yaml.docs.items[0].map;
90 try testing.expect(map.contains("a"));
91 try testing.expectEqual(@as(i64, 0), map.get("a").?.int);
92}
93
94test "simple map untyped with a list of maps" {
95 const source =
96 \\a: 0
97 \\b:
98 \\ - foo: 1
99 \\ bar: 2
100 \\ - foo: 3
101 \\ bar: 4
102 \\c: 1
103 ;
104
105 var yaml = try Yaml.load(testing.allocator, source);
106 defer yaml.deinit();
107
108 try testing.expectEqual(yaml.docs.items.len, 1);
109
110 const map = yaml.docs.items[0].map;
111 try testing.expect(map.contains("a"));
112 try testing.expect(map.contains("b"));
113 try testing.expect(map.contains("c"));
114 try testing.expectEqual(@as(i64, 0), map.get("a").?.int);
115 try testing.expectEqual(@as(i64, 1), map.get("c").?.int);
116 try testing.expectEqual(@as(i64, 1), map.get("b").?.list[0].map.get("foo").?.int);
117 try testing.expectEqual(@as(i64, 2), map.get("b").?.list[0].map.get("bar").?.int);
118 try testing.expectEqual(@as(i64, 3), map.get("b").?.list[1].map.get("foo").?.int);
119 try testing.expectEqual(@as(i64, 4), map.get("b").?.list[1].map.get("bar").?.int);
120}
121
122test "simple map untyped with a list of maps. no indent" {
123 const source =
124 \\b:
125 \\- foo: 1
126 \\c: 1
127 ;
128
129 var yaml = try Yaml.load(testing.allocator, source);
130 defer yaml.deinit();
131
132 try testing.expectEqual(yaml.docs.items.len, 1);
133
134 const map = yaml.docs.items[0].map;
135 try testing.expect(map.contains("b"));
136 try testing.expect(map.contains("c"));
137 try testing.expectEqual(@as(i64, 1), map.get("c").?.int);
138 try testing.expectEqual(@as(i64, 1), map.get("b").?.list[0].map.get("foo").?.int);
139}
140
141test "simple map untyped with a list of maps. no indent 2" {
142 const source =
143 \\a: 0
144 \\b:
145 \\- foo: 1
146 \\ bar: 2
147 \\- foo: 3
148 \\ bar: 4
149 \\c: 1
150 ;
151
152 var yaml = try Yaml.load(testing.allocator, source);
153 defer yaml.deinit();
154
155 try testing.expectEqual(yaml.docs.items.len, 1);
156
157 const map = yaml.docs.items[0].map;
158 try testing.expect(map.contains("a"));
159 try testing.expect(map.contains("b"));
160 try testing.expect(map.contains("c"));
161 try testing.expectEqual(@as(i64, 0), map.get("a").?.int);
162 try testing.expectEqual(@as(i64, 1), map.get("c").?.int);
163 try testing.expectEqual(@as(i64, 1), map.get("b").?.list[0].map.get("foo").?.int);
164 try testing.expectEqual(@as(i64, 2), map.get("b").?.list[0].map.get("bar").?.int);
165 try testing.expectEqual(@as(i64, 3), map.get("b").?.list[1].map.get("foo").?.int);
166 try testing.expectEqual(@as(i64, 4), map.get("b").?.list[1].map.get("bar").?.int);
167}
168
169test "simple map typed" {
170 const source =
171 \\a: 0
172 \\b: hello there
173 \\c: 'wait, what?'
174 ;
175
176 var yaml = try Yaml.load(testing.allocator, source);
177 defer yaml.deinit();
178
179 const simple = try yaml.parse(struct { a: usize, b: []const u8, c: []const u8 });
180 try testing.expectEqual(@as(usize, 0), simple.a);
181 try testing.expectEqualStrings("hello there", simple.b);
182 try testing.expectEqualStrings("wait, what?", simple.c);
183}
184
185test "typed nested structs" {
186 const source =
187 \\a:
188 \\ b: hello there
189 \\ c: 'wait, what?'
190 ;
191
192 var yaml = try Yaml.load(testing.allocator, source);
193 defer yaml.deinit();
194
195 const simple = try yaml.parse(struct {
196 a: struct {
197 b: []const u8,
198 c: []const u8,
199 },
200 });
201 try testing.expectEqualStrings("hello there", simple.a.b);
202 try testing.expectEqualStrings("wait, what?", simple.a.c);
203}
204
205test "single quoted string" {
206 const source =
207 \\- 'hello'
208 \\- 'here''s an escaped quote'
209 \\- 'newlines and tabs\nare not\tsupported'
210 ;
211
212 var yaml = try Yaml.load(testing.allocator, source);
213 defer yaml.deinit();
214
215 const arr = try yaml.parse([3][]const u8);
216 try testing.expectEqual(arr.len, 3);
217 try testing.expectEqualStrings("hello", arr[0]);
218 try testing.expectEqualStrings("here's an escaped quote", arr[1]);
219 try testing.expectEqualStrings("newlines and tabs\\nare not\\tsupported", arr[2]);
220}
221
222test "double quoted string" {
223 const source =
224 \\- "hello"
225 \\- "\"here\" are some escaped quotes"
226 \\- "newlines and tabs\nare\tsupported"
227 \\- "let's have
228 \\some fun!"
229 ;
230
231 var yaml = try Yaml.load(testing.allocator, source);
232 defer yaml.deinit();
233
234 const arr = try yaml.parse([4][]const u8);
235 try testing.expectEqual(arr.len, 4);
236 try testing.expectEqualStrings("hello", arr[0]);
237 try testing.expectEqualStrings(
238 \\"here" are some escaped quotes
239 , arr[1]);
240 try testing.expectEqualStrings(
241 \\newlines and tabs
242 \\are supported
243 , arr[2]);
244 try testing.expectEqualStrings(
245 \\let's have
246 \\some fun!
247 , arr[3]);
248}
249
250test "multidoc typed as a slice of structs" {
251 const source =
252 \\---
253 \\a: 0
254 \\---
255 \\a: 1
256 \\...
257 ;
258
259 var yaml = try Yaml.load(testing.allocator, source);
260 defer yaml.deinit();
261
262 {
263 const result = try yaml.parse([2]struct { a: usize });
264 try testing.expectEqual(result.len, 2);
265 try testing.expectEqual(result[0].a, 0);
266 try testing.expectEqual(result[1].a, 1);
267 }
268
269 {
270 const result = try yaml.parse([]struct { a: usize });
271 try testing.expectEqual(result.len, 2);
272 try testing.expectEqual(result[0].a, 0);
273 try testing.expectEqual(result[1].a, 1);
274 }
275}
276
277test "multidoc typed as a struct is an error" {
278 const source =
279 \\---
280 \\a: 0
281 \\---
282 \\b: 1
283 \\...
284 ;
285
286 var yaml = try Yaml.load(testing.allocator, source);
287 defer yaml.deinit();
288
289 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize }));
290 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { b: usize }));
291 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize, b: usize }));
292}
293
294test "multidoc typed as a slice of structs with optionals" {
295 const source =
296 \\---
297 \\a: 0
298 \\c: 1.0
299 \\---
300 \\a: 1
301 \\b: different field
302 \\...
303 ;
304
305 var yaml = try Yaml.load(testing.allocator, source);
306 defer yaml.deinit();
307
308 const result = try yaml.parse([]struct { a: usize, b: ?[]const u8, c: ?f16 });
309 try testing.expectEqual(result.len, 2);
310
311 try testing.expectEqual(result[0].a, 0);
312 try testing.expect(result[0].b == null);
313 try testing.expect(result[0].c != null);
314 try testing.expectEqual(result[0].c.?, 1.0);
315
316 try testing.expectEqual(result[1].a, 1);
317 try testing.expect(result[1].b != null);
318 try testing.expectEqualStrings("different field", result[1].b.?);
319 try testing.expect(result[1].c == null);
320}
321
322test "empty yaml can be represented as void" {
323 const source = "";
324 var yaml = try Yaml.load(testing.allocator, source);
325 defer yaml.deinit();
326 const result = try yaml.parse(void);
327 try testing.expect(@TypeOf(result) == void);
328}
329
330test "nonempty yaml cannot be represented as void" {
331 const source =
332 \\a: b
333 ;
334
335 var yaml = try Yaml.load(testing.allocator, source);
336 defer yaml.deinit();
337
338 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(void));
339}
340
341test "typed array size mismatch" {
342 const source =
343 \\- 0
344 \\- 0
345 ;
346
347 var yaml = try Yaml.load(testing.allocator, source);
348 defer yaml.deinit();
349
350 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([1]usize));
351 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([5]usize));
352}
353
354test "comments" {
355 const source =
356 \\
357 \\key: # this is the key
358 \\# first value
359 \\
360 \\- val1
361 \\
362 \\# second value
363 \\- val2
364 ;
365
366 var yaml = try Yaml.load(testing.allocator, source);
367 defer yaml.deinit();
368
369 const simple = try yaml.parse(struct {
370 key: []const []const u8,
371 });
372 try testing.expect(simple.key.len == 2);
373 try testing.expectEqualStrings("val1", simple.key[0]);
374 try testing.expectEqualStrings("val2", simple.key[1]);
375}
376
377test "promote ints to floats in a list mixed numeric types" {
378 const source =
379 \\a_list: [0, 1.0]
380 ;
381
382 var yaml = try Yaml.load(testing.allocator, source);
383 defer yaml.deinit();
384
385 const simple = try yaml.parse(struct {
386 a_list: []const f64,
387 });
388 try testing.expectEqualSlices(f64, &[_]f64{ 0.0, 1.0 }, simple.a_list);
389}
390
391test "demoting floats to ints in a list is an error" {
392 const source =
393 \\a_list: [0, 1.0]
394 ;
395
396 var yaml = try Yaml.load(testing.allocator, source);
397 defer yaml.deinit();
398
399 try testing.expectError(error.TypeMismatch, yaml.parse(struct {
400 a_list: []const u64,
401 }));
402}
403
404test "duplicate map keys" {
405 const source =
406 \\a: b
407 \\a: c
408 ;
409 try testing.expectError(error.DuplicateMapKey, Yaml.load(testing.allocator, source));
410}
411
412fn testStringify(expected: []const u8, input: anytype) !void {
413 var output = std.ArrayList(u8).init(testing.allocator);
414 defer output.deinit();
415
416 try yaml_mod.stringify(testing.allocator, input, output.writer());
417 try testing.expectEqualStrings(expected, output.items);
418}
419
420test "stringify an int" {
421 try testStringify("128", @as(u32, 128));
422}
423
424test "stringify a simple struct" {
425 try testStringify(
426 \\a: 1
427 \\b: 2
428 \\c: 2.5
429 , struct { a: i64, b: f64, c: f64 }{ .a = 1, .b = 2.0, .c = 2.5 });
430}
431
432test "stringify a struct with an optional" {
433 try testStringify(
434 \\a: 1
435 \\b: 2
436 \\c: 2.5
437 , struct { a: i64, b: ?f64, c: f64 }{ .a = 1, .b = 2.0, .c = 2.5 });
438
439 try testStringify(
440 \\a: 1
441 \\c: 2.5
442 , struct { a: i64, b: ?f64, c: f64 }{ .a = 1, .b = null, .c = 2.5 });
443}
444
445test "stringify a struct with all optionals" {
446 try testStringify("", struct { a: ?i64, b: ?f64 }{ .a = null, .b = null });
447}
448
449test "stringify an optional" {
450 try testStringify("", null);
451 try testStringify("", @as(?u64, null));
452}
453
454test "stringify a union" {
455 const Dummy = union(enum) {
456 x: u64,
457 y: f64,
458 };
459 try testStringify("a: 1", struct { a: Dummy }{ .a = .{ .x = 1 } });
460 try testStringify("a: 2.1", struct { a: Dummy }{ .a = .{ .y = 2.1 } });
461}
462
463test "stringify a string" {
464 try testStringify("a: name", struct { a: []const u8 }{ .a = "name" });
465 try testStringify("name", "name");
466}
467
468test "stringify a list" {
469 try testStringify("[ 1, 2, 3 ]", @as([]const u64, &.{ 1, 2, 3 }));
470 try testStringify("[ 1, 2, 3 ]", .{ @as(i64, 1), 2, 3 });
471 try testStringify("[ 1, name, 3 ]", .{ 1, "name", 3 });
472
473 const arr: [3]i64 = .{ 1, 2, 3 };
474 try testStringify("[ 1, 2, 3 ]", arr);
475}