1 | /*
|
2 | * Souffle - A Datalog Compiler
|
3 | * Copyright (c) 2017, The Souffle Developers. All rights reserved
|
4 | * Licensed under the Universal Permissive License v 1.0 as shown at:
|
5 | * - https://opensource.org/licenses/UPL
|
6 | * - <souffle root>/licenses/SOUFFLE-UPL.txt
|
7 | */
|
8 |
|
9 | /************************************************************************
|
10 | *
|
11 | * @file Explain.h
|
12 | *
|
13 | * Provenance interface for Souffle; works for compiler and interpreter
|
14 | *
|
15 | ***********************************************************************/
|
16 |
|
17 | #pragma once
|
18 |
|
19 | #include "souffle/provenance/ExplainProvenance.h"
|
20 | #include "souffle/provenance/ExplainProvenanceImpl.h"
|
21 | #include "souffle/provenance/ExplainTree.h"
|
22 | #include <algorithm>
|
23 | #include <csignal>
|
24 | #include <cstdio>
|
25 | #include <fstream>
|
26 | #include <iostream>
|
27 | #include <map>
|
28 | #include <memory>
|
29 | #include <regex>
|
30 | #include <string>
|
31 | #include <utility>
|
32 | #include <vector>
|
33 | #include <unistd.h>
|
34 |
|
35 | #ifdef USE_NCURSES
|
36 | #include <ncurses.h>
|
37 | #endif
|
38 |
|
39 | #define MAX_TREE_HEIGHT 500
|
40 | #define MAX_TREE_WIDTH 500
|
41 |
|
42 | namespace souffle {
|
43 | class SouffleProgram;
|
44 |
|
45 | class ExplainConfig {
|
46 | public:
|
47 | /* Deleted copy constructor */
|
48 | ExplainConfig(const ExplainConfig&) = delete;
|
49 |
|
50 | /* Deleted assignment operator */
|
51 | ExplainConfig& operator=(const ExplainConfig&) = delete;
|
52 |
|
53 | /* Obtain the global ExplainConfiguration */
|
54 | static ExplainConfig& getExplainConfig() {
|
55 | static ExplainConfig config;
|
56 | return config;
|
57 | }
|
58 |
|
59 | /* Configuration variables */
|
60 | Own<std::ostream> outputStream = nullptr;
|
61 | bool json = false;
|
62 | int depthLimit = 4;
|
63 |
|
64 | private:
|
65 | ExplainConfig() = default;
|
66 | };
|
67 |
|
68 | class Explain {
|
69 | public:
|
70 | ExplainProvenance& prov;
|
71 |
|
72 | Explain(ExplainProvenance& prov) : prov(prov) {}
|
73 | ~Explain() = default;
|
74 |
|
75 | /* Process a command, a return value of true indicates to continue, returning false indicates to break (if
|
76 | * the command is q/exit) */
|
77 | bool processCommand(std::string& commandLine) {
|
78 | std::vector<std::string> command = split(commandLine, ' ', 1);
|
79 |
|
80 | if (command.empty()) {
|
81 | return true;
|
82 | }
|
83 |
|
84 | if (command[0] == "setdepth") {
|
85 | if (command.size() != 2) {
|
86 | printError("Usage: setdepth <depth>\n");
|
87 | return true;
|
88 | }
|
89 | try {
|
90 | ExplainConfig::getExplainConfig().depthLimit = std::stoi(command[1]);
|
91 | } catch (std::exception& e) {
|
92 | printError("<" + command[1] + "> is not a valid depth\n");
|
93 | return true;
|
94 | }
|
95 | printInfo("Depth is now " + std::to_string(ExplainConfig::getExplainConfig().depthLimit) + "\n");
|
96 | } else if (command[0] == "explain") {
|
97 | std::pair<std::string, std::vector<std::string>> query;
|
98 | if (command.size() != 2) {
|
99 | printError("Usage: explain relation_name(\"<string element1>\", <number element2>, ...)\n");
|
100 | return true;
|
101 | }
|
102 | query = parseTuple(command[1]);
|
103 | printTree(prov.explain(query.first, query.second, ExplainConfig::getExplainConfig().depthLimit));
|
104 | } else if (command[0] == "subproof") {
|
105 | std::pair<std::string, std::vector<std::string>> query;
|
106 | int label = -1;
|
107 | if (command.size() <= 1) {
|
108 | printError("Usage: subproof relation_name(<label>)\n");
|
109 | return true;
|
110 | }
|
111 | query = parseTuple(command[1]);
|
112 | label = std::stoi(query.second[0]);
|
113 | printTree(prov.explainSubproof(query.first, label, ExplainConfig::getExplainConfig().depthLimit));
|
114 | } else if (command[0] == "explainnegation") {
|
115 | std::pair<std::string, std::vector<std::string>> query;
|
116 | if (command.size() != 2) {
|
117 | printError(
|
118 | "Usage: explainnegation relation_name(\"<string element1>\", <number element2>, "
|
119 | "...)\n");
|
120 | return true;
|
121 | }
|
122 | query = parseTuple(command[1]);
|
123 |
|
124 | // a counter for the rule numbers
|
125 | std::size_t i = 1;
|
126 | std::string rules;
|
127 |
|
128 | // if there are no rules, then this must be an EDB relation
|
129 | if (prov.getRules(query.first).size() == 0) {
|
130 | printInfo("The tuple would be an input fact!\n");
|
131 | return true;
|
132 | }
|
133 |
|
134 | for (auto rule : prov.getRules(query.first)) {
|
135 | rules += std::to_string(i) + ": ";
|
136 | rules += rule;
|
137 | rules += "\n\n";
|
138 | i++;
|
139 | }
|
140 | printInfo(rules);
|
141 |
|
142 | printPrompt("Pick a rule number: ");
|
143 |
|
144 | std::string ruleNum = getInput();
|
145 | auto variables = prov.explainNegationGetVariables(query.first, query.second, std::stoi(ruleNum));
|
146 |
|
147 | // @ and @non_matching are special sentinel values returned by ExplainProvenance
|
148 | if (variables.size() == 1 && variables[0] == "@") {
|
149 | printInfo("The tuple exists, cannot explain negation of it!\n");
|
150 | return true;
|
151 | } else if (variables.size() == 1 && variables[0] == "@non_matching") {
|
152 | printInfo("The variable bindings don't match, cannot explain!\n");
|
153 | return true;
|
154 | } else if (variables.size() == 1 && variables[0] == "@fact") {
|
155 | printInfo("The rule is a fact!\n");
|
156 | return true;
|
157 | }
|
158 |
|
159 | std::map<std::string, std::string> varValues;
|
160 | for (auto var : variables) {
|
161 | printPrompt("Pick a value for " + var + ": ");
|
162 | varValues[var] = getInput();
|
163 | }
|
164 |
|
165 | printTree(prov.explainNegation(query.first, std::stoi(ruleNum), query.second, varValues));
|
166 | } else if (command[0] == "rule" && command.size() == 2) {
|
167 | auto query = split(command[1], ' ');
|
168 | if (query.size() != 2) {
|
169 | printError("Usage: rule <relation name> <rule number>\n");
|
170 | return true;
|
171 | }
|
172 | try {
|
173 | printInfo(prov.getRule(query[0], std::stoi(query[1])) + "\n");
|
174 | } catch (std::exception& e) {
|
175 | printError("Usage: rule <relation name> <rule number>\n");
|
176 | }
|
177 | } else if (command[0] == "measure") {
|
178 | try {
|
179 | printInfo(prov.measureRelation(command[1]));
|
180 | } catch (std::exception& e) {
|
181 | printError("Usage: measure <relation name>\n");
|
182 | }
|
183 | } else if (command[0] == "output") {
|
184 | if (command.size() == 2) {
|
185 | // assign a new filestream, the old one is deleted by unique_ptr
|
186 | ExplainConfig::getExplainConfig().outputStream = mk<std::ofstream>(command[1]);
|
187 | } else if (command.size() == 1) {
|
188 | ExplainConfig::getExplainConfig().outputStream = nullptr;
|
189 | } else {
|
190 | printError("Usage: output [<filename>]\n");
|
191 | }
|
192 | } else if (command[0] == "format") {
|
193 | if (command.size() == 2 && command[1] == "json") {
|
194 | ExplainConfig::getExplainConfig().json = true;
|
195 | } else if (command.size() == 2 && command[1] == "proof") {
|
196 | ExplainConfig::getExplainConfig().json = false;
|
197 | } else {
|
198 | printError("Usage: format <json|proof>\n");
|
199 | }
|
200 | } else if (command[0] == "exit" || command[0] == "q" || command[0] == "quit") {
|
201 | // close file stream so that output is actually written to file
|
202 | printPrompt("Exiting explain\n");
|
203 | return false;
|
204 | } else if (command[0] == "query") {
|
205 | // if there is no given relations, return directly
|
206 | if (command.size() != 2) {
|
207 | printError(
|
208 | "Usage: query <relation1>(<element1>, <element2>, ...), "
|
209 | "<relation2>(<element1>, <element2>, ...), ...\n");
|
210 | return true;
|
211 | }
|
212 | // vector relations stores relation name, args pair parsed by parseQueryTuple()
|
213 | std::vector<std::pair<std::string, std::vector<std::string>>> relations;
|
214 | // regex for relation string
|
215 | std::regex relationRegex(
|
216 | "([a-zA-Z0-9_.-]*)[[:blank:]]*\\(([[:blank:]]*([0-9]+|\"[^\"]*\"|[a-zA-Z_][a-zA-Z_0-9]*)("
|
217 | "[[:blank:]]*,[[:blank:]]*(["
|
218 | "0-"
|
219 | "9]+|\"[^\"]*\"|[a-zA-Z_][a-zA-Z_0-9]*))*)?\\)",
|
220 | std::regex_constants::extended);
|
221 | std::smatch relationMatcher;
|
222 | std::string relationStr = command[1];
|
223 | // use relationRegex to match each relation string and call parseQueryTuple() to parse the
|
224 | // relation name and arguments
|
225 | while (std::regex_search(relationStr, relationMatcher, relationRegex)) {
|
226 | relations.push_back(parseQueryTuple(relationMatcher[0]));
|
227 |
|
228 | // check return value for parseQueryTuple, return if relation name is empty string or tuple
|
229 | // arguments is empty
|
230 | if (relations.back().first.size() == 0 || relations.back().second.size() == 0) {
|
231 | printError(
|
232 | "Usage: query <relation1>(<element1>, <element2>, ...), "
|
233 | "<relation2>(<element1>, <element2>, ...), ...\n");
|
234 | return true;
|
235 | }
|
236 | relationStr = relationMatcher.suffix().str();
|
237 | }
|
238 |
|
239 | // is no valid relation can be identified, return directly
|
240 | if (relations.size() == 0) {
|
241 | printError(
|
242 | "Usage: query <relation1>(<element1>, <element2>, ...), "
|
243 | "<relation2>(<element1>, <element2>, ...), ...\n");
|
244 | return true;
|
245 | }
|
246 |
|
247 | // call queryProcess function to process query
|
248 | prov.queryProcess(relations);
|
249 | } else {
|
250 | printError(
|
251 | "\n----------\n"
|
252 | "Commands:\n"
|
253 | "----------\n"
|
254 | "setdepth <depth>: Set a limit for printed derivation tree height\n"
|
255 | "explain <relation>(<element1>, <element2>, ...): Prints derivation tree\n"
|
256 | "explainnegation <relation>(<element1>, <element2>, ...): Enters an interactive\n"
|
257 | " interface where the non-existence of a tuple can be explained\n"
|
258 | "subproof <relation>(<label>): Prints derivation tree for a subproof, label is\n"
|
259 | " generated if a derivation tree exceeds height limit\n"
|
260 | "rule <relation name> <rule number>: Prints a rule\n"
|
261 | "output <filename>: Write output into a file, or provide empty filename to\n"
|
262 | " disable output\n"
|
263 | "format <json|proof>: switch format between json and proof-trees\n"
|
264 | "query <relation1>(<element1>, <element2>, ...), <relation2>(<element1>, <element2>), "
|
265 | "... :\n"
|
266 | "check existence of constant tuples or find solutions for parameterised tuples\n"
|
267 | "for parameterised query, use semicolon to find next solution and dot to break from "
|
268 | "query\n"
|
269 | "exit: Exits this interface\n\n");
|
270 | }
|
271 |
|
272 | return true;
|
273 | }
|
274 |
|
275 | /* The main explain call */
|
276 | virtual void explain() = 0;
|
277 |
|
278 | private:
|
279 | /* Get input */
|
280 | virtual std::string getInput() = 0;
|
281 |
|
282 | /* Print a command prompt, disabled for non-terminal outputs */
|
283 | virtual void printPrompt(const std::string& prompt) = 0;
|
284 |
|
285 | /* Print a tree */
|
286 | virtual void printTree(Own<TreeNode> tree) = 0;
|
287 |
|
288 | /* Print any other information, disabled for non-terminal outputs */
|
289 | virtual void printInfo(const std::string& info) = 0;
|
290 |
|
291 | /* Print an error, such as a wrong command */
|
292 | virtual void printError(const std::string& error) = 0;
|
293 |
|
294 | /**
|
295 | * Parse tuple, split into relation name and values
|
296 | * @param str The string to parse, should be something like "R(x1, x2, x3, ...)"
|
297 | */
|
298 | std::pair<std::string, std::vector<std::string>> parseTuple(const std::string& str) {
|
299 | std::string relName;
|
300 | std::vector<std::string> args;
|
301 |
|
302 | // regex for matching tuples
|
303 | // values matches numbers or strings enclosed in quotation marks
|
304 | std::regex relationRegex(
|
305 | "([a-zA-Z0-9_.-]*)[[:blank:]]*\\(([[:blank:]]*([0-9]+|\"[^\"]*\")([[:blank:]]*,[[:blank:]]*(["
|
306 | "0-"
|
307 | "9]+|\"[^\"]*\"))*)?\\)",
|
308 | std::regex_constants::extended);
|
309 | std::smatch relMatch;
|
310 |
|
311 | // first check that format matches correctly
|
312 | // and extract relation name
|
313 | if (!std::regex_match(str, relMatch, relationRegex) || relMatch.size() < 3) {
|
314 | return std::make_pair(relName, args);
|
315 | }
|
316 |
|
317 | // set relation name
|
318 | relName = relMatch[1];
|
319 |
|
320 | // extract each argument
|
321 | std::string argsList = relMatch[2];
|
322 | std::smatch argsMatcher;
|
323 | std::regex argRegex(R"([0-9]+|"[^"]*")", std::regex_constants::extended);
|
324 |
|
325 | while (std::regex_search(argsList, argsMatcher, argRegex)) {
|
326 | // match the start of the arguments
|
327 | std::string currentArg = argsMatcher[0];
|
328 | args.push_back(currentArg);
|
329 |
|
330 | // use the rest of the arguments
|
331 | argsList = argsMatcher.suffix().str();
|
332 | }
|
333 |
|
334 | return std::make_pair(relName, args);
|
335 | }
|
336 |
|
337 | /**
|
338 | * Parse tuple for query, split into relation name and args, additionally allow varaible as argument in
|
339 | * relation tuple
|
340 | * @param str The string to parse, should be in form "R(x1, x2, x3, ...)"
|
341 | */
|
342 | std::pair<std::string, std::vector<std::string>> parseQueryTuple(const std::string& str) {
|
343 | std::string relName;
|
344 | std::vector<std::string> args;
|
345 | // regex for matching tuples
|
346 | // values matches numbers or strings enclosed in quotation marks
|
347 | std::regex relationRegex(
|
348 | "([a-zA-Z0-9_.-]*)[[:blank:]]*\\(([[:blank:]]*([0-9]+|\"[^\"]*\"|[a-zA-Z_][a-zA-Z_0-9]*)([[:"
|
349 | "blank:]]*,[[:blank:]]*(["
|
350 | "0-"
|
351 | "9]+|\"[^\"]*\"|[a-zA-Z_][a-zA-Z_0-9]*))*)?\\)",
|
352 | std::regex_constants::extended);
|
353 | std::smatch relMatch;
|
354 |
|
355 | // if the given string does not match relationRegex, return a pair of empty string and empty vector
|
356 | if (!std::regex_match(str, relMatch, relationRegex) || relMatch.size() < 3) {
|
357 | return std::make_pair(relName, args);
|
358 | }
|
359 |
|
360 | // set relation name
|
361 | relName = relMatch[1];
|
362 |
|
363 | // extract each argument
|
364 | std::string argsList = relMatch[2];
|
365 | std::smatch argsMatcher;
|
366 | std::regex argRegex(R"([0-9]+|"[^"]*"|[a-zA-Z_][a-zA-Z_0-9]*)", std::regex_constants::extended);
|
367 | while (std::regex_search(argsList, argsMatcher, argRegex)) {
|
368 | // match the start of the arguments
|
369 | std::string currentArg = argsMatcher[0];
|
370 | args.push_back(currentArg);
|
371 |
|
372 | // use the rest of the arguments
|
373 | argsList = argsMatcher.suffix().str();
|
374 | }
|
375 |
|
376 | return std::make_pair(relName, args);
|
377 | }
|
378 | };
|
379 |
|
380 | class ExplainConsole : public Explain {
|
381 | public:
|
382 | explicit ExplainConsole(ExplainProvenance& provenance) : Explain(provenance) {}
|
383 |
|
384 | /* The main explain call */
|
385 | void explain() override {
|
386 | printPrompt("Explain is invoked.\n");
|
387 |
|
388 | while (true) {
|
389 | printPrompt("Enter command > ");
|
390 | std::string line = getInput();
|
391 | // a return value of false indicates that an exit/q command has been processed
|
392 | if (!processCommand(line)) {
|
393 | break;
|
394 | }
|
395 | }
|
396 | }
|
397 |
|
398 | private:
|
399 | /* Get input */
|
400 | std::string getInput() override {
|
401 | std::string line;
|
402 |
|
403 | if (!getline(std::cin, line)) {
|
404 | // if EOF has been reached, quit
|
405 | line = "q";
|
406 | }
|
407 |
|
408 | return line;
|
409 | }
|
410 |
|
411 | /* Print a command prompt, disabled for non-terminal outputs */
|
412 | void printPrompt(const std::string& prompt) override {
|
413 | if (isatty(fileno(stdin)) == 0) {
|
414 | return;
|
415 | }
|
416 | std::cout << prompt;
|
417 | }
|
418 |
|
419 | /* Print a tree */
|
420 | void printTree(Own<TreeNode> tree) override {
|
421 | if (!tree) {
|
422 | return;
|
423 | }
|
424 |
|
425 | // handle a file ostream output
|
426 | std::ostream* output;
|
427 | if (ExplainConfig::getExplainConfig().outputStream == nullptr) {
|
428 | output = &std::cout;
|
429 | } else {
|
430 | output = ExplainConfig::getExplainConfig().outputStream.get();
|
431 | }
|
432 |
|
433 | if (!ExplainConfig::getExplainConfig().json) {
|
434 | tree->place(0, 0);
|
435 | ScreenBuffer screenBuffer(tree->getWidth(), tree->getHeight());
|
436 | tree->render(screenBuffer);
|
437 | *output << screenBuffer.getString();
|
438 | } else {
|
439 | *output << "{ \"proof\":\n";
|
440 | tree->printJSON(*output, 1);
|
441 | *output << ",";
|
442 | prov.printRulesJSON(*output);
|
443 | *output << "}\n";
|
444 | }
|
445 | }
|
446 |
|
447 | /* Print any other information, disabled for non-terminal outputs */
|
448 | void printInfo(const std::string& info) override {
|
449 | if (isatty(fileno(stdin)) == 0) {
|
450 | return;
|
451 | }
|
452 | std::cout << info;
|
453 | }
|
454 |
|
455 | /* Print an error, such as a wrong command */
|
456 | void printError(const std::string& error) override {
|
457 | std::cout << error;
|
458 | }
|
459 | };
|
460 |
|
461 | #ifdef USE_NCURSES
|
462 | class ExplainNcurses : public Explain {
|
463 | public:
|
464 | explicit ExplainNcurses(ExplainProvenance& provenance) : Explain(provenance) {}
|
465 |
|
466 | /* The main explain call */
|
467 | void explain() override {
|
468 | if (ExplainConfig::getExplainConfig().outputStream == nullptr) {
|
469 | initialiseWindow();
|
470 | std::signal(SIGWINCH, nullptr);
|
471 | }
|
472 |
|
473 | printPrompt("Explain is invoked.\n");
|
474 |
|
475 | while (true) {
|
476 | clearDisplay();
|
477 | printPrompt("Enter command > ");
|
478 | std::string line = getInput();
|
479 |
|
480 | // a return value of false indicates that an exit/q command has been processed
|
481 | if (!processCommand(line)) {
|
482 | break;
|
483 | }
|
484 |
|
485 | // refresh treePad and allow scrolling
|
486 | prefresh(treePad, 0, 0, 0, 0, maxy - 3, maxx - 1);
|
487 | scrollTree(maxx, maxy);
|
488 | }
|
489 |
|
490 | // clean up
|
491 | endwin();
|
492 | }
|
493 |
|
494 | private:
|
495 | WINDOW* treePad = nullptr;
|
496 | WINDOW* queryWindow = nullptr;
|
497 | int maxx, maxy;
|
498 |
|
499 | /* Get input */
|
500 | std::string getInput() override {
|
501 | char buf[100];
|
502 |
|
503 | curs_set(1);
|
504 | echo();
|
505 |
|
506 | // get next command
|
507 | wgetnstr(queryWindow, buf, 100);
|
508 | noecho();
|
509 | curs_set(0);
|
510 | std::string line = buf;
|
511 |
|
512 | return line;
|
513 | }
|
514 |
|
515 | /* Print a command prompt, disabled for non-terminal outputs */
|
516 | void printPrompt(const std::string& prompt) override {
|
517 | if (!isatty(fileno(stdin))) {
|
518 | return;
|
519 | }
|
520 | werase(queryWindow);
|
521 | wrefresh(queryWindow);
|
522 | mvwprintw(queryWindow, 1, 0, "%s", prompt.c_str());
|
523 | }
|
524 |
|
525 | /* Print a tree */
|
526 | void printTree(Own<TreeNode> tree) override {
|
527 | if (!tree) {
|
528 | return;
|
529 | }
|
530 |
|
531 | if (!ExplainConfig::getExplainConfig().json) {
|
532 | tree->place(0, 0);
|
533 | ScreenBuffer screenBuffer(tree->getWidth(), tree->getHeight());
|
534 | tree->render(screenBuffer);
|
535 | wprintw(treePad, "%s", screenBuffer.getString().c_str());
|
536 | } else {
|
537 | if (ExplainConfig::getExplainConfig().outputStream == nullptr) {
|
538 | std::stringstream ss;
|
539 | ss << "{ \"proof\":\n";
|
540 | tree->printJSON(ss, 1);
|
541 | ss << ",";
|
542 | prov.printRulesJSON(ss);
|
543 | ss << "}\n";
|
544 |
|
545 | wprintw(treePad, "%s", ss.str().c_str());
|
546 | } else {
|
547 | std::ostream* output = ExplainConfig::getExplainConfig().outputStream.get();
|
548 | *output << "{ \"proof\":\n";
|
549 | tree->printJSON(*output, 1);
|
550 | *output << ",";
|
551 | prov.printRulesJSON(*output);
|
552 | *output << "}\n";
|
553 | }
|
554 | }
|
555 | }
|
556 |
|
557 | /* Print any other information, disabled for non-terminal outputs */
|
558 | void printInfo(const std::string& info) override {
|
559 | if (!isatty(fileno(stdin))) {
|
560 | return;
|
561 | }
|
562 | wprintw(treePad, "%s", info.c_str());
|
563 | prefresh(treePad, 0, 0, 0, 0, maxy - 3, maxx - 1);
|
564 | }
|
565 |
|
566 | /* Print an error, such as a wrong command */
|
567 | void printError(const std::string& error) override {
|
568 | wprintw(treePad, "%s", error.c_str());
|
569 | prefresh(treePad, 0, 0, 0, 0, maxy - 3, maxx - 1);
|
570 | }
|
571 |
|
572 | /* Initialise ncurses command prompt window */
|
573 | WINDOW* makeQueryWindow() {
|
574 | WINDOW* queryWindow = newwin(3, maxx, maxy - 2, 0);
|
575 | wrefresh(queryWindow);
|
576 | return queryWindow;
|
577 | }
|
578 |
|
579 | /* Initialise ncurses window */
|
580 | void initialiseWindow() {
|
581 | initscr();
|
582 |
|
583 | // get size of window
|
584 | getmaxyx(stdscr, maxy, maxx);
|
585 |
|
586 | // create windows for query and tree display
|
587 | queryWindow = makeQueryWindow();
|
588 | treePad = newpad(MAX_TREE_HEIGHT, MAX_TREE_WIDTH);
|
589 |
|
590 | keypad(treePad, true);
|
591 | }
|
592 |
|
593 | /* Allow scrolling of provenance tree */
|
594 | void scrollTree(int maxx, int maxy) {
|
595 | int x = 0;
|
596 | int y = 0;
|
597 |
|
598 | while (true) {
|
599 | int ch = wgetch(treePad);
|
600 |
|
601 | if (ch == KEY_LEFT) {
|
602 | if (x > 2) x -= 3;
|
603 | } else if (ch == KEY_RIGHT) {
|
604 | if (x < MAX_TREE_WIDTH - 3) x += 3;
|
605 | } else if (ch == KEY_UP) {
|
606 | if (y > 0) y -= 1;
|
607 | } else if (ch == KEY_DOWN) {
|
608 | if (y < MAX_TREE_HEIGHT - 1) y += 1;
|
609 | } else {
|
610 | ungetch(ch);
|
611 | break;
|
612 | }
|
613 |
|
614 | prefresh(treePad, y, x, 0, 0, maxy - 3, maxx - 1);
|
615 | }
|
616 | }
|
617 |
|
618 | /* Clear the tree display */
|
619 | void clearDisplay() {
|
620 | // reset tree display on each loop
|
621 | werase(treePad);
|
622 | prefresh(treePad, 0, 0, 0, 0, maxy - 3, maxx - 1);
|
623 | }
|
624 | };
|
625 | #endif
|
626 |
|
627 | inline void explain(SouffleProgram& prog, bool ncurses) {
|
628 | ExplainProvenanceImpl prov(prog);
|
629 |
|
630 | if (ncurses) {
|
631 | #ifdef USE_NCURSES
|
632 | ExplainNcurses exp(prov);
|
633 | exp.explain();
|
634 | #else
|
635 | std::cout << "The ncurses-based interface is not enabled\n";
|
636 | #endif
|
637 | } else {
|
638 | ExplainConsole exp(prov);
|
639 | exp.explain();
|
640 | }
|
641 | }
|
642 |
|
643 | // this is necessary because ncurses.h defines TRUE and FALSE macros, and they otherwise clash with our parser
|
644 | #ifdef USE_NCURSES
|
645 | #undef TRUE
|
646 | #undef FALSE
|
647 | #endif
|
648 |
|
649 | } // end of namespace souffle
|