| ... | @@ -228,7 +228,7 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -228,7 +228,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 228 | .rhs_continuation_linefeed, | 228 | .rhs_continuation_linefeed, |
| 229 | => return null, | 229 | => return null, |
| 230 | .target => { | 230 | .target => { |
| 231 | return Token{ .incomplete_target = self.bytes[start..] }; | 231 | return errorPosition(.incomplete_target, start, self.bytes[start..]); |
| 232 | }, | 232 | }, |
| 233 | .target_reverse_solidus, | 233 | .target_reverse_solidus, |
| 234 | .target_dollar_sign, | 234 | .target_dollar_sign, |
| ... | @@ -259,7 +259,7 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -259,7 +259,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 259 | return null; | 259 | return null; |
| 260 | }, | 260 | }, |
| 261 | .prereq_quote => { | 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 | .prereq => { | 264 | .prereq => { |
| 265 | self.state = .lhs; | 265 | self.state = .lhs; |
| ... | @@ -278,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -278,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { |
| 278 | unreachable; | 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 | fn errorIllegalChar(comptime id: @TagType(Token), index: usize, char: u8) Token { | 285 | fn errorIllegalChar(comptime id: @TagType(Token), index: usize, char: u8) Token { |
| 282 | return @unionInit(Token, @tagName(id), .{ .index = index, .char = char }); | 286 | return @unionInit(Token, @tagName(id), .{ .index = index, .char = char }); |
| 283 | } | 287 | } |
| ... | @@ -309,8 +313,10 @@ pub const Token = union(enum) { | ... | @@ -309,8 +313,10 @@ pub const Token = union(enum) { |
| 309 | target: []const u8, | 313 | target: []const u8, |
| 310 | target_must_resolve: []const u8, | 314 | target_must_resolve: []const u8, |
| 311 | prereq: []const u8, | 315 | prereq: []const u8, |
| 312 | incomplete_quoted_prerequisite: []const u8, | 316 | |
| 313 | incomplete_target: []const u8, | 317 | incomplete_quoted_prerequisite: IndexAndBytes, |
| | 318 | incomplete_target: IndexAndBytes, |
| | 319 | |
| 314 | invalid_target: IndexAndChar, | 320 | invalid_target: IndexAndChar, |
| 315 | bad_target_escape: IndexAndChar, | 321 | bad_target_escape: IndexAndChar, |
| 316 | expected_dollar_sign: IndexAndChar, | 322 | expected_dollar_sign: IndexAndChar, |
| ... | @@ -322,11 +328,15 @@ pub const Token = union(enum) { | ... | @@ -322,11 +328,15 @@ pub const Token = union(enum) { |
| 322 | char: u8, | 328 | char: u8, |
| 323 | }; | 329 | }; |
| 324 | | 330 | |
| | 331 | pub const IndexAndBytes = struct { |
| | 332 | index: usize, |
| | 333 | bytes: []const u8, |
| | 334 | }; |
| | 335 | |
| 325 | /// Resolve escapes in target. Only valid with .target_must_resolve. | 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 | const bytes = self.target_must_resolve; // resolve called on incorrect token | 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 | var state: enum { start, escape, dollar } = .start; | 340 | var state: enum { start, escape, dollar } = .start; |
| 331 | for (bytes) |c| { | 341 | for (bytes) |c| { |
| 332 | switch (state) { | 342 | switch (state) { |
| ... | @@ -334,33 +344,74 @@ pub const Token = union(enum) { | ... | @@ -334,33 +344,74 @@ pub const Token = union(enum) { |
| 334 | switch (c) { | 344 | switch (c) { |
| 335 | '\\' => state = .escape, | 345 | '\\' => state = .escape, |
| 336 | '$' => state = .dollar, | 346 | '$' => state = .dollar, |
| 337 | else => buf.appendAssumeCapacity(c), | 347 | else => try writer.writeByte(c), |
| 338 | } | 348 | } |
| 339 | }, | 349 | }, |
| 340 | .escape => { | 350 | .escape => { |
| 341 | switch (c) { | 351 | switch (c) { |
| 342 | ' ', '#', '\\' => {}, | 352 | ' ', '#', '\\' => {}, |
| 343 | '$' => { | 353 | '$' => { |
| 344 | buf.appendAssumeCapacity('\\'); | 354 | try writer.writeByte('\\'); |
| 345 | state = .dollar; | 355 | state = .dollar; |
| 346 | continue; | 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 | state = .start; | 361 | state = .start; |
| 352 | }, | 362 | }, |
| 353 | .dollar => { | 363 | .dollar => { |
| 354 | buf.appendAssumeCapacity('$'); | 364 | try writer.writeByte('$'); |
| 355 | switch (c) { | 365 | switch (c) { |
| 356 | '$' => {}, | 366 | '$' => {}, |
| 357 | else => buf.appendAssumeCapacity(c), | 367 | else => try writer.writeByte(c), |
| 358 | } | 368 | } |
| 359 | state = .start; | 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 | test "empty file" { | 417 | test "empty file" { |
| ... | @@ -755,16 +806,16 @@ test "error incomplete target" { | ... | @@ -755,16 +806,16 @@ test "error incomplete target" { |
| 755 | ); | 806 | ); |
| 756 | | 807 | |
| 757 | try depTokenizer("\\ foo.o", | 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 | try depTokenizer("\\#foo.o", | 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 | try depTokenizer("\\\\foo.o", | 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 | try depTokenizer("$$foo.o", | 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,7 +913,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 862 | }, | 913 | }, |
| 863 | .target_must_resolve => { | 914 | .target_must_resolve => { |
| 864 | try buffer.appendSlice("target = {"); | 915 | try buffer.appendSlice("target = {"); |
| 865 | try token.resolve(&resolve_buf); | 916 | try token.resolve(resolve_buf.writer()); |
| 866 | for (resolve_buf.items) |b| { | 917 | for (resolve_buf.items) |b| { |
| 867 | try buffer.append(printable_char_tab[b]); | 918 | try buffer.append(printable_char_tab[b]); |
| 868 | } | 919 | } |
| ... | @@ -870,7 +921,9 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { | ... | @@ -870,7 +921,9 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 870 | try buffer.appendSlice("}"); | 921 | try buffer.appendSlice("}"); |
| 871 | }, | 922 | }, |
| 872 | else => { | 923 | else => { |
| 873 | @panic("TODO"); | 924 | try buffer.appendSlice("ERROR: "); |
| | 925 | try token.printError(buffer.outStream()); |
| | 926 | break; |
| 874 | }, | 927 | }, |
| 875 | } | 928 | } |
| 876 | i += 1; | 929 | i += 1; |
| ... | @@ -1005,23 +1058,19 @@ fn printCharValues(out: anytype, bytes: []const u8) !void { | ... | @@ -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 | if (!std.ascii.isPrint(char) or char == ' ') { | 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 | } else { | 1064 | } else { |
| 1012 | try buffer.appendSlice("'"); | 1065 | try out.print("'{c}'", .{printable_char_tab[char]}); |
| 1013 | try buffer.append(printable_char_tab[char]); | | |
| 1014 | try buffer.appendSlice("'"); | | |
| 1015 | } | 1066 | } |
| 1016 | } | 1067 | } |
| 1017 | | 1068 | |
| 1018 | // zig fmt: off | 1069 | // zig fmt: off |
| 1019 | const printable_char_tab: []const u8 = | 1070 | const printable_char_tab: [256]u8 = ( |
| 1020 | "................................ !\"#$%&'()*+,-./0123456789:;<=>?" ++ | 1071 | "................................ !\"#$%&'()*+,-./0123456789:;<=>?" ++ |
| 1021 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~." ++ | 1072 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~." ++ |
| 1022 | "................................................................" ++ | 1073 | "................................................................" ++ |
| 1023 | "................................................................"; | 1074 | "................................................................" |
| 1024 | // zig fmt: on | 1075 | ).*; |
| 1025 | comptime { | 1076 | |
| 1026 | assert(printable_char_tab.len == 256); | | |
| 1027 | } | | |