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  }