| 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,6 +588,11 @@ set(ZIG_STAGE2_SOURCES |
| 588 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig" | 588 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig" |
| 589 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig" | 589 | "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig" |
| 590 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" | 590 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" |
| 591 | "${CMAKE_SOURCE_DIR}/src/link/tapi.zig" | ||
| 592 | "${CMAKE_SOURCE_DIR}/src/link/tapi/parse.zig" | ||
| 593 | "${CMAKE_SOURCE_DIR}/src/link/tapi/parse/test.zig" | ||
| 594 | "${CMAKE_SOURCE_DIR}/src/link/tapi/Tokenizer.zig" | ||
| 595 | "${CMAKE_SOURCE_DIR}/src/link/tapi/yaml.zig" | ||
| 591 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" | 596 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" |
| 592 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" | 597 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
| 593 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" | 598 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" |
src/link/tapi.zig created+67| ... | @@ -0,0 +1,67 @@ | ||
| 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 | } | ||