ldc/driver/response.cpp
Martin Kinkelin 42619762e9 Windows: Read environment variables as UTF-8
And use the wide API for pure is-env-variable-set checks too, as the
first call to a narrow env API function would lead to the C runtime
preparing and maintaining both narrow and wide environments.
2019-08-08 22:38:38 +02:00

188 lines
5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===-- response.cpp ------------------------------------------------------===//
//
// LDC the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// This is an open-source reimplementation of the DMD response_expand function
// (Safety0ff/response_expand on GitHub, see LDC issue #267).
//
//===----------------------------------------------------------------------===//
#include "driver/args.h"
#include <fstream>
#include <iterator>
#include <list>
#include <map>
#include <set>
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include <cctype>
// returns true if the quote is unescaped
bool applyBackslashRule(std::string &arg) {
std::string::reverse_iterator it;
for (it = arg.rbegin(); it != arg.rend() && *it == '\\'; ++it) {
}
size_t numbs = std::distance(arg.rbegin(), it);
bool escapedquote = numbs % 2;
size_t numescaped = numbs / 2 + escapedquote;
arg.resize(arg.size() - numescaped);
if (escapedquote) {
arg += '"';
}
return !escapedquote;
}
// returns true if the end of the quote is the end of the argument
bool dealWithQuote(std::istream &is, std::string &arg) {
// first go back and deal with backslashes
if (!applyBackslashRule(arg)) {
return false;
}
// keep appending until we find a quote terminator
while (is.good()) {
auto c = is.get();
switch (c) {
case '"':
if (applyBackslashRule(arg)) {
return false;
}
break;
case EOF: // new line or EOF ends quote and argument
case '\n':
return true;
case '\r': // ignore carriage returns in quotes
break;
default:
arg += c;
}
}
return true; // EOF
}
void dealWithComment(std::istream &is) {
while (is.good()) {
char c = is.get();
if (c == '\n' || c == '\r') {
return; // newline, carriage return and EOF end comment
}
}
}
std::vector<std::string> expand(std::istream &is) {
std::vector<std::string> expanded;
std::string arg;
is >> std::ws;
while (is.good()) {
auto c = is.get();
if (c == EOF) {
break;
} else if (std::isspace(c)) {
is >> std::ws;
if (!arg.empty()) {
expanded.push_back(arg);
arg.clear();
}
} else if (c == '"') {
if (dealWithQuote(is, arg) && !arg.empty()) {
expanded.push_back(arg);
arg.clear();
}
} else if (c == '#') {
dealWithComment(is);
} else {
arg += c;
while (is.good()) {
c = is.peek();
if (std::isspace(c) || c == '"' || c == EOF) {
break; // N.B. comments can't be placed in the middle of an arg
} else {
arg += is.get();
}
}
}
}
if (!arg.empty()) {
expanded.push_back(arg);
}
return expanded;
}
int response_expand(size_t *pargc, char ***ppargv) {
// N.B. It is possible to create an infinite loop with response arguments
// in the original implementation, we artificially limmit re-parsing responses
const unsigned reexpand_limit = 32;
std::map<std::string, unsigned> response_args;
// Compile a list of arguments, at the end convert them to the proper C string
// form
std::vector<std::string> processed_args;
std::list<std::string> unprocessed_args;
// fill unprocessed with initial arguments
for (size_t i = 0; i < *pargc; ++i) {
unprocessed_args.push_back((*ppargv)[i]);
}
processed_args.reserve(*pargc);
while (!unprocessed_args.empty()) {
std::string arg = unprocessed_args.front();
unprocessed_args.pop_front();
if (!arg.empty() && arg[0] == '@') {
arg.erase(arg.begin()); // remove leading '@'
if (arg.empty()) {
return 1;
}
if (response_args.find(arg) == response_args.end()) {
response_args[arg] = 1;
} else if (++response_args[arg] > reexpand_limit) {
return 2; // We might be in an infinite loop
}
std::vector<std::string> expanded_args;
std::string env = env::get(arg.c_str());
if (!env.empty()) {
std::istringstream ss(env);
expanded_args = expand(ss);
} else {
std::ifstream ifs(arg.c_str());
if (ifs.good()) {
expanded_args = expand(ifs);
} else {
return 3; // response not found between environment and files
}
}
unprocessed_args.insert(unprocessed_args.begin(), expanded_args.begin(),
expanded_args.end());
} else if (!arg.empty()) {
processed_args.push_back(arg);
}
}
char **pargv =
reinterpret_cast<char **>(malloc(sizeof(char *) * processed_args.size()));
for (size_t i = 0; i < processed_args.size(); ++i) {
pargv[i] = reinterpret_cast<char *>(
malloc(sizeof(pargv[i]) * (1 + processed_args[i].length())));
strcpy(pargv[i], processed_args[i].c_str());
}
*pargc = processed_args.size();
*ppargv = pargv;
return 0;
}