| ... | ... | @@ -228,7 +228,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 228 | 228 | .rhs_continuation_linefeed, |
| 229 | 229 | => return null, |
| 230 | 230 | .target => { |
| 231 | | return Token{ .incomplete_target = self.bytes[start..] }; |
| 231 | return errorPosition(.incomplete_target, start, self.bytes[start..]); |
| 232 | 232 | }, |
| 233 | 233 | .target_reverse_solidus, |
| 234 | 234 | .target_dollar_sign, |
| ... | ... | @@ -259,7 +259,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 259 | 259 | return null; |
| 260 | 260 | }, |
| 261 | 261 | .prereq_quote => { |
| 262 | | return Token{ .incomplete_quoted_prerequisite = self.bytes[start..] }; |
| 262 | return errorPosition(.incomplete_quoted_prerequisite, start, self.bytes[start..]); |
| 263 | 263 | }, |
| 264 | 264 | .prereq => { |
| 265 | 265 | self.state = .lhs; |
| ... | ... | @@ -278,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { |
| 278 | 278 | unreachable; |
| 279 | 279 | } |
| 280 | 280 | |
| 281 | fn errorPosition(comptime id: @TagType(Token), index: usize, bytes: []const u8) Token { |
| 282 | return @unionInit(Token, @tagName(id), .{ .index = index, .bytes = bytes }); |
| 283 | } |
| 284 | |
| 281 | 285 | fn errorIllegalChar(comptime id: @TagType(Token), index: usize, char: u8) Token { |
| 282 | 286 | return @unionInit(Token, @tagName(id), .{ .index = index, .char = char }); |
| 283 | 287 | } |
| ... | ... | @@ -309,8 +313,10 @@ pub const Token = union(enum) { |
| 309 | 313 | target: []const u8, |
| 310 | 314 | target_must_resolve: []const u8, |
| 311 | 315 | prereq: []const u8, |
| 312 | | incomplete_quoted_prerequisite: []const u8, |
| 313 | | incomplete_target: []const u8, |
| 316 | |
| 317 | incomplete_quoted_prerequisite: IndexAndBytes, |
| 318 | incomplete_target: IndexAndBytes, |
| 319 | |
| 314 | 320 | invalid_target: IndexAndChar, |
| 315 | 321 | bad_target_escape: IndexAndChar, |
| 316 | 322 | expected_dollar_sign: IndexAndChar, |
| ... | ... | @@ -322,11 +328,15 @@ pub const Token = union(enum) { |
| 322 | 328 | char: u8, |
| 323 | 329 | }; |
| 324 | 330 | |
| 331 | pub const IndexAndBytes = struct { |
| 332 | index: usize, |
| 333 | bytes: []const u8, |
| 334 | }; |
| 335 | |
| 325 | 336 | /// Resolve escapes in target. Only valid with .target_must_resolve. |
| 326 | | pub fn resolve(self: Token, buf: *std.ArrayList(u8)) std.mem.Allocator.Error!void { |
| 337 | pub fn resolve(self: Token, writer: anytype) @TypeOf(writer).Error!void { |
| 327 | 338 | const bytes = self.target_must_resolve; // resolve called on incorrect token |
| 328 | 339 | |
| 329 | | try buf.ensureCapacity(bytes.len); // cannot be longer than the unescaped string |
| 330 | 340 | var state: enum { start, escape, dollar } = .start; |
| 331 | 341 | for (bytes) |c| { |
| 332 | 342 | switch (state) { |
| ... | ... | @@ -334,33 +344,74 @@ pub const Token = union(enum) { |
| 334 | 344 | switch (c) { |
| 335 | 345 | '\\' => state = .escape, |
| 336 | 346 | '$' => state = .dollar, |
| 337 | | else => buf.appendAssumeCapacity(c), |
| 347 | else => try writer.writeByte(c), |
| 338 | 348 | } |
| 339 | 349 | }, |
| 340 | 350 | .escape => { |
| 341 | 351 | switch (c) { |
| 342 | 352 | ' ', '#', '\\' => {}, |
| 343 | 353 | '$' => { |
| 344 | | buf.appendAssumeCapacity('\\'); |
| 354 | try writer.writeByte('\\'); |
| 345 | 355 | state = .dollar; |
| 346 | 356 | continue; |
| 347 | 357 | }, |
| 348 | | else => buf.appendAssumeCapacity('\\'), |
| 358 | else => try writer.writeByte('\\'), |
| 349 | 359 | } |
| 350 | | buf.appendAssumeCapacity(c); |
| 360 | try writer.writeByte(c); |
| 351 | 361 | state = .start; |
| 352 | 362 | }, |
| 353 | 363 | .dollar => { |
| 354 | | buf.appendAssumeCapacity('$'); |
| 364 | try writer.writeByte('$'); |
| 355 | 365 | switch (c) { |
| 356 | 366 | '$' => {}, |
| 357 | | else => buf.appendAssumeCapacity(c), |
| 367 | else => try writer.writeByte(c), |
| 358 | 368 | } |
| 359 | 369 | state = .start; |
| 360 | 370 | }, |
| 361 | 371 | } |
| 362 | 372 | } |
| 363 | 373 | } |
| 374 | |
| 375 | pub fn printError(self: Token, writer: anytype) @TypeOf(writer).Error!void { |
| 376 | switch (self) { |
| 377 | .target, .target_must_resolve, .prereq => unreachable, // not an error |
| 378 | .incomplete_quoted_prerequisite, |
| 379 | .incomplete_target, |
| 380 | => |index_and_bytes| { |
| 381 | try writer.print("{} '", .{self.errStr()}); |
| 382 | if (self == .incomplete_target) { |
| 383 | const tmp = Token{ .target_must_resolve = index_and_bytes.bytes }; |
| 384 | try tmp.resolve(writer); |
| 385 | } else { |
| 386 | try printCharValues(writer, index_and_bytes.bytes); |
| 387 | } |
| 388 | try writer.print("' at position {}", .{index_and_bytes.index}); |
| 389 | }, |
| 390 | .invalid_target, |
| 391 | .bad_target_escape, |
| 392 | .expected_dollar_sign, |
| 393 | .continuation_eol, |
| 394 | .incomplete_escape, |
| 395 | => |index_and_char| { |
| 396 | try writer.writeAll("illegal char "); |
| 397 | try printUnderstandableChar(writer, index_and_char.char); |
| 398 | try writer.print(" at position {}: {}", .{ index_and_char.index, self.errStr() }); |
| 399 | }, |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | fn errStr(self: Token) []const u8 { |
| 404 | return switch (self) { |
| 405 | .target, .target_must_resolve, .prereq => unreachable, // not an error |
| 406 | .incomplete_quoted_prerequisite => "incomplete quoted prerequisite", |
| 407 | .incomplete_target => "incomplete target", |
| 408 | .invalid_target => "invalid target", |
| 409 | .bad_target_escape => "bad target escape", |
| 410 | .expected_dollar_sign => "expecting '$'", |
| 411 | .continuation_eol => "continuation expecting end-of-line", |
| 412 | .incomplete_escape => "incomplete escape", |
| 413 | }; |
| 414 | } |
| 364 | 415 | }; |
| 365 | 416 | |
| 366 | 417 | test "empty file" { |
| ... | ... | @@ -755,16 +806,16 @@ test "error incomplete target" { |
| 755 | 806 | ); |
| 756 | 807 | |
| 757 | 808 | try depTokenizer("\\ foo.o", |
| 758 | | \\ERROR: incomplete target ' foo.o' at position 1 |
| 809 | \\ERROR: incomplete target ' foo.o' at position 0 |
| 759 | 810 | ); |
| 760 | 811 | try depTokenizer("\\#foo.o", |
| 761 | | \\ERROR: incomplete target '#foo.o' at position 1 |
| 812 | \\ERROR: incomplete target '#foo.o' at position 0 |
| 762 | 813 | ); |
| 763 | 814 | try depTokenizer("\\\\foo.o", |
| 764 | | \\ERROR: incomplete target '\foo.o' at position 1 |
| 815 | \\ERROR: incomplete target '\foo.o' at position 0 |
| 765 | 816 | ); |
| 766 | 817 | try depTokenizer("$$foo.o", |
| 767 | | \\ERROR: incomplete target '$foo.o' at position 1 |
| 818 | \\ERROR: incomplete target '$foo.o' at position 0 |
| 768 | 819 | ); |
| 769 | 820 | } |
| 770 | 821 | |
| ... | ... | @@ -862,7 +913,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 862 | 913 | }, |
| 863 | 914 | .target_must_resolve => { |
| 864 | 915 | try buffer.appendSlice("target = {"); |
| 865 | | try token.resolve(&resolve_buf); |
| 916 | try token.resolve(resolve_buf.writer()); |
| 866 | 917 | for (resolve_buf.items) |b| { |
| 867 | 918 | try buffer.append(printable_char_tab[b]); |
| 868 | 919 | } |
| ... | ... | @@ -870,7 +921,9 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 870 | 921 | try buffer.appendSlice("}"); |
| 871 | 922 | }, |
| 872 | 923 | else => { |
| 873 | | @panic("TODO"); |
| 924 | try buffer.appendSlice("ERROR: "); |
| 925 | try token.printError(buffer.outStream()); |
| 926 | break; |
| 874 | 927 | }, |
| 875 | 928 | } |
| 876 | 929 | i += 1; |
| ... | ... | @@ -1005,23 +1058,19 @@ fn printCharValues(out: anytype, bytes: []const u8) !void { |
| 1005 | 1058 | } |
| 1006 | 1059 | } |
| 1007 | 1060 | |
| 1008 | | fn printUnderstandableChar(buffer: *std.ArrayListSentineled(u8, 0), char: u8) !void { |
| 1061 | fn printUnderstandableChar(out: anytype, char: u8) !void { |
| 1009 | 1062 | if (!std.ascii.isPrint(char) or char == ' ') { |
| 1010 | | try buffer.outStream().print("\\x{X:0>2}", .{char}); |
| 1063 | try out.print("\\x{X:0>2}", .{char}); |
| 1011 | 1064 | } else { |
| 1012 | | try buffer.appendSlice("'"); |
| 1013 | | try buffer.append(printable_char_tab[char]); |
| 1014 | | try buffer.appendSlice("'"); |
| 1065 | try out.print("'{c}'", .{printable_char_tab[char]}); |
| 1015 | 1066 | } |
| 1016 | 1067 | } |
| 1017 | 1068 | |
| 1018 | 1069 | // zig fmt: off |
| 1019 | | const printable_char_tab: []const u8 = |
| 1070 | const printable_char_tab: [256]u8 = ( |
| 1020 | 1071 | "................................ !\"#$%&'()*+,-./0123456789:;<=>?" ++ |
| 1021 | 1072 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~." ++ |
| 1022 | 1073 | "................................................................" ++ |
| 1023 | | "................................................................"; |
| 1024 | | // zig fmt: on |
| 1025 | | comptime { |
| 1026 | | assert(printable_char_tab.len == 256); |
| 1027 | | } |
| 1074 | "................................................................" |
| 1075 | ).*; |
| 1076 | |