| // Copyright 2016 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | #ifndef THIRD_PARTY_BAZEL_SRC_TOOLS_SINGLEJAR_TOKEN_STREAM_H_ | 
 | #define THIRD_PARTY_BAZEL_SRC_TOOLS_SINGLEJAR_TOKEN_STREAM_H_ 1 | 
 |  | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "src/main/cpp/util/path_platform.h" | 
 | #include "src/tools/singlejar/diag.h" | 
 |  | 
 | /* | 
 |  * Tokenize command line containing indirect command line arguments. | 
 |  * An '@' at the beginning of a command line argument indicates that | 
 |  * the rest of the argument is the name of the file which should be | 
 |  * read and tokenized as Bash does it: tokens are separated by the | 
 |  * whitespace, quotes and double quotes can be used to have whitespace | 
 |  * and the other quote inside the token, and backslash followed by | 
 |  * newline is treated as empty string. | 
 |  */ | 
 |  | 
 | class ArgTokenStream { | 
 |   /* This class is used as follows: | 
 |    * | 
 |    *  int main(int argc, char* argv[]) { | 
 |    *    ArgTokenStream tokens(argc-1, argv+1); | 
 |    *    while (!tokens.AtEnd()) { | 
 |    *       if (tokens.MatchAndSet("--opt1", ...) || | 
 |    *           tokens.MatchAndSet("--opt2", ...) || | 
 |    *           ...) { | 
 |    *         continue; | 
 |    *       } | 
 |    *       // Process non-option argument or report an error. | 
 |    *       // ArgTokenStream::token() returns the current token. | 
 |    *    } | 
 |    *  } | 
 |    */ | 
 |  | 
 |  private: | 
 |   // Internal class to handle indirect command files. | 
 |   class FileTokenStream { | 
 |    public: | 
 |     FileTokenStream(const char *filename) { | 
 | #ifdef _WIN32 | 
 |       std::wstring wpath; | 
 |       std::string error; | 
 |       if (!blaze_util::AsAbsoluteWindowsPath(filename, &wpath, &error)) { | 
 |         diag_err(1, "%s:%d: AsAbsoluteWindowsPath failed: %s", __FILE__, | 
 |                  __LINE__, error.c_str()); | 
 |       } | 
 |       fp_ = _wfopen(wpath.c_str(), L"r"); | 
 | #else | 
 |       fp_ = fopen(filename, "r"); | 
 | #endif | 
 |  | 
 |       if (!fp_) { | 
 |         diag_err(1, "%s", filename); | 
 |       } | 
 |       filename_ = filename; | 
 |       next_char(); | 
 |     } | 
 |  | 
 |     ~FileTokenStream() { close(); } | 
 |  | 
 |     // Assign next token to TOKEN, return true on success, false on EOF. | 
 |     bool next_token(std::string *token) { | 
 |       if (!fp_) { | 
 |         return false; | 
 |       } | 
 |       *token = ""; | 
 |       while (current_char_ != EOF && isspace(current_char_)) { | 
 |         next_char(); | 
 |       } | 
 |       if (current_char_ == EOF) { | 
 |         close(); | 
 |         return false; | 
 |       } | 
 |       for (;;) { | 
 |         if (current_char_ == '\'' || current_char_ == '"') { | 
 |           process_quoted(token); | 
 |           if (isspace(current_char_)) { | 
 |             next_char(); | 
 |             return true; | 
 |           } else { | 
 |             next_char(); | 
 |           } | 
 |         } else if (current_char_ == '\\') { | 
 |           next_char(); | 
 |           if ((current_char_ != EOF)) { | 
 |             token->push_back(current_char_); | 
 |             next_char(); | 
 |           } else { | 
 |             diag_errx(1, "Expected character after \\, got EOF in %s", | 
 |                       filename_.c_str()); | 
 |           } | 
 |         } else if (current_char_ == EOF || isspace(current_char_)) { | 
 |           next_char(); | 
 |           return true; | 
 |         } else { | 
 |           token->push_back(current_char_); | 
 |           next_char(); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |    private: | 
 |     void close() { | 
 |       if (fp_) { | 
 |         fclose(fp_); | 
 |         fp_ = nullptr; | 
 |       } | 
 |       filename_.clear(); | 
 |     } | 
 |  | 
 |     // Append the quoted string to the TOKEN. The quote character (which can be | 
 |     // single or double quote) is in the current character. Everything up to the | 
 |     // matching quote character is appended. | 
 |     void process_quoted(std::string *token) { | 
 |       char quote = current_char_; | 
 |       next_char(); | 
 |       while (current_char_ != quote) { | 
 |         if (current_char_ == '\\' && quote == '"') { | 
 |           // In the "-quoted token, \" stands for ", and \x | 
 |           // is copied literally for any other x. | 
 |           next_char(); | 
 |           if (current_char_ == '"') { | 
 |             token->push_back('"'); | 
 |             next_char(); | 
 |           } else if (current_char_ != EOF) { | 
 |             token->push_back('\\'); | 
 |             token->push_back(current_char_); | 
 |             next_char(); | 
 |           } else { | 
 |             diag_errx(1, "No closing %c in %s", quote, filename_.c_str()); | 
 |           } | 
 |         } else if (current_char_ != EOF) { | 
 |           token->push_back(current_char_); | 
 |           next_char(); | 
 |         } else { | 
 |           diag_errx(1, "No closing %c in %s", quote, filename_.c_str()); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     // Get the next character from the input stream. Skip backslash followed | 
 |     // by the newline. | 
 |     void next_char() { | 
 |       if (feof(fp_)) { | 
 |         current_char_ = EOF; | 
 |         return; | 
 |       } | 
 |       current_char_ = getc(fp_); | 
 |       // Eat "\\\n" sequence. | 
 |       while (current_char_ == '\\') { | 
 |         int c = getc(fp_); | 
 |         if (c == '\n') { | 
 |           current_char_ = getc(fp_); | 
 |         } else { | 
 |           if (c != EOF) { | 
 |             ungetc(c, fp_); | 
 |           } | 
 |           break; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     FILE *fp_; | 
 |     std::string filename_; | 
 |     int current_char_; | 
 |   }; | 
 |  | 
 |  public: | 
 |   // Constructor. Automatically reads the first token. | 
 |   ArgTokenStream(int argc, const char *const *argv) | 
 |       : argv_(argv), argv_end_(argv + argc) { | 
 |     next(); | 
 |   } | 
 |  | 
 |   // Process --OPTION | 
 |   // If the current token is --OPTION, set given FLAG to true, proceed to next | 
 |   // token and return true | 
 |   bool MatchAndSet(const char *option, bool *flag) { | 
 |     if (token_.compare(option) != 0) { | 
 |       return false; | 
 |     } | 
 |     *flag = true; | 
 |     next(); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Process --OPTION OPTARG | 
 |   // If the current token is --OPTION, set OPTARG to the next token, proceed to | 
 |   // the next token after it and return true. | 
 |   bool MatchAndSet(const char *option, std::string *optarg) { | 
 |     if (token_.compare(option) != 0) { | 
 |       return false; | 
 |     } | 
 |     next(); | 
 |     if (AtEnd()) { | 
 |       diag_errx(1, "%s requires argument", option); | 
 |     } | 
 |     *optarg = token_; | 
 |     next(); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Process --OPTION OPTARG1 OPTARG2 ... | 
 |   // If a current token is --OPTION, push_back all subsequent tokens up to the | 
 |   // next option to the OPTARGS array, proceed to the next option and return | 
 |   // true. | 
 |   bool MatchAndSet(const char *option, std::vector<std::string> *optargs) { | 
 |     if (token_.compare(option) != 0) { | 
 |       return false; | 
 |     } | 
 |     next(); | 
 |     while (!AtEnd() && '-' != token_.at(0)) { | 
 |       optargs->push_back(token_); | 
 |       next(); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Process --OPTION OPTARG1,OPTSUFF1 OPTARG2,OPTSUFF2 ... | 
 |   // If a current token is --OPTION, push_back all subsequent tokens up to the | 
 |   // next option to the OPTARGS array, splitting the OPTARG,OPTSUFF by a comma, | 
 |   // proceed to the next option and return true. | 
 |   bool MatchAndSet(const char *option, | 
 |                    std::vector<std::pair<std::string, std::string> > *optargs) { | 
 |     if (token_.compare(option) != 0) { | 
 |       return false; | 
 |     } | 
 |     next(); | 
 |     while (!AtEnd() && '-' != token_.at(0)) { | 
 |       size_t commapos = token_.find(','); | 
 |       if (commapos == std::string::npos) { | 
 |         optargs->push_back(std::pair<std::string, std::string>(token_, "")); | 
 |       } else { | 
 |         std::string first = token_.substr(0, commapos); | 
 |         token_.erase(0, commapos + 1); | 
 |         optargs->push_back(std::pair<std::string, std::string>(first, token_)); | 
 |       } | 
 |  | 
 |       next(); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Current token. | 
 |   const std::string &token() const { return token_; } | 
 |  | 
 |   // Read the next token. | 
 |   void next() { | 
 |     if (AtEnd()) { | 
 |       return; | 
 |     } | 
 |     if (file_token_stream_.get() && token_from_file()) { | 
 |       return; | 
 |     } | 
 |     while (argv_ < argv_end_) { | 
 |       if (**argv_ != '@') { | 
 |         token_ = *argv_++; | 
 |         return; | 
 |       } | 
 |       file_token_stream_.reset(new FileTokenStream(*(argv_++) + 1)); | 
 |       if (token_from_file()) { | 
 |         return; | 
 |       } | 
 |     } | 
 |     argv_++; | 
 |   } | 
 |  | 
 |   // True if there are no more tokens. | 
 |   bool AtEnd() const { return argv_ > argv_end_; } | 
 |  | 
 |  private: | 
 |   bool token_from_file() { | 
 |     if (file_token_stream_->next_token(&token_)) { | 
 |       return true; | 
 |     } | 
 |     file_token_stream_.reset(nullptr); | 
 |     return false; | 
 |   } | 
 |   std::unique_ptr<FileTokenStream> file_token_stream_; | 
 |   const char *const *argv_; | 
 |   const char *const *argv_end_; | 
 |   std::string token_; | 
 | }; | 
 |  | 
 | #endif  //  THIRD_PARTY_BAZEL_SRC_TOOLS_SINGLEJAR_TOKEN_STREAM_H_ |