| ... | ... | @@ -25,7 +25,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 25 | 25 | }, |
| 26 | 26 | }, |
| 27 | 27 | .target => switch (char) { |
| 28 | | '\t', '\n', '\r', ' ' => { |
| 28 | '\n', '\r' => { |
| 29 | 29 | return errorIllegalChar(.invalid_target, self.index, char); |
| 30 | 30 | }, |
| 31 | 31 | '$' => { |
| ... | ... | @@ -40,6 +40,15 @@ pub fn next(self: *Tokenizer) ?Token { |
| 40 | 40 | self.state = .target_colon; |
| 41 | 41 | self.index += 1; |
| 42 | 42 | }, |
| 43 | '\t', ' ' => { |
| 44 | self.state = .target_space; |
| 45 | |
| 46 | const bytes = self.bytes[start..self.index]; |
| 47 | std.debug.assert(bytes.len != 0); |
| 48 | self.index += 1; |
| 49 | |
| 50 | return finishTarget(must_resolve, bytes); |
| 51 | }, |
| 43 | 52 | else => { |
| 44 | 53 | self.index += 1; |
| 45 | 54 | }, |
| ... | ... | @@ -110,6 +119,19 @@ pub fn next(self: *Tokenizer) ?Token { |
| 110 | 119 | self.state = .target; |
| 111 | 120 | }, |
| 112 | 121 | }, |
| 122 | .target_space => switch (char) { |
| 123 | '\t', ' ' => { |
| 124 | // silently ignore additional horizontal whitespace |
| 125 | self.index += 1; |
| 126 | }, |
| 127 | ':' => { |
| 128 | self.state = .rhs; |
| 129 | self.index += 1; |
| 130 | }, |
| 131 | else => { |
| 132 | return errorIllegalChar(.expected_colon, self.index, char); |
| 133 | }, |
| 134 | }, |
| 113 | 135 | .rhs => switch (char) { |
| 114 | 136 | '\t', ' ' => { |
| 115 | 137 | // silently ignore horizontal whitespace |
| ... | ... | @@ -256,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { |
| 256 | 278 | self.state = .lhs; |
| 257 | 279 | return null; |
| 258 | 280 | }, |
| 281 | .target_space => { |
| 282 | const idx = self.index - 1; |
| 283 | return errorIllegalChar(.expected_colon, idx, self.bytes[idx]); |
| 284 | }, |
| 259 | 285 | .prereq_quote => { |
| 260 | 286 | return errorPosition(.incomplete_quoted_prerequisite, start, self.bytes[start..]); |
| 261 | 287 | }, |
| ... | ... | @@ -299,6 +325,7 @@ const State = enum { |
| 299 | 325 | target_dollar_sign, |
| 300 | 326 | target_colon, |
| 301 | 327 | target_colon_reverse_solidus, |
| 328 | target_space, |
| 302 | 329 | rhs, |
| 303 | 330 | rhs_continuation, |
| 304 | 331 | rhs_continuation_linefeed, |
| ... | ... | @@ -322,6 +349,7 @@ pub const Token = union(enum) { |
| 322 | 349 | expected_dollar_sign: IndexAndChar, |
| 323 | 350 | continuation_eol: IndexAndChar, |
| 324 | 351 | incomplete_escape: IndexAndChar, |
| 352 | expected_colon: IndexAndChar, |
| 325 | 353 | |
| 326 | 354 | pub const IndexAndChar = struct { |
| 327 | 355 | index: usize, |
| ... | ... | @@ -420,6 +448,7 @@ pub const Token = union(enum) { |
| 420 | 448 | .expected_dollar_sign, |
| 421 | 449 | .continuation_eol, |
| 422 | 450 | .incomplete_escape, |
| 451 | .expected_colon, |
| 423 | 452 | => |index_and_char| { |
| 424 | 453 | try writer.writeAll("illegal char "); |
| 425 | 454 | try printUnderstandableChar(writer, index_and_char.char); |
| ... | ... | @@ -438,6 +467,7 @@ pub const Token = union(enum) { |
| 438 | 467 | .expected_dollar_sign => "expecting '$'", |
| 439 | 468 | .continuation_eol => "continuation expecting end-of-line", |
| 440 | 469 | .incomplete_escape => "incomplete escape", |
| 470 | .expected_colon => "expecting ':'", |
| 441 | 471 | }; |
| 442 | 472 | } |
| 443 | 473 | }; |
| ... | ... | @@ -545,6 +575,16 @@ test "empty target linefeeds + hspace + continuations" { |
| 545 | 575 | , expect); |
| 546 | 576 | } |
| 547 | 577 | |
| 578 | test "empty target + hspace + colon" { |
| 579 | const expect = "target = {foo.o}"; |
| 580 | |
| 581 | try depTokenizer("foo.o :", expect); |
| 582 | try depTokenizer("foo.o\t\t\t:", expect); |
| 583 | try depTokenizer("foo.o \t \t :", expect); |
| 584 | try depTokenizer("\r\nfoo.o :", expect); |
| 585 | try depTokenizer(" foo.o :", expect); |
| 586 | } |
| 587 | |
| 548 | 588 | test "prereq" { |
| 549 | 589 | const expect = |
| 550 | 590 | \\target = {foo.o} |
| ... | ... | @@ -923,9 +963,6 @@ test "error illegal char at position - expecting dollar_sign" { |
| 923 | 963 | } |
| 924 | 964 | |
| 925 | 965 | test "error illegal char at position - invalid target" { |
| 926 | | try depTokenizer("foo\t.o", |
| 927 | | \\ERROR: illegal char \x09 at position 3: invalid target |
| 928 | | ); |
| 929 | 966 | try depTokenizer("foo\n.o", |
| 930 | 967 | \\ERROR: illegal char \x0A at position 3: invalid target |
| 931 | 968 | ); |
| ... | ... | @@ -963,6 +1000,25 @@ test "error prereq - continuation expecting end-of-line" { |
| 963 | 1000 | ); |
| 964 | 1001 | } |
| 965 | 1002 | |
| 1003 | test "error illegal char at position - expecting colon" { |
| 1004 | try depTokenizer("foo\t.o:", |
| 1005 | \\target = {foo} |
| 1006 | \\ERROR: illegal char '.' at position 4: expecting ':' |
| 1007 | ); |
| 1008 | try depTokenizer("foo .o:", |
| 1009 | \\target = {foo} |
| 1010 | \\ERROR: illegal char '.' at position 4: expecting ':' |
| 1011 | ); |
| 1012 | try depTokenizer("foo \n.o:", |
| 1013 | \\target = {foo} |
| 1014 | \\ERROR: illegal char \x0A at position 4: expecting ':' |
| 1015 | ); |
| 1016 | try depTokenizer("foo.o\t\n:", |
| 1017 | \\target = {foo.o} |
| 1018 | \\ERROR: illegal char \x0A at position 6: expecting ':' |
| 1019 | ); |
| 1020 | } |
| 1021 | |
| 966 | 1022 | // - tokenize input, emit textual representation, and compare to expect |
| 967 | 1023 | fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 968 | 1024 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); |