github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/depfile_parser.go (about) 1 // Code generated by re2c, DO NOT EDIT. 2 // Copyright 2011 Google Inc. All Rights Reserved. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package nin 17 18 import "errors" 19 20 // DepfileParser is the parser for the dependency information emitted by gcc's 21 // -M flags. 22 type DepfileParser struct { 23 outs []string 24 ins []string 25 } 26 27 // Parse parses a dependency file. 28 // 29 // content must contain a terminating zero byte. 30 // 31 // Warning: mutate the slice content in-place. 32 // 33 // A note on backslashes in Makefiles, from reading the docs: 34 // Backslash-newline is the line continuation character. 35 // Backslash-# escapes a # (otherwise meaningful as a comment start). 36 // Backslash-% escapes a % (otherwise meaningful as a special). 37 // Finally, quoting the GNU manual, "Backslashes that are not in danger 38 // of quoting ‘%’ characters go unmolested." 39 // How do you end a line with a backslash? The netbsd Make docs suggest 40 // reading the result of a shell command echoing a backslash! 41 // 42 // Rather than implement all of above, we follow what GCC/Clang produces: 43 // Backslashes escape a space or hash sign. 44 // When a space is preceded by 2N+1 backslashes, it is represents N backslashes 45 // followed by space. 46 // When a space is preceded by 2N backslashes, it represents 2N backslashes at 47 // the end of a filename. 48 // A hash sign is escaped by a single backslash. All other backslashes remain 49 // unchanged. 50 // 51 // If anyone actually has depfiles that rely on the more complicated 52 // behavior we can adjust this. 53 func (d *DepfileParser) Parse(content []byte) error { 54 // in: current parser input point. 55 // end: end of input. 56 // parsingTargets: whether we are parsing targets or dependencies. 57 in := 0 58 end := len(content) 59 if end > 0 && content[len(content)-1] != 0 { 60 panic("internal error") 61 } 62 haveTarget := false 63 parsingTargets := true 64 poisonedInput := false 65 for in < end { 66 haveNewline := false 67 // out: current output point (typically same as in, but can fall behind 68 // as we de-escape backslashes). 69 out := in 70 // filename: start of the current parsed filename. 71 filename := out 72 backup := 0 73 for { 74 // start: beginning of the current parsed span. 75 start := in 76 yymarker := 0 77 /* 78 re2c:define:YYCTYPE = "byte"; 79 re2c:define:YYCURSOR = "l.input[p]"; 80 re2c:define:YYMARKER = q; 81 re2c:yyfill:enable = 0; 82 re2c:flags:nested-ifs = 1; 83 */ 84 85 { 86 var yych byte 87 yych = content[in] 88 switch yych { 89 case 0x00: 90 goto yy2 91 case 0x01: 92 fallthrough 93 case 0x02: 94 fallthrough 95 case 0x03: 96 fallthrough 97 case 0x04: 98 fallthrough 99 case 0x05: 100 fallthrough 101 case 0x06: 102 fallthrough 103 case 0x07: 104 fallthrough 105 case 0x08: 106 fallthrough 107 case '\t': 108 fallthrough 109 case '\v': 110 fallthrough 111 case '\f': 112 fallthrough 113 case 0x0E: 114 fallthrough 115 case 0x0F: 116 fallthrough 117 case 0x10: 118 fallthrough 119 case 0x11: 120 fallthrough 121 case 0x12: 122 fallthrough 123 case 0x13: 124 fallthrough 125 case 0x14: 126 fallthrough 127 case 0x15: 128 fallthrough 129 case 0x16: 130 fallthrough 131 case 0x17: 132 fallthrough 133 case 0x18: 134 fallthrough 135 case 0x19: 136 fallthrough 137 case 0x1A: 138 fallthrough 139 case 0x1B: 140 fallthrough 141 case 0x1C: 142 fallthrough 143 case 0x1D: 144 fallthrough 145 case 0x1E: 146 fallthrough 147 case 0x1F: 148 fallthrough 149 case ' ': 150 fallthrough 151 case '"': 152 fallthrough 153 case '#': 154 fallthrough 155 case '&': 156 fallthrough 157 case '\'': 158 fallthrough 159 case '*': 160 fallthrough 161 case ';': 162 fallthrough 163 case '<': 164 fallthrough 165 case '>': 166 fallthrough 167 case '?': 168 fallthrough 169 case '^': 170 fallthrough 171 case '`': 172 fallthrough 173 case '|': 174 fallthrough 175 case 0x7F: 176 goto yy4 177 case '\n': 178 goto yy6 179 case '\r': 180 goto yy8 181 case '$': 182 goto yy12 183 case '\\': 184 goto yy13 185 default: 186 goto yy9 187 } 188 yy2: 189 in++ 190 { 191 break 192 } 193 yy4: 194 in++ 195 yy5: 196 { 197 // For any other character (e.g. whitespace), swallow it here, 198 // allowing the outer logic to loop around again. 199 break 200 } 201 yy6: 202 in++ 203 { 204 // A newline ends the current file name and the current rule. 205 haveNewline = true 206 break 207 } 208 yy8: 209 in++ 210 yych = content[in] 211 switch yych { 212 case '\n': 213 goto yy6 214 default: 215 goto yy5 216 } 217 yy9: 218 in++ 219 yych = content[in] 220 switch yych { 221 case 0x00: 222 fallthrough 223 case 0x01: 224 fallthrough 225 case 0x02: 226 fallthrough 227 case 0x03: 228 fallthrough 229 case 0x04: 230 fallthrough 231 case 0x05: 232 fallthrough 233 case 0x06: 234 fallthrough 235 case 0x07: 236 fallthrough 237 case 0x08: 238 fallthrough 239 case '\t': 240 fallthrough 241 case '\n': 242 fallthrough 243 case '\v': 244 fallthrough 245 case '\f': 246 fallthrough 247 case '\r': 248 fallthrough 249 case 0x0E: 250 fallthrough 251 case 0x0F: 252 fallthrough 253 case 0x10: 254 fallthrough 255 case 0x11: 256 fallthrough 257 case 0x12: 258 fallthrough 259 case 0x13: 260 fallthrough 261 case 0x14: 262 fallthrough 263 case 0x15: 264 fallthrough 265 case 0x16: 266 fallthrough 267 case 0x17: 268 fallthrough 269 case 0x18: 270 fallthrough 271 case 0x19: 272 fallthrough 273 case 0x1A: 274 fallthrough 275 case 0x1B: 276 fallthrough 277 case 0x1C: 278 fallthrough 279 case 0x1D: 280 fallthrough 281 case 0x1E: 282 fallthrough 283 case 0x1F: 284 fallthrough 285 case ' ': 286 fallthrough 287 case '"': 288 fallthrough 289 case '#': 290 fallthrough 291 case '$': 292 fallthrough 293 case '&': 294 fallthrough 295 case '\'': 296 fallthrough 297 case '*': 298 fallthrough 299 case ';': 300 fallthrough 301 case '<': 302 fallthrough 303 case '>': 304 fallthrough 305 case '?': 306 fallthrough 307 case '\\': 308 fallthrough 309 case '^': 310 fallthrough 311 case '`': 312 fallthrough 313 case '|': 314 fallthrough 315 case 0x7F: 316 goto yy11 317 default: 318 goto yy9 319 } 320 yy11: 321 { 322 // Got a span of plain text. 323 l := in - start 324 // Need to shift it over if we're overwriting backslashes. 325 if out < start { 326 copy(content[out:out+l], content[start:start+l]) 327 } 328 out += l 329 continue 330 } 331 yy12: 332 in++ 333 yych = content[in] 334 switch yych { 335 case '$': 336 goto yy14 337 default: 338 goto yy5 339 } 340 yy13: 341 in++ 342 backup = yymarker 343 yych = content[in] 344 switch yych { 345 case 0x00: 346 goto yy5 347 case '\n': 348 goto yy17 349 case '\r': 350 goto yy19 351 case ' ': 352 goto yy21 353 case '#': 354 goto yy23 355 case ':': 356 goto yy25 357 case '\\': 358 goto yy27 359 default: 360 goto yy16 361 } 362 yy14: 363 in++ 364 { 365 // De-escape dollar character. 366 content[out] = '$' 367 out++ 368 continue 369 } 370 yy16: 371 in++ 372 goto yy11 373 yy17: 374 in++ 375 { 376 // A line continuation ends the current file name. 377 break 378 } 379 yy19: 380 in++ 381 yych = content[in] 382 switch yych { 383 case '\n': 384 goto yy17 385 default: 386 goto yy20 387 } 388 yy20: 389 yymarker = backup 390 goto yy5 391 yy21: 392 in++ 393 { 394 // 2N+1 backslashes plus space -> N backslashes plus space. 395 l := in - start 396 n := l/2 - 1 397 if out < start { 398 for i := 0; i < n; i++ { 399 content[out+i] = '\\' 400 } 401 } 402 out += n 403 content[out] = ' ' 404 out++ 405 continue 406 } 407 yy23: 408 in++ 409 { 410 // De-escape hash sign, but preserve other leading backslashes. 411 l := in - start 412 if l > 2 && out < start { 413 for i := 0; i < l-2; i++ { 414 content[out+i] = '\\' 415 } 416 } 417 out += l - 2 418 content[out] = '#' 419 out++ 420 continue 421 } 422 yy25: 423 in++ 424 yych = content[in] 425 switch yych { 426 case 0x00: 427 fallthrough 428 case '\t': 429 fallthrough 430 case '\n': 431 fallthrough 432 case '\r': 433 fallthrough 434 case ' ': 435 goto yy28 436 default: 437 goto yy26 438 } 439 yy26: 440 { 441 // De-escape colon sign, but preserve other leading backslashes. 442 // Regular expression uses lookahead to make sure that no whitespace 443 // nor EOF follows. In that case it'd be the : at the end of a target 444 l := in - start 445 if l > 2 && out < start { 446 for i := 0; i < l-2; i++ { 447 content[out+i] = '\\' 448 } 449 } 450 out += l - 2 451 content[out] = ':' 452 out++ 453 continue 454 } 455 yy27: 456 in++ 457 yych = content[in] 458 switch yych { 459 case 0x00: 460 fallthrough 461 case '\n': 462 fallthrough 463 case '\r': 464 goto yy11 465 case ' ': 466 goto yy30 467 case '#': 468 goto yy23 469 case ':': 470 goto yy25 471 case '\\': 472 goto yy32 473 default: 474 goto yy16 475 } 476 yy28: 477 in++ 478 { 479 // Backslash followed by : and whitespace. 480 // It is therefore normal text and not an escaped colon 481 l := in - start - 1 482 // Need to shift it over if we're overwriting backslashes. 483 if out < start { 484 copy(content[out:out+l], content[start:start+l]) 485 } 486 out += l 487 if content[in-1] == '\n' { 488 haveNewline = true 489 } 490 break 491 } 492 yy30: 493 in++ 494 { 495 // 2N backslashes plus space -> 2N backslashes, end of filename. 496 l := in - start 497 if out < start { 498 for i := 0; i < l-1; i++ { 499 content[out+i] = '\\' 500 } 501 } 502 out += l - 1 503 break 504 } 505 yy32: 506 in++ 507 yych = content[in] 508 switch yych { 509 case 0x00: 510 fallthrough 511 case '\n': 512 fallthrough 513 case '\r': 514 goto yy11 515 case ' ': 516 goto yy21 517 case '#': 518 goto yy23 519 case ':': 520 goto yy25 521 case '\\': 522 goto yy27 523 default: 524 goto yy16 525 } 526 } 527 528 } 529 530 l := out - filename 531 isDependency := !parsingTargets 532 if l > 0 && content[filename+l-1] == ':' { 533 l-- // Strip off trailing colon, if any. 534 parsingTargets = false 535 haveTarget = true 536 } 537 538 if l > 0 { 539 piece := unsafeString(content[filename : filename+l]) 540 // If we've seen this as an input before, skip it. 541 // TODO(maruel): Use a map[string]struct{} while constructing. 542 pos := -1 543 for i, v := range d.ins { 544 if piece == v { 545 pos = i 546 break 547 } 548 } 549 if pos == -1 { 550 if isDependency { 551 if poisonedInput { 552 return errors.New("inputs may not also have inputs") 553 } 554 // New input. 555 d.ins = append(d.ins, piece) 556 } else { 557 // Check for a new output. 558 pos = -1 559 for i, v := range d.outs { 560 if piece == v { 561 pos = i 562 break 563 } 564 } 565 if pos == -1 { 566 d.outs = append(d.outs, piece) 567 } 568 } 569 } else if !isDependency { 570 // We've passed an input on the left side; reject new inputs. 571 poisonedInput = true 572 } 573 } 574 575 if haveNewline { 576 // A newline ends a rule so the next filename will be a new target. 577 parsingTargets = true 578 poisonedInput = false 579 } 580 } 581 if !haveTarget { 582 return errors.New("expected ':' in depfile") 583 } 584 return nil 585 }