mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-04-30 07:00:46 +03:00

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.
188 lines
5 KiB
C++
188 lines
5 KiB
C++
//===-- 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;
|
||
}
|