The models, scripts, and results of the benchmarks performed for a Half Reification Journal paper
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/* This (main) file coordinates flattening and solving.
13 * The corresponding modules are flexibly plugged in
14 * as derived classes, prospectively from DLLs.
15 * A flattening module should provide MinZinc::GetFlattener()
16 * A solving module should provide an object of a class derived from SolverFactory.
17 * Need to get more flexible for multi-pass & multi-solving stuff TODO
18 */
19
20#include <minizinc/file_utils.hh>
21#include <minizinc/json_parser.hh>
22#include <minizinc/parser.hh>
23#include <minizinc/prettyprinter.hh>
24
25#include <fstream>
26
27using namespace std;
28
29int mzn_yylex_init(void** scanner);
30void mzn_yyset_extra(void* user_defined, void* yyscanner);
31int mzn_yylex_destroy(void* scanner);
32
33namespace {
34// fastest way to read a file into a string (especially big files)
35// see: http://insanecoding.blogspot.be/2011/11/how-to-read-in-file-in-c.html
36std::string get_file_contents(std::ifstream& in) {
37 if (in) {
38 std::string contents;
39 in.seekg(0, std::ios::end);
40 contents.resize(static_cast<unsigned int>(in.tellg()));
41 in.seekg(0, std::ios::beg);
42 in.read(&contents[0], contents.size());
43 in.close();
44 if (!contents.empty() && contents[0] == '@') {
45 contents = MiniZinc::FileUtils::decode_base64(contents);
46 MiniZinc::FileUtils::inflate_string(contents);
47 }
48 return (contents);
49 }
50 throw(errno);
51}
52} // namespace
53
54namespace MiniZinc {
55
56std::string ParserState::canonicalFilename(const std::string& f) const {
57 if (FileUtils::is_absolute(f) || std::string(filename).empty()) {
58 return f;
59 }
60 for (const auto& ip : includePaths) {
61 std::string fullname = FileUtils::file_path(ip + "/" + f);
62 if (FileUtils::file_exists(fullname)) {
63 return fullname;
64 }
65 }
66 std::string parentPath = FileUtils::dir_name(filename);
67 if (parentPath.empty()) {
68 parentPath = ".";
69 }
70 std::string fullname = FileUtils::file_path(parentPath + "/" + f);
71 if (FileUtils::file_exists(fullname)) {
72 return fullname;
73 }
74 return f;
75}
76
77void parse(Env& env, Model*& model, const vector<string>& filenames,
78 const vector<string>& datafiles, const std::string& modelString,
79 const std::string& modelStringName, const vector<string>& ip, bool isFlatZinc,
80 bool ignoreStdlib, bool parseDocComments, bool verbose, ostream& err,
81 std::vector<SyntaxError>& syntaxErrors) {
82 vector<string> includePaths;
83 for (const auto& i : ip) {
84 includePaths.push_back(i);
85 }
86
87 vector<ParseWorkItem> files;
88 map<string, Model*> seenModels;
89
90 string workingDir = FileUtils::working_directory();
91
92 if (!filenames.empty()) {
93 GCLock lock;
94 model->setFilename(FileUtils::base_name(filenames[0]));
95 if (FileUtils::is_absolute(filenames[0])) {
96 files.emplace_back(model, nullptr, "", filenames[0]);
97 } else {
98 files.emplace_back(model, nullptr, "", workingDir + "/" + filenames[0]);
99 }
100
101 for (unsigned int i = 1; i < filenames.size(); i++) {
102 GCLock lock;
103 string fullName = filenames[i];
104 string baseName = FileUtils::base_name(filenames[i]);
105 if (!FileUtils::is_absolute(fullName)) {
106 fullName = FileUtils::file_path(workingDir + "/" + fullName);
107 }
108 bool isFzn = (baseName.compare(baseName.length() - 4, 4, ".fzn") == 0);
109 if (isFzn) {
110 files.emplace_back(model, nullptr, "", fullName);
111 } else {
112 auto* includedModel = new Model;
113 includedModel->setFilename(baseName);
114 files.emplace_back(includedModel, nullptr, "", fullName);
115 seenModels.insert(pair<string, Model*>(fullName, includedModel));
116 Location loc(ASTString(filenames[i]), 0, 0, 0, 0);
117 auto* inc = new IncludeI(loc, includedModel->filename());
118 inc->m(includedModel, true);
119 model->addItem(inc);
120 }
121 }
122 if (!modelString.empty()) {
123 auto* includedModel = new Model;
124 includedModel->setFilename(modelStringName);
125 files.emplace_back(includedModel, nullptr, modelString, modelStringName, false, true);
126 seenModels.insert(pair<string, Model*>(modelStringName, includedModel));
127 Location loc(ASTString(modelStringName), 0, 0, 0, 0);
128 auto* inc = new IncludeI(loc, includedModel->filename());
129 inc->m(includedModel, true);
130 model->addItem(inc);
131 }
132 } else if (!modelString.empty()) {
133 GCLock lock;
134 model->setFilename(modelStringName);
135 files.emplace_back(model, nullptr, modelString, modelStringName, false, true);
136 }
137
138 auto include_file = [&](const std::string& libname, bool builtin) {
139 GCLock lock;
140 auto* lib = new Model;
141 std::string fullname;
142 for (const auto& ip : includePaths) {
143 std::string n = FileUtils::file_path(ip + "/" + libname);
144 if (FileUtils::file_exists(n)) {
145 fullname = n;
146 break;
147 }
148 }
149 lib->setFilename(fullname);
150 files.emplace_back(lib, nullptr, "./", fullname, builtin);
151 seenModels.insert(pair<string, Model*>(fullname, lib));
152 Location libloc(ASTString(model->filename()), 0, 0, 0, 0);
153 auto* libinc = new IncludeI(libloc, libname);
154 libinc->m(lib, true);
155 model->addItem(libinc);
156 };
157
158 // TODO: It should be possible to use just flatzinc builtins instead of stdlib when parsing
159 // FlatZinc if (!isFlatZinc) {
160 if (!ignoreStdlib) {
161 include_file("solver_redefinitions.mzn", false);
162 include_file("stdlib.mzn", true); // Added last, so it is processed first
163 }
164 // } else {
165 // include_file("flatzincbuiltins.mzn", true);
166 // }
167
168 while (!files.empty()) {
169 GCLock lock;
170 ParseWorkItem& np = files.back();
171 string parentPath = np.dirName;
172 Model* m = np.m;
173 bool isModelString = np.isModelString;
174 bool isSTDLib = np.isSTDLib;
175 IncludeI* np_ii = np.ii;
176 string f(np.fileName);
177 files.pop_back();
178
179 std::string s;
180 std::string fullname;
181 std::string basename;
182 bool isFzn;
183 if (!isModelString) {
184 for (Model* p = m->parent(); p != nullptr; p = p->parent()) {
185 if (p->filename() == f) {
186 err << "Error: cyclic includes: " << std::endl;
187 for (Model* pe = m; pe != nullptr; pe = pe->parent()) {
188 err << " " << pe->filename() << std::endl;
189 }
190 goto error;
191 }
192 }
193 ifstream file;
194 if (FileUtils::is_absolute(f)) {
195 fullname = f;
196 basename = FileUtils::base_name(fullname);
197 if (FileUtils::file_exists(fullname)) {
198 file.open(FILE_PATH(fullname), std::ios::binary);
199 }
200 }
201 if (file.is_open() &&
202 FileUtils::file_path(FileUtils::dir_name(fullname)) != FileUtils::file_path(workingDir) &&
203 FileUtils::file_exists(workingDir + "/" + basename)) {
204 err << "Warning: file " << basename
205 << " included from library, but also exists in current working directory" << endl;
206 }
207 for (const auto& includePath : includePaths) {
208 std::string deprecatedName = includePath + "/" + basename + ".deprecated.mzn";
209 if (FileUtils::file_exists(deprecatedName)) {
210 string deprecatedFullPath = FileUtils::file_path(deprecatedName);
211 string deprecatedBaseName = FileUtils::base_name(deprecatedFullPath);
212 string deprecatedDirName = FileUtils::dir_name(deprecatedFullPath);
213 auto* includedModel = new Model;
214 includedModel->setFilename(deprecatedName);
215 files.emplace_back(includedModel, nullptr, "", deprecatedName, isSTDLib, false);
216 seenModels.insert(pair<string, Model*>(deprecatedName, includedModel));
217 Location loc(ASTString(deprecatedName), 0, 0, 0, 0);
218 auto* inc = new IncludeI(loc, includedModel->filename());
219 inc->m(includedModel, true);
220 m->addItem(inc);
221 files.emplace_back(includedModel, inc, deprecatedDirName, deprecatedFullPath, isSTDLib,
222 false);
223 }
224 }
225 if (!file.is_open()) {
226 if (np_ii != nullptr) {
227 err << np_ii->loc().toString() << ":\n";
228 err << "MiniZinc: error in include item, cannot open file '" << f << "'." << endl;
229 } else {
230 err << "Error: cannot open file '" << f << "'." << endl;
231 }
232 goto error;
233 }
234 if (verbose) {
235 std::cerr << "processing file '" << fullname << "'" << endl;
236 }
237 s = get_file_contents(file);
238
239 if (m->filepath().size() == 0) {
240 m->setFilepath(fullname);
241 }
242 isFzn = (fullname.compare(fullname.length() - 4, 4, ".fzn") == 0);
243 isFzn |= (fullname.compare(fullname.length() - 4, 4, ".ozn") == 0);
244 isFzn |= (fullname.compare(fullname.length() - 4, 4, ".szn") == 0);
245 isFzn |= (fullname.compare(fullname.length() - 4, 4, ".mzc") == 0);
246 } else {
247 isFzn = false;
248 fullname = f;
249 s = parentPath;
250 }
251 ParserState pp(fullname, s, err, includePaths, files, seenModels, m, false, isFzn, isSTDLib,
252 parseDocComments);
253 mzn_yylex_init(&pp.yyscanner);
254 mzn_yyset_extra(&pp, pp.yyscanner);
255 mzn_yyparse(&pp);
256 if (pp.yyscanner != nullptr) {
257 mzn_yylex_destroy(pp.yyscanner);
258 }
259 if (pp.hadError) {
260 for (const auto& syntaxError : pp.syntaxErrors) {
261 syntaxErrors.push_back(syntaxError);
262 }
263 goto error;
264 }
265 }
266
267 for (const auto& f : datafiles) {
268 GCLock lock;
269 if (f.size() >= 6 && f.substr(f.size() - 5, string::npos) == ".json") {
270 JSONParser jp(env.envi());
271 jp.parse(model, f, true);
272 } else {
273 string s;
274 if (f.size() > 5 && f.substr(0, 5) == "cmd:/") {
275 s = f.substr(5);
276 } else {
277 std::ifstream file(FILE_PATH(f), std::ios::binary);
278 if (!FileUtils::file_exists(f) || !file.is_open()) {
279 err << "Error: cannot open data file '" << f << "'." << endl;
280 goto error;
281 }
282 if (verbose) {
283 std::cerr << "processing data file '" << f << "'" << endl;
284 }
285 s = get_file_contents(file);
286 }
287
288 ParserState pp(f, s, err, includePaths, files, seenModels, model, true, false, false,
289 parseDocComments);
290 mzn_yylex_init(&pp.yyscanner);
291 mzn_yyset_extra(&pp, pp.yyscanner);
292 mzn_yyparse(&pp);
293 if (pp.yyscanner != nullptr) {
294 mzn_yylex_destroy(pp.yyscanner);
295 }
296 if (pp.hadError) {
297 for (const auto& syntaxError : pp.syntaxErrors) {
298 syntaxErrors.push_back(syntaxError);
299 }
300 goto error;
301 }
302 }
303 }
304
305 return;
306error:
307 delete model;
308 model = nullptr;
309}
310
311Model* parse(Env& env, const vector<string>& filenames, const vector<string>& datafiles,
312 const string& textModel, const string& textModelName,
313 const vector<string>& includePaths, bool isFlatZinc, bool ignoreStdlib,
314 bool parseDocComments, bool verbose, ostream& err) {
315 if (filenames.empty() && textModel.empty()) {
316 err << "Error: no model given" << std::endl;
317 return nullptr;
318 }
319
320 Model* model;
321 {
322 GCLock lock;
323 model = new Model();
324 }
325 std::vector<SyntaxError> se;
326 parse(env, model, filenames, datafiles, textModel, textModelName, includePaths, isFlatZinc,
327 ignoreStdlib, parseDocComments, verbose, err, se);
328 return model;
329}
330
331Model* parse_data(Env& env, Model* model, const vector<string>& datafiles,
332 const vector<string>& includePaths, bool isFlatZinc, bool ignoreStdlib,
333 bool parseDocComments, bool verbose, ostream& err) {
334 vector<string> filenames;
335 std::vector<SyntaxError> se;
336 parse(env, model, filenames, datafiles, "", "", includePaths, isFlatZinc, ignoreStdlib,
337 parseDocComments, verbose, err, se);
338 return model;
339}
340
341Model* parse_from_string(Env& env, const string& text, const string& filename,
342 const vector<string>& includePaths, bool isFlatZinc, bool ignoreStdlib,
343 bool parseDocComments, bool verbose, ostream& err,
344 std::vector<SyntaxError>& syntaxErrors) {
345 vector<string> filenames;
346 vector<string> datafiles;
347 Model* model;
348 {
349 GCLock lock;
350 model = new Model();
351 }
352 parse(env, model, filenames, datafiles, text, filename, includePaths, isFlatZinc, ignoreStdlib,
353 parseDocComments, verbose, err, syntaxErrors);
354 return model;
355}
356
357} // namespace MiniZinc