| ... | @@ -25,7 +25,7 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -25,7 +25,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 25 | }, | 25 | }, |
| 26 | }, | 26 | }, |
| 27 | .target => switch (char) { | 27 | .target => switch (char) { |
| 28 | '\t', '\n', '\r', ' ' => { | 28 | '\n', '\r' => { |
| 29 | return errorIllegalChar(.invalid_target, self.index, char); | 29 | return errorIllegalChar(.invalid_target, self.index, char); |
| 30 | }, | 30 | }, |
| 31 | '$' => { | 31 | '$' => { |
| ... | @@ -40,6 +40,15 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -40,6 +40,15 @@ pub fn next(self: *Tokenizer) ?Token { |
| 40 | self.state = .target_colon; | 40 | self.state = .target_colon; |
| 41 | self.index += 1; | 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 | else => { | 52 | else => { |
| 44 | self.index += 1; | 53 | self.index += 1; |
| 45 | }, | 54 | }, |
| ... | @@ -110,6 +119,19 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -110,6 +119,19 @@ pub fn next(self: *Tokenizer) ?Token { |
| 110 | self.state = .target; | 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 | .rhs => switch (char) { | 135 | .rhs => switch (char) { |
| 114 | '\t', ' ' => { | 136 | '\t', ' ' => { |
| 115 | // silently ignore horizontal whitespace | 137 | // silently ignore horizontal whitespace |
| ... | @@ -256,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { | ... | @@ -256,6 +278,10 @@ pub fn next(self: *Tokenizer) ?Token { |
| 256 | self.state = .lhs; | 278 | self.state = .lhs; |
| 257 | return null; | 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 | .prereq_quote => { | 285 | .prereq_quote => { |
| 260 | return errorPosition(.incomplete_quoted_prerequisite, start, self.bytes[start..]); | 286 | return errorPosition(.incomplete_quoted_prerequisite, start, self.bytes[start..]); |
| 261 | }, | 287 | }, |
| ... | @@ -299,6 +325,7 @@ const State = enum { | ... | @@ -299,6 +325,7 @@ const State = enum { |
| 299 | target_dollar_sign, | 325 | target_dollar_sign, |
| 300 | target_colon, | 326 | target_colon, |
| 301 | target_colon_reverse_solidus, | 327 | target_colon_reverse_solidus, |
| | 328 | target_space, |
| 302 | rhs, | 329 | rhs, |
| 303 | rhs_continuation, | 330 | rhs_continuation, |
| 304 | rhs_continuation_linefeed, | 331 | rhs_continuation_linefeed, |
| ... | @@ -322,6 +349,7 @@ pub const Token = union(enum) { | ... | @@ -322,6 +349,7 @@ pub const Token = union(enum) { |
| 322 | expected_dollar_sign: IndexAndChar, | 349 | expected_dollar_sign: IndexAndChar, |
| 323 | continuation_eol: IndexAndChar, | 350 | continuation_eol: IndexAndChar, |
| 324 | incomplete_escape: IndexAndChar, | 351 | incomplete_escape: IndexAndChar, |
| | 352 | expected_colon: IndexAndChar, |
| 325 | | 353 | |
| 326 | pub const IndexAndChar = struct { | 354 | pub const IndexAndChar = struct { |
| 327 | index: usize, | 355 | index: usize, |
| ... | @@ -420,6 +448,7 @@ pub const Token = union(enum) { | ... | @@ -420,6 +448,7 @@ pub const Token = union(enum) { |
| 420 | .expected_dollar_sign, | 448 | .expected_dollar_sign, |
| 421 | .continuation_eol, | 449 | .continuation_eol, |
| 422 | .incomplete_escape, | 450 | .incomplete_escape, |
| | 451 | .expected_colon, |
| 423 | => |index_and_char| { | 452 | => |index_and_char| { |
| 424 | try writer.writeAll("illegal char "); | 453 | try writer.writeAll("illegal char "); |
| 425 | try printUnderstandableChar(writer, index_and_char.char); | 454 | try printUnderstandableChar(writer, index_and_char.char); |
| ... | @@ -438,6 +467,7 @@ pub const Token = union(enum) { | ... | @@ -438,6 +467,7 @@ pub const Token = union(enum) { |
| 438 | .expected_dollar_sign => "expecting '$'", | 467 | .expected_dollar_sign => "expecting '$'", |
| 439 | .continuation_eol => "continuation expecting end-of-line", | 468 | .continuation_eol => "continuation expecting end-of-line", |
| 440 | .incomplete_escape => "incomplete escape", | 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,6 +575,16 @@ test "empty target linefeeds + hspace + continuations" { |
| 545 | , expect); | 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 | test "prereq" { | 588 | test "prereq" { |
| 549 | const expect = | 589 | const expect = |
| 550 | \\target = {foo.o} | 590 | \\target = {foo.o} |
| ... | @@ -923,9 +963,6 @@ test "error illegal char at position - expecting dollar_sign" { | ... | @@ -923,9 +963,6 @@ test "error illegal char at position - expecting dollar_sign" { |
| 923 | } | 963 | } |
| 924 | | 964 | |
| 925 | test "error illegal char at position - invalid target" { | 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 | try depTokenizer("foo\n.o", | 966 | try depTokenizer("foo\n.o", |
| 930 | \\ERROR: illegal char \x0A at position 3: invalid target | 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,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 | // - tokenize input, emit textual representation, and compare to expect | 1022 | // - tokenize input, emit textual representation, and compare to expect |
| 967 | fn depTokenizer(input: []const u8, expect: []const u8) !void { | 1023 | fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 968 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); | 1024 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); |