| ... | ... | @@ -158,7 +158,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 158 | 158 | '"' => { |
| 159 | 159 | self.index += 1; |
| 160 | 160 | self.state = .rhs; |
| 161 | | return Token{ .prereq = self.bytes[start .. self.index - 1] }; |
| 161 | return finishPrereq(must_resolve, self.bytes[start .. self.index - 1]); |
| 162 | 162 | }, |
| 163 | 163 | else => { |
| 164 | 164 | self.index += 1; |
| ... | ... | @@ -167,11 +167,11 @@ pub fn next(self: *Tokenizer) ?Token { |
| 167 | 167 | .prereq => switch (char) { |
| 168 | 168 | '\t', ' ' => { |
| 169 | 169 | self.state = .rhs; |
| 170 | | return Token{ .prereq = self.bytes[start..self.index] }; |
| 170 | return finishPrereq(must_resolve, self.bytes[start..self.index]); |
| 171 | 171 | }, |
| 172 | 172 | '\n', '\r' => { |
| 173 | 173 | self.state = .lhs; |
| 174 | | return Token{ .prereq = self.bytes[start..self.index] }; |
| 174 | return finishPrereq(must_resolve, self.bytes[start..self.index]); |
| 175 | 175 | }, |
| 176 | 176 | '\\' => { |
| 177 | 177 | self.state = .prereq_continuation; |
| ... | ... | @@ -185,12 +185,22 @@ pub fn next(self: *Tokenizer) ?Token { |
| 185 | 185 | '\n' => { |
| 186 | 186 | self.index += 1; |
| 187 | 187 | self.state = .rhs; |
| 188 | | return Token{ .prereq = self.bytes[start .. self.index - 2] }; |
| 188 | return finishPrereq(must_resolve, self.bytes[start .. self.index - 2]); |
| 189 | 189 | }, |
| 190 | 190 | '\r' => { |
| 191 | 191 | self.state = .prereq_continuation_linefeed; |
| 192 | 192 | self.index += 1; |
| 193 | 193 | }, |
| 194 | '\\' => { |
| 195 | // The previous \ wasn't a continuation, but this one might be. |
| 196 | self.index += 1; |
| 197 | }, |
| 198 | ' ' => { |
| 199 | // not continuation, but escaped space must be resolved |
| 200 | must_resolve = true; |
| 201 | self.state = .prereq; |
| 202 | self.index += 1; |
| 203 | }, |
| 194 | 204 | else => { |
| 195 | 205 | // not continuation |
| 196 | 206 | self.state = .prereq; |
| ... | ... | @@ -201,7 +211,7 @@ pub fn next(self: *Tokenizer) ?Token { |
| 201 | 211 | '\n' => { |
| 202 | 212 | self.index += 1; |
| 203 | 213 | self.state = .rhs; |
| 204 | | return Token{ .prereq = self.bytes[start .. self.index - 1] }; |
| 214 | return finishPrereq(must_resolve, self.bytes[start .. self.index - 3]); |
| 205 | 215 | }, |
| 206 | 216 | else => { |
| 207 | 217 | return errorIllegalChar(.continuation_eol, self.index, char); |
| ... | ... | @@ -251,15 +261,15 @@ pub fn next(self: *Tokenizer) ?Token { |
| 251 | 261 | }, |
| 252 | 262 | .prereq => { |
| 253 | 263 | self.state = .lhs; |
| 254 | | return Token{ .prereq = self.bytes[start..] }; |
| 264 | return finishPrereq(must_resolve, self.bytes[start..]); |
| 255 | 265 | }, |
| 256 | 266 | .prereq_continuation => { |
| 257 | 267 | self.state = .lhs; |
| 258 | | return Token{ .prereq = self.bytes[start .. self.index - 1] }; |
| 268 | return finishPrereq(must_resolve, self.bytes[start .. self.index - 1]); |
| 259 | 269 | }, |
| 260 | 270 | .prereq_continuation_linefeed => { |
| 261 | 271 | self.state = .lhs; |
| 262 | | return Token{ .prereq = self.bytes[start .. self.index - 2] }; |
| 272 | return finishPrereq(must_resolve, self.bytes[start .. self.index - 2]); |
| 263 | 273 | }, |
| 264 | 274 | } |
| 265 | 275 | } |
| ... | ... | @@ -278,6 +288,10 @@ fn finishTarget(must_resolve: bool, bytes: []const u8) Token { |
| 278 | 288 | return if (must_resolve) .{ .target_must_resolve = bytes } else .{ .target = bytes }; |
| 279 | 289 | } |
| 280 | 290 | |
| 291 | fn finishPrereq(must_resolve: bool, bytes: []const u8) Token { |
| 292 | return if (must_resolve) .{ .prereq_must_resolve = bytes } else .{ .prereq = bytes }; |
| 293 | } |
| 294 | |
| 281 | 295 | const State = enum { |
| 282 | 296 | lhs, |
| 283 | 297 | target, |
| ... | ... | @@ -298,6 +312,7 @@ pub const Token = union(enum) { |
| 298 | 312 | target: []const u8, |
| 299 | 313 | target_must_resolve: []const u8, |
| 300 | 314 | prereq: []const u8, |
| 315 | prereq_must_resolve: []const u8, |
| 301 | 316 | |
| 302 | 317 | incomplete_quoted_prerequisite: IndexAndBytes, |
| 303 | 318 | incomplete_target: IndexAndBytes, |
| ... | ... | @@ -318,48 +333,76 @@ pub const Token = union(enum) { |
| 318 | 333 | bytes: []const u8, |
| 319 | 334 | }; |
| 320 | 335 | |
| 321 | | /// Resolve escapes in target. Only valid with .target_must_resolve. |
| 336 | /// Resolve escapes in target or prereq. Only valid with .target_must_resolve or .prereq_must_resolve. |
| 322 | 337 | pub fn resolve(self: Token, writer: anytype) @TypeOf(writer).Error!void { |
| 323 | | const bytes = self.target_must_resolve; // resolve called on incorrect token |
| 324 | | |
| 325 | | var state: enum { start, escape, dollar } = .start; |
| 326 | | for (bytes) |c| { |
| 327 | | switch (state) { |
| 328 | | .start => { |
| 329 | | switch (c) { |
| 330 | | '\\' => state = .escape, |
| 331 | | '$' => state = .dollar, |
| 332 | | else => try writer.writeByte(c), |
| 333 | | } |
| 334 | | }, |
| 335 | | .escape => { |
| 336 | | switch (c) { |
| 337 | | ' ', '#', '\\' => {}, |
| 338 | | '$' => { |
| 339 | | try writer.writeByte('\\'); |
| 340 | | state = .dollar; |
| 341 | | continue; |
| 338 | switch (self) { |
| 339 | .target_must_resolve => |bytes| { |
| 340 | var state: enum { start, escape, dollar } = .start; |
| 341 | for (bytes) |c| { |
| 342 | switch (state) { |
| 343 | .start => { |
| 344 | switch (c) { |
| 345 | '\\' => state = .escape, |
| 346 | '$' => state = .dollar, |
| 347 | else => try writer.writeByte(c), |
| 348 | } |
| 349 | }, |
| 350 | .escape => { |
| 351 | switch (c) { |
| 352 | ' ', '#', '\\' => {}, |
| 353 | '$' => { |
| 354 | try writer.writeByte('\\'); |
| 355 | state = .dollar; |
| 356 | continue; |
| 357 | }, |
| 358 | else => try writer.writeByte('\\'), |
| 359 | } |
| 360 | try writer.writeByte(c); |
| 361 | state = .start; |
| 362 | }, |
| 363 | .dollar => { |
| 364 | try writer.writeByte('$'); |
| 365 | switch (c) { |
| 366 | '$' => {}, |
| 367 | else => try writer.writeByte(c), |
| 368 | } |
| 369 | state = .start; |
| 342 | 370 | }, |
| 343 | | else => try writer.writeByte('\\'), |
| 344 | 371 | } |
| 345 | | try writer.writeByte(c); |
| 346 | | state = .start; |
| 347 | | }, |
| 348 | | .dollar => { |
| 349 | | try writer.writeByte('$'); |
| 350 | | switch (c) { |
| 351 | | '$' => {}, |
| 352 | | else => try writer.writeByte(c), |
| 372 | } |
| 373 | }, |
| 374 | .prereq_must_resolve => |bytes| { |
| 375 | var state: enum { start, escape } = .start; |
| 376 | for (bytes) |c| { |
| 377 | switch (state) { |
| 378 | .start => { |
| 379 | switch (c) { |
| 380 | '\\' => state = .escape, |
| 381 | else => try writer.writeByte(c), |
| 382 | } |
| 383 | }, |
| 384 | .escape => { |
| 385 | switch (c) { |
| 386 | ' ' => {}, |
| 387 | '\\' => { |
| 388 | try writer.writeByte(c); |
| 389 | continue; |
| 390 | }, |
| 391 | else => try writer.writeByte('\\'), |
| 392 | } |
| 393 | try writer.writeByte(c); |
| 394 | state = .start; |
| 395 | }, |
| 353 | 396 | } |
| 354 | | state = .start; |
| 355 | | }, |
| 356 | | } |
| 397 | } |
| 398 | }, |
| 399 | else => unreachable, |
| 357 | 400 | } |
| 358 | 401 | } |
| 359 | 402 | |
| 360 | 403 | pub fn printError(self: Token, writer: anytype) @TypeOf(writer).Error!void { |
| 361 | 404 | switch (self) { |
| 362 | | .target, .target_must_resolve, .prereq => unreachable, // not an error |
| 405 | .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error |
| 363 | 406 | .incomplete_quoted_prerequisite, |
| 364 | 407 | .incomplete_target, |
| 365 | 408 | => |index_and_bytes| { |
| ... | ... | @@ -387,7 +430,7 @@ pub const Token = union(enum) { |
| 387 | 430 | |
| 388 | 431 | fn errStr(self: Token) []const u8 { |
| 389 | 432 | return switch (self) { |
| 390 | | .target, .target_must_resolve, .prereq => unreachable, // not an error |
| 433 | .target, .target_must_resolve, .prereq, .prereq_must_resolve => unreachable, // not an error |
| 391 | 434 | .incomplete_quoted_prerequisite => "incomplete quoted prerequisite", |
| 392 | 435 | .incomplete_target => "incomplete target", |
| 393 | 436 | .invalid_target => "invalid target", |
| ... | ... | @@ -538,6 +581,15 @@ test "prereq continuation" { |
| 538 | 581 | , expect); |
| 539 | 582 | } |
| 540 | 583 | |
| 584 | test "prereq continuation (CRLF)" { |
| 585 | const expect = |
| 586 | \\target = {foo.o} |
| 587 | \\prereq = {foo.h} |
| 588 | \\prereq = {bar.h} |
| 589 | ; |
| 590 | try depTokenizer("foo.o: foo.h\\\r\nbar.h", expect); |
| 591 | } |
| 592 | |
| 541 | 593 | test "multiple prereqs" { |
| 542 | 594 | const expect = |
| 543 | 595 | \\target = {foo.o} |
| ... | ... | @@ -728,6 +780,32 @@ test "windows funky targets" { |
| 728 | 780 | ); |
| 729 | 781 | } |
| 730 | 782 | |
| 783 | test "windows funky prereqs" { |
| 784 | // Note we don't support unquoted escaped spaces at the very beginning of a relative path |
| 785 | // e.g. `\ SpaceAtTheBeginning.c` |
| 786 | // This typically wouldn't be seen in the wild, since depfiles usually use absolute paths |
| 787 | // and supporting it would degrade error messages for cases where it was meant to be a |
| 788 | // continuation, but the line ending is missing. |
| 789 | try depTokenizer( |
| 790 | \\cimport.o: \ |
| 791 | \\ trailingbackslash\\ |
| 792 | \\ C:\Users\John\ Smith\AppData\Local\zig\p\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\cimport.c \ |
| 793 | \\ somedir\\ a.c\ |
| 794 | \\ somedir/\ a.c\ |
| 795 | \\ somedir\\ \ \ b.c\ |
| 796 | \\ somedir\\ \\ \c.c\ |
| 797 | \\ |
| 798 | , |
| 799 | \\target = {cimport.o} |
| 800 | \\prereq = {trailingbackslash\} |
| 801 | \\prereq = {C:\Users\John Smith\AppData\Local\zig\p\1220d14057af1a9d6dde4643293527bd5ee5099517d655251a066666a4320737ea7c\cimport.c} |
| 802 | \\prereq = {somedir\ a.c} |
| 803 | \\prereq = {somedir/ a.c} |
| 804 | \\prereq = {somedir\ b.c} |
| 805 | \\prereq = {somedir\ \ \c.c} |
| 806 | ); |
| 807 | } |
| 808 | |
| 731 | 809 | test "windows drive and forward slashes" { |
| 732 | 810 | try depTokenizer( |
| 733 | 811 | \\C:/msys64/what/zig-cache\tmp\48ac4d78dd531abd-cxa_thread_atexit.obj: \ |
| ... | ... | @@ -915,6 +993,15 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { |
| 915 | 993 | resolve_buf.items.len = 0; |
| 916 | 994 | try buffer.appendSlice("}"); |
| 917 | 995 | }, |
| 996 | .prereq_must_resolve => { |
| 997 | try buffer.appendSlice("prereq = {"); |
| 998 | try token.resolve(resolve_buf.writer()); |
| 999 | for (resolve_buf.items) |b| { |
| 1000 | try buffer.append(printable_char_tab[b]); |
| 1001 | } |
| 1002 | resolve_buf.items.len = 0; |
| 1003 | try buffer.appendSlice("}"); |
| 1004 | }, |
| 918 | 1005 | else => { |
| 919 | 1006 | try buffer.appendSlice("ERROR: "); |
| 920 | 1007 | try token.printError(buffer.writer()); |