authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-06-19 10:45:56+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-06-24 14:45:45+02:00
logfbdc5154184b0752175dcafc5bfdc4ea6a0cfebf
tree0689ba18a8ed01dea95cfe242a04646a05d9107e
parent31c49ad64ded02b9dde57f5d3ef102a771fa5cf7

link: add basic TAPI parser for linkers

Parser uses kubkon/zig-yaml gitrev c3eae1e40a02aedd44ad1171e5c8b259896cbda0

6 files changed, 2424 insertions(+), 0 deletions(-)

CMakeLists.txt+5
...@@ -588,6 +588,11 @@ set(ZIG_STAGE2_SOURCES...@@ -588,6 +588,11 @@ set(ZIG_STAGE2_SOURCES
588 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig"588 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig"
589 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig"589 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig"
590 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"590 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
591 "${CMAKE_SOURCE_DIR}/src/link/tapi.zig"
592 "${CMAKE_SOURCE_DIR}/src/link/tapi/parse.zig"
593 "${CMAKE_SOURCE_DIR}/src/link/tapi/parse/test.zig"
594 "${CMAKE_SOURCE_DIR}/src/link/tapi/Tokenizer.zig"
595 "${CMAKE_SOURCE_DIR}/src/link/tapi/yaml.zig"
591 "${CMAKE_SOURCE_DIR}/src/link/C/zig.h"596 "${CMAKE_SOURCE_DIR}/src/link/C/zig.h"
592 "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"597 "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"
593 "${CMAKE_SOURCE_DIR}/src/liveness.zig"598 "${CMAKE_SOURCE_DIR}/src/liveness.zig"
src/link/tapi.zig created+67
...@@ -0,0 +1,67 @@
1const std = @import("std");
2const fs = std.fs;
3const mem = std.mem;
4const log = std.log.scoped(.tapi);
5
6const Allocator = mem.Allocator;
7const Yaml = @import("tapi/yaml.zig").Yaml;
8
9pub const LibStub = struct {
10 /// Underlying memory for stub's contents.
11 yaml: Yaml,
12
13 /// Typed contents of the tbd file.
14 inner: []Tbd,
15
16 const Tbd = struct {
17 tbd_version: u3,
18 targets: []const []const u8,
19 uuids: []const struct {
20 target: []const u8,
21 value: []const u8,
22 },
23 install_name: []const u8,
24 current_version: union(enum) {
25 string: []const u8,
26 int: u32,
27 },
28 reexported_libraries: ?[]const struct {
29 targets: []const []const u8,
30 libraries: []const []const u8,
31 },
32 parent_umbrella: ?[]const struct {
33 targets: []const []const u8,
34 umbrella: []const u8,
35 },
36 exports: []const struct {
37 targets: []const []const u8,
38 symbols: []const []const u8,
39 },
40 allowable_clients: ?[]const struct {
41 targets: []const []const u8,
42 clients: []const []const u8,
43 },
44 objc_classes: ?[]const []const u8,
45 };
46
47 pub fn loadFromFile(allocator: *Allocator, file_path: []const u8) !LibStub {
48 const file = try fs.cwd().openFile(file_path, .{});
49 defer file.close();
50
51 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
52 defer allocator.free(source);
53
54 var lib_stub = LibStub{
55 .yaml = try Yaml.load(allocator, source),
56 .inner = undefined,
57 };
58
59 lib_stub.inner = try lib_stub.yaml.parse([]Tbd);
60
61 return lib_stub;
62 }
63
64 pub fn deinit(self: *LibStub) void {
65 self.yaml.deinit();
66 }
67};
src/link/tapi/Tokenizer.zig created+439
...@@ -0,0 +1,439 @@
1const Tokenizer = @This();
2
3const std = @import("std");
4const log = std.log.scoped(.tapi);
5const testing = std.testing;
6
7buffer: []const u8,
8index: usize = 0,
9
10pub const Token = struct {
11 id: Id,
12 start: usize,
13 end: usize,
14 // Count of spaces/tabs.
15 // Only active for .Space and .Tab tokens.
16 count: ?usize = null,
17
18 pub const Id = enum {
19 Eof,
20
21 NewLine,
22 DocStart, // ---
23 DocEnd, // ...
24 SeqItemInd, // -
25 MapValueInd, // :
26 FlowMapStart, // {
27 FlowMapEnd, // }
28 FlowSeqStart, // [
29 FlowSeqEnd, // ]
30
31 Comma,
32 Space,
33 Tab,
34 Comment, // #
35 Alias, // *
36 Anchor, // &
37 Tag, // !
38 SingleQuote, // '
39 DoubleQuote, // "
40
41 Literal,
42 };
43};
44
45pub const TokenIndex = usize;
46
47pub const TokenIterator = struct {
48 buffer: []const Token,
49 pos: TokenIndex = 0,
50
51 pub fn next(self: *TokenIterator) Token {
52 const token = self.buffer[self.pos];
53 self.pos += 1;
54 return token;
55 }
56
57 pub fn peek(self: TokenIterator) ?Token {
58 if (self.pos >= self.buffer.len) return null;
59 return self.buffer[self.pos];
60 }
61
62 pub fn reset(self: *TokenIterator) void {
63 self.pos = 0;
64 }
65
66 pub fn seekTo(self: *TokenIterator, pos: TokenIndex) void {
67 self.pos = pos;
68 }
69
70 pub fn seekBy(self: *TokenIterator, offset: isize) void {
71 const new_pos = @bitCast(isize, self.pos) + offset;
72 if (new_pos < 0) {
73 self.pos = 0;
74 } else {
75 self.pos = @intCast(usize, new_pos);
76 }
77 }
78};
79
80pub fn next(self: *Tokenizer) Token {
81 var result = Token{
82 .id = .Eof,
83 .start = self.index,
84 .end = undefined,
85 };
86
87 var state: union(enum) {
88 Start,
89 NewLine,
90 Space: usize,
91 Tab: usize,
92 Hyphen: usize,
93 Dot: usize,
94 Literal,
95 } = .Start;
96
97 while (self.index < self.buffer.len) : (self.index += 1) {
98 const c = self.buffer[self.index];
99 switch (state) {
100 .Start => switch (c) {
101 ' ' => {
102 state = .{ .Space = 1 };
103 },
104 '\t' => {
105 state = .{ .Tab = 1 };
106 },
107 '\n' => {
108 result.id = .NewLine;
109 self.index += 1;
110 break;
111 },
112 '\r' => {
113 state = .NewLine;
114 },
115 '-' => {
116 state = .{ .Hyphen = 1 };
117 },
118 '.' => {
119 state = .{ .Dot = 1 };
120 },
121 ',' => {
122 result.id = .Comma;
123 self.index += 1;
124 break;
125 },
126 '#' => {
127 result.id = .Comment;
128 self.index += 1;
129 break;
130 },
131 '*' => {
132 result.id = .Alias;
133 self.index += 1;
134 break;
135 },
136 '&' => {
137 result.id = .Anchor;
138 self.index += 1;
139 break;
140 },
141 '!' => {
142 result.id = .Tag;
143 self.index += 1;
144 break;
145 },
146 '\'' => {
147 result.id = .SingleQuote;
148 self.index += 1;
149 break;
150 },
151 '"' => {
152 result.id = .DoubleQuote;
153 self.index += 1;
154 break;
155 },
156 '[' => {
157 result.id = .FlowSeqStart;
158 self.index += 1;
159 break;
160 },
161 ']' => {
162 result.id = .FlowSeqEnd;
163 self.index += 1;
164 break;
165 },
166 ':' => {
167 result.id = .MapValueInd;
168 self.index += 1;
169 break;
170 },
171 '{' => {
172 result.id = .FlowMapStart;
173 self.index += 1;
174 break;
175 },
176 '}' => {
177 result.id = .FlowMapEnd;
178 self.index += 1;
179 break;
180 },
181 else => {
182 state = .Literal;
183 },
184 },
185 .Space => |*count| switch (c) {
186 ' ' => {
187 count.* += 1;
188 },
189 else => {
190 result.id = .Space;
191 result.count = count.*;
192 break;
193 },
194 },
195 .Tab => |*count| switch (c) {
196 ' ' => {
197 count.* += 1;
198 },
199 else => {
200 result.id = .Tab;
201 result.count = count.*;
202 break;
203 },
204 },
205 .NewLine => switch (c) {
206 '\n' => {
207 result.id = .NewLine;
208 self.index += 1;
209 break;
210 },
211 else => {}, // TODO this should be an error condition
212 },
213 .Hyphen => |*count| switch (c) {
214 ' ' => {
215 result.id = .SeqItemInd;
216 self.index += 1;
217 break;
218 },
219 '-' => {
220 count.* += 1;
221
222 if (count.* == 3) {
223 result.id = .DocStart;
224 self.index += 1;
225 break;
226 }
227 },
228 else => {
229 state = .Literal;
230 },
231 },
232 .Dot => |*count| switch (c) {
233 '.' => {
234 count.* += 1;
235
236 if (count.* == 3) {
237 result.id = .DocEnd;
238 self.index += 1;
239 break;
240 }
241 },
242 else => {
243 state = .Literal;
244 },
245 },
246 .Literal => switch (c) {
247 '\r', '\n', ' ', '\'', '"', ',', ':', ']', '}' => {
248 result.id = .Literal;
249 break;
250 },
251 else => {
252 result.id = .Literal;
253 },
254 },
255 }
256 }
257
258 if (state == .Literal and result.id == .Eof) {
259 result.id = .Literal;
260 }
261
262 result.end = self.index;
263
264 log.debug("{any}", .{result});
265 log.debug(" | {s}", .{self.buffer[result.start..result.end]});
266
267 return result;
268}
269
270fn testExpected(source: []const u8, expected: []const Token.Id) !void {
271 var tokenizer = Tokenizer{
272 .buffer = source,
273 };
274
275 for (expected) |exp| {
276 const token = tokenizer.next();
277 try testing.expectEqual(exp, token.id);
278 }
279}
280
281test "empty doc" {
282 try testExpected("", &[_]Token.Id{.Eof});
283}
284
285test "empty doc with explicit markers" {
286 try testExpected(
287 \\---
288 \\...
289 , &[_]Token.Id{
290 .DocStart, .NewLine, .DocEnd, .Eof,
291 });
292}
293
294test "sequence of values" {
295 try testExpected(
296 \\- 0
297 \\- 1
298 \\- 2
299 , &[_]Token.Id{
300 .SeqItemInd,
301 .Literal,
302 .NewLine,
303 .SeqItemInd,
304 .Literal,
305 .NewLine,
306 .SeqItemInd,
307 .Literal,
308 .Eof,
309 });
310}
311
312test "sequence of sequences" {
313 try testExpected(
314 \\- [ val1, val2]
315 \\- [val3, val4 ]
316 , &[_]Token.Id{
317 .SeqItemInd,
318 .FlowSeqStart,
319 .Space,
320 .Literal,
321 .Comma,
322 .Space,
323 .Literal,
324 .FlowSeqEnd,
325 .NewLine,
326 .SeqItemInd,
327 .FlowSeqStart,
328 .Literal,
329 .Comma,
330 .Space,
331 .Literal,
332 .Space,
333 .FlowSeqEnd,
334 .Eof,
335 });
336}
337
338test "mappings" {
339 try testExpected(
340 \\key1: value1
341 \\key2: value2
342 , &[_]Token.Id{
343 .Literal,
344 .MapValueInd,
345 .Space,
346 .Literal,
347 .NewLine,
348 .Literal,
349 .MapValueInd,
350 .Space,
351 .Literal,
352 .Eof,
353 });
354}
355
356test "inline mapped sequence of values" {
357 try testExpected(
358 \\key : [ val1,
359 \\ val2 ]
360 , &[_]Token.Id{
361 .Literal,
362 .Space,
363 .MapValueInd,
364 .Space,
365 .FlowSeqStart,
366 .Space,
367 .Literal,
368 .Comma,
369 .Space,
370 .NewLine,
371 .Space,
372 .Literal,
373 .Space,
374 .FlowSeqEnd,
375 .Eof,
376 });
377}
378
379test "part of tdb" {
380 try testExpected(
381 \\--- !tapi-tbd
382 \\tbd-version: 4
383 \\targets: [ x86_64-macos ]
384 \\
385 \\uuids:
386 \\ - target: x86_64-macos
387 \\ value: F86CC732-D5E4-30B5-AA7D-167DF5EC2708
388 \\
389 \\install-name: '/usr/lib/libSystem.B.dylib'
390 \\...
391 , &[_]Token.Id{
392 .DocStart,
393 .Space,
394 .Tag,
395 .Literal,
396 .NewLine,
397 .Literal,
398 .MapValueInd,
399 .Space,
400 .Literal,
401 .NewLine,
402 .Literal,
403 .MapValueInd,
404 .Space,
405 .FlowSeqStart,
406 .Space,
407 .Literal,
408 .Space,
409 .FlowSeqEnd,
410 .NewLine,
411 .NewLine,
412 .Literal,
413 .MapValueInd,
414 .NewLine,
415 .Space,
416 .SeqItemInd,
417 .Literal,
418 .MapValueInd,
419 .Space,
420 .Literal,
421 .NewLine,
422 .Space,
423 .Literal,
424 .MapValueInd,
425 .Space,
426 .Literal,
427 .NewLine,
428 .NewLine,
429 .Literal,
430 .MapValueInd,
431 .Space,
432 .SingleQuote,
433 .Literal,
434 .SingleQuote,
435 .NewLine,
436 .DocEnd,
437 .Eof,
438 });
439}
src/link/tapi/parse.zig created+705
...@@ -0,0 +1,705 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const log = std.log.scoped(.tapi);
4const mem = std.mem;
5const testing = std.testing;
6
7const Allocator = mem.Allocator;
8const Tokenizer = @import("Tokenizer.zig");
9const Token = Tokenizer.Token;
10const TokenIndex = Tokenizer.TokenIndex;
11const TokenIterator = Tokenizer.TokenIterator;
12
13pub const ParseError = error{
14 MalformedYaml,
15 NestedDocuments,
16 UnexpectedTag,
17 UnexpectedEof,
18 UnexpectedToken,
19 Unhandled,
20} || Allocator.Error;
21
22pub const Node = struct {
23 tag: Tag,
24 tree: *const Tree,
25
26 pub const Tag = enum {
27 doc,
28 map,
29 list,
30 value,
31 };
32
33 pub fn cast(self: *const Node, comptime T: type) ?*const T {
34 if (self.tag != T.base_tag) {
35 return null;
36 }
37 return @fieldParentPtr(T, "base", self);
38 }
39
40 pub fn deinit(self: *Node, allocator: *Allocator) void {
41 switch (self.tag) {
42 .doc => @fieldParentPtr(Node.Doc, "base", self).deinit(allocator),
43 .map => @fieldParentPtr(Node.Map, "base", self).deinit(allocator),
44 .list => @fieldParentPtr(Node.List, "base", self).deinit(allocator),
45 .value => @fieldParentPtr(Node.Value, "base", self).deinit(allocator),
46 }
47 }
48
49 pub fn format(
50 self: *const Node,
51 comptime fmt: []const u8,
52 options: std.fmt.FormatOptions,
53 writer: anytype,
54 ) !void {
55 return switch (self.tag) {
56 .doc => @fieldParentPtr(Node.Doc, "base", self).format(fmt, options, writer),
57 .map => @fieldParentPtr(Node.Map, "base", self).format(fmt, options, writer),
58 .list => @fieldParentPtr(Node.List, "base", self).format(fmt, options, writer),
59 .value => @fieldParentPtr(Node.Value, "base", self).format(fmt, options, writer),
60 };
61 }
62
63 pub const Doc = struct {
64 base: Node = Node{ .tag = Tag.doc, .tree = undefined },
65 start: ?TokenIndex = null,
66 end: ?TokenIndex = null,
67 directive: ?TokenIndex = null,
68 value: ?*Node = null,
69
70 pub const base_tag: Node.Tag = .doc;
71
72 pub fn deinit(self: *Doc, allocator: *Allocator) void {
73 if (self.value) |node| {
74 node.deinit(allocator);
75 allocator.destroy(node);
76 }
77 }
78
79 pub fn format(
80 self: *const Doc,
81 comptime fmt: []const u8,
82 options: std.fmt.FormatOptions,
83 writer: anytype,
84 ) !void {
85 if (self.directive) |id| {
86 try std.fmt.format(writer, "{{ ", .{});
87 const directive = self.base.tree.tokens[id];
88 try std.fmt.format(writer, ".directive = {s}, ", .{
89 self.base.tree.source[directive.start..directive.end],
90 });
91 }
92 if (self.value) |node| {
93 try std.fmt.format(writer, "{}", .{node});
94 }
95 if (self.directive != null) {
96 try std.fmt.format(writer, " }}", .{});
97 }
98 }
99 };
100
101 pub const Map = struct {
102 base: Node = Node{ .tag = Tag.map, .tree = undefined },
103 start: ?TokenIndex = null,
104 end: ?TokenIndex = null,
105 values: std.ArrayListUnmanaged(Entry) = .{},
106
107 pub const base_tag: Node.Tag = .map;
108
109 pub const Entry = struct {
110 key: TokenIndex,
111 value: *Node,
112 };
113
114 pub fn deinit(self: *Map, allocator: *Allocator) void {
115 for (self.values.items) |entry| {
116 entry.value.deinit(allocator);
117 allocator.destroy(entry.value);
118 }
119 self.values.deinit(allocator);
120 }
121
122 pub fn format(
123 self: *const Map,
124 comptime fmt: []const u8,
125 options: std.fmt.FormatOptions,
126 writer: anytype,
127 ) !void {
128 try std.fmt.format(writer, "{{ ", .{});
129 for (self.values.items) |entry| {
130 const key = self.base.tree.tokens[entry.key];
131 try std.fmt.format(writer, "{s} => {}, ", .{
132 self.base.tree.source[key.start..key.end],
133 entry.value,
134 });
135 }
136 return std.fmt.format(writer, " }}", .{});
137 }
138 };
139
140 pub const List = struct {
141 base: Node = Node{ .tag = Tag.list, .tree = undefined },
142 start: ?TokenIndex = null,
143 end: ?TokenIndex = null,
144 values: std.ArrayListUnmanaged(*Node) = .{},
145
146 pub const base_tag: Node.Tag = .list;
147
148 pub fn deinit(self: *List, allocator: *Allocator) void {
149 for (self.values.items) |node| {
150 node.deinit(allocator);
151 allocator.destroy(node);
152 }
153 self.values.deinit(allocator);
154 }
155
156 pub fn format(
157 self: *const List,
158 comptime fmt: []const u8,
159 options: std.fmt.FormatOptions,
160 writer: anytype,
161 ) !void {
162 try std.fmt.format(writer, "[ ", .{});
163 for (self.values.items) |node| {
164 try std.fmt.format(writer, "{}, ", .{node});
165 }
166 return std.fmt.format(writer, " ]", .{});
167 }
168 };
169
170 pub const Value = struct {
171 base: Node = Node{ .tag = Tag.value, .tree = undefined },
172 start: ?TokenIndex = null,
173 end: ?TokenIndex = null,
174
175 pub const base_tag: Node.Tag = .value;
176
177 pub fn deinit(self: *Value, allocator: *Allocator) void {}
178
179 pub fn format(
180 self: *const Value,
181 comptime fmt: []const u8,
182 options: std.fmt.FormatOptions,
183 writer: anytype,
184 ) !void {
185 const start = self.base.tree.tokens[self.start.?];
186 const end = self.base.tree.tokens[self.end.?];
187 return std.fmt.format(writer, "{s}", .{
188 self.base.tree.source[start.start..end.end],
189 });
190 }
191 };
192};
193
194pub const Tree = struct {
195 allocator: *Allocator,
196 source: []const u8,
197 tokens: []Token,
198 docs: std.ArrayListUnmanaged(*Node) = .{},
199
200 pub fn init(allocator: *Allocator) Tree {
201 return .{
202 .allocator = allocator,
203 .source = undefined,
204 .tokens = undefined,
205 };
206 }
207
208 pub fn deinit(self: *Tree) void {
209 self.allocator.free(self.tokens);
210 for (self.docs.items) |doc| {
211 doc.deinit(self.allocator);
212 self.allocator.destroy(doc);
213 }
214 self.docs.deinit(self.allocator);
215 }
216
217 pub fn parse(self: *Tree, source: []const u8) !void {
218 var tokenizer = Tokenizer{ .buffer = source };
219 var tokens = std.ArrayList(Token).init(self.allocator);
220 errdefer tokens.deinit();
221
222 while (true) {
223 const token = tokenizer.next();
224 try tokens.append(token);
225 if (token.id == .Eof) break;
226 }
227
228 self.source = source;
229 self.tokens = tokens.toOwnedSlice();
230
231 var it = TokenIterator{ .buffer = self.tokens };
232 var parser = Parser{
233 .allocator = self.allocator,
234 .tree = self,
235 .token_it = &it,
236 };
237 defer parser.deinit();
238
239 try parser.scopes.append(self.allocator, .{
240 .indent = 0,
241 });
242
243 while (true) {
244 if (parser.token_it.peek() == null) return;
245 const pos = parser.token_it.pos;
246 const token = parser.token_it.next();
247
248 log.debug("Next token: {}, {}", .{ pos, token });
249
250 switch (token.id) {
251 .Space, .Comment, .NewLine => {},
252 .Eof => break,
253 else => {
254 const doc = try parser.doc(pos);
255 try self.docs.append(self.allocator, &doc.base);
256 },
257 }
258 }
259 }
260};
261
262const Parser = struct {
263 allocator: *Allocator,
264 tree: *Tree,
265 token_it: *TokenIterator,
266 scopes: std.ArrayListUnmanaged(Scope) = .{},
267
268 const Scope = struct {
269 indent: usize,
270 };
271
272 fn deinit(self: *Parser) void {
273 self.scopes.deinit(self.allocator);
274 }
275
276 fn doc(self: *Parser, start: TokenIndex) ParseError!*Node.Doc {
277 const node = try self.allocator.create(Node.Doc);
278 errdefer self.allocator.destroy(node);
279 node.* = .{
280 .start = start,
281 };
282 node.base.tree = self.tree;
283
284 self.token_it.seekTo(start);
285
286 log.debug("Doc start: {}, {}", .{ start, self.tree.tokens[start] });
287
288 const explicit_doc: bool = if (self.eatToken(.DocStart)) |_| explicit_doc: {
289 if (self.eatToken(.Tag)) |_| {
290 node.directive = try self.expectToken(.Literal);
291 }
292 _ = try self.expectToken(.NewLine);
293 break :explicit_doc true;
294 } else false;
295
296 while (true) {
297 const pos = self.token_it.pos;
298 const token = self.token_it.next();
299
300 log.debug("Next token: {}, {}", .{ pos, token });
301
302 switch (token.id) {
303 .Tag => {
304 return error.UnexpectedTag;
305 },
306 .Literal, .SingleQuote, .DoubleQuote => {
307 _ = try self.expectToken(.MapValueInd);
308 const map_node = try self.map(pos);
309 node.value = &map_node.base;
310 },
311 .SeqItemInd => {
312 const list_node = try self.list(pos);
313 node.value = &list_node.base;
314 },
315 .FlowSeqStart => {
316 const list_node = try self.list_bracketed(pos);
317 node.value = &list_node.base;
318 },
319 .DocEnd => {
320 if (explicit_doc) break;
321 return error.UnexpectedToken;
322 },
323 .DocStart, .Eof => {
324 self.token_it.seekBy(-1);
325 break;
326 },
327 else => {
328 return error.UnexpectedToken;
329 },
330 }
331 }
332
333 node.end = self.token_it.pos - 1;
334
335 log.debug("Doc end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
336
337 return node;
338 }
339
340 fn map(self: *Parser, start: TokenIndex) ParseError!*Node.Map {
341 const node = try self.allocator.create(Node.Map);
342 errdefer self.allocator.destroy(node);
343 node.* = .{
344 .start = start,
345 };
346 node.base.tree = self.tree;
347
348 self.token_it.seekTo(start);
349
350 log.debug("Map start: {}, {}", .{ start, self.tree.tokens[start] });
351 log.debug("Current scope: {}", .{self.scopes.items[self.scopes.items.len - 1]});
352
353 while (true) {
354 // Parse key.
355 const key_pos = self.token_it.pos;
356 const key = self.token_it.next();
357 switch (key.id) {
358 .Literal => {},
359 else => {
360 self.token_it.seekBy(-1);
361 break;
362 },
363 }
364
365 log.debug("Map key: {}, '{s}'", .{ key, self.tree.source[key.start..key.end] });
366
367 // Separator
368 _ = try self.expectToken(.MapValueInd);
369 self.eatCommentsAndSpace();
370
371 // Parse value.
372 const value: *Node = value: {
373 if (self.eatToken(.NewLine)) |_| {
374 // Explicit, complex value such as list or map.
375 try self.openScope();
376 const value_pos = self.token_it.pos;
377 const value = self.token_it.next();
378 switch (value.id) {
379 .Literal, .SingleQuote, .DoubleQuote => {
380 // Assume nested map.
381 const map_node = try self.map(value_pos);
382 break :value &map_node.base;
383 },
384 .SeqItemInd => {
385 // Assume list of values.
386 const list_node = try self.list(value_pos);
387 break :value &list_node.base;
388 },
389 else => {
390 log.err("{}", .{key});
391 return error.Unhandled;
392 },
393 }
394 } else {
395 const value_pos = self.token_it.pos;
396 const value = self.token_it.next();
397 switch (value.id) {
398 .Literal, .SingleQuote, .DoubleQuote => {
399 // Assume leaf value.
400 const leaf_node = try self.leaf_value(value_pos);
401 break :value &leaf_node.base;
402 },
403 .FlowSeqStart => {
404 const list_node = try self.list_bracketed(value_pos);
405 break :value &list_node.base;
406 },
407 else => {
408 log.err("{}", .{key});
409 return error.Unhandled;
410 },
411 }
412 }
413 };
414 log.debug("Map value: {}", .{value});
415
416 try node.values.append(self.allocator, .{
417 .key = key_pos,
418 .value = value,
419 });
420
421 if (self.eatToken(.NewLine)) |_| {
422 if (try self.closeScope()) {
423 break;
424 }
425 }
426 }
427
428 node.end = self.token_it.pos - 1;
429
430 log.debug("Map end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
431
432 return node;
433 }
434
435 fn list(self: *Parser, start: TokenIndex) ParseError!*Node.List {
436 const node = try self.allocator.create(Node.List);
437 errdefer self.allocator.destroy(node);
438 node.* = .{
439 .start = start,
440 };
441 node.base.tree = self.tree;
442
443 self.token_it.seekTo(start);
444
445 log.debug("List start: {}, {}", .{ start, self.tree.tokens[start] });
446 log.debug("Current scope: {}", .{self.scopes.items[self.scopes.items.len - 1]});
447
448 while (true) {
449 _ = self.eatToken(.SeqItemInd) orelse {
450 _ = try self.closeScope();
451 break;
452 };
453 self.eatCommentsAndSpace();
454
455 const pos = self.token_it.pos;
456 const token = self.token_it.next();
457 const value: *Node = value: {
458 switch (token.id) {
459 .Literal, .SingleQuote, .DoubleQuote => {
460 if (self.eatToken(.MapValueInd)) |_| {
461 if (self.eatToken(.NewLine)) |_| {
462 try self.openScope();
463 }
464 // nested map
465 const map_node = try self.map(pos);
466 break :value &map_node.base;
467 } else {
468 // standalone (leaf) value
469 const leaf_node = try self.leaf_value(pos);
470 break :value &leaf_node.base;
471 }
472 },
473 .FlowSeqStart => {
474 const list_node = try self.list_bracketed(pos);
475 break :value &list_node.base;
476 },
477 else => {
478 log.err("{}", .{token});
479 return error.Unhandled;
480 },
481 }
482 };
483 try node.values.append(self.allocator, value);
484
485 _ = self.eatToken(.NewLine);
486 }
487
488 node.end = self.token_it.pos - 1;
489
490 log.debug("List end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
491
492 return node;
493 }
494
495 fn list_bracketed(self: *Parser, start: TokenIndex) ParseError!*Node.List {
496 const node = try self.allocator.create(Node.List);
497 errdefer self.allocator.destroy(node);
498 node.* = .{
499 .start = start,
500 };
501 node.base.tree = self.tree;
502
503 self.token_it.seekTo(start);
504
505 log.debug("List start: {}, {}", .{ start, self.tree.tokens[start] });
506 log.debug("Current scope: {}", .{self.scopes.items[self.scopes.items.len - 1]});
507
508 _ = try self.expectToken(.FlowSeqStart);
509
510 while (true) {
511 _ = self.eatToken(.NewLine);
512 self.eatCommentsAndSpace();
513
514 const pos = self.token_it.pos;
515 const token = self.token_it.next();
516
517 log.debug("Next token: {}, {}", .{ pos, token });
518
519 const value: *Node = value: {
520 switch (token.id) {
521 .FlowSeqStart => {
522 const list_node = try self.list_bracketed(pos);
523 break :value &list_node.base;
524 },
525 .FlowSeqEnd => {
526 break;
527 },
528 .Literal, .SingleQuote, .DoubleQuote => {
529 const leaf_node = try self.leaf_value(pos);
530 _ = self.eatToken(.Comma);
531 // TODO newline
532 break :value &leaf_node.base;
533 },
534 else => {
535 log.err("{}", .{token});
536 return error.Unhandled;
537 },
538 }
539 };
540 try node.values.append(self.allocator, value);
541 }
542
543 node.end = self.token_it.pos - 1;
544
545 log.debug("List end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
546
547 return node;
548 }
549
550 fn leaf_value(self: *Parser, start: TokenIndex) ParseError!*Node.Value {
551 const node = try self.allocator.create(Node.Value);
552 errdefer self.allocator.destroy(node);
553 node.* = .{
554 .start = start,
555 };
556 node.base.tree = self.tree;
557
558 self.token_it.seekTo(start);
559
560 log.debug("Leaf start: {}, {}", .{ node.start.?, self.tree.tokens[node.start.?] });
561
562 parse: {
563 if (self.eatToken(.SingleQuote)) |_| {
564 node.start = node.start.? + 1;
565 while (true) {
566 const pos = self.token_it.pos;
567 const tok = self.token_it.next();
568 switch (tok.id) {
569 .SingleQuote => {
570 node.end = self.token_it.pos - 2;
571 break :parse;
572 },
573 .NewLine => return error.UnexpectedToken,
574 else => {},
575 }
576 }
577 }
578
579 if (self.eatToken(.DoubleQuote)) |_| {
580 node.start = node.start.? + 1;
581 while (true) {
582 const pos = self.token_it.pos;
583 const tok = self.token_it.next();
584 switch (tok.id) {
585 .DoubleQuote => {
586 node.end = self.token_it.pos - 2;
587 break :parse;
588 },
589 .NewLine => return error.UnexpectedToken,
590 else => {},
591 }
592 }
593 }
594
595 // TODO handle multiline strings in new block scope
596 while (true) {
597 const pos = self.token_it.pos;
598 const tok = self.token_it.next();
599 switch (tok.id) {
600 .Literal => {},
601 .Space => {
602 const trailing = self.token_it.pos - 2;
603 self.eatCommentsAndSpace();
604 if (self.token_it.peek()) |peek| {
605 if (peek.id != .Literal) {
606 node.end = trailing;
607 break;
608 }
609 }
610 },
611 else => {
612 self.token_it.seekBy(-1);
613 node.end = self.token_it.pos - 1;
614 break;
615 },
616 }
617 }
618 }
619
620 log.debug("Leaf end: {}, {}", .{ node.end.?, self.tree.tokens[node.end.?] });
621
622 return node;
623 }
624
625 fn openScope(self: *Parser) !void {
626 const peek = self.token_it.peek() orelse return error.UnexpectedEof;
627 if (peek.id != .Space and peek.id != .Tab) {
628 // No need to open scope.
629 return;
630 }
631 const indent = self.token_it.next().count.?;
632 const prev_scope = self.scopes.items[self.scopes.items.len - 1];
633 if (indent < prev_scope.indent) {
634 return error.MalformedYaml;
635 }
636
637 log.debug("Opening scope...", .{});
638
639 try self.scopes.append(self.allocator, .{
640 .indent = indent,
641 });
642 }
643
644 fn closeScope(self: *Parser) !bool {
645 const indent = indent: {
646 const peek = self.token_it.peek() orelse return error.UnexpectedEof;
647 switch (peek.id) {
648 .Space, .Tab => {
649 break :indent self.token_it.next().count.?;
650 },
651 else => {
652 break :indent 0;
653 },
654 }
655 };
656
657 const scope = self.scopes.items[self.scopes.items.len - 1];
658 if (indent < scope.indent) {
659 log.debug("Closing scope...", .{});
660 _ = self.scopes.pop();
661 return true;
662 }
663
664 return false;
665 }
666
667 fn eatCommentsAndSpace(self: *Parser) void {
668 while (true) {
669 _ = self.token_it.peek() orelse return;
670 const token = self.token_it.next();
671 switch (token.id) {
672 .Comment, .Space => {},
673 else => {
674 self.token_it.seekBy(-1);
675 break;
676 },
677 }
678 }
679 }
680
681 fn eatToken(self: *Parser, id: Token.Id) ?TokenIndex {
682 while (true) {
683 const pos = self.token_it.pos;
684 _ = self.token_it.peek() orelse return null;
685 const token = self.token_it.next();
686 switch (token.id) {
687 .Comment, .Space => continue,
688 else => |next_id| if (next_id == id) {
689 return pos;
690 } else {
691 self.token_it.seekTo(pos);
692 return null;
693 },
694 }
695 }
696 }
697
698 fn expectToken(self: *Parser, id: Token.Id) ParseError!TokenIndex {
699 return self.eatToken(id) orelse error.UnexpectedToken;
700 }
701};
702
703test {
704 _ = @import("parse/test.zig");
705}
src/link/tapi/parse/test.zig created+556
...@@ -0,0 +1,556 @@
1const std = @import("std");
2const mem = std.mem;
3const testing = std.testing;
4
5usingnamespace @import("../parse.zig");
6
7test "explicit doc" {
8 const source =
9 \\--- !tapi-tbd
10 \\tbd-version: 4
11 \\abc-version: 5
12 \\...
13 ;
14
15 var tree = Tree.init(testing.allocator);
16 defer tree.deinit();
17 try tree.parse(source);
18
19 try testing.expectEqual(tree.docs.items.len, 1);
20
21 const doc = tree.docs.items[0].cast(Node.Doc).?;
22 try testing.expectEqual(doc.start.?, 0);
23 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
24
25 const directive = tree.tokens[doc.directive.?];
26 try testing.expectEqual(directive.id, .Literal);
27 try testing.expect(mem.eql(u8, "tapi-tbd", tree.source[directive.start..directive.end]));
28
29 try testing.expect(doc.value != null);
30 try testing.expectEqual(doc.value.?.tag, .map);
31
32 const map = doc.value.?.cast(Node.Map).?;
33 try testing.expectEqual(map.start.?, 5);
34 try testing.expectEqual(map.end.?, 14);
35 try testing.expectEqual(map.values.items.len, 2);
36
37 {
38 const entry = map.values.items[0];
39
40 const key = tree.tokens[entry.key];
41 try testing.expectEqual(key.id, .Literal);
42 try testing.expect(mem.eql(u8, "tbd-version", tree.source[key.start..key.end]));
43
44 const value = entry.value.cast(Node.Value).?;
45 const value_tok = tree.tokens[value.start.?];
46 try testing.expectEqual(value_tok.id, .Literal);
47 try testing.expect(mem.eql(u8, "4", tree.source[value_tok.start..value_tok.end]));
48 }
49
50 {
51 const entry = map.values.items[1];
52
53 const key = tree.tokens[entry.key];
54 try testing.expectEqual(key.id, .Literal);
55 try testing.expect(mem.eql(u8, "abc-version", tree.source[key.start..key.end]));
56
57 const value = entry.value.cast(Node.Value).?;
58 const value_tok = tree.tokens[value.start.?];
59 try testing.expectEqual(value_tok.id, .Literal);
60 try testing.expect(mem.eql(u8, "5", tree.source[value_tok.start..value_tok.end]));
61 }
62}
63
64test "leaf in quotes" {
65 const source =
66 \\key1: no quotes
67 \\key2: 'single quoted'
68 \\key3: "double quoted"
69 ;
70
71 var tree = Tree.init(testing.allocator);
72 defer tree.deinit();
73 try tree.parse(source);
74
75 try testing.expectEqual(tree.docs.items.len, 1);
76
77 const doc = tree.docs.items[0].cast(Node.Doc).?;
78 try testing.expectEqual(doc.start.?, 0);
79 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
80 try testing.expect(doc.directive == null);
81
82 try testing.expect(doc.value != null);
83 try testing.expectEqual(doc.value.?.tag, .map);
84
85 const map = doc.value.?.cast(Node.Map).?;
86 try testing.expectEqual(map.start.?, 0);
87 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
88 try testing.expectEqual(map.values.items.len, 3);
89
90 {
91 const entry = map.values.items[0];
92
93 const key = tree.tokens[entry.key];
94 try testing.expectEqual(key.id, .Literal);
95 try testing.expect(mem.eql(
96 u8,
97 "key1",
98 tree.source[key.start..key.end],
99 ));
100
101 const value = entry.value.cast(Node.Value).?;
102 const start = tree.tokens[value.start.?];
103 const end = tree.tokens[value.end.?];
104 try testing.expectEqual(start.id, .Literal);
105 try testing.expectEqual(end.id, .Literal);
106 try testing.expect(mem.eql(
107 u8,
108 "no quotes",
109 tree.source[start.start..end.end],
110 ));
111 }
112}
113
114test "nested maps" {
115 const source =
116 \\key1:
117 \\ key1_1 : value1_1
118 \\ key1_2 : value1_2
119 \\key2 : value2
120 ;
121
122 var tree = Tree.init(testing.allocator);
123 defer tree.deinit();
124 try tree.parse(source);
125
126 try testing.expectEqual(tree.docs.items.len, 1);
127
128 const doc = tree.docs.items[0].cast(Node.Doc).?;
129 try testing.expectEqual(doc.start.?, 0);
130 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
131 try testing.expect(doc.directive == null);
132
133 try testing.expect(doc.value != null);
134 try testing.expectEqual(doc.value.?.tag, .map);
135
136 const map = doc.value.?.cast(Node.Map).?;
137 try testing.expectEqual(map.start.?, 0);
138 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
139 try testing.expectEqual(map.values.items.len, 2);
140
141 {
142 const entry = map.values.items[0];
143
144 const key = tree.tokens[entry.key];
145 try testing.expectEqual(key.id, .Literal);
146 try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));
147
148 const nested_map = entry.value.cast(Node.Map).?;
149 try testing.expectEqual(nested_map.start.?, 4);
150 try testing.expectEqual(nested_map.end.?, 16);
151 try testing.expectEqual(nested_map.values.items.len, 2);
152
153 {
154 const nested_entry = nested_map.values.items[0];
155
156 const nested_key = tree.tokens[nested_entry.key];
157 try testing.expectEqual(nested_key.id, .Literal);
158 try testing.expect(mem.eql(
159 u8,
160 "key1_1",
161 tree.source[nested_key.start..nested_key.end],
162 ));
163
164 const nested_value = nested_entry.value.cast(Node.Value).?;
165 const nested_value_tok = tree.tokens[nested_value.start.?];
166 try testing.expectEqual(nested_value_tok.id, .Literal);
167 try testing.expect(mem.eql(
168 u8,
169 "value1_1",
170 tree.source[nested_value_tok.start..nested_value_tok.end],
171 ));
172 }
173
174 {
175 const nested_entry = nested_map.values.items[1];
176
177 const nested_key = tree.tokens[nested_entry.key];
178 try testing.expectEqual(nested_key.id, .Literal);
179 try testing.expect(mem.eql(
180 u8,
181 "key1_2",
182 tree.source[nested_key.start..nested_key.end],
183 ));
184
185 const nested_value = nested_entry.value.cast(Node.Value).?;
186 const nested_value_tok = tree.tokens[nested_value.start.?];
187 try testing.expectEqual(nested_value_tok.id, .Literal);
188 try testing.expect(mem.eql(
189 u8,
190 "value1_2",
191 tree.source[nested_value_tok.start..nested_value_tok.end],
192 ));
193 }
194 }
195
196 {
197 const entry = map.values.items[1];
198
199 const key = tree.tokens[entry.key];
200 try testing.expectEqual(key.id, .Literal);
201 try testing.expect(mem.eql(u8, "key2", tree.source[key.start..key.end]));
202
203 const value = entry.value.cast(Node.Value).?;
204 const value_tok = tree.tokens[value.start.?];
205 try testing.expectEqual(value_tok.id, .Literal);
206 try testing.expect(mem.eql(
207 u8,
208 "value2",
209 tree.source[value_tok.start..value_tok.end],
210 ));
211 }
212}
213
214test "map of list of values" {
215 const source =
216 \\ints:
217 \\ - 0
218 \\ - 1
219 \\ - 2
220 ;
221 var tree = Tree.init(testing.allocator);
222 defer tree.deinit();
223 try tree.parse(source);
224
225 try testing.expectEqual(tree.docs.items.len, 1);
226
227 const doc = tree.docs.items[0].cast(Node.Doc).?;
228 try testing.expectEqual(doc.start.?, 0);
229 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
230
231 try testing.expect(doc.value != null);
232 try testing.expectEqual(doc.value.?.tag, .map);
233
234 const map = doc.value.?.cast(Node.Map).?;
235 try testing.expectEqual(map.start.?, 0);
236 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
237 try testing.expectEqual(map.values.items.len, 1);
238
239 const entry = map.values.items[0];
240 const key = tree.tokens[entry.key];
241 try testing.expectEqual(key.id, .Literal);
242 try testing.expect(mem.eql(u8, "ints", tree.source[key.start..key.end]));
243
244 const value = entry.value.cast(Node.List).?;
245 try testing.expectEqual(value.start.?, 4);
246 try testing.expectEqual(value.end.?, tree.tokens.len - 2);
247 try testing.expectEqual(value.values.items.len, 3);
248
249 {
250 const elem = value.values.items[0].cast(Node.Value).?;
251 const leaf = tree.tokens[elem.start.?];
252 try testing.expectEqual(leaf.id, .Literal);
253 try testing.expect(mem.eql(u8, "0", tree.source[leaf.start..leaf.end]));
254 }
255
256 {
257 const elem = value.values.items[1].cast(Node.Value).?;
258 const leaf = tree.tokens[elem.start.?];
259 try testing.expectEqual(leaf.id, .Literal);
260 try testing.expect(mem.eql(u8, "1", tree.source[leaf.start..leaf.end]));
261 }
262
263 {
264 const elem = value.values.items[2].cast(Node.Value).?;
265 const leaf = tree.tokens[elem.start.?];
266 try testing.expectEqual(leaf.id, .Literal);
267 try testing.expect(mem.eql(u8, "2", tree.source[leaf.start..leaf.end]));
268 }
269}
270
271test "map of list of maps" {
272 const source =
273 \\key1:
274 \\- key2 : value2
275 \\- key3 : value3
276 \\- key4 : value4
277 ;
278
279 var tree = Tree.init(testing.allocator);
280 defer tree.deinit();
281 try tree.parse(source);
282
283 try testing.expectEqual(tree.docs.items.len, 1);
284
285 const doc = tree.docs.items[0].cast(Node.Doc).?;
286 try testing.expectEqual(doc.start.?, 0);
287 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
288
289 try testing.expect(doc.value != null);
290 try testing.expectEqual(doc.value.?.tag, .map);
291
292 const map = doc.value.?.cast(Node.Map).?;
293 try testing.expectEqual(map.start.?, 0);
294 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
295 try testing.expectEqual(map.values.items.len, 1);
296
297 const entry = map.values.items[0];
298 const key = tree.tokens[entry.key];
299 try testing.expectEqual(key.id, .Literal);
300 try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));
301
302 const value = entry.value.cast(Node.List).?;
303 try testing.expectEqual(value.start.?, 3);
304 try testing.expectEqual(value.end.?, tree.tokens.len - 2);
305 try testing.expectEqual(value.values.items.len, 3);
306
307 {
308 const elem = value.values.items[0].cast(Node.Map).?;
309 const nested = elem.values.items[0];
310 const nested_key = tree.tokens[nested.key];
311 try testing.expectEqual(nested_key.id, .Literal);
312 try testing.expect(mem.eql(u8, "key2", tree.source[nested_key.start..nested_key.end]));
313
314 const nested_v = nested.value.cast(Node.Value).?;
315 const leaf = tree.tokens[nested_v.start.?];
316 try testing.expectEqual(leaf.id, .Literal);
317 try testing.expect(mem.eql(u8, "value2", tree.source[leaf.start..leaf.end]));
318 }
319
320 {
321 const elem = value.values.items[1].cast(Node.Map).?;
322 const nested = elem.values.items[0];
323 const nested_key = tree.tokens[nested.key];
324 try testing.expectEqual(nested_key.id, .Literal);
325 try testing.expect(mem.eql(u8, "key3", tree.source[nested_key.start..nested_key.end]));
326
327 const nested_v = nested.value.cast(Node.Value).?;
328 const leaf = tree.tokens[nested_v.start.?];
329 try testing.expectEqual(leaf.id, .Literal);
330 try testing.expect(mem.eql(u8, "value3", tree.source[leaf.start..leaf.end]));
331 }
332
333 {
334 const elem = value.values.items[2].cast(Node.Map).?;
335 const nested = elem.values.items[0];
336 const nested_key = tree.tokens[nested.key];
337 try testing.expectEqual(nested_key.id, .Literal);
338 try testing.expect(mem.eql(u8, "key4", tree.source[nested_key.start..nested_key.end]));
339
340 const nested_v = nested.value.cast(Node.Value).?;
341 const leaf = tree.tokens[nested_v.start.?];
342 try testing.expectEqual(leaf.id, .Literal);
343 try testing.expect(mem.eql(u8, "value4", tree.source[leaf.start..leaf.end]));
344 }
345}
346
347test "list of lists" {
348 const source =
349 \\- [name , hr, avg ]
350 \\- [Mark McGwire , 65, 0.278]
351 \\- [Sammy Sosa , 63, 0.288]
352 ;
353
354 var tree = Tree.init(testing.allocator);
355 defer tree.deinit();
356 try tree.parse(source);
357
358 try testing.expectEqual(tree.docs.items.len, 1);
359
360 const doc = tree.docs.items[0].cast(Node.Doc).?;
361 try testing.expectEqual(doc.start.?, 0);
362 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
363
364 try testing.expect(doc.value != null);
365 try testing.expectEqual(doc.value.?.tag, .list);
366
367 const list = doc.value.?.cast(Node.List).?;
368 try testing.expectEqual(list.start.?, 0);
369 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
370 try testing.expectEqual(list.values.items.len, 3);
371
372 {
373 try testing.expectEqual(list.values.items[0].tag, .list);
374 const nested = list.values.items[0].cast(Node.List).?;
375 try testing.expectEqual(nested.values.items.len, 3);
376
377 {
378 try testing.expectEqual(nested.values.items[0].tag, .value);
379 const value = nested.values.items[0].cast(Node.Value).?;
380 const leaf = tree.tokens[value.start.?];
381 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
382 }
383
384 {
385 try testing.expectEqual(nested.values.items[1].tag, .value);
386 const value = nested.values.items[1].cast(Node.Value).?;
387 const leaf = tree.tokens[value.start.?];
388 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
389 }
390
391 {
392 try testing.expectEqual(nested.values.items[2].tag, .value);
393 const value = nested.values.items[2].cast(Node.Value).?;
394 const leaf = tree.tokens[value.start.?];
395 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
396 }
397 }
398
399 {
400 try testing.expectEqual(list.values.items[1].tag, .list);
401 const nested = list.values.items[1].cast(Node.List).?;
402 try testing.expectEqual(nested.values.items.len, 3);
403
404 {
405 try testing.expectEqual(nested.values.items[0].tag, .value);
406 const value = nested.values.items[0].cast(Node.Value).?;
407 const start = tree.tokens[value.start.?];
408 const end = tree.tokens[value.end.?];
409 try testing.expect(mem.eql(u8, "Mark McGwire", tree.source[start.start..end.end]));
410 }
411
412 {
413 try testing.expectEqual(nested.values.items[1].tag, .value);
414 const value = nested.values.items[1].cast(Node.Value).?;
415 const leaf = tree.tokens[value.start.?];
416 try testing.expect(mem.eql(u8, "65", tree.source[leaf.start..leaf.end]));
417 }
418
419 {
420 try testing.expectEqual(nested.values.items[2].tag, .value);
421 const value = nested.values.items[2].cast(Node.Value).?;
422 const leaf = tree.tokens[value.start.?];
423 try testing.expect(mem.eql(u8, "0.278", tree.source[leaf.start..leaf.end]));
424 }
425 }
426
427 {
428 try testing.expectEqual(list.values.items[2].tag, .list);
429 const nested = list.values.items[2].cast(Node.List).?;
430 try testing.expectEqual(nested.values.items.len, 3);
431
432 {
433 try testing.expectEqual(nested.values.items[0].tag, .value);
434 const value = nested.values.items[0].cast(Node.Value).?;
435 const start = tree.tokens[value.start.?];
436 const end = tree.tokens[value.end.?];
437 try testing.expect(mem.eql(u8, "Sammy Sosa", tree.source[start.start..end.end]));
438 }
439
440 {
441 try testing.expectEqual(nested.values.items[1].tag, .value);
442 const value = nested.values.items[1].cast(Node.Value).?;
443 const leaf = tree.tokens[value.start.?];
444 try testing.expect(mem.eql(u8, "63", tree.source[leaf.start..leaf.end]));
445 }
446
447 {
448 try testing.expectEqual(nested.values.items[2].tag, .value);
449 const value = nested.values.items[2].cast(Node.Value).?;
450 const leaf = tree.tokens[value.start.?];
451 try testing.expect(mem.eql(u8, "0.288", tree.source[leaf.start..leaf.end]));
452 }
453 }
454}
455
456test "inline list" {
457 const source =
458 \\[name , hr, avg ]
459 ;
460
461 var tree = Tree.init(testing.allocator);
462 defer tree.deinit();
463 try tree.parse(source);
464
465 try testing.expectEqual(tree.docs.items.len, 1);
466
467 const doc = tree.docs.items[0].cast(Node.Doc).?;
468 try testing.expectEqual(doc.start.?, 0);
469 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
470
471 try testing.expect(doc.value != null);
472 try testing.expectEqual(doc.value.?.tag, .list);
473
474 const list = doc.value.?.cast(Node.List).?;
475 try testing.expectEqual(list.start.?, 0);
476 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
477 try testing.expectEqual(list.values.items.len, 3);
478
479 {
480 try testing.expectEqual(list.values.items[0].tag, .value);
481 const value = list.values.items[0].cast(Node.Value).?;
482 const leaf = tree.tokens[value.start.?];
483 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
484 }
485
486 {
487 try testing.expectEqual(list.values.items[1].tag, .value);
488 const value = list.values.items[1].cast(Node.Value).?;
489 const leaf = tree.tokens[value.start.?];
490 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
491 }
492
493 {
494 try testing.expectEqual(list.values.items[2].tag, .value);
495 const value = list.values.items[2].cast(Node.Value).?;
496 const leaf = tree.tokens[value.start.?];
497 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
498 }
499}
500
501test "inline list as mapping value" {
502 const source =
503 \\key : [
504 \\ name ,
505 \\ hr, avg ]
506 ;
507
508 var tree = Tree.init(testing.allocator);
509 defer tree.deinit();
510 try tree.parse(source);
511
512 try testing.expectEqual(tree.docs.items.len, 1);
513
514 const doc = tree.docs.items[0].cast(Node.Doc).?;
515 try testing.expectEqual(doc.start.?, 0);
516 try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
517
518 try testing.expect(doc.value != null);
519 try testing.expectEqual(doc.value.?.tag, .map);
520
521 const map = doc.value.?.cast(Node.Map).?;
522 try testing.expectEqual(map.start.?, 0);
523 try testing.expectEqual(map.end.?, tree.tokens.len - 2);
524 try testing.expectEqual(map.values.items.len, 1);
525
526 const entry = map.values.items[0];
527 const key = tree.tokens[entry.key];
528 try testing.expectEqual(key.id, .Literal);
529 try testing.expect(mem.eql(u8, "key", tree.source[key.start..key.end]));
530
531 const list = entry.value.cast(Node.List).?;
532 try testing.expectEqual(list.start.?, 4);
533 try testing.expectEqual(list.end.?, tree.tokens.len - 2);
534 try testing.expectEqual(list.values.items.len, 3);
535
536 {
537 try testing.expectEqual(list.values.items[0].tag, .value);
538 const value = list.values.items[0].cast(Node.Value).?;
539 const leaf = tree.tokens[value.start.?];
540 try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
541 }
542
543 {
544 try testing.expectEqual(list.values.items[1].tag, .value);
545 const value = list.values.items[1].cast(Node.Value).?;
546 const leaf = tree.tokens[value.start.?];
547 try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
548 }
549
550 {
551 try testing.expectEqual(list.values.items[2].tag, .value);
552 const value = list.values.items[2].cast(Node.Value).?;
553 const leaf = tree.tokens[value.start.?];
554 try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
555 }
556}
src/link/tapi/yaml.zig created+652
...@@ -0,0 +1,652 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const math = std.math;
4const mem = std.mem;
5const testing = std.testing;
6const log = std.log.scoped(.tapi);
7
8const Allocator = mem.Allocator;
9const ArenaAllocator = std.heap.ArenaAllocator;
10
11pub const Tokenizer = @import("Tokenizer.zig");
12pub const parse = @import("parse.zig");
13
14const Node = parse.Node;
15const Tree = parse.Tree;
16const ParseError = parse.ParseError;
17
18pub const YamlError = error{
19 UnexpectedNodeType,
20 OutOfMemory,
21} || ParseError || std.fmt.ParseIntError;
22
23pub const ValueType = enum {
24 empty,
25 int,
26 float,
27 string,
28 list,
29 map,
30};
31
32pub const List = []Value;
33pub const Map = std.StringArrayHashMap(Value);
34
35pub const Value = union(ValueType) {
36 empty,
37 int: i64,
38 float: f64,
39 string: []const u8,
40 list: List,
41 map: Map,
42
43 pub fn asInt(self: Value) !i64 {
44 if (self != .int) return error.TypeMismatch;
45 return self.int;
46 }
47
48 pub fn asFloat(self: Value) !f64 {
49 if (self != .float) return error.TypeMismatch;
50 return self.float;
51 }
52
53 pub fn asString(self: Value) ![]const u8 {
54 if (self != .string) return error.TypeMismatch;
55 return self.string;
56 }
57
58 pub fn asList(self: Value) !List {
59 if (self != .list) return error.TypeMismatch;
60 return self.list;
61 }
62
63 pub fn asMap(self: Value) !Map {
64 if (self != .map) return error.TypeMismatch;
65 return self.map;
66 }
67
68 const StringifyArgs = struct {
69 indentation: usize = 0,
70 should_inline_first_key: bool = false,
71 };
72
73 pub const StringifyError = std.os.WriteError;
74
75 pub fn stringify(self: Value, writer: anytype, args: StringifyArgs) StringifyError!void {
76 switch (self) {
77 .empty => return,
78 .int => |int| return writer.print("{}", .{int}),
79 .float => |float| return writer.print("{d}", .{float}),
80 .string => |string| return writer.print("{s}", .{string}),
81 .list => |list| {
82 const len = list.len;
83 if (len == 0) return;
84
85 const first = list[0];
86 if (first.is_compound()) {
87 for (list) |elem, i| {
88 try writer.writeByteNTimes(' ', args.indentation);
89 try writer.writeAll("- ");
90 try elem.stringify(writer, .{
91 .indentation = args.indentation + 2,
92 .should_inline_first_key = true,
93 });
94 if (i < len - 1) {
95 try writer.writeByte('\n');
96 }
97 }
98 return;
99 }
100
101 try writer.writeAll("[ ");
102 for (list) |elem, i| {
103 try elem.stringify(writer, args);
104 if (i < len - 1) {
105 try writer.writeAll(", ");
106 }
107 }
108 try writer.writeAll(" ]");
109 },
110 .map => |map| {
111 const keys = map.keys();
112 const len = keys.len;
113 if (len == 0) return;
114
115 for (keys) |key, i| {
116 if (!args.should_inline_first_key or i != 0) {
117 try writer.writeByteNTimes(' ', args.indentation);
118 }
119 try writer.print("{s}: ", .{key});
120
121 const value = map.get(key) orelse unreachable;
122 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;
125 break :blk false;
126 };
127
128 if (should_inline) {
129 try value.stringify(writer, args);
130 } else {
131 try writer.writeByte('\n');
132 try value.stringify(writer, .{
133 .indentation = args.indentation + 4,
134 });
135 }
136
137 if (i < len - 1) {
138 try writer.writeByte('\n');
139 }
140 }
141 },
142 }
143 }
144
145 fn is_compound(self: Value) bool {
146 return switch (self) {
147 .list, .map => true,
148 else => false,
149 };
150 }
151
152 fn fromNode(arena: *Allocator, tree: *const Tree, node: *const Node, type_hint: ?ValueType) YamlError!Value {
153 if (node.cast(Node.Doc)) |doc| {
154 const inner = doc.value orelse {
155 // empty doc
156 return Value{ .empty = .{} };
157 };
158 return Value.fromNode(arena, tree, inner, null);
159 } 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);
162
163 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);
169 }
170
171 return Value{ .map = out_map };
172 } else if (node.cast(Node.List)) |list| {
173 var out_list = std.ArrayList(Value).init(arena);
174 try out_list.ensureUnusedCapacity(list.values.items.len);
175
176 if (list.values.items.len > 0) {
177 const hint = if (list.values.items[0].cast(Node.Value)) |value| hint: {
178 const elem = list.values.items[0];
179 const start = tree.tokens[value.start.?];
180 const end = tree.tokens[value.end.?];
181 const raw = tree.source[start.start..end.end];
182 _ = std.fmt.parseInt(i64, raw, 10) catch {
183 _ = std.fmt.parseFloat(f64, raw) catch {
184 break :hint ValueType.string;
185 };
186 break :hint ValueType.float;
187 };
188 break :hint ValueType.int;
189 } else null;
190
191 for (list.values.items) |elem| {
192 const value = try Value.fromNode(arena, tree, elem, hint);
193 out_list.appendAssumeCapacity(value);
194 }
195 }
196
197 return Value{ .list = out_list.toOwnedSlice() };
198 } else if (node.cast(Node.Value)) |value| {
199 const start = tree.tokens[value.start.?];
200 const end = tree.tokens[value.end.?];
201 const raw = tree.source[start.start..end.end];
202
203 if (type_hint) |hint| {
204 return switch (hint) {
205 .int => Value{ .int = try std.fmt.parseInt(i64, raw, 10) },
206 .float => Value{ .float = try std.fmt.parseFloat(f64, raw) },
207 .string => Value{ .string = try arena.dupe(u8, raw) },
208 else => unreachable,
209 };
210 }
211
212 try_int: {
213 // TODO infer base for int
214 const int = std.fmt.parseInt(i64, raw, 10) catch break :try_int;
215 return Value{ .int = int };
216 }
217 try_float: {
218 const float = std.fmt.parseFloat(f64, raw) catch break :try_float;
219 return Value{ .float = float };
220 }
221 return Value{ .string = try arena.dupe(u8, raw) };
222 } else {
223 log.err("Unexpected node type: {}", .{node.tag});
224 return error.UnexpectedNodeType;
225 }
226 }
227};
228
229pub const Yaml = struct {
230 arena: ArenaAllocator,
231 tree: ?Tree = null,
232 docs: std.ArrayList(Value),
233
234 pub fn deinit(self: *Yaml) void {
235 self.arena.deinit();
236 }
237
238 pub fn stringify(self: Yaml, writer: anytype) !void {
239 for (self.docs.items) |doc| {
240 // if (doc.directive) |directive| {
241 // try writer.print("--- !{s}\n", .{directive});
242 // }
243 try doc.stringify(writer, .{});
244 // if (doc.directive != null) {
245 // try writer.writeAll("...\n");
246 // }
247 }
248 }
249
250 pub fn load(allocator: *Allocator, source: []const u8) !Yaml {
251 var arena = ArenaAllocator.init(allocator);
252
253 var tree = Tree.init(&arena.allocator);
254 try tree.parse(source);
255
256 var docs = std.ArrayList(Value).init(&arena.allocator);
257 try docs.ensureUnusedCapacity(tree.docs.items.len);
258
259 for (tree.docs.items) |node| {
260 const value = try Value.fromNode(&arena.allocator, &tree, node, null);
261 docs.appendAssumeCapacity(value);
262 }
263
264 return Yaml{
265 .arena = arena,
266 .tree = tree,
267 .docs = docs,
268 };
269 }
270
271 pub const Error = error{
272 Unimplemented,
273 TypeMismatch,
274 StructFieldMissing,
275 ArraySizeMismatch,
276 UntaggedUnion,
277 UnionTagMissing,
278 Overflow,
279 OutOfMemory,
280 };
281
282 pub fn parse(self: *Yaml, comptime T: type) Error!T {
283 if (self.docs.items.len == 0) {
284 if (@typeInfo(T) == .Void) return {};
285 return error.TypeMismatch;
286 }
287
288 if (self.docs.items.len == 1) {
289 return self.parseValue(T, self.docs.items[0]);
290 }
291
292 switch (@typeInfo(T)) {
293 .Array => |info| {
294 var parsed: T = undefined;
295 for (self.docs.items) |doc, i| {
296 parsed[i] = try self.parseValue(info.child, doc);
297 }
298 return parsed;
299 },
300 .Pointer => |info| {
301 switch (info.size) {
302 .Slice => {
303 var parsed = try self.arena.allocator.alloc(info.child, self.docs.items.len);
304 for (self.docs.items) |doc, i| {
305 parsed[i] = try self.parseValue(info.child, doc);
306 }
307 return parsed;
308 },
309 else => return error.TypeMismatch,
310 }
311 },
312 .Union => return error.Unimplemented,
313 else => return error.TypeMismatch,
314 }
315 }
316
317 fn parseValue(self: *Yaml, comptime T: type, value: Value) Error!T {
318 return switch (@typeInfo(T)) {
319 .Int => math.cast(T, try value.asInt()),
320 .Float => math.lossyCast(T, try value.asFloat()),
321 .Struct => self.parseStruct(T, try value.asMap()),
322 .Union => self.parseUnion(T, value),
323 .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 }
330 },
331 .Void => error.TypeMismatch,
332 .Optional => unreachable,
333 else => error.Unimplemented,
334 };
335 }
336
337 fn parseUnion(self: *Yaml, comptime T: type, value: Value) Error!T {
338 const union_info = @typeInfo(T).Union;
339
340 if (union_info.tag_type) |_| {
341 inline for (union_info.fields) |field| {
342 if (self.parseValue(field.field_type, value)) |u_value| {
343 return @unionInit(T, field.name, u_value);
344 } else |err| {
345 if (@as(@TypeOf(err) || error{TypeMismatch}, err) != error.TypeMismatch) return err;
346 }
347 }
348 } else return error.UntaggedUnion;
349
350 return error.UnionTagMissing;
351 }
352
353 fn parseOptional(self: *Yaml, comptime T: type, value: ?Value) Error!T {
354 const unwrapped = value orelse return null;
355 const opt_info = @typeInfo(T).Optional;
356 return @as(T, try self.parseValue(opt_info.child, unwrapped));
357 }
358
359 fn parseStruct(self: *Yaml, comptime T: type, map: Map) Error!T {
360 const struct_info = @typeInfo(T).Struct;
361 var parsed: T = undefined;
362
363 inline for (struct_info.fields) |field| {
364 const value: ?Value = map.get(field.name) orelse blk: {
365 const field_name = try mem.replaceOwned(u8, &self.arena.allocator, field.name, "_", "-");
366 break :blk map.get(field_name);
367 };
368
369 if (@typeInfo(field.field_type) == .Optional) {
370 @field(parsed, field.name) = try self.parseOptional(field.field_type, value);
371 continue;
372 }
373
374 const unwrapped = value orelse {
375 log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) });
376 return error.StructFieldMissing;
377 };
378 @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped);
379 }
380
381 return parsed;
382 }
383
384 fn parsePointer(self: *Yaml, comptime T: type, value: Value) Error!T {
385 const ptr_info = @typeInfo(T).Pointer;
386 const arena = &self.arena.allocator;
387
388 switch (ptr_info.size) {
389 .Slice => {
390 const child_info = @typeInfo(ptr_info.child);
391 if (child_info == .Int and child_info.Int.bits == 8) {
392 return value.asString();
393 }
394
395 var parsed = try arena.alloc(ptr_info.child, value.list.len);
396 for (value.list) |elem, i| {
397 parsed[i] = try self.parseValue(ptr_info.child, elem);
398 }
399 return parsed;
400 },
401 else => return error.Unimplemented,
402 }
403 }
404
405 fn parseArray(self: *Yaml, comptime T: type, list: List) Error!T {
406 const array_info = @typeInfo(T).Array;
407 if (array_info.len != list.len) return error.ArraySizeMismatch;
408
409 var parsed: T = undefined;
410 for (list) |elem, i| {
411 parsed[i] = try self.parseValue(array_info.child, elem);
412 }
413
414 return parsed;
415 }
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();
431
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 typed" {
515 const source =
516 \\a: 0
517 \\b: hello there
518 \\c: 'wait, what?'
519 ;
520
521 var yaml = try Yaml.load(testing.allocator, source);
522 defer yaml.deinit();
523
524 const simple = try yaml.parse(struct { a: usize, b: []const u8, c: []const u8 });
525 try testing.expectEqual(simple.a, 0);
526 try testing.expect(mem.eql(u8, simple.b, "hello there"));
527 try testing.expect(mem.eql(u8, simple.c, "wait, what?"));
528}
529
530test "typed nested structs" {
531 const source =
532 \\a:
533 \\ b: hello there
534 \\ c: 'wait, what?'
535 ;
536
537 var yaml = try Yaml.load(testing.allocator, source);
538 defer yaml.deinit();
539
540 const simple = try yaml.parse(struct {
541 a: struct {
542 b: []const u8,
543 c: []const u8,
544 },
545 });
546 try testing.expect(mem.eql(u8, simple.a.b, "hello there"));
547 try testing.expect(mem.eql(u8, simple.a.c, "wait, what?"));
548}
549
550test "multidoc typed as a slice of structs" {
551 const source =
552 \\---
553 \\a: 0
554 \\---
555 \\a: 1
556 \\...
557 ;
558
559 var yaml = try Yaml.load(testing.allocator, source);
560 defer yaml.deinit();
561
562 {
563 const result = try yaml.parse([2]struct { a: usize });
564 try testing.expectEqual(result.len, 2);
565 try testing.expectEqual(result[0].a, 0);
566 try testing.expectEqual(result[1].a, 1);
567 }
568
569 {
570 const result = try yaml.parse([]struct { a: usize });
571 try testing.expectEqual(result.len, 2);
572 try testing.expectEqual(result[0].a, 0);
573 try testing.expectEqual(result[1].a, 1);
574 }
575}
576
577test "multidoc typed as a struct is an error" {
578 const source =
579 \\---
580 \\a: 0
581 \\---
582 \\b: 1
583 \\...
584 ;
585
586 var yaml = try Yaml.load(testing.allocator, source);
587 defer yaml.deinit();
588
589 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize }));
590 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { b: usize }));
591 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(struct { a: usize, b: usize }));
592}
593
594test "multidoc typed as a slice of structs with optionals" {
595 const source =
596 \\---
597 \\a: 0
598 \\c: 1.0
599 \\---
600 \\a: 1
601 \\b: different field
602 \\...
603 ;
604
605 var yaml = try Yaml.load(testing.allocator, source);
606 defer yaml.deinit();
607
608 const result = try yaml.parse([]struct { a: usize, b: ?[]const u8, c: ?f16 });
609 try testing.expectEqual(result.len, 2);
610
611 try testing.expectEqual(result[0].a, 0);
612 try testing.expect(result[0].b == null);
613 try testing.expect(result[0].c != null);
614 try testing.expectEqual(result[0].c.?, 1.0);
615
616 try testing.expectEqual(result[1].a, 1);
617 try testing.expect(result[1].b != null);
618 try testing.expect(mem.eql(u8, result[1].b.?, "different field"));
619 try testing.expect(result[1].c == null);
620}
621
622test "empty yaml can be represented as void" {
623 const source = "";
624 var yaml = try Yaml.load(testing.allocator, source);
625 defer yaml.deinit();
626 const result = try yaml.parse(void);
627 try testing.expect(@TypeOf(result) == void);
628}
629
630test "nonempty yaml cannot be represented as void" {
631 const source =
632 \\a: b
633 ;
634
635 var yaml = try Yaml.load(testing.allocator, source);
636 defer yaml.deinit();
637
638 try testing.expectError(Yaml.Error.TypeMismatch, yaml.parse(void));
639}
640
641test "typed array size mismatch" {
642 const source =
643 \\- 0
644 \\- 0
645 ;
646
647 var yaml = try Yaml.load(testing.allocator, source);
648 defer yaml.deinit();
649
650 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([1]usize));
651 try testing.expectError(Yaml.Error.ArraySizeMismatch, yaml.parse([5]usize));
652}