1 /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2 
3 /*
4  *  Main authors:
5  *     Guido Tack <guido.tack@monash.edu>
6  */
7 
8 /* This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 
12 #include <minizinc/file_utils.hh>
13 #include <minizinc/json_parser.hh>
14 #include <minizinc/parser.hh>
15 #include <minizinc/prettyprinter.hh>
16 #include <minizinc/solver_config.hh>
17 
18 #include <algorithm>
19 #include <cctype>
20 #include <iterator>
21 #include <set>
22 #include <sstream>
23 #include <string>
24 
25 using namespace std;
26 
27 namespace MiniZinc {
28 
validate(const std::string & v) const29 bool SolverConfig::ExtraFlag::validate(const std::string& v) const {
30   try {
31     switch (flagType) {
32       case FlagType::T_BOOL:
33       case FlagType::T_STRING:
34         return range.empty() || std::find(range.begin(), range.end(), v) != range.end();
35       case FlagType::T_INT: {
36         long long i = stoll(v);
37         return range.empty() || (i >= stoll(range[0]) && i <= stoll(range[1]));
38       }
39       case FlagType::T_FLOAT: {
40         double i = stod(v);
41         return range.empty() || (i >= stod(range[0]) && i <= stod(range[1]));
42       }
43     }
44   } catch (const invalid_argument&) {
45     return false;
46   } catch (const out_of_range&) {
47     return false;
48   }
49   return false;
50 }
51 
52 namespace {
get_string(AssignI * ai)53 std::string get_string(AssignI* ai) {
54   if (auto* sl = ai->e()->dynamicCast<StringLit>()) {
55     return std::string(sl->v().c_str(), sl->v().size());
56   }
57   throw ConfigException("invalid configuration item (right hand side must be string)");
58 }
get_bool(AssignI * ai)59 bool get_bool(AssignI* ai) {
60   if (auto* bl = ai->e()->dynamicCast<BoolLit>()) {
61     return bl->v();
62   }
63   throw ConfigException("invalid configuration item (right hand side must be bool)");
64 }
get_int(AssignI * ai)65 int get_int(AssignI* ai) {
66   if (auto* il = ai->e()->dynamicCast<IntLit>()) {
67     return static_cast<int>(il->v().toInt());
68   }
69   throw ConfigException("invalid configuration item (right hand side must be int)");
70 }
get_string_list(AssignI * ai)71 std::vector<std::string> get_string_list(AssignI* ai) {
72   if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
73     std::vector<std::string> ret;
74     for (unsigned int i = 0; i < al->size(); i++) {
75       if (auto* sl = (*al)[i]->dynamicCast<StringLit>()) {
76         ret.emplace_back(sl->v().c_str(), sl->v().size());
77       } else {
78         throw ConfigException(
79             "invalid configuration item (right hand side must be a list of strings)");
80       }
81     }
82     return ret;
83   }
84   throw ConfigException("invalid configuration item (right hand side must be a list of strings)");
85 }
get_string_pair_list(AssignI * ai)86 std::vector<std::pair<std::string, std::string> > get_string_pair_list(AssignI* ai) {
87   if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
88     std::vector<std::pair<std::string, std::string> > ret;
89     if (al->dims() != 2 || al->min(1) != 1 || al->max(1) != 2) {
90       throw ConfigException(
91           "invalid configuration item (right hand side must be a 2d array of strings)");
92     }
93     for (unsigned int i = 0; i < al->size(); i += 2) {
94       auto* sl1 = (*al)[i]->dynamicCast<StringLit>();
95       auto* sl2 = (*al)[i + 1]->dynamicCast<StringLit>();
96       if ((sl1 != nullptr) && (sl2 != nullptr)) {
97         ret.emplace_back(std::string(sl1->v().c_str(), sl1->v().size()),
98                          std::string(sl2->v().c_str(), sl2->v().size()));
99       } else {
100         throw ConfigException(
101             "invalid configuration item (right hand side must be a 2d array of strings)");
102       }
103     }
104     return ret;
105   }
106   throw ConfigException(
107       "invalid configuration item (right hand side must be a 2d array of strings)");
108 }
get_default_option_list(AssignI * ai)109 std::vector<std::vector<std::string> > get_default_option_list(AssignI* ai) {
110   if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
111     std::vector<std::vector<std::string> > ret;
112     if (al->size() == 0) {
113       return ret;
114     }
115     if (al->dims() != 2) {
116       throw ConfigException(
117           "invalid configuration item (right hand side must be a 2d array of strings)");
118     }
119     int nCols = al->max(1) - al->min(1) + 1;
120     if (nCols != 3) {
121       throw ConfigException(
122           "invalid configuration item (right hand side must be a 2d array of strings with 3 "
123           "columns)");
124     }
125     for (unsigned int i = 0; i < al->size(); i += nCols) {
126       auto* sl0 = (*al)[i]->dynamicCast<StringLit>();
127       auto* sl1 = (*al)[i + 1]->dynamicCast<StringLit>();
128       auto* sl2 = (*al)[i + 2]->dynamicCast<StringLit>();
129       if ((sl0 != nullptr) && (sl1 != nullptr) && (sl2 != nullptr)) {
130         ret.push_back(std::vector<std::string>({std::string(sl0->v().c_str(), sl0->v().size()),
131                                                 std::string(sl1->v().c_str(), sl1->v().size()),
132                                                 std::string(sl2->v().c_str(), sl2->v().size())}));
133       } else {
134         throw ConfigException(
135             "invalid configuration item (right hand side must be a list of strings)");
136       }
137     }
138     return ret;
139   }
140   throw ConfigException(
141       "invalid configuration item (right hand side must be a 2d array of strings)");
142 }
get_extra_flag_list(AssignI * ai)143 std::vector<SolverConfig::ExtraFlag> get_extra_flag_list(AssignI* ai) {
144   if (auto* al = ai->e()->dynamicCast<ArrayLit>()) {
145     std::vector<SolverConfig::ExtraFlag> ret;
146     if (al->size() == 0) {
147       return ret;
148     }
149     if (al->dims() != 2) {
150       throw ConfigException(
151           "invalid configuration item (right hand side must be a 2d array of strings)");
152     }
153     int nCols = al->max(1) - al->min(1) + 1;
154     if (nCols < 2 || nCols > 4) {
155       throw ConfigException(
156           "invalid configuration item (right hand side must be a 2d array of strings)");
157     }
158     bool haveType = (nCols >= 3);
159     bool haveDefault = (nCols >= 4);
160     for (unsigned int i = 0; i < al->size(); i += nCols) {
161       auto* sl1 = (*al)[i]->dynamicCast<StringLit>();
162       auto* sl2 = (*al)[i + 1]->dynamicCast<StringLit>();
163       StringLit* sl3 = haveType ? (*al)[i + 2]->dynamicCast<StringLit>() : nullptr;
164       StringLit* sl4 = haveDefault ? (*al)[i + 3]->dynamicCast<StringLit>() : nullptr;
165       std::string opt_type_full =
166           sl3 != nullptr ? std::string(sl3->v().c_str(), sl3->v().size()) : "bool";
167       std::vector<std::string> opt_range;
168       std::string opt_def;
169       if (sl4 != nullptr) {
170         opt_def = std::string(sl4->v().c_str(), sl4->v().size());
171       } else if (opt_type_full == "bool") {
172         opt_def = "false";
173       } else if (opt_type_full == "int") {
174         opt_def = "0";
175       } else if (opt_type_full == "float") {
176         opt_def = "0.0";
177       }
178       size_t split = opt_type_full.find(":");
179       std::string opt_type = opt_type_full.substr(0, split);
180       SolverConfig::ExtraFlag::FlagType flag_type;
181       if (opt_type == "bool") {
182         flag_type = SolverConfig::ExtraFlag::FlagType::T_BOOL;
183       } else if (opt_type == "int") {
184         flag_type = SolverConfig::ExtraFlag::FlagType::T_INT;
185       } else if (opt_type == "float") {
186         flag_type = SolverConfig::ExtraFlag::FlagType::T_FLOAT;
187       } else if (opt_type == "string" || opt_type == "opt") {
188         flag_type = SolverConfig::ExtraFlag::FlagType::T_STRING;
189       }
190       if (split != std::string::npos) {
191         opt_type_full = opt_type_full.substr(split + 1);
192         while (!opt_type_full.empty()) {
193           split = opt_type_full.find(":");
194           opt_range.push_back(opt_type_full.substr(0, split));
195           opt_type_full = split == std::string::npos ? "" : opt_type_full.substr(split + 1);
196         }
197       }
198 
199       if ((sl1 != nullptr) && (sl2 != nullptr)) {
200         ret.emplace_back(std::string(sl1->v().c_str(), sl1->v().size()),
201                          std::string(sl2->v().c_str(), sl2->v().size()), flag_type, opt_range,
202                          opt_def);
203       } else {
204         throw ConfigException(
205             "invalid configuration item (right hand side must be a 2d array of strings)");
206       }
207     }
208     return ret;
209   }
210   throw ConfigException(
211       "invalid configuration item (right hand side must be a 2d array of strings)");
212 }
213 
get_env(const char * v)214 std::string get_env(const char* v) {
215   std::string ret;
216 #ifdef _MSC_VER
217   size_t len;
218   getenv_s(&len, nullptr, 0, v);
219   if (len > 0) {
220     char* p = static_cast<char*>(malloc(len * sizeof(char)));
221     getenv_s(&len, p, len, v);
222     if (len > 0) {
223       ret = p;
224     }
225     free(p);
226   }
227 #else
228   char* p = getenv(v);
229   if (p != nullptr) {
230     ret = p;
231   }
232 #endif
233   return ret;
234 }
235 
char_to_lower(char c)236 char char_to_lower(char c) {
237   return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
238 }
string_to_lower(std::string s)239 std::string string_to_lower(std::string s) {
240   std::transform(s.begin(), s.end(), s.begin(), char_to_lower);
241   return s;
242 }
243 struct SortByLowercase {
operator ()MiniZinc::__anon1ff463660111::SortByLowercase244   bool operator()(const std::string& n1, const std::string& n2) {
245     for (size_t i = 0; i < n1.size() && i < n2.size(); i++) {
246       if (std::tolower(n1[i]) != std::tolower(n2[i])) {
247         return std::tolower(n1[i]) < std::tolower(n2[i]);
248       }
249     }
250     return n1.size() < n2.size();
251   }
252 };
253 struct SortByName {
254   const std::vector<SolverConfig>& solvers;
255   SortByLowercase sortByLowercase;
SortByNameMiniZinc::__anon1ff463660111::SortByName256   SortByName(const std::vector<SolverConfig>& solvers0) : solvers(solvers0) {}
operator ()MiniZinc::__anon1ff463660111::SortByName257   bool operator()(int idx1, int idx2) {
258     return sortByLowercase(solvers[idx1].name(), solvers[idx2].name());
259   }
260 };
261 
262 }  // namespace
263 
load(const string & filename)264 SolverConfig SolverConfig::load(const string& filename) {
265   SolverConfig sc;
266   sc._configFile = FileUtils::file_path(filename);
267   ostringstream errstream;
268   try {
269     Env confenv;
270     Model* m = nullptr;
271     if (JSONParser::fileIsJSON(filename)) {
272       JSONParser jp(confenv.envi());
273       try {
274         m = new Model;
275         GCLock lock;
276         jp.parse(m, filename, false);
277       } catch (JSONError& e) {
278         delete m;
279         m = nullptr;
280         throw ConfigException(e.msg());
281       }
282     } else {
283       vector<string> filenames;
284       filenames.push_back(filename);
285       m = parse(confenv, filenames, vector<string>(), "", "", vector<string>(), false, true, false,
286                 false, errstream);
287     }
288     if (m != nullptr) {
289       bool hadId = false;
290       bool hadVersion = false;
291       bool hadName = false;
292       string basePath = FileUtils::dir_name(sc._configFile);
293       for (auto& i : *m) {
294         if (auto* ai = i->dynamicCast<AssignI>()) {
295           if (ai->id() == "id") {
296             sc._id = get_string(ai);
297             hadId = true;
298           } else if (ai->id() == "name") {
299             sc._name = get_string(ai);
300             hadName = true;
301           } else if (ai->id() == "executable") {
302             std::string exePath = get_string(ai);
303             sc._executable = exePath;
304             std::string exe = FileUtils::find_executable(FileUtils::file_path(exePath, basePath));
305             int nr_found = (int)(!exe.empty());
306             std::string tmp = FileUtils::file_path(FileUtils::find_executable(exePath));
307             nr_found += (int)((!tmp.empty()) && tmp != exe);
308             exe = exe.empty() ? tmp : exe;
309             if (nr_found > 0) {
310               sc._executableResolved = exe;
311               if (nr_found > 1) {
312                 std::cerr << "Warning: multiple executables '" << exePath
313                           << "' found on the system, using '" << exe << "'" << std::endl;
314               }
315             }
316           } else if (ai->id() == "mznlib") {
317             std::string libPath = get_string(ai);
318             sc._mznlib = libPath;
319             if (!libPath.empty()) {
320               if (libPath[0] == '-') {
321                 sc._mznlibResolved = libPath;
322               } else {
323                 sc._mznlibResolved = FileUtils::file_path(libPath, basePath);
324               }
325             }
326           } else if (ai->id() == "version") {
327             sc._version = get_string(ai);
328             hadVersion = true;
329           } else if (ai->id() == "mznlibVersion") {
330             sc._mznlibVersion = get_int(ai);
331           } else if (ai->id() == "description") {
332             sc._description = get_string(ai);
333           } else if (ai->id() == "contact") {
334             sc._contact = get_string(ai);
335           } else if (ai->id() == "website") {
336             sc._website = get_string(ai);
337           } else if (ai->id() == "supportsMzn") {
338             sc._supportsMzn = get_bool(ai);
339           } else if (ai->id() == "supportsFzn") {
340             sc._supportsFzn = get_bool(ai);
341           } else if (ai->id() == "supportsNL") {
342             sc._supportsNL = get_bool(ai);
343           } else if (ai->id() == "needsSolns2Out") {
344             sc._needsSolns2Out = get_bool(ai);
345           } else if (ai->id() == "isGUIApplication") {
346             sc._isGUIApplication = get_bool(ai);
347           } else if (ai->id() == "needsMznExecutable") {
348             sc._needsMznExecutable = get_bool(ai);
349           } else if (ai->id() == "needsStdlibDir") {
350             sc._needsStdlibDir = get_bool(ai);
351           } else if (ai->id() == "needsPathsFile") {
352             sc._needsPathsFile = get_bool(ai);
353           } else if (ai->id() == "tags") {
354             sc._tags = get_string_list(ai);
355           } else if (ai->id() == "stdFlags") {
356             sc._stdFlags = get_string_list(ai);
357           } else if (ai->id() == "requiredFlags") {
358             sc._requiredFlags = get_string_list(ai);
359           } else if (ai->id() == "extraFlags") {
360             sc._extraFlags = get_extra_flag_list(ai);
361           } else {
362             std::ostringstream ss;
363             ss << "invalid configuration item (" << ai->id() << ")";
364             throw ConfigException(ss.str());
365           }
366         } else {
367           throw ConfigException("invalid configuration item");
368         }
369       }
370       if (!hadId) {
371         throw ConfigException("invalid solver configuration (missing id)");
372       }
373       if (!hadVersion) {
374         throw ConfigException("invalid solver configuration (missing version)");
375       }
376       if (!hadName) {
377         throw ConfigException("invalid solver configuration (missing name)");
378       }
379     } else {
380       throw ConfigException(errstream.str());
381     }
382   } catch (ConfigException&) {
383     throw;
384   } catch (Exception& e) {
385     throw ConfigException(e.what());
386   }
387 
388   return sc;
389 }
390 
toJSON(const SolverConfigs & configs) const391 std::string SolverConfig::toJSON(const SolverConfigs& configs) const {
392   GCLock lock;
393   std::ostringstream oss;
394   auto def_id = configs.defaultSolver("");
395   oss << "{\n";
396   oss << "  \"extraInfo\": {\n";
397   if (!def_id.empty() && def_id == id()) {
398     oss << "    \"isDefault\": true,\n";
399   }
400   if (!mznlibResolved().empty()) {
401     oss << "    \"mznlib\": \"" << Printer::escapeStringLit(mznlibResolved()) << "\",\n";
402   }
403   if (!executableResolved().empty()) {
404     oss << "    \"executable\": \"" << Printer::escapeStringLit(executableResolved()) << "\",\n";
405   }
406   oss << "    \"configFile\": \"" << Printer::escapeStringLit(configFile()) << "\"";
407   if (!defaultFlags().empty()) {
408     oss << ",\n    \"defaultFlags\": [";
409     for (unsigned int j = 0; j < defaultFlags().size(); j++) {
410       oss << "\"" << Printer::escapeStringLit(defaultFlags()[j]) << "\"";
411       if (j < defaultFlags().size() - 1) {
412         oss << ",";
413       }
414     }
415     oss << "]";
416   }
417   oss << "\n";
418   oss << "  },\n";
419   oss << "  \"id\": \"" << Printer::escapeStringLit(id()) << "\",\n";
420   oss << "  \"name\": \"" << Printer::escapeStringLit(name()) << "\",\n";
421   oss << "  \"version\": \"" << Printer::escapeStringLit(version()) << "\",\n";
422   if (!mznlib().empty()) {
423     oss << "  \"mznlib\": \"" << Printer::escapeStringLit(mznlib()) << "\",\n";
424   }
425   if (!executable().empty()) {
426     oss << "  \"executable\": \"" << Printer::escapeStringLit(executable()) << "\",\n";
427   }
428   oss << "  \"mznlibVersion\": " << mznlibVersion() << ",\n";
429   if (!description().empty()) {
430     oss << "  \"description\": \"" << Printer::escapeStringLit(description()) << "\",\n";
431   }
432   if (!contact().empty()) {
433     oss << "  \"contact\": \"" << Printer::escapeStringLit(contact()) << "\",\n";
434   }
435   if (!website().empty()) {
436     oss << "  \"website\": \"" << Printer::escapeStringLit(website()) << "\",\n";
437   }
438   if (!requiredFlags().empty()) {
439     oss << "  \"requiredFlags\": [";
440     for (unsigned int j = 0; j < requiredFlags().size(); j++) {
441       oss << "\"" << requiredFlags()[j] << "\"";
442       if (j < requiredFlags().size() - 1) {
443         oss << ",";
444       }
445     }
446     oss << "],\n";
447   }
448   if (!stdFlags().empty()) {
449     oss << "  \"stdFlags\": [";
450     for (unsigned int j = 0; j < stdFlags().size(); j++) {
451       oss << "\"" << stdFlags()[j] << "\"";
452       if (j < stdFlags().size() - 1) {
453         oss << ",";
454       }
455     }
456     oss << "],\n";
457   }
458   if (!extraFlags().empty()) {
459     oss << "  \"extraFlags\": [";
460     for (unsigned int j = 0; j < extraFlags().size(); j++) {
461       oss << "\n    ["
462           << "\"" << Printer::escapeStringLit(extraFlags()[j].flag) << "\",\""
463           << Printer::escapeStringLit(extraFlags()[j].description) << "\",\"";
464       switch (extraFlags()[j].flagType) {
465         case ExtraFlag::FlagType::T_BOOL:
466           oss << "bool";
467           break;
468         case ExtraFlag::FlagType::T_INT:
469           oss << "int";
470           break;
471         case ExtraFlag::FlagType::T_FLOAT:
472           oss << "float";
473           break;
474         case ExtraFlag::FlagType::T_STRING:
475           oss << (extraFlags()[j].range.empty() ? "string" : "opt");
476           break;
477       }
478       for (const auto& v : extraFlags()[j].range) {
479         oss << ":" << Printer::escapeStringLit(v);
480       }
481       oss << "\",\"" << Printer::escapeStringLit(extraFlags()[j].defaultValue) << "\"]";
482       if (j < extraFlags().size() - 1) {
483         oss << ",";
484       }
485     }
486     oss << "\n  ],\n";
487   }
488 
489   if (!tags().empty()) {
490     oss << "  \"tags\": [";
491     for (unsigned int j = 0; j < tags().size(); j++) {
492       oss << "\"" << Printer::escapeStringLit(tags()[j]) << "\"";
493       if (j < tags().size() - 1) {
494         oss << ",";
495       }
496     }
497     oss << "],\n";
498   }
499   oss << "  \"supportsMzn\": " << (supportsMzn() ? "true" : "false") << ",\n";
500   oss << "  \"supportsFzn\": " << (supportsFzn() ? "true" : "false") << ",\n";
501   oss << "  \"supportsNL\": " << (supportsNL() ? "true" : "false") << ",\n";
502   oss << "  \"needsSolns2Out\": " << (needsSolns2Out() ? "true" : "false") << ",\n";
503   oss << "  \"needsMznExecutable\": " << (needsMznExecutable() ? "true" : "false") << ",\n";
504   oss << "  \"needsStdlibDir\": " << (needsStdlibDir() ? "true" : "false") << ",\n";
505   oss << "  \"needsPathsFile\": " << (needsPathsFile() ? "true" : "false") << ",\n";
506   oss << "  \"isGUIApplication\": " << (isGUIApplication() ? "true" : "false") << "\n";
507   oss << "}";
508 
509   return oss.str();
510 }
511 
512 class BuiltinSolverConfigs {
513 public:
514   std::unordered_map<std::string, SolverConfig> builtinSolvers;
515 };
516 
builtin_solver_configs()517 BuiltinSolverConfigs& builtin_solver_configs() {
518   static BuiltinSolverConfigs c;
519   return c;
520 }
521 
addConfig(const MiniZinc::SolverConfig & sc)522 void SolverConfigs::addConfig(const MiniZinc::SolverConfig& sc) {
523   int newIdx = static_cast<int>(_solvers.size());
524   _solvers.push_back(sc);
525   std::vector<string> sc_tags = sc.tags();
526   std::string id = string_to_lower(sc.id());
527   std::string name = string_to_lower(sc.name());
528   sc_tags.push_back(id);
529   size_t last_dot = id.find_last_of('.');
530   if (last_dot != std::string::npos) {
531     std::string last_id = id.substr(last_dot + 1);
532     if (last_id != name) {
533       sc_tags.push_back(last_id);
534     }
535   }
536   sc_tags.push_back(name);
537   for (const auto& t : sc_tags) {
538     auto it = _tags.find(t);
539     if (it == _tags.end()) {
540       _tags.insert(std::make_pair(t, std::vector<int>({newIdx})));
541     } else {
542       it->second.push_back(newIdx);
543     }
544   }
545 }
546 
solverConfigsPath() const547 std::vector<std::string> SolverConfigs::solverConfigsPath() const { return _solverPath; }
548 
SolverConfigs(std::ostream & log)549 SolverConfigs::SolverConfigs(std::ostream& log) {
550 #ifdef _MSC_VER
551   const char* PATHSEP = ";";
552 #else
553   const char* PATHSEP = ":";
554 #endif
555   std::string mzn_solver_path = get_env("MZN_SOLVER_PATH");
556   while (!mzn_solver_path.empty()) {
557     size_t next_sep = mzn_solver_path.find(PATHSEP);
558     string cur_path = mzn_solver_path.substr(0, next_sep);
559     _solverPath.push_back(cur_path);
560     if (next_sep != string::npos) {
561       mzn_solver_path = mzn_solver_path.substr(next_sep + 1, string::npos);
562     } else {
563       mzn_solver_path = "";
564     }
565   }
566   std::string userConfigDir = FileUtils::user_config_dir();
567   if (FileUtils::directory_exists(userConfigDir + "/solvers")) {
568     _solverPath.push_back(userConfigDir + "/solvers");
569   }
570   std::vector<std::string> configFiles(
571       {FileUtils::global_config_file(), FileUtils::user_config_file()});
572 
573   for (auto& cf : configFiles) {
574     if (!cf.empty() && FileUtils::file_exists(cf)) {
575       ostringstream errstream;
576       try {
577         Env userconfenv;
578         Model* m = nullptr;
579         if (JSONParser::fileIsJSON(cf)) {
580           JSONParser jp(userconfenv.envi());
581           try {
582             m = new Model;
583             GCLock lock;
584             jp.parse(m, cf, false);
585           } catch (JSONError&) {
586             delete m;
587             m = nullptr;
588           }
589         }
590         if (m != nullptr) {
591           for (auto& i : *m) {
592             if (auto* ai = i->dynamicCast<AssignI>()) {
593               if (ai->id() == "mzn_solver_path") {
594                 std::vector<std::string> sp = get_string_list(ai);
595                 for (const auto& s : sp) {
596                   _solverPath.push_back(s);
597                 }
598               } else if (ai->id() == "mzn_lib_dir") {
599                 _mznlibDir = get_string(ai);
600               } else if (ai->id() == "tagDefaults") {
601                 std::vector<std::pair<std::string, std::string> > tagDefs =
602                     get_string_pair_list(ai);
603                 for (auto& td : tagDefs) {
604                   std::string tag = td.first;
605                   std::string solver_id = td.second;
606                   _tagDefault[tag] = solver_id;
607                 }
608               } else if (ai->id() == "solverDefaults") {
609                 std::vector<std::vector<std::string> > solverDefs = get_default_option_list(ai);
610                 for (auto& sd : solverDefs) {
611                   assert(sd.size() == 3);
612                   std::string solver = sd[0];
613                   auto it = _solverDefaultOptions.find(solver);
614                   if (it == _solverDefaultOptions.end()) {
615                     std::vector<std::string> solverOptions({sd[1], sd[2]});
616                     _solverDefaultOptions.insert(std::make_pair(solver, solverOptions));
617                   } else {
618                     std::vector<std::string>& opts = it->second;
619                     bool found = false;
620                     for (unsigned int i = 0; i < opts.size(); i += 2) {
621                       if (opts[i] == sd[1]) {
622                         // Override existing option value
623                         opts[i + 1] = sd[2];
624                         found = true;
625                         break;
626                       }
627                     }
628                     if (!found) {
629                       // Option didn't exist, add to end of list
630                       opts.push_back(sd[1]);
631                       opts.push_back(sd[2]);
632                     }
633                   }
634                 }
635               } else {
636                 throw ConfigException("invalid configuration item");
637               }
638             } else {
639               throw ConfigException("invalid configuration item");
640             }
641           }
642         } else {
643           std::cerr << errstream.str();
644           throw ConfigException("internal error");
645         }
646       } catch (ConfigException& e) {
647         log << "Warning: invalid configuration file: " << e.msg() << "\n";
648       } catch (Exception& e) {
649         log << "Warning: invalid configuration file: " << e.what() << "\n";
650       }
651     }
652   }
653 
654   if (_mznlibDir.empty()) {
655     _mznlibDir = FileUtils::file_path(FileUtils::share_directory());
656   }
657   if (!_mznlibDir.empty()) {
658     _solverPath.push_back(_mznlibDir + "/solvers");
659   }
660 #ifndef _MSC_VER
661   if (_mznlibDir != "/usr/local/share/minizinc" &&
662       FileUtils::directory_exists("/usr/local/share")) {
663     _solverPath.emplace_back("/usr/local/share/minizinc/solvers");
664   }
665 #  if !defined(__FreeBSD__)
666   if (_mznlibDir != "/usr/share/minizinc" && FileUtils::directory_exists("/usr/share")) {
667     _solverPath.emplace_back("/usr/share/minizinc/solvers");
668   }
669 #  endif
670 #endif
671 }
672 
populate(std::ostream & log)673 void SolverConfigs::populate(std::ostream& log) {
674   for (const auto& sc : builtin_solver_configs().builtinSolvers) {
675     addConfig(sc.second);
676   }
677 
678   for (const string& cur_path : _solverPath) {
679     std::vector<std::string> configFiles = FileUtils::directory_list(cur_path, "msc");
680     for (auto& configFile : configFiles) {
681       try {
682         SolverConfig sc = SolverConfig::load(cur_path + "/" + configFile);
683         addConfig(sc);
684       } catch (ConfigException& e) {
685         log << "Warning: error loading solver configuration from file " << cur_path << "/"
686             << configFile << "\n";
687         log << "Error was:\n" << e.msg() << "\n";
688       }
689     }
690   }
691 
692   // Add default options to loaded solver configurations
693   for (auto& sc : _solvers) {
694     sc.defaultFlags(defaultOptions(sc.id()));
695   }
696 }
697 
solvers() const698 vector<string> SolverConfigs::solvers() const {
699   // Find default solver, if present
700   std::string def_id;
701   auto def_it = _tagDefault.find("");
702   if (def_it != _tagDefault.end()) {
703     def_id = def_it->second;
704   }
705   // Create sorted list of solvers
706   vector<string> s;
707   for (const auto& sc : _solvers) {
708     if (std::find(sc.tags().begin(), sc.tags().end(), "__internal__") != sc.tags().end()) {
709       continue;
710     }
711     std::ostringstream oss;
712     oss << sc.name() << " " << sc.version() << " (" << sc.id();
713     if (!def_id.empty() && sc.id() == def_id) {
714       oss << ", default solver";
715     }
716     for (const std::string& t : sc.tags()) {
717       oss << ", " << t;
718     }
719     oss << ")";
720     s.push_back(oss.str());
721   }
722   SortByLowercase sortByLowercase;
723   std::sort(s.begin(), s.end(), sortByLowercase);
724   return s;
725 }
726 
solverConfigsJSON() const727 std::string SolverConfigs::solverConfigsJSON() const {
728   std::ostringstream oss;
729 
730   SortByName sortByName(_solvers);
731   std::vector<int> solversIdx(_solvers.size());
732   for (unsigned int i = 0; i < solversIdx.size(); i++) {
733     solversIdx[i] = i;
734   }
735   std::sort(solversIdx.begin(), solversIdx.end(), sortByName);
736 
737   bool hadSolver = false;
738   oss << "[";
739   for (unsigned int i = 0; i < _solvers.size(); i++) {
740     const SolverConfig& sc = _solvers[solversIdx[i]];
741     if (std::find(sc.tags().begin(), sc.tags().end(), "__internal__") != sc.tags().end()) {
742       continue;
743     }
744     if (hadSolver) {
745       oss << ",";
746     }
747     hadSolver = true;
748     std::istringstream iss(sc.toJSON(*this));
749     std::string line;
750     while (std::getline(iss, line)) {
751       oss << "\n  " << line;
752     }
753   }
754   oss << "\n]\n";
755   return oss.str();
756 }
757 
758 namespace {
get_tag(const std::string & t)759 std::string get_tag(const std::string& t) { return t.substr(0, t.find('@')); }
get_version(const std::string & t)760 std::string get_version(const std::string& t) {
761   size_t sep = t.find('@');
762   return sep == string::npos ? "" : t.substr(sep + 1);
763 }
764 }  // namespace
765 
config(const std::string & _s)766 const SolverConfig& SolverConfigs::config(const std::string& _s) {
767   std::string s;
768   if (_s.size() > 4 && _s.substr(_s.size() - 4) == ".msc") {
769     SolverConfig sc = SolverConfig::load(_s);
770     addConfig(sc);
771     s = sc.id() + "@" + sc.version();
772   } else {
773     s = _s;
774   }
775   s = string_to_lower(s);
776   std::vector<std::string> tags;
777   std::istringstream iss(s);
778   std::string next_s;
779   while (std::getline(iss, next_s, ',')) {
780     tags.push_back(next_s);
781   }
782   std::set<std::string> defaultSolvers;
783   std::set<int> selectedSolvers;
784 
785   std::string firstTag;
786   if (tags.empty()) {
787     DefaultMap::const_iterator def_it = _tagDefault.find("");
788     if (def_it != _tagDefault.end()) {
789       firstTag = def_it->second;
790     } else {
791       throw ConfigException("no solver selected");
792     }
793   } else {
794     firstTag = tags[0];
795   }
796   TagMap::const_iterator tag_it = _tags.find(get_tag(firstTag));
797 
798   if (tag_it == _tags.end()) {
799     throw ConfigException("no solver with tag " + get_tag(firstTag) + " found");
800   }
801   std::string tv = get_version(firstTag);
802   for (int sidx : tag_it->second) {
803     if (tv.empty() || tv == _solvers[sidx].version()) {
804       selectedSolvers.insert(sidx);
805     }
806   }
807   if (selectedSolvers.empty()) {
808     // No matching version, only matching tag
809     for (int sidx : tag_it->second) {
810       selectedSolvers.insert(sidx);
811     }
812   }
813   DefaultMap::const_iterator def_it = _tagDefault.find(get_tag(firstTag));
814   if (def_it != _tagDefault.end()) {
815     defaultSolvers.insert(def_it->second);
816   }
817   for (unsigned int i = 1; i < tags.size(); i++) {
818     tag_it = _tags.find(get_tag(tags[i]));
819     if (tag_it == _tags.end()) {
820       throw ConfigException("no solver with tag " + tags[i] + " found");
821     }
822     tv = get_version(tags[i]);
823     std::set<int> newSolvers;
824     for (int sidx : tag_it->second) {
825       if (tv.empty() || tv == _solvers[sidx].version()) {
826         newSolvers.insert(sidx);
827       }
828     }
829     std::set<int> intersection;
830     std::set_intersection(selectedSolvers.begin(), selectedSolvers.end(), newSolvers.begin(),
831                           newSolvers.end(), std::inserter(intersection, intersection.begin()));
832     selectedSolvers = intersection;
833     if (selectedSolvers.empty()) {
834       throw ConfigException("no solver with tags " + s + " found");
835     }
836     def_it = _tagDefault.find(get_tag(tags[i]));
837     if (def_it != _tagDefault.end()) {
838       defaultSolvers.insert(def_it->second);
839     }
840   }
841   int selectedSolver = -1;
842   if (selectedSolvers.size() > 1) {
843     // use default information for the tags to select a solver
844     for (int sc_idx : selectedSolvers) {
845       if (defaultSolvers.find(_solvers[sc_idx].id()) != defaultSolvers.end()) {
846         selectedSolver = sc_idx;
847         break;
848       }
849     }
850     if (selectedSolver == -1) {
851       selectedSolver = *selectedSolvers.begin();
852     }
853   } else {
854     selectedSolver = *selectedSolvers.begin();
855   }
856   return _solvers[selectedSolver];
857 }
858 
defaultOptions(const std::string & id)859 std::vector<std::string> SolverConfigs::defaultOptions(const std::string& id) {
860   auto it = _solverDefaultOptions.find(id);
861   if (it != _solverDefaultOptions.end()) {
862     std::vector<std::string> defaultOptions;
863     for (const auto& df : it->second) {
864       if (!df.empty()) {
865         defaultOptions.push_back(df);
866       }
867     }
868     return defaultOptions;
869   }
870   return {};
871 }
872 
registerBuiltinSolver(const SolverConfig & sc)873 void SolverConfigs::registerBuiltinSolver(const SolverConfig& sc) {
874   builtin_solver_configs().builtinSolvers.insert(make_pair(sc.id(), sc));
875 }
876 
877 }  // namespace MiniZinc
878