1const std = @import("std");
2const string = []const u8;
3
4const c = @cImport({
5 @cInclude("yaml.h");
6});
7
8//
9//
10
11pub const Stream = struct {
12 docs: []const Document,
13
14 pub fn deinit(self: *const Stream, alloc: std.mem.Allocator) void {
15 for (self.docs) |*item| item.deinit(alloc);
16 alloc.free(self.docs);
17 }
18};
19
20pub const Document = union(enum) {
21 mapping: Mapping,
22 sequence: Sequence,
23
24 pub fn deinit(self: *const Document, alloc: std.mem.Allocator) void {
25 switch (self.*) {
26 .mapping => |m| m.deinit(alloc),
27 .sequence => |s| {
28 for (s) |*item| item.deinit(alloc);
29 alloc.free(s);
30 },
31 }
32 }
33};
34
35pub const Item = union(enum) {
36 event: Token,
37 kv: Key,
38 mapping: Mapping,
39 sequence: Sequence,
40 string: [:0]const u8,
41 stream: Stream,
42 anchor: [:0]const u8,
43
44 pub fn deinit(self: *const Item, alloc: std.mem.Allocator) void {
45 switch (self.*) {
46 .event => {},
47 .kv => |kv| kv.deinit(alloc),
48 .mapping => |m| m.deinit(alloc),
49 .sequence => |s| {
50 for (s) |*item| item.deinit(alloc);
51 alloc.free(s);
52 },
53 .string => |s| alloc.free(s),
54 .stream => |s| s.deinit(alloc),
55 .anchor => {},
56 }
57 }
58
59 pub fn format(self: Value, writer: *std.Io.Writer) !void {
60 return nprint(self, writer);
61 }
62
63 pub fn nprint(self: Item, writer: anytype) !void {
64 try writer.writeAll("Item{");
65 switch (self) {
66 .event => {
67 try writer.print("event {}", .{self.event});
68 },
69 .kv, .stream => {
70 unreachable;
71 },
72 .mapping => {
73 try writer.print("{f}", .{self.mapping});
74 },
75 .sequence => {
76 try writer.writeAll("[ ");
77 for (self.sequence) |it| {
78 try writer.print("{f}, ", .{it});
79 }
80 try writer.writeAll("]");
81 },
82 .string => {
83 try writer.print("{s}", .{self.string});
84 },
85 .anchor => {
86 //
87 },
88 }
89 try writer.writeAll("}");
90 }
91};
92
93pub const Sequence = []const Item;
94
95pub const Key = struct {
96 key: [:0]const u8,
97 value: Value,
98
99 pub fn deinit(self: *const Key, alloc: std.mem.Allocator) void {
100 alloc.free(self.key);
101 self.value.deinit(alloc);
102 }
103};
104
105pub const Value = union(enum) {
106 string: [:0]const u8,
107 mapping: Mapping,
108 sequence: Sequence,
109 anchor: [:0]const u8,
110
111 pub fn deinit(self: *const Value, alloc: std.mem.Allocator) void {
112 switch (self.*) {
113 .string => |s| alloc.free(s),
114 .mapping => |*m| {
115 m.deinit(alloc);
116 },
117 .sequence => |s| {
118 for (s) |*item| item.deinit(alloc);
119 alloc.free(s);
120 },
121 .anchor => {},
122 }
123 }
124
125 pub fn format(self: Value, writer: *std.Io.Writer) !void {
126 return nprint(self, writer);
127 }
128
129 pub fn nprint(self: Value, writer: anytype) !void {
130 try writer.writeAll("Value{");
131 switch (self) {
132 .string => {
133 try writer.print("{s}", .{self.string});
134 },
135 .mapping => {
136 try writer.print("{f}", .{self.mapping});
137 },
138 .sequence => {
139 try writer.writeAll("[ ");
140 for (self.sequence) |it| {
141 try writer.print("{f}, ", .{it});
142 }
143 try writer.writeAll("]");
144 },
145 .anchor => {
146 //
147 },
148 }
149 try writer.writeAll("}");
150 }
151};
152
153pub const Mapping = struct {
154 items: []const Key,
155
156 pub fn deinit(self: *const Mapping, alloc: std.mem.Allocator) void {
157 for (self.items) |*item| item.deinit(alloc);
158 alloc.free(self.items);
159 }
160
161 pub fn get(self: Mapping, k: string) ?Value {
162 for (self.items) |item| {
163 if (std.mem.eql(u8, item.key, k)) {
164 return item.value;
165 }
166 }
167 return null;
168 }
169
170 pub fn getT(self: Mapping, k: string, comptime f: std.meta.FieldEnum(Value)) ?@FieldType(Value, @tagName(f)) {
171 for (self.items) |item| {
172 if (std.mem.eql(u8, item.key, k)) {
173 return @field(item.value, @tagName(f));
174 }
175 }
176 return null;
177 }
178
179 pub fn get_string(self: Mapping, k: string) ?[:0]const u8 {
180 return self.getT(k, .string);
181 }
182
183 pub fn get_string_array(self: Mapping, alloc: std.mem.Allocator, k: string) ![][:0]const u8 {
184 var list = std.array_list.Managed([:0]const u8).init(alloc);
185 errdefer list.deinit();
186 if (self.get(k)) |val| {
187 if (val == .sequence) {
188 for (val.sequence) |item| {
189 if (item != .string) {
190 continue;
191 }
192 try list.append(item.string);
193 }
194 }
195 }
196 return list.toOwnedSlice();
197 }
198
199 pub fn getMap(self: Mapping, k: string) ?Mapping {
200 return self.getT(k, .mapping);
201 }
202
203 pub fn format(self: Value, writer: *std.Io.Writer) !void {
204 return nprint(self, writer);
205 }
206
207 pub fn nprint(self: Mapping, writer: anytype) !void {
208 try writer.writeAll("{ ");
209 for (self.items) |it| {
210 try writer.print("{s}: ", .{it.key});
211 try writer.print("{f}, ", .{it.value});
212 }
213 try writer.writeAll("}");
214 }
215};
216
217pub const Token = c.yaml_event_t;
218pub const TokenList = []const Token;
219
220//
221//
222
223pub fn parse(alloc: std.mem.Allocator, input: string) !Document {
224 var parser: c.yaml_parser_t = undefined;
225 _ = c.yaml_parser_initialize(&parser);
226 defer c.yaml_parser_delete(&parser);
227
228 const lines = try split(alloc, input);
229 defer alloc.free(lines);
230
231 _ = c.yaml_parser_set_input_string(&parser, input.ptr, input.len);
232
233 var all_events = std.array_list.Managed(Token).init(alloc);
234 defer all_events.deinit();
235 var event: Token = undefined;
236 while (true) {
237 const p = c.yaml_parser_parse(&parser, &event);
238 if (p == 0) {
239 break;
240 }
241
242 const et = event.type;
243 try all_events.append(event);
244 c.yaml_event_delete(&event);
245
246 if (et == c.YAML_STREAM_END_EVENT) {
247 break;
248 }
249 }
250
251 var p = Parser{
252 .alloc = alloc,
253 .tokens = all_events.items,
254 .lines = lines,
255 .index = 0,
256 };
257 const stream = try p.parse();
258 defer alloc.free(stream.docs);
259 return stream.docs[0];
260}
261
262pub const Parser = struct {
263 alloc: std.mem.Allocator,
264 tokens: TokenList,
265 lines: []const string,
266 index: usize,
267
268 pub fn parse(self: *Parser) !Stream {
269 const item = try parse_item(self, null);
270 return item.stream;
271 }
272
273 fn next(self: *Parser) !Token {
274 if (self.index >= self.tokens.len) {
275 return error.YamlEndOfStream;
276 }
277 defer self.index += 1;
278 return self.tokens[self.index];
279 }
280};
281
282pub const Error =
283 std.mem.Allocator.Error ||
284 error{ YamlUnexpectedToken, YamlEndOfStream, YamlInvalidMultilineString };
285
286fn parse_item(p: *Parser, start: ?Token) Error!Item {
287 const tok = start orelse try p.next();
288 return switch (tok.type) {
289 c.YAML_STREAM_START_EVENT => Item{ .stream = try parse_stream(p) },
290 c.YAML_MAPPING_START_EVENT => Item{ .mapping = try parse_mapping(p) },
291 c.YAML_SEQUENCE_START_EVENT => Item{ .sequence = try parse_sequence(p) },
292 c.YAML_SCALAR_EVENT => Item{ .string = try get_event_string(tok, p) },
293 c.YAML_ALIAS_EVENT => .{ .anchor = std.mem.sliceTo(tok.data.alias.anchor, 0) },
294 else => unreachable,
295 };
296}
297
298fn parse_stream(p: *Parser) Error!Stream {
299 var res = std.array_list.Managed(Document).init(p.alloc);
300 errdefer res.deinit();
301 errdefer for (res.items) |k| k.deinit(p.alloc);
302
303 while (true) {
304 const tok = try p.next();
305 if (tok.type == c.YAML_STREAM_END_EVENT) {
306 return Stream{ .docs = try res.toOwnedSlice() };
307 }
308 if (tok.type != c.YAML_DOCUMENT_START_EVENT) {
309 return error.YamlUnexpectedToken;
310 }
311 try res.append(try parse_document(p));
312 }
313}
314
315fn parse_document(p: *Parser) Error!Document {
316 const tok = try p.next();
317 switch (tok.type) {
318 c.YAML_MAPPING_START_EVENT => {
319 const item = try parse_item(p, tok);
320 errdefer item.deinit(p.alloc);
321 const tok2 = try p.next();
322 if (tok2.type != c.YAML_DOCUMENT_END_EVENT) {
323 return error.YamlUnexpectedToken;
324 }
325 return Document{ .mapping = item.mapping };
326 },
327 c.YAML_SEQUENCE_START_EVENT => {
328 const item = try parse_item(p, tok);
329 errdefer item.deinit(p.alloc);
330 const tok2 = try p.next();
331 if (tok2.type != c.YAML_DOCUMENT_END_EVENT) {
332 return error.YamlUnexpectedToken;
333 }
334 return Document{ .sequence = item.sequence };
335 },
336 else => {
337 return error.YamlUnexpectedToken;
338 },
339 }
340}
341
342fn parse_mapping(p: *Parser) Error!Mapping {
343 var res = std.array_list.Managed(Key).init(p.alloc);
344 errdefer res.deinit();
345 errdefer for (res.items) |k| k.deinit(p.alloc);
346
347 while (true) {
348 const tok = try p.next();
349 if (tok.type == c.YAML_MAPPING_END_EVENT) {
350 return Mapping{ .items = try res.toOwnedSlice() };
351 }
352 if (tok.type != c.YAML_SCALAR_EVENT) {
353 return error.YamlUnexpectedToken;
354 }
355
356 const key = try get_event_string(tok, p);
357 errdefer p.alloc.free(key);
358 const value = try parse_value(p);
359 errdefer value.deinit(p.alloc);
360 try res.append(Key{ .key = key, .value = value });
361 }
362}
363
364fn parse_value(p: *Parser) Error!Value {
365 const item = try parse_item(p, null);
366 return switch (item) {
367 .mapping => |x| Value{ .mapping = x },
368 .sequence => |x| Value{ .sequence = x },
369 .string => |x| Value{ .string = x },
370 .anchor => |x| Value{ .anchor = x },
371 else => unreachable,
372 };
373}
374
375fn parse_sequence(p: *Parser) Error!Sequence {
376 var res = std.array_list.Managed(Item).init(p.alloc);
377 errdefer res.deinit();
378 errdefer for (res.items) |k| k.deinit(p.alloc);
379
380 while (true) {
381 const tok = try p.next();
382 if (tok.type == c.YAML_SEQUENCE_END_EVENT) {
383 return try res.toOwnedSlice();
384 }
385 try res.append(try parse_item(p, tok));
386 }
387}
388
389fn get_event_string(event: Token, p: *const Parser) ![:0]u8 {
390 const sm = event.start_mark;
391 const em = event.end_mark;
392 const lines = p.lines;
393 if (sm.line != em.line) {
394 const starter = lines[sm.line][sm.column..];
395 if (starter.len != 1) return error.YamlInvalidMultilineString;
396 switch (starter[0]) {
397 '|' => {
398 var list = std.array_list.Managed(u8).init(p.alloc);
399 errdefer list.deinit();
400 var i = sm.line + 1;
401 while (i < em.line) : (i += 1) {
402 try list.appendSlice(std.mem.trimStart(u8, lines[i], " "));
403 try list.append('\n');
404 }
405 return try list.toOwnedSliceSentinel(0);
406 },
407 else => return error.YamlInvalidMultilineString,
408 }
409 }
410 const s = lines[sm.line][sm.column..em.column];
411 if (s.len < 2) return try p.alloc.dupeZ(u8, s);
412 if (s[0] == '"' and s[s.len - 1] == '"') return try p.alloc.dupeZ(u8, std.mem.trim(u8, s, "\""));
413 if (s[0] == '\'' and s[s.len - 1] == '\'') return try p.alloc.dupeZ(u8, std.mem.trim(u8, s, "'"));
414 return try p.alloc.dupeZ(u8, s);
415}
416
417//
418//
419
420fn split(alloc: std.mem.Allocator, in: string) ![]string {
421 var list = std.array_list.Managed(string).init(alloc);
422 errdefer list.deinit();
423
424 var iter = std.mem.splitAny(u8, in, "\r\n");
425 while (iter.next()) |str| {
426 if (str.len == 0) {
427 if ((str.ptr - 1)[0] == '\r' and str.ptr[0] == '\n') {
428 try list.append(iter.next() orelse break);
429 continue;
430 }
431 }
432 try list.append(str);
433 }
434 return try list.toOwnedSlice();
435}