| author | |
| committer | |
| log | fbdc5154184b0752175dcafc5bfdc4ea6a0cfebf |
| tree | 0689ba18a8ed01dea95cfe242a04646a05d9107e |
| parent | 31c49ad64ded02b9dde57f5d3ef102a771fa5cf7 |
Parser uses kubkon/zig-yaml gitrev c3eae1e40a02aedd44ad1171e5c8b259896cbda06 files changed, 2424 insertions(+), 0 deletions(-)
CMakeLists.txt+5| ... | ... | @@ -588,6 +588,11 @@ set(ZIG_STAGE2_SOURCES |
| 588 | 588 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig" |
| 589 | 589 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig" |
| 590 | 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 | 596 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" |
| 592 | 597 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
| 593 | 598 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" |
src/link/tapi.zig created+67| ... | ... | @@ -0,0 +1,67 @@ |
| 1 | const std = @import("std"); | |
| 2 | const fs = std.fs; | |
| 3 | const mem = std.mem; | |
| 4 | const log = std.log.scoped(.tapi); | |
| 5 | ||
| 6 | const Allocator = mem.Allocator; | |
| 7 | const Yaml = @import("tapi/yaml.zig").Yaml; | |
| 8 | ||
| 9 | pub 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 @@ |
| 1 | const Tokenizer = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const log = std.log.scoped(.tapi); | |
| 5 | const testing = std.testing; | |
| 6 | ||
| 7 | buffer: []const u8, | |
| 8 | index: usize = 0, | |
| 9 | ||
| 10 | pub 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 | ||
| 45 | pub const TokenIndex = usize; | |
| 46 | ||
| 47 | pub 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 | ||
| 80 | pub 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 | ||
| 270 | fn 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 | ||
| 281 | test "empty doc" { | |
| 282 | try testExpected("", &[_]Token.Id{.Eof}); | |
| 283 | } | |
| 284 | ||
| 285 | test "empty doc with explicit markers" { | |
| 286 | try testExpected( | |
| 287 | \\--- | |
| 288 | \\... | |
| 289 | , &[_]Token.Id{ | |
| 290 | .DocStart, .NewLine, .DocEnd, .Eof, | |
| 291 | }); | |
| 292 | } | |
| 293 | ||
| 294 | test "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 | ||
| 312 | test "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 | ||
| 338 | test "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 | ||
| 356 | test "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 | ||
| 379 | test "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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const log = std.log.scoped(.tapi); | |
| 4 | const mem = std.mem; | |
| 5 | const testing = std.testing; | |
| 6 | ||
| 7 | const Allocator = mem.Allocator; | |
| 8 | const Tokenizer = @import("Tokenizer.zig"); | |
| 9 | const Token = Tokenizer.Token; | |
| 10 | const TokenIndex = Tokenizer.TokenIndex; | |
| 11 | const TokenIterator = Tokenizer.TokenIterator; | |
| 12 | ||
| 13 | pub const ParseError = error{ | |
| 14 | MalformedYaml, | |
| 15 | NestedDocuments, | |
| 16 | UnexpectedTag, | |
| 17 | UnexpectedEof, | |
| 18 | UnexpectedToken, | |
| 19 | Unhandled, | |
| 20 | } || Allocator.Error; | |
| 21 | ||
| 22 | pub 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 | ||
| 194 | pub 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 | ||
| 262 | const 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 | ||
| 703 | test { | |
| 704 | _ = @import("parse/test.zig"); | |
| 705 | } |
src/link/tapi/parse/test.zig created+556| ... | ... | @@ -0,0 +1,556 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | usingnamespace @import("../parse.zig"); | |
| 6 | ||
| 7 | test "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 | ||
| 64 | test "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 | ||
| 114 | test "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 | ||
| 214 | test "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 | ||
| 271 | test "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 | ||
| 347 | test "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 | ||
| 456 | test "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 | ||
| 501 | test "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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const math = std.math; | |
| 4 | const mem = std.mem; | |
| 5 | const testing = std.testing; | |
| 6 | const log = std.log.scoped(.tapi); | |
| 7 | ||
| 8 | const Allocator = mem.Allocator; | |
| 9 | const ArenaAllocator = std.heap.ArenaAllocator; | |
| 10 | ||
| 11 | pub const Tokenizer = @import("Tokenizer.zig"); | |
| 12 | pub const parse = @import("parse.zig"); | |
| 13 | ||
| 14 | const Node = parse.Node; | |
| 15 | const Tree = parse.Tree; | |
| 16 | const ParseError = parse.ParseError; | |
| 17 | ||
| 18 | pub const YamlError = error{ | |
| 19 | UnexpectedNodeType, | |
| 20 | OutOfMemory, | |
| 21 | } || ParseError || std.fmt.ParseIntError; | |
| 22 | ||
| 23 | pub const ValueType = enum { | |
| 24 | empty, | |
| 25 | int, | |
| 26 | float, | |
| 27 | string, | |
| 28 | list, | |
| 29 | map, | |
| 30 | }; | |
| 31 | ||
| 32 | pub const List = []Value; | |
| 33 | pub const Map = std.StringArrayHashMap(Value); | |
| 34 | ||
| 35 | pub 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 | ||
| 229 | pub 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 | ||
| 418 | test { | |
| 419 | testing.refAllDecls(@This()); | |
| 420 | } | |
| 421 | ||
| 422 | test "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 | ||
| 442 | test "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 | ||
| 461 | test "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 | ||
| 480 | test "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 | ||
| 499 | test "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 | ||
| 514 | test "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 | ||
| 530 | test "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 | ||
| 550 | test "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 | ||
| 577 | test "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 | ||
| 594 | test "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 | ||
| 622 | test "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 | ||
| 630 | test "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 | ||
| 641 | test "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 | } |